mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-12 20:06:24 +00:00
Compare commits
2
Commits
trim-cli-flags
..
v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0643a5638e | ||
|
|
81523d4a84 |
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-QJn59dTbxspHl35vGxs6akynlfaWaPqvbMNsi0EqnbM=",
|
||||
"aarch64-linux": "sha256-wa54uUIAth/ZOyejZ/TfU0zZdkhNirwq7yK1218jhBQ=",
|
||||
"aarch64-darwin": "sha256-VvsfVdtxcVijCvlrWpBUkgmkYFoXX7QiXM0Fhz1t2VE=",
|
||||
"x86_64-darwin": "sha256-IxYFBG5I+znXcTVqrAWKhG2qvVtjM+Amp68xIGKZogE="
|
||||
"x86_64-linux": "sha256-euVUyj0CzjCA1nYbN2vKctEPzLkUlNGTK2dMNbackqM=",
|
||||
"aarch64-linux": "sha256-qQkjqaxpjAae+rohoWI601QnrgKYghJ+ttqeiQBTwCM=",
|
||||
"aarch64-darwin": "sha256-HYWs31TJlDZsDBNmbPARo16r7zNKy9x840uHGcUMYsk=",
|
||||
"x86_64-darwin": "sha256-89FOrX813FENk3u8RAHCfyD7voaZWW++Z4Gpa3SkOJs="
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -248,7 +248,7 @@ for (const direction of ["ltr", "rtl"]) {
|
||||
expect(messageAfter).toEqual(messageBefore)
|
||||
await page.locator('[data-component="composer-editor"]').pressSequentially("Also: ")
|
||||
await expect(page.locator('[data-component="composer-editor"]')).toHaveText(`Also: ${followUp}`)
|
||||
expect(mock.calls).toEqual(["worktree", "session", "prompt"])
|
||||
await expect.poll(() => mock.calls).toEqual(["worktree", "session", "prompt"])
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ for (const direction of ["ltr", "rtl"] as const) {
|
||||
"href",
|
||||
`#opencode-v2-icon-${workspace ? "outline-worktree" : "monitor"}`,
|
||||
)
|
||||
// Initial layout scrolls this sticky header's ancestor and dismisses its tooltip.
|
||||
await expect(page.locator("[data-timeline-virtual-content]")).toHaveCSS("visibility", "visible")
|
||||
const background = await trigger.evaluate((element) => getComputedStyle(element).backgroundColor)
|
||||
await trigger.hover()
|
||||
await expect(trigger).not.toHaveCSS("background-color", background)
|
||||
|
||||
@@ -103,7 +103,7 @@ export function createTimelineVirtualizer(input: Input) {
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
const [rendering, setRendering] = createStore({ initialTail: coldBottomMount })
|
||||
const [rendering, setRendering] = createStore({ initialTail: coldBottomMount, scrollAdjustment: 0 })
|
||||
const rows = input.projection.rows
|
||||
const rowByKey = input.projection.rowByKey
|
||||
const rowKeys = createMemo(() => rows().map(TimelineRow.key), undefined, {
|
||||
@@ -154,6 +154,10 @@ export function createTimelineVirtualizer(input: Input) {
|
||||
})
|
||||
const measuredElements = new WeakSet<Element>()
|
||||
let touchStart: number | undefined
|
||||
let touchTarget: EventTarget | null = null
|
||||
let touchNested = false
|
||||
let touchScrolling = false
|
||||
let touchAdjustment = 0
|
||||
let pointerHeld = false
|
||||
let maxScroll = 0
|
||||
let virtualContent: HTMLDivElement | undefined
|
||||
@@ -177,7 +181,23 @@ export function createTimelineVirtualizer(input: Input) {
|
||||
observeElementOffset: (instance, callback) => {
|
||||
reportOffset = (offset, scrolling) => {
|
||||
if (!active()) return
|
||||
callback(offset, scrolling)
|
||||
// Rows and the sizer use the opposite translation while native touch
|
||||
// scrolling keeps its own offset. Range selection uses the logical offset.
|
||||
batch(() => {
|
||||
const logicalOffset = offset + rendering.scrollAdjustment
|
||||
callback(rendering.scrollAdjustment ? Math.max(0, logicalOffset) : offset, scrolling)
|
||||
// Reconcile both start boundaries in one native write. Gradually
|
||||
// clamping row translations lets the compositor paint between
|
||||
// corrections and makes the content oscillate at the top.
|
||||
const root = listRoot()
|
||||
if (
|
||||
rendering.scrollAdjustment !== 0 &&
|
||||
root &&
|
||||
(logicalOffset <= 0 || offset <= 0 || (touchStart !== undefined && offset <= root.clientHeight))
|
||||
)
|
||||
flushTouchAdjustment()
|
||||
if (!scrolling && touchStart === undefined) finishTouchScroll()
|
||||
})
|
||||
settleColdBottom()
|
||||
}
|
||||
return observeElementOffsetReconnectAware(instance, reportOffset, () => {
|
||||
@@ -212,6 +232,7 @@ export function createTimelineVirtualizer(input: Input) {
|
||||
scrollToFn: (offset, options, instance) => {
|
||||
if (!active()) return
|
||||
if (batchingColdSizes && input.pinned()) return
|
||||
setRendering("scrollAdjustment", 0)
|
||||
if (virtualContent) virtualContent.style.height = `${instance.getTotalSize()}px`
|
||||
elementScroll(offset, options, instance)
|
||||
},
|
||||
@@ -263,7 +284,15 @@ export function createTimelineVirtualizer(input: Input) {
|
||||
batch(() => {
|
||||
sizes.forEach(([index, value]) => {
|
||||
const row = rows()[index]
|
||||
if (row && TimelineRow.key(row) === value.key) resizeItem(index, value.size)
|
||||
if (!row || TimelineRow.key(row) !== value.key) return
|
||||
resizeItem(index, value.size)
|
||||
// TanStack recalculates its range after each resize. Advance the
|
||||
// logical fold before deciding whether the next row needs anchoring.
|
||||
if (!touchAdjustment) return
|
||||
setRendering("scrollAdjustment", (value) => value + touchAdjustment)
|
||||
touchAdjustment = 0
|
||||
const root = listRoot()
|
||||
if (root) reportOffset?.(root.scrollTop, virtualizer.isScrolling)
|
||||
})
|
||||
})
|
||||
batchingColdSizes = false
|
||||
@@ -278,13 +307,40 @@ export function createTimelineVirtualizer(input: Input) {
|
||||
})
|
||||
}
|
||||
onCleanup(() => pendingSizes.clear())
|
||||
virtualizer.shouldAdjustScrollPositionOnItemSizeChange = (item, _delta, instance) => {
|
||||
virtualizer.shouldAdjustScrollPositionOnItemSizeChange = (item, delta, instance) => {
|
||||
// Prepended rows can resize more than once as deferred content mounts. Keep
|
||||
// compensating while they remain entirely above the visible content fold.
|
||||
if (addedKeys.has(String(item.key)))
|
||||
return item.end <= (instance.scrollOffset ?? 0) + instance.scrollAdjustments + instance.options.scrollMargin
|
||||
const first = instance.range?.startIndex
|
||||
return first !== undefined && item.index < first
|
||||
const adjust = addedKeys.has(String(item.key))
|
||||
? item.end <= (instance.scrollOffset ?? 0) + instance.scrollAdjustments + instance.options.scrollMargin
|
||||
: first !== undefined && item.index < first
|
||||
if (!touchScrolling || input.pinned()) return adjust
|
||||
// iOS defers native scroll writes until momentum ends. Keep the same visual
|
||||
// anchor now, rather than moving rows now and snapping the viewport back later.
|
||||
if (adjust) touchAdjustment += delta
|
||||
return false
|
||||
}
|
||||
|
||||
function finishTouchScroll() {
|
||||
touchScrolling = false
|
||||
flushTouchAdjustment()
|
||||
}
|
||||
|
||||
function prepareNavigation() {
|
||||
if (touchStart === undefined) touchScrolling = false
|
||||
flushTouchAdjustment()
|
||||
}
|
||||
|
||||
function flushTouchAdjustment() {
|
||||
const adjustment = rendering.scrollAdjustment
|
||||
const root = listRoot()
|
||||
if (!adjustment || !root) return
|
||||
// Transfer the translation into the native offset in the same paint.
|
||||
batch(() => {
|
||||
setRendering("scrollAdjustment", 0)
|
||||
if (virtualContent) virtualContent.style.height = `${virtualizer.getTotalSize()}px`
|
||||
elementScroll(Math.max(0, root.scrollTop + adjustment), {}, virtualizer)
|
||||
})
|
||||
}
|
||||
const virtualItemByKey = createMemo(
|
||||
() => new Map(virtualizer.getVirtualItems().map((item) => [item.key, item] as const)),
|
||||
@@ -314,10 +370,12 @@ export function createTimelineVirtualizer(input: Input) {
|
||||
: -1
|
||||
const index = partIndex >= 0 ? partIndex : input.projection.messageRowIndex().get(id)
|
||||
if (index === undefined) return
|
||||
prepareNavigation()
|
||||
virtualizer.scrollToIndex(index, { align: "center" })
|
||||
})
|
||||
input.setScrollToEnd?.(() => {
|
||||
if (!active() || !listRoot()?.isConnected) return
|
||||
prepareNavigation()
|
||||
input.onPin()
|
||||
virtualizer.scrollToEnd()
|
||||
})
|
||||
@@ -422,19 +480,53 @@ export function createTimelineVirtualizer(input: Input) {
|
||||
}
|
||||
|
||||
const handleListTouchStart = (event: TouchEvent) => {
|
||||
clearTouchTarget()
|
||||
input.onUserScroll(event.target)
|
||||
touchScrolling = true
|
||||
touchStart = event.touches[0]?.clientY
|
||||
const root = listRoot()
|
||||
const nested = event.target instanceof Element ? event.target.closest<HTMLElement>("[data-scrollable]") : null
|
||||
touchNested = !!nested && nested !== root && nested.scrollHeight > nested.clientHeight
|
||||
// Native touch events keep their original target, even when streaming or
|
||||
// virtualization detaches it. Listen there instead of relying on bubbling.
|
||||
touchTarget = event.target
|
||||
touchTarget?.addEventListener("touchmove", handleListTouchMove, { passive: true })
|
||||
touchTarget?.addEventListener("touchend", handleListTouchEnd, { passive: true })
|
||||
touchTarget?.addEventListener("touchcancel", handleListTouchEnd, { passive: true })
|
||||
if (root) reportOffset?.(root.scrollTop, virtualizer.isScrolling)
|
||||
}
|
||||
|
||||
const handleListTouchMove = (event: TouchEvent & { currentTarget: HTMLDivElement }) => {
|
||||
const handleListTouchMove = (event: Event) => {
|
||||
if (!(event instanceof TouchEvent)) return
|
||||
const current = event.touches[0]?.clientY
|
||||
if (current === undefined || touchStart === undefined) return
|
||||
// Dragging the content downward reveals earlier messages.
|
||||
if (current <= touchStart) return
|
||||
const previous = touchStart
|
||||
touchStart = current
|
||||
// A retained target can outlive its whole session view. Only the active
|
||||
// timeline may change the shared follow state; release still cleans up below.
|
||||
if (!active()) return
|
||||
// Dragging the content downward reveals earlier messages.
|
||||
if (current <= previous) return
|
||||
// A nested scrollport owns the intent. If it chains into the timeline at a
|
||||
// boundary, the resulting native timeline scroll below will unpin instead.
|
||||
if (touchNested) return
|
||||
input.onUnpin()
|
||||
}
|
||||
|
||||
const handleListTouchEnd = () => {
|
||||
clearTouchTarget()
|
||||
touchStart = undefined
|
||||
if (!virtualizer.isScrolling) finishTouchScroll()
|
||||
}
|
||||
|
||||
function clearTouchTarget() {
|
||||
touchTarget?.removeEventListener("touchmove", handleListTouchMove)
|
||||
touchTarget?.removeEventListener("touchend", handleListTouchEnd)
|
||||
touchTarget?.removeEventListener("touchcancel", handleListTouchEnd)
|
||||
touchTarget = null
|
||||
}
|
||||
onCleanup(clearTouchTarget)
|
||||
|
||||
// Drag-selecting past the edge and dragging the scrollbar both scroll without a wheel or key,
|
||||
// so a held pointer is what separates those from the virtualizer's own measurement adjustments.
|
||||
const handleListPointerDown = (event: PointerEvent & { currentTarget: HTMLDivElement }) => {
|
||||
@@ -475,7 +567,7 @@ export function createTimelineVirtualizer(input: Input) {
|
||||
const atEnd = maxScroll - scrollTop <= endEpsilon
|
||||
const arrived = scrollTop > previousTop + endEpsilon || maxScroll < previousMaxScroll
|
||||
if (maxScroll <= 1 || (atEnd && arrived)) input.onPin()
|
||||
else if (pointerHeld && scrollTop < previousTop - endEpsilon) input.onUnpin()
|
||||
else if ((pointerHeld || touchScrolling) && scrollTop < previousTop - endEpsilon) input.onUnpin()
|
||||
settleColdBottom()
|
||||
input.onScheduleScrollState(root)
|
||||
input.onHistoryScroll()
|
||||
@@ -504,7 +596,7 @@ export function createTimelineVirtualizer(input: Input) {
|
||||
data-timeline-key={rowProps.rowKey}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: `${item().start - topOffset()}px`,
|
||||
top: `${item().start - topOffset() - rendering.scrollAdjustment}px`,
|
||||
left: "0",
|
||||
width: "100%",
|
||||
height: `${item().size}px`,
|
||||
@@ -582,9 +674,10 @@ export function createTimelineVirtualizer(input: Input) {
|
||||
<ScrollView
|
||||
data-slot="session-timeline-scroll"
|
||||
viewportRef={bindListRoot}
|
||||
onBeforeScroll={prepareNavigation}
|
||||
verticalScrollAdjustment={rendering.scrollAdjustment}
|
||||
onWheel={handleListWheel}
|
||||
onTouchStart={handleListTouchStart}
|
||||
onTouchMove={handleListTouchMove}
|
||||
onPointerDown={handleListPointerDown}
|
||||
onKeyDown={handleListKeyDown}
|
||||
onScroll={handleListScroll}
|
||||
@@ -602,7 +695,7 @@ export function createTimelineVirtualizer(input: Input) {
|
||||
if (active()) input.setContentRef(element)
|
||||
}}
|
||||
style={{
|
||||
height: `${virtualizer.getTotalSize()}px`,
|
||||
height: `${virtualizer.getTotalSize() - rendering.scrollAdjustment}px`,
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
visibility: coldBottomMount ? "hidden" : undefined,
|
||||
@@ -612,7 +705,7 @@ export function createTimelineVirtualizer(input: Input) {
|
||||
<div
|
||||
data-timeline-row="bottom-spacer"
|
||||
class="h-16 absolute top-0 left-0 w-full"
|
||||
style={{ transform: `translateY(${virtualizer.getTotalSize() - 64}px)` }}
|
||||
style={{ transform: `translateY(${virtualizer.getTotalSize() - 64 - rendering.scrollAdjustment}px)` }}
|
||||
>
|
||||
{props.bottomSpacer}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Effect, FileSystem, Scope } from "effect"
|
||||
import { CliConfig, Command, GlobalFlag } from "effect/unstable/cli"
|
||||
import { Command } from "effect/unstable/cli"
|
||||
import { PrintLogs } from "../commands/commands"
|
||||
import { Spec } from "./spec"
|
||||
import { Global } from "@opencode/util/global"
|
||||
@@ -82,13 +82,11 @@ export function handlers<const Root extends Spec.Any>(root: Root, handlers: Hand
|
||||
}
|
||||
|
||||
export function run(commands: Spec.Any, handlers: ReadonlyArray<LazyHandler>, options: { readonly version: string }) {
|
||||
return Command.run(provide(commands, handlers).pipe(Command.withGlobalFlags([PrintLogs])), options).pipe(
|
||||
Effect.provide(
|
||||
CliConfig.layer({
|
||||
builtIns: [GlobalFlag.Help, GlobalFlag.Version, GlobalFlag.Completions],
|
||||
}),
|
||||
),
|
||||
) as Effect.Effect<void, unknown, Command.Environment>
|
||||
return Command.run(provide(commands, handlers).pipe(Command.withGlobalFlags([PrintLogs])), options) as Effect.Effect<
|
||||
void,
|
||||
unknown,
|
||||
Command.Environment
|
||||
>
|
||||
}
|
||||
|
||||
function provide(node: Spec.Any, handlers: ReadonlyArray<LazyHandler>): ProvidedCommand {
|
||||
|
||||
@@ -20,6 +20,10 @@ export interface ScrollViewProps extends ComponentProps<"div"> {
|
||||
thumbContainer?: HTMLElement
|
||||
/** Element whose hover reveals the thumb. Defaults to the ScrollView root when unset. */
|
||||
thumbHoverTarget?: HTMLElement
|
||||
/** Reconcile native scroll geometry before keyboard or scrollbar navigation reads it. */
|
||||
onBeforeScroll?: () => void
|
||||
/** Offset/extent correction for a virtualized vertical scrollbar. */
|
||||
verticalScrollAdjustment?: number
|
||||
}
|
||||
|
||||
export const scrollKey = (event: Pick<KeyboardEvent, "key" | "altKey" | "ctrlKey" | "metaKey" | "shiftKey">) => {
|
||||
@@ -124,6 +128,8 @@ export function ScrollView(props: ScrollViewProps) {
|
||||
"thumbVisibility",
|
||||
"thumbContainer",
|
||||
"thumbHoverTarget",
|
||||
"onBeforeScroll",
|
||||
"verticalScrollAdjustment",
|
||||
"style",
|
||||
],
|
||||
[
|
||||
@@ -189,17 +195,19 @@ export function ScrollView(props: ScrollViewProps) {
|
||||
const minThumbSize = 32
|
||||
|
||||
if (vertical()) {
|
||||
const adjustment = local.verticalScrollAdjustment ?? 0
|
||||
const scrollHeight = viewportRef.scrollHeight + adjustment
|
||||
const trackSize = Math.max(0, (thumbMount()?.clientHeight || viewportRef.clientHeight) - trackPadding * 2)
|
||||
const size = trackSize
|
||||
? Math.min(trackSize, Math.max((viewportRef.clientHeight / viewportRef.scrollHeight) * trackSize, minThumbSize))
|
||||
? Math.min(trackSize, Math.max((viewportRef.clientHeight / scrollHeight) * trackSize, minThumbSize))
|
||||
: 0
|
||||
const maxScroll = viewportRef.scrollHeight - viewportRef.clientHeight
|
||||
const maxScroll = scrollHeight - viewportRef.clientHeight
|
||||
const maxStart = trackSize - size
|
||||
setState("showVerticalThumb", maxScroll > 0)
|
||||
setState("verticalThumbSize", size)
|
||||
setState(
|
||||
"verticalThumbStart",
|
||||
trackPadding + (maxScroll > 0 ? (viewportRef.scrollTop / maxScroll) * maxStart : 0),
|
||||
trackPadding + (maxScroll > 0 ? ((viewportRef.scrollTop + adjustment) / maxScroll) * maxStart : 0),
|
||||
)
|
||||
} else {
|
||||
setState("showVerticalThumb", false)
|
||||
@@ -263,9 +271,17 @@ export function ScrollView(props: ScrollViewProps) {
|
||||
})
|
||||
})
|
||||
|
||||
const prepareScroll = () => {
|
||||
if (local.onBeforeScroll) {
|
||||
local.onBeforeScroll()
|
||||
updateThumb()
|
||||
}
|
||||
}
|
||||
|
||||
const onThumbPointerDown = (axis: "vertical" | "horizontal", e: PointerEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
prepareScroll()
|
||||
setState("dragging", axis)
|
||||
const thumb = axis === "vertical" ? verticalThumbRef : horizontalThumbRef
|
||||
const grabOffset =
|
||||
@@ -277,6 +293,7 @@ export function ScrollView(props: ScrollViewProps) {
|
||||
thumb.setPointerCapture(e.pointerId)
|
||||
|
||||
const onPointerMove = (e: PointerEvent) => {
|
||||
prepareScroll()
|
||||
const vertical = axis === "vertical"
|
||||
const rtl = !vertical && getComputedStyle(viewportRef).direction === "rtl"
|
||||
const offset = scrollOffsetFromThumbPointer({
|
||||
@@ -355,10 +372,23 @@ export function ScrollView(props: ScrollViewProps) {
|
||||
return
|
||||
}
|
||||
const next = scrollKey(e)
|
||||
if (!next) return
|
||||
if (!isScrollKeyTarget(e.target, next)) return
|
||||
if (scrollKeyOwner(viewportRef, e.target, next) !== viewportRef) return
|
||||
// Modified navigation (for example Ctrl+Home) stays native, but must read
|
||||
// the same reconciled geometry as the keys handled by this component.
|
||||
const intent =
|
||||
next ??
|
||||
scrollKey({
|
||||
key: e.key,
|
||||
shiftKey: e.key === " " && e.shiftKey,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
})
|
||||
if (!intent) return
|
||||
if (!isScrollKeyTarget(e.target, intent)) return
|
||||
if (scrollKeyOwner(viewportRef, e.target, intent) !== viewportRef) return
|
||||
|
||||
prepareScroll()
|
||||
if (!next) return
|
||||
const scrollAmount = viewportRef.clientHeight * 0.8
|
||||
const lineAmount = 40
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/dist/cjs/index.cjs b/dist/cjs/index.cjs
|
||||
index e470032a9572b3ced764ca02238a8c6be435a9d4..84a7bfca638f70e32a9567bc732a84b83578af4e 100644
|
||||
index e470032a9572b3ced764ca02238a8c6be435a9d4..4c25e03b5db221a78793083d97970b2d90e28efa 100644
|
||||
--- a/dist/cjs/index.cjs
|
||||
+++ b/dist/cjs/index.cjs
|
||||
@@ -289,7 +289,7 @@ class Virtualizer {
|
||||
@@ -82,7 +82,50 @@ index e470032a9572b3ced764ca02238a8c6be435a9d4..84a7bfca638f70e32a9567bc732a84b8
|
||||
followOnAppend,
|
||||
anchorDelta
|
||||
];
|
||||
@@ -725,17 +735,20 @@ class Virtualizer {
|
||||
@@ -440,7 +450,17 @@ class Virtualizer {
|
||||
);
|
||||
if ("addEventListener" in this.scrollElement) {
|
||||
const scrollEl = this.scrollElement;
|
||||
- const onTouchStart = () => {
|
||||
+ let touchTarget = null;
|
||||
+ const clearTouchTarget = () => {
|
||||
+ touchTarget?.removeEventListener("touchend", onTouchEnd);
|
||||
+ touchTarget?.removeEventListener("touchcancel", onTouchEnd);
|
||||
+ touchTarget = null;
|
||||
+ };
|
||||
+ const onTouchStart = (event) => {
|
||||
+ clearTouchTarget();
|
||||
+ touchTarget = event.target ?? scrollEl;
|
||||
+ touchTarget.addEventListener("touchend", onTouchEnd, addEventListenerOptions);
|
||||
+ touchTarget.addEventListener("touchcancel", onTouchEnd, addEventListenerOptions);
|
||||
this._iosTouching = true;
|
||||
this._iosJustTouchEnded = false;
|
||||
if (this._iosTouchEndTimerId !== null && this.targetWindow != null) {
|
||||
@@ -449,6 +469,7 @@ class Virtualizer {
|
||||
}
|
||||
};
|
||||
const onTouchEnd = () => {
|
||||
+ clearTouchTarget();
|
||||
this._iosTouching = false;
|
||||
if (!isIOSWebKit() || this.targetWindow == null) {
|
||||
return;
|
||||
@@ -465,14 +486,9 @@ class Virtualizer {
|
||||
onTouchStart,
|
||||
addEventListenerOptions
|
||||
);
|
||||
- scrollEl.addEventListener(
|
||||
- "touchend",
|
||||
- onTouchEnd,
|
||||
- addEventListenerOptions
|
||||
- );
|
||||
this.unsubs.push(() => {
|
||||
scrollEl.removeEventListener("touchstart", onTouchStart);
|
||||
- scrollEl.removeEventListener("touchend", onTouchEnd);
|
||||
+ clearTouchTarget();
|
||||
if (this._iosTouchEndTimerId !== null && this.targetWindow != null) {
|
||||
this.targetWindow.clearTimeout(this._iosTouchEndTimerId);
|
||||
this._iosTouchEndTimerId = null;
|
||||
@@ -725,17 +741,20 @@ class Virtualizer {
|
||||
this.getMeasurements(),
|
||||
this.getSize(),
|
||||
this.getScrollOffset(),
|
||||
@@ -106,7 +149,7 @@ index e470032a9572b3ced764ca02238a8c6be435a9d4..84a7bfca638f70e32a9567bc732a84b8
|
||||
lanes,
|
||||
// Pass the typed array so binary search + forward-walk can read
|
||||
// start/end directly from Float64Array, skipping the Proxy traps.
|
||||
@@ -1095,8 +1108,10 @@ class Virtualizer {
|
||||
@@ -1095,8 +1114,10 @@ class Virtualizer {
|
||||
const snapshot = [];
|
||||
if (this.itemSizeCache.size === 0) return snapshot;
|
||||
const m = this.getMeasurements();
|
||||
@@ -146,7 +189,7 @@ index 6b43c0aea7ed9eeef75cbfb1351fcbd243913bdd..7be2680967934ddfbc4583a210a3d11e
|
||||
getVirtualIndexes: {
|
||||
(): number[];
|
||||
diff --git a/dist/esm/index.js b/dist/esm/index.js
|
||||
index 2495b26cf2c3589213546b3958eaadf2eb6b751d..accd38a89bee766fd568aa8cec9bb90c6789277a 100644
|
||||
index 2495b26cf2c3589213546b3958eaadf2eb6b751d..1398bc7dd33690a8fe3280343b6aabdeaea6435a 100644
|
||||
--- a/dist/esm/index.js
|
||||
+++ b/dist/esm/index.js
|
||||
@@ -287,7 +287,7 @@ class Virtualizer {
|
||||
@@ -229,7 +272,50 @@ index 2495b26cf2c3589213546b3958eaadf2eb6b751d..accd38a89bee766fd568aa8cec9bb90c
|
||||
followOnAppend,
|
||||
anchorDelta
|
||||
];
|
||||
@@ -723,17 +733,20 @@ class Virtualizer {
|
||||
@@ -438,7 +448,17 @@ class Virtualizer {
|
||||
);
|
||||
if ("addEventListener" in this.scrollElement) {
|
||||
const scrollEl = this.scrollElement;
|
||||
- const onTouchStart = () => {
|
||||
+ let touchTarget = null;
|
||||
+ const clearTouchTarget = () => {
|
||||
+ touchTarget?.removeEventListener("touchend", onTouchEnd);
|
||||
+ touchTarget?.removeEventListener("touchcancel", onTouchEnd);
|
||||
+ touchTarget = null;
|
||||
+ };
|
||||
+ const onTouchStart = (event) => {
|
||||
+ clearTouchTarget();
|
||||
+ touchTarget = event.target ?? scrollEl;
|
||||
+ touchTarget.addEventListener("touchend", onTouchEnd, addEventListenerOptions);
|
||||
+ touchTarget.addEventListener("touchcancel", onTouchEnd, addEventListenerOptions);
|
||||
this._iosTouching = true;
|
||||
this._iosJustTouchEnded = false;
|
||||
if (this._iosTouchEndTimerId !== null && this.targetWindow != null) {
|
||||
@@ -447,6 +467,7 @@ class Virtualizer {
|
||||
}
|
||||
};
|
||||
const onTouchEnd = () => {
|
||||
+ clearTouchTarget();
|
||||
this._iosTouching = false;
|
||||
if (!isIOSWebKit() || this.targetWindow == null) {
|
||||
return;
|
||||
@@ -463,14 +484,9 @@ class Virtualizer {
|
||||
onTouchStart,
|
||||
addEventListenerOptions
|
||||
);
|
||||
- scrollEl.addEventListener(
|
||||
- "touchend",
|
||||
- onTouchEnd,
|
||||
- addEventListenerOptions
|
||||
- );
|
||||
this.unsubs.push(() => {
|
||||
scrollEl.removeEventListener("touchstart", onTouchStart);
|
||||
- scrollEl.removeEventListener("touchend", onTouchEnd);
|
||||
+ clearTouchTarget();
|
||||
if (this._iosTouchEndTimerId !== null && this.targetWindow != null) {
|
||||
this.targetWindow.clearTimeout(this._iosTouchEndTimerId);
|
||||
this._iosTouchEndTimerId = null;
|
||||
@@ -723,17 +739,20 @@ class Virtualizer {
|
||||
this.getMeasurements(),
|
||||
this.getSize(),
|
||||
this.getScrollOffset(),
|
||||
@@ -253,7 +339,7 @@ index 2495b26cf2c3589213546b3958eaadf2eb6b751d..accd38a89bee766fd568aa8cec9bb90c
|
||||
lanes,
|
||||
// Pass the typed array so binary search + forward-walk can read
|
||||
// start/end directly from Float64Array, skipping the Proxy traps.
|
||||
@@ -1093,8 +1106,10 @@ class Virtualizer {
|
||||
@@ -1093,8 +1112,10 @@ class Virtualizer {
|
||||
const snapshot = [];
|
||||
if (this.itemSizeCache.size === 0) return snapshot;
|
||||
const m = this.getMeasurements();
|
||||
@@ -267,7 +353,7 @@ index 2495b26cf2c3589213546b3958eaadf2eb6b751d..accd38a89bee766fd568aa8cec9bb90c
|
||||
index: item.index,
|
||||
key: item.key,
|
||||
diff --git a/src/index.ts b/src/index.ts
|
||||
index dc6f1010c4d4758de9c46fb8d69209e582e47171..2578338abb5d9237624dd6d96bb50546c6dc3e68 100644
|
||||
index dc6f1010c4d4758de9c46fb8d69209e582e47171..9299287d72ce79d5a597cdc3440339ee148e6385 100644
|
||||
--- a/src/index.ts
|
||||
+++ b/src/index.ts
|
||||
@@ -567,7 +567,7 @@ export class Virtualizer<
|
||||
@@ -380,7 +466,51 @@ index dc6f1010c4d4758de9c46fb8d69209e582e47171..2578338abb5d9237624dd6d96bb50546
|
||||
followOnAppend,
|
||||
anchorDelta,
|
||||
]
|
||||
@@ -1410,16 +1421,25 @@ export class Virtualizer<
|
||||
@@ -895,7 +906,18 @@ export class Virtualizer<
|
||||
// and _flushIosDeferredIfReady so we only burn the path on iOS.
|
||||
if ('addEventListener' in this.scrollElement) {
|
||||
const scrollEl = this.scrollElement as unknown as EventTarget
|
||||
- const onTouchStart = () => {
|
||||
+ let touchTarget: EventTarget | null = null
|
||||
+ const clearTouchTarget = () => {
|
||||
+ touchTarget?.removeEventListener('touchend', onTouchEnd)
|
||||
+ touchTarget?.removeEventListener('touchcancel', onTouchEnd)
|
||||
+ touchTarget = null
|
||||
+ }
|
||||
+ const onTouchStart = (event: Event) => {
|
||||
+ clearTouchTarget()
|
||||
+ // The original target still receives release after its DOM removal.
|
||||
+ touchTarget = event.target ?? scrollEl
|
||||
+ touchTarget.addEventListener('touchend', onTouchEnd, addEventListenerOptions)
|
||||
+ touchTarget.addEventListener('touchcancel', onTouchEnd, addEventListenerOptions)
|
||||
this._iosTouching = true
|
||||
this._iosJustTouchEnded = false
|
||||
if (this._iosTouchEndTimerId !== null && this.targetWindow != null) {
|
||||
@@ -904,6 +926,7 @@ export class Virtualizer<
|
||||
}
|
||||
}
|
||||
const onTouchEnd = () => {
|
||||
+ clearTouchTarget()
|
||||
this._iosTouching = false
|
||||
if (!isIOSWebKit() || this.targetWindow == null) {
|
||||
// Non-iOS: nothing more to track. Just clear the touching flag.
|
||||
@@ -924,14 +947,9 @@ export class Virtualizer<
|
||||
onTouchStart,
|
||||
addEventListenerOptions,
|
||||
)
|
||||
- scrollEl.addEventListener(
|
||||
- 'touchend',
|
||||
- onTouchEnd,
|
||||
- addEventListenerOptions,
|
||||
- )
|
||||
this.unsubs.push(() => {
|
||||
scrollEl.removeEventListener('touchstart', onTouchStart)
|
||||
- scrollEl.removeEventListener('touchend', onTouchEnd)
|
||||
+ clearTouchTarget()
|
||||
if (this._iosTouchEndTimerId !== null && this.targetWindow != null) {
|
||||
this.targetWindow.clearTimeout(this._iosTouchEndTimerId)
|
||||
this._iosTouchEndTimerId = null
|
||||
@@ -1410,16 +1428,25 @@ export class Virtualizer<
|
||||
this.getSize(),
|
||||
this.getScrollOffset(),
|
||||
this.options.lanes,
|
||||
@@ -408,7 +538,7 @@ index dc6f1010c4d4758de9c46fb8d69209e582e47171..2578338abb5d9237624dd6d96bb50546
|
||||
lanes,
|
||||
// Pass the typed array so binary search + forward-walk can read
|
||||
// start/end directly from Float64Array, skipping the Proxy traps.
|
||||
@@ -1937,12 +1957,13 @@ export class Virtualizer<
|
||||
@@ -1937,12 +1964,13 @@ export class Virtualizer<
|
||||
takeSnapshot = (): Array<VirtualItem> => {
|
||||
const snapshot: Array<VirtualItem> = []
|
||||
if (this.itemSizeCache.size === 0) return snapshot
|
||||
|
||||
Reference in New Issue
Block a user