@particle-academy/react-fancy 6.6.0 → 6.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/README.md +1 -0
  3. package/dist/{chunk-L5AI4MGV.cjs → chunk-25HRO7HQ.cjs} +4 -4
  4. package/dist/{chunk-L5AI4MGV.cjs.map → chunk-25HRO7HQ.cjs.map} +1 -1
  5. package/dist/{chunk-MF2DCMOA.cjs → chunk-2TLAZ5MX.cjs} +7 -5
  6. package/dist/chunk-2TLAZ5MX.cjs.map +1 -0
  7. package/dist/{chunk-ARNFT23K.js → chunk-5IXOQGPX.js} +7 -5
  8. package/dist/chunk-5IXOQGPX.js.map +1 -0
  9. package/dist/chunk-5WMVBFBD.js +3 -0
  10. package/dist/{chunk-VXHFR5MV.js.map → chunk-5WMVBFBD.js.map} +1 -1
  11. package/dist/chunk-75PB3IMT.js +139 -0
  12. package/dist/chunk-75PB3IMT.js.map +1 -0
  13. package/dist/chunk-7STOB5MB.cjs +4 -0
  14. package/dist/{chunk-O5IO7TCO.cjs.map → chunk-7STOB5MB.cjs.map} +1 -1
  15. package/dist/chunk-GHTHPDKY.cjs +141 -0
  16. package/dist/chunk-GHTHPDKY.cjs.map +1 -0
  17. package/dist/{chunk-JEHV7PYP.js → chunk-SCGTO3PX.js} +21 -5
  18. package/dist/chunk-SCGTO3PX.js.map +1 -0
  19. package/dist/{chunk-AGO4AA3K.cjs → chunk-XHUV3VSM.cjs} +21 -5
  20. package/dist/chunk-XHUV3VSM.cjs.map +1 -0
  21. package/dist/{chunk-LYG2NLSV.js → chunk-ZPBKRJMH.js} +3 -3
  22. package/dist/{chunk-LYG2NLSV.js.map → chunk-ZPBKRJMH.js.map} +1 -1
  23. package/dist/file-upload.cjs +3 -3
  24. package/dist/file-upload.d.cts +17 -2
  25. package/dist/file-upload.d.ts +17 -2
  26. package/dist/file-upload.js +1 -1
  27. package/dist/index.cjs +14 -9
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +2 -1
  30. package/dist/index.d.ts +2 -1
  31. package/dist/index.js +5 -4
  32. package/dist/index.js.map +1 -1
  33. package/dist/inputs.cjs +6 -1
  34. package/dist/inputs.d.cts +1 -0
  35. package/dist/inputs.d.ts +1 -0
  36. package/dist/inputs.js +2 -1
  37. package/dist/number-field.cjs +30 -0
  38. package/dist/number-field.cjs.map +1 -0
  39. package/dist/number-field.d.cts +48 -0
  40. package/dist/number-field.d.ts +48 -0
  41. package/dist/number-field.js +21 -0
  42. package/dist/number-field.js.map +1 -0
  43. package/dist/tree-nav.cjs +4 -4
  44. package/dist/tree-nav.js +2 -2
  45. package/dist/virtual-list.cjs +2 -2
  46. package/dist/virtual-list.js +1 -1
  47. package/docs/FileUpload.md +32 -2
  48. package/docs/Input.md +2 -0
  49. package/docs/NumberField.md +85 -0
  50. package/package.json +11 -1
  51. package/dist/chunk-AGO4AA3K.cjs.map +0 -1
  52. package/dist/chunk-ARNFT23K.js.map +0 -1
  53. package/dist/chunk-JEHV7PYP.js.map +0 -1
  54. package/dist/chunk-MF2DCMOA.cjs.map +0 -1
  55. package/dist/chunk-O5IO7TCO.cjs +0 -4
  56. package/dist/chunk-VXHFR5MV.js +0 -3
package/CHANGELOG.md CHANGED
@@ -11,6 +11,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [6.7.0] - 2026-11-03
15
+
16
+ ### Added
17
+
18
+ - **`FileUpload` reports what it refuses: `onReject(rejections)`.** A file failing `accept` or `maxSize`, the overflow past `maxFiles`, and the extra files of a batch dropped on a `multiple={false}` field were all dropped in silence, leaving a host nothing to tell the person with. Each entry is plain JSON (`{ name, size, type, reason }`, with `reason` one of `"accept"`, `"maxSize"`, `"maxFiles"`), one per file under the first rule it failed. It covers the picker, a drop and `useFileUpload().addFiles`, fires after `onChange`, and does not fire when nothing was refused. New types: `FileRejection`, `FileRejectionReason`. **Consumer action:** none. Nothing changes unless you pass it.
19
+ - **`NumberField`: a numeric field whose value is `number | null`.** Blank is `null`, never `0`. Partial text (`-`, `.`, `1e`) and unreadable text (`0x10`, `1,000`) are `null`, never a wrong number. The mouse wheel does not change it. `min` and `max` stop the arrow keys and report a typed value outside them as invalid through native form validation, without rewriting it. A declared `step` moves the arrow keys and makes off-grid values invalid. Takes a unit through the existing `suffix` prop, posts the number (not the text) under `name`, and has a `./number-field` subpath. **Consumer action:** none. `<Input type="number">` is unchanged.
20
+
21
+ ## [6.6.1] - 2026-11-03
22
+
23
+ ### Fixed
24
+
25
+ - **`VirtualList` no longer pauses `followTail` by itself when its viewport grows.** With no wheel, key or touch, growing the viewport emitted `onFollowChange(false)`. The browser clamps `scrollTop` to the new maximum, and once the page has seen a pointer event Chromium delivers that scroll event before the `ResizeObserver` callback, so the list compared the clamped position with the height from its last commit and read an upward scroll. A consumer whose "Back to latest" takes layout space then lost that space from the viewport, which left a gap and the newest row clipped, and nothing moved it back. The list now judges a scroll against the viewport as it is laid out at that moment, and turns the browser's scroll anchoring off while following (`overflow-anchor: none`), because a width reflow made Chromium move `scrollTop` up by itself in the same way. A paused list keeps anchoring. Reported by Maipa on 5.71.3. Wheel, keyboard, touch and scrollbar drags still pause, and "Back to latest" still resumes. **Consumer action:** none.
26
+ - **A clamp no longer resumes a paused `VirtualList`.** A reader paused just above the bottom was dragged to it by a growing viewport, and that asked for `onFollowChange(true)`. Only moving down to the bottom asks now. **Consumer action:** none.
27
+ - **`ArrowUp` at the bottom pauses a `VirtualList`.** The key asked for `onFollowChange(false)`, then the first small step of the browser's smooth scroll landed inside the 4px bottom slack and asked for `true`, so it never paused. The same change fixes it. **Consumer action:** none.
28
+
14
29
  ## [6.6.0] - 2026-11-02
15
30
 
16
31
  ### Fixed
@@ -22,7 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
22
37
 
23
38
  - **Token spans read CSS variables instead of carrying hex colours** (`style="color:var(--fc-keyword,var(--fc-keyword-l))"`), and the root's `background-color` / `color` / `border-color` are `var()` references too. **Consumer action:** none for rendering. If a test asserted a literal colour on a `CodeBlock` / `CodeView` element (e.g. `style.backgroundColor === "rgb(24, 24, 27)"` under `.dark`), assert the `--fc-*` variables instead; jsdom does not resolve `var()`. To recolour a slot, set its variables on the component: `style={{ "--fc-keyword-l": "...", "--fc-keyword-d": "..." }}`.
24
39
  - **The internal `useIsDark` hook is removed.** It was never exported. Nothing in either component reads `matchMedia` or the document's class any more.
25
- - **Requires `@particle-academy/fancy-file-commons` `>=0.5`** (still `<2.0`), the first release with `highlightCodeWithVariables`, `themeVariables` and `tokenize`'s `snippet` option. npm installs it for you. **Consumer action:** none. *(Release step, not yet applied: raise the `dependencies` range from `>=0.4` together with the lockfile once 0.5.0 is on the registry.)*
40
+ - **Requires `@particle-academy/fancy-file-commons` `>=0.5`** (still `<2.0`), the first release with `highlightCodeWithVariables`, `themeVariables` and `tokenize`'s `snippet` option. npm installs it for you. **Consumer action:** none.
26
41
 
27
42
  ## [6.5.0] - 2026-11-02
28
43
 
package/README.md CHANGED
@@ -103,6 +103,7 @@ npx vite build # Build demo app (verifies imports work)
103
103
  | Checkbox / CheckboxGroup | Checkbox inputs | [docs](docs/Checkbox.md) |
104
104
  | RadioGroup | Radio button group | [docs](docs/RadioGroup.md) |
105
105
  | Switch | Toggle switch | [docs](docs/Switch.md) |
106
+ | NumberField | Numeric input with a `number \| null` value: blank stays null, partial text never emits a wrong number, no wheel changes | [docs](docs/NumberField.md) |
106
107
  | Slider | Range slider (single and range modes) | [docs](docs/Slider.md) |
107
108
  | MultiSwitch | Segmented toggle between multiple options | [docs](docs/MultiSwitch.md) |
108
109
  | DatePicker | Date selection (single and range modes) | [docs](docs/DatePicker.md) |
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkMF2DCMOA_cjs = require('./chunk-MF2DCMOA.cjs');
3
+ var chunk2TLAZ5MX_cjs = require('./chunk-2TLAZ5MX.cjs');
4
4
  var chunkZU5Q77OT_cjs = require('./chunk-ZU5Q77OT.cjs');
5
5
  var react = require('react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
@@ -484,7 +484,7 @@ function TreeNavRoot({
484
484
  className: chunkZU5Q77OT_cjs.cn("flex flex-col gap-0.5 py-1 text-sm", className),
485
485
  onDragEnd: dropEnabled ? handleDragEnd : void 0,
486
486
  children: virtualize ? /* @__PURE__ */ jsxRuntime.jsx(
487
- chunkMF2DCMOA_cjs.VirtualList,
487
+ chunk2TLAZ5MX_cjs.VirtualList,
488
488
  {
489
489
  items: rows,
490
490
  height,
@@ -502,5 +502,5 @@ var TreeNav = Object.assign(TreeNavRoot, {
502
502
 
503
503
  exports.TreeNav = TreeNav;
504
504
  exports.useTreeNav = useTreeNav;
505
- //# sourceMappingURL=chunk-L5AI4MGV.cjs.map
506
- //# sourceMappingURL=chunk-L5AI4MGV.cjs.map
505
+ //# sourceMappingURL=chunk-25HRO7HQ.cjs.map
506
+ //# sourceMappingURL=chunk-25HRO7HQ.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/TreeNav/TreeNav.context.ts","../src/components/TreeNav/tree-store.ts","../src/components/TreeNav/TreeNode.tsx","../src/components/TreeNav/TreeNav.tsx"],"names":["createContext","useContext","useSyncExternalStore","jsxs","jsx","cn","useRef","useCallback","useEffect","memo","useState","useMemo","useLayoutEffect","VirtualList"],"mappings":"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","file":"chunk-L5AI4MGV.cjs","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { TreeNavContextValue } from \"./TreeNav.types\";\n\nexport const TreeNavContext = createContext<TreeNavContextValue | null>(null);\n\nexport function useTreeNav(): TreeNavContextValue {\n const ctx = useContext(TreeNavContext);\n if (!ctx) {\n throw new Error(\"useTreeNav must be used within a <TreeNav> component\");\n }\n return ctx;\n}\n","import { createContext, useContext, useSyncExternalStore } from \"react\";\nimport type { DragState, DropPosition, TreeNodeData } from \"./TreeNav.types\";\n\n/** What changes as someone clicks, toggles and drags — the state rows subscribe to. */\nexport interface TreeState {\n selectedId?: string;\n expanded: ReadonlySet<string>;\n dragState: DragState;\n}\n\nexport interface TreeStore {\n get(): TreeState;\n set(next: TreeState): void;\n subscribe(listener: () => void): () => void;\n}\n\nexport function createTreeStore(initial: TreeState): TreeStore {\n let state = initial;\n const listeners = new Set<() => void>();\n return {\n get: () => state,\n set(next) {\n state = next;\n for (const listener of listeners) listener();\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n };\n}\n\n/**\n * Everything a row needs that does NOT change on a click. Handlers are stable\n * (they read the latest props and state when called), so this value only changes\n * when the tree's data or configuration does.\n *\n * Kept apart from the public `TreeNavContext` on purpose: that one still carries\n * live `selectedId` / `expandedIds` for `useTreeNav()` callers, and any component\n * reading it re-renders on every click. Rows no longer read it — which is the\n * whole fix: a click used to re-render every row in the tree.\n */\nexport interface TreeInternals {\n store: TreeStore;\n nodes: TreeNodeData[];\n select: (node: TreeNodeData) => void;\n toggle: (id: string) => void;\n expandNode: (id: string) => void;\n setDragState: (state: DragState) => void;\n contextMenu?: (e: React.MouseEvent, node: TreeNodeData) => void;\n moveNode?: (sourceId: string, targetId: string, position: DropPosition) => void;\n externalDrop?: (event: React.DragEvent, target: TreeNodeData, position: DropPosition) => void;\n indentSize: number;\n showIcons: boolean;\n draggable: boolean;\n dragCursor: \"grab\" | \"none\";\n acceptExternalDrops: boolean;\n /** Rows are rendered flat by a virtual list, so a row never renders its children. */\n flat: boolean;\n}\n\nexport const TreeInternalsContext = createContext<TreeInternals | null>(null);\n\nexport function useTreeInternals(): TreeInternals {\n const internals = useContext(TreeInternalsContext);\n if (!internals) throw new Error(\"TreeNav.Node must be used within a <TreeNav> component\");\n return internals;\n}\n\n/** Subscribe to ONE derived value; the row re-renders only when that value changes. */\nexport function useTreeSlice<T>(select: (state: TreeState) => T): T {\n const { store } = useTreeInternals();\n return useSyncExternalStore(\n store.subscribe,\n () => select(store.get()),\n () => select(store.get()),\n );\n}\n","import { memo, useRef, useEffect, useCallback } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useTreeInternals, useTreeSlice } from \"./tree-store\";\nimport type { TreeNodeProps, TreeNodeData, DropPosition } from \"./TreeNav.types\";\n\nconst EXT_COLORS: Record<string, string> = {\n ts: \"#3178c6\",\n tsx: \"#3178c6\",\n js: \"#f7df1e\",\n jsx: \"#f7df1e\",\n php: \"#777bb4\",\n html: \"#e34c26\",\n htm: \"#e34c26\",\n css: \"#264de4\",\n json: \"#a1a1aa\",\n md: \"#71717a\",\n yaml: \"#cb171e\",\n yml: \"#cb171e\",\n};\n\nfunction FileIcon({ ext }: { ext?: string }) {\n const color = (ext && EXT_COLORS[ext.toLowerCase()]) || \"#71717a\";\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M4 1h5.5L13 4.5V14a1 1 0 01-1 1H4a1 1 0 01-1-1V2a1 1 0 011-1z\" stroke={color} strokeWidth=\"1.2\" />\n <path d=\"M9 1v4h4\" stroke={color} strokeWidth=\"1.2\" />\n </svg>\n );\n}\n\nfunction FolderIcon({ open }: { open: boolean }) {\n if (open) {\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M1.5 3.5a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1V5H2.5V3.5z\" fill=\"#fbbf24\" />\n <path d=\"M1 6h13l-1.5 7.5H2.5L1 6z\" fill=\"#fbbf24\" opacity=\"0.7\" />\n </svg>\n );\n }\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M1.5 3a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1v8a1 1 0 01-1 1H2.5a1 1 0 01-1-1V3z\" fill=\"#fbbf24\" />\n </svg>\n );\n}\n\nfunction ChevronIcon({ open }: { open: boolean }) {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={cn(\"shrink-0 transition-transform duration-150\", open && \"rotate-90\")}\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\n// ---------------------------------------------------------------------------\n\nfunction isDescendantOf(\n nodes: TreeNodeData[],\n ancestorId: string,\n targetId: string,\n): boolean {\n function findNode(haystack: TreeNodeData[], id: string): TreeNodeData | undefined {\n for (const n of haystack) {\n if (n.id === id) return n;\n if (n.children) {\n const found = findNode(n.children, id);\n if (found) return found;\n }\n }\n return undefined;\n }\n\n function hasDescendant(node: TreeNodeData, id: string): boolean {\n if (!node.children) return false;\n for (const child of node.children) {\n if (child.id === id) return true;\n if (hasDescendant(child, id)) return true;\n }\n return false;\n }\n\n const ancestor = findNode(nodes, ancestorId);\n return ancestor ? hasDescendant(ancestor, targetId) : false;\n}\n\nfunction computeDropPosition(\n e: React.DragEvent,\n isFolder: boolean,\n): DropPosition {\n const rect = e.currentTarget.getBoundingClientRect();\n const offsetY = e.clientY - rect.top;\n const third = rect.height / 3;\n\n if (offsetY < third) return \"before\";\n if (offsetY > third * 2) return \"after\";\n return isFolder ? \"inside\" : \"after\";\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\n\n/**\n * One row (and, when nested, its expanded children).\n *\n * Reads what changes on a click — selection, expansion, drag — as per-row slices\n * of a store, so a click re-renders only the rows whose slice changed. It used to\n * read one context holding all of it, so every click re-rendered every row:\n * 175-267 ms per click on a 5,000-node tree. `memo` keeps a parent's re-render\n * from reaching rows whose `node` and `depth` did not change.\n */\nfunction TreeNodeImpl({ node, depth }: TreeNodeProps) {\n const {\n store, nodes, select, toggle, expandNode, setDragState, contextMenu, moveNode, externalDrop,\n indentSize, showIcons, draggable, dragCursor, acceptExternalDrops, flat,\n } = useTreeInternals();\n\n const isFolder = node.type === \"folder\" || (node.children && node.children.length > 0);\n const isExpanded = useTreeSlice((s) => s.expanded.has(node.id));\n const isSelected = useTreeSlice((s) => s.selectedId === node.id);\n const isDragging = useTreeSlice((s) => s.dragState.draggedNodeId === node.id);\n const dropPosition = useTreeSlice((s) =>\n s.dragState.dropTargetId === node.id ? s.dragState.dropPosition : null,\n );\n const isDropTarget = dropPosition !== null;\n const paddingLeft = depth * indentSize + 4;\n\n // Auto-expand timer for folders during drag\n const autoExpandTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearAutoExpand = useCallback(() => {\n if (autoExpandTimer.current) {\n clearTimeout(autoExpandTimer.current);\n autoExpandTimer.current = null;\n }\n }, []);\n\n useEffect(() => clearAutoExpand, [clearAutoExpand]);\n\n // ---------------------------------------------------------------------------\n // Click handlers\n // ---------------------------------------------------------------------------\n\n const handleClick = () => {\n if (node.disabled) return;\n if (isFolder) {\n toggle(node.id);\n }\n select(node);\n };\n\n const handleContextMenu = (e: React.MouseEvent) => {\n if (contextMenu) {\n e.preventDefault();\n contextMenu(e, node);\n }\n };\n\n // ---------------------------------------------------------------------------\n // Drag source handlers\n // ---------------------------------------------------------------------------\n\n const handleDragStart = useCallback((e: React.DragEvent) => {\n e.dataTransfer.setData(\"text/plain\", node.id);\n e.dataTransfer.effectAllowed = \"move\";\n // Set state async so the browser captures the element for the drag ghost first\n requestAnimationFrame(() => {\n setDragState({ draggedNodeId: node.id, dropTargetId: null, dropPosition: null });\n });\n }, [node.id, setDragState]);\n\n const handleDragEnd = useCallback(() => {\n clearAutoExpand();\n setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });\n }, [clearAutoExpand, setDragState]);\n\n // ---------------------------------------------------------------------------\n // Drop target handlers (on outer div). Drag state is read from the store when\n // the event fires, so a row does not re-render as a drag moves elsewhere.\n // ---------------------------------------------------------------------------\n\n const handleDragOver = useCallback((e: React.DragEvent) => {\n const { dragState } = store.get();\n const isExternalDrag = !dragState.draggedNodeId;\n if (isExternalDrag) {\n // External drop accepted only when host opted in.\n if (!acceptExternalDrops) return;\n } else {\n const sourceId = dragState.draggedNodeId!;\n if (sourceId === node.id) return;\n if (isDescendantOf(nodes, sourceId, node.id)) return;\n }\n\n e.preventDefault();\n e.stopPropagation();\n e.dataTransfer.dropEffect = isExternalDrag ? \"copy\" : \"move\";\n\n const position = computeDropPosition(e, !!isFolder);\n\n // Auto-expand collapsed folders when hovering \"inside\"\n if (isFolder && !store.get().expanded.has(node.id) && position === \"inside\") {\n if (!autoExpandTimer.current) {\n autoExpandTimer.current = setTimeout(() => {\n expandNode(node.id);\n autoExpandTimer.current = null;\n }, 500);\n }\n } else {\n clearAutoExpand();\n }\n\n if (dragState.dropTargetId !== node.id || dragState.dropPosition !== position) {\n setDragState({ draggedNodeId: dragState.draggedNodeId, dropTargetId: node.id, dropPosition: position });\n }\n }, [store, acceptExternalDrops, node.id, isFolder, nodes, setDragState, expandNode, clearAutoExpand]);\n\n const handleDragLeave = useCallback((e: React.DragEvent) => {\n if (!e.currentTarget.contains(e.relatedTarget as Node)) {\n clearAutoExpand();\n const { dragState } = store.get();\n if (dragState.dropTargetId === node.id) {\n setDragState({ ...dragState, dropTargetId: null, dropPosition: null });\n }\n }\n }, [store, node.id, setDragState, clearAutoExpand]);\n\n const handleDrop = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n clearAutoExpand();\n\n const { dragState } = store.get();\n const sourceId = dragState.draggedNodeId;\n const position = dragState.dropPosition ?? computeDropPosition(e, !!isFolder);\n\n if (sourceId) {\n // Internal node move.\n if (sourceId === node.id) return;\n if (isDescendantOf(nodes, sourceId, node.id)) return;\n moveNode?.(sourceId, node.id, position);\n } else if (acceptExternalDrops) {\n // External drop — files, items from other components, etc.\n externalDrop?.(e, node, position);\n }\n\n setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });\n }, [store, node, isFolder, nodes, moveNode, acceptExternalDrops, externalDrop, setDragState, clearAutoExpand]);\n\n // ---------------------------------------------------------------------------\n // Render\n // ---------------------------------------------------------------------------\n\n const canDrag = draggable && !node.disabled;\n const dropEnabled = draggable || acceptExternalDrops;\n\n return (\n <div\n data-react-fancy-tree-node=\"\"\n onDragOver={dropEnabled ? handleDragOver : undefined}\n onDragLeave={dropEnabled ? handleDragLeave : undefined}\n onDrop={dropEnabled ? handleDrop : undefined}\n >\n {/* Before drop indicator */}\n {isDropTarget && dropPosition === \"before\" && (\n <div\n data-react-fancy-tree-drop-indicator=\"before\"\n className=\"pointer-events-none h-0.5 rounded-full bg-blue-500\"\n style={{ marginLeft: paddingLeft }}\n />\n )}\n\n <button\n type=\"button\"\n draggable={canDrag}\n onDragStart={canDrag ? handleDragStart : undefined}\n onDragEnd={canDrag ? handleDragEnd : undefined}\n onClick={handleClick}\n onContextMenu={handleContextMenu}\n disabled={node.disabled}\n className={cn(\n \"flex w-full items-center gap-1 rounded-md py-0.5 text-left text-[13px] transition-colors\",\n isSelected\n ? \"bg-blue-500/15 text-blue-600 dark:text-blue-400\"\n : \"text-[color:var(--fancy-text-soft,var(--color-zinc-700))] hover:bg-[color:var(--fancy-surface-muted,var(--color-zinc-100))] dark:text-[color:var(--fancy-text-soft-dark,var(--color-zinc-300))] dark:hover:bg-[color:var(--fancy-surface-muted-dark,var(--color-zinc-800))]\",\n node.disabled && \"pointer-events-none opacity-40\",\n isDragging && \"opacity-50\",\n // Grab affordance. By default (`dragCursor=\"none\"`) the row keeps its\n // normal click cursor on hover — no open-hand \"move me\" cursor before\n // any drag — and only shows the closed-hand `grabbing` cursor while a\n // drag is actually underway (mousedown/drag). `dragCursor=\"grab\"` opts\n // back into the always-on grab cursor for whole-row drag handles (#14).\n canDrag &&\n (dragCursor === \"grab\" ? \"cursor-grab active:cursor-grabbing\" : \"active:cursor-grabbing\"),\n isDropTarget && dropPosition === \"inside\" && \"bg-blue-500/10 ring-1 ring-blue-500/30 ring-inset\",\n )}\n style={{ paddingLeft }}\n >\n {isFolder && <ChevronIcon open={isExpanded} />}\n {!isFolder && <span className=\"w-3.5 shrink-0\" />}\n {showIcons && (\n node.icon ?? (isFolder\n ? <FolderIcon open={isExpanded} />\n : <FileIcon ext={node.ext ?? node.label.split(\".\").pop()} />)\n )}\n <span className=\"truncate\">{node.label}</span>\n </button>\n\n {/* After drop indicator */}\n {isDropTarget && dropPosition === \"after\" && (\n <div\n data-react-fancy-tree-drop-indicator=\"after\"\n className=\"pointer-events-none h-0.5 rounded-full bg-blue-500\"\n style={{ marginLeft: paddingLeft }}\n />\n )}\n\n {/* A virtual list renders rows flat, so a row never renders its own children there. */}\n {!flat && isFolder && isExpanded && node.children && (\n <div data-react-fancy-tree-node-children=\"\">\n {node.children.map((child) => (\n <TreeNode key={child.id} node={child} depth={depth + 1} />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nexport const TreeNode = memo(TreeNodeImpl);\n\nTreeNode.displayName = \"TreeNode\";\n","import { useMemo, useCallback, useLayoutEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { VirtualList } from \"../VirtualList\";\nimport { TreeNavContext } from \"./TreeNav.context\";\nimport { TreeNode } from \"./TreeNode\";\nimport { createTreeStore, TreeInternalsContext, type TreeInternals } from \"./tree-store\";\nimport type { TreeNavProps, TreeNodeData, DragState } from \"./TreeNav.types\";\n\nfunction collectFolderIds(nodes: TreeNodeData[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n if (node.children && node.children.length > 0) {\n ids.push(node.id);\n ids.push(...collectFolderIds(node.children));\n }\n }\n return ids;\n}\n\nconst EMPTY_DRAG_STATE: DragState = {\n draggedNodeId: null,\n dropTargetId: null,\n dropPosition: null,\n};\n\ninterface FlatRow {\n id: string;\n node: TreeNodeData;\n depth: number;\n}\n\n/** The rows currently visible — every node, descending only into expanded folders. */\nfunction flattenVisible(nodes: TreeNodeData[], expanded: ReadonlySet<string>): FlatRow[] {\n const rows: FlatRow[] = [];\n const walk = (list: TreeNodeData[], depth: number) => {\n for (const node of list) {\n rows.push({ id: node.id, node, depth });\n const isFolder = node.type === \"folder\" || (node.children && node.children.length > 0);\n if (isFolder && node.children && expanded.has(node.id)) walk(node.children, depth + 1);\n }\n };\n walk(nodes, 0);\n return rows;\n}\n\nfunction TreeNavRoot({\n nodes,\n selectedId,\n onSelect,\n onNodeContextMenu,\n draggable = false,\n onNodeMove,\n acceptExternalDrops = false,\n onExternalDrop,\n expandedIds: controlledExpanded,\n defaultExpandedIds,\n onExpandedChange,\n defaultExpandAll = false,\n indentSize = 16,\n showIcons = true,\n dragCursor = \"none\",\n virtualize = false,\n height = 400,\n rowHeight = 26,\n className,\n}: TreeNavProps) {\n const [internalExpanded, setInternalExpanded] = useState<string[]>(() => {\n if (defaultExpandedIds) return defaultExpandedIds;\n if (defaultExpandAll) return collectFolderIds(nodes);\n return [];\n });\n\n const isControlled = controlledExpanded !== undefined;\n const expandedIds = isControlled ? controlledExpanded : internalExpanded;\n // One Set per change, so a row's \"am I expanded?\" is a lookup, not a scan.\n const expanded = useMemo(() => new Set(expandedIds), [expandedIds]);\n\n const [dragState, setDragState] = useState<DragState>(EMPTY_DRAG_STATE);\n\n /*\n * The latest props and state, for handlers that must stay STABLE. A handler\n * whose identity changes on every click would change the context rows read,\n * and re-render every row — the exact cost this component is avoiding.\n */\n const latest = useRef({ expandedIds, isControlled, onExpandedChange, onSelect, onNodeContextMenu, onNodeMove, onExternalDrop });\n latest.current = { expandedIds, isControlled, onExpandedChange, onSelect, onNodeContextMenu, onNodeMove, onExternalDrop };\n\n const setExpanded = useCallback((next: string[]) => {\n if (!latest.current.isControlled) setInternalExpanded(next);\n latest.current.onExpandedChange?.(next);\n }, []);\n\n const toggle = useCallback(\n (id: string) => {\n const current = latest.current.expandedIds;\n setExpanded(current.includes(id) ? current.filter((v) => v !== id) : [...current, id]);\n },\n [setExpanded],\n );\n\n const expandNode = useCallback(\n (id: string) => {\n const current = latest.current.expandedIds;\n if (!current.includes(id)) setExpanded([...current, id]);\n },\n [setExpanded],\n );\n\n const select = useCallback((node: TreeNodeData) => latest.current.onSelect?.(node.id, node), []);\n const contextMenu = useCallback(\n (e: React.MouseEvent, node: TreeNodeData) => latest.current.onNodeContextMenu?.(e, node),\n [],\n );\n const moveNode = useCallback(\n (sourceId: string, targetId: string, position: Parameters<NonNullable<TreeNavProps[\"onNodeMove\"]>>[2]) =>\n latest.current.onNodeMove?.(sourceId, targetId, position),\n [],\n );\n const externalDrop = useCallback(\n (event: React.DragEvent, target: TreeNodeData, position: Parameters<NonNullable<TreeNavProps[\"onExternalDrop\"]>>[2]) =>\n latest.current.onExternalDrop?.(event, target, position),\n [],\n );\n\n // What rows subscribe to. Seeded with this render's values so the first paint\n // is right; kept current after each render that changes them.\n const [store] = useState(() => createTreeStore({ selectedId, expanded, dragState }));\n useLayoutEffect(() => {\n const current = store.get();\n if (current.selectedId !== selectedId || current.expanded !== expanded || current.dragState !== dragState) {\n store.set({ selectedId, expanded, dragState });\n }\n }, [store, selectedId, expanded, dragState]);\n\n const hasContextMenu = onNodeContextMenu !== undefined;\n const internals = useMemo<TreeInternals>(\n () => ({\n store,\n nodes,\n select,\n toggle,\n expandNode,\n setDragState,\n // Present only when the host gave one: a row then claims the right-click.\n contextMenu: hasContextMenu ? contextMenu : undefined,\n moveNode,\n externalDrop,\n indentSize,\n showIcons,\n draggable,\n dragCursor,\n acceptExternalDrops,\n flat: virtualize,\n }),\n [store, nodes, select, toggle, expandNode, hasContextMenu, contextMenu, moveNode, externalDrop,\n indentSize, showIcons, draggable, dragCursor, acceptExternalDrops, virtualize],\n );\n\n // Unchanged public context, still live for `useTreeNav()` callers. Rows no\n // longer read it, so its changing on a click re-renders only those callers.\n const ctx = useMemo(\n () => ({\n selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\n draggable, dragCursor, dragState, setDragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode,\n }),\n [selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\n draggable, dragCursor, dragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode],\n );\n\n const rows = useMemo(\n () => (virtualize ? flattenVisible(nodes, expanded) : []),\n [virtualize, nodes, expanded],\n );\n\n const handleDragEnd = useCallback(() => {\n setDragState(EMPTY_DRAG_STATE);\n }, []);\n\n const dropEnabled = draggable || acceptExternalDrops;\n\n return (\n <TreeNavContext.Provider value={ctx}>\n <TreeInternalsContext.Provider value={internals}>\n <nav\n data-react-fancy-tree-nav=\"\"\n data-react-fancy-tree-nav-virtual={virtualize ? \"\" : undefined}\n className={cn(\"flex flex-col gap-0.5 py-1 text-sm\", className)}\n onDragEnd={dropEnabled ? handleDragEnd : undefined}\n >\n {virtualize ? (\n <VirtualList\n items={rows}\n height={height}\n estimateRowHeight={rowHeight}\n renderRow={(row) => <TreeNode node={row.node} depth={row.depth} />}\n />\n ) : (\n nodes.map((node) => <TreeNode key={node.id} node={node} depth={0} />)\n )}\n </nav>\n </TreeInternalsContext.Provider>\n </TreeNavContext.Provider>\n );\n}\n\nTreeNavRoot.displayName = \"TreeNav\";\n\nexport const TreeNav = Object.assign(TreeNavRoot, {\n Node: TreeNode,\n});\n"]}
1
+ {"version":3,"sources":["../src/components/TreeNav/TreeNav.context.ts","../src/components/TreeNav/tree-store.ts","../src/components/TreeNav/TreeNode.tsx","../src/components/TreeNav/TreeNav.tsx"],"names":["createContext","useContext","useSyncExternalStore","jsxs","jsx","cn","useRef","useCallback","useEffect","memo","useState","useMemo","useLayoutEffect","VirtualList"],"mappings":"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","file":"chunk-25HRO7HQ.cjs","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { TreeNavContextValue } from \"./TreeNav.types\";\n\nexport const TreeNavContext = createContext<TreeNavContextValue | null>(null);\n\nexport function useTreeNav(): TreeNavContextValue {\n const ctx = useContext(TreeNavContext);\n if (!ctx) {\n throw new Error(\"useTreeNav must be used within a <TreeNav> component\");\n }\n return ctx;\n}\n","import { createContext, useContext, useSyncExternalStore } from \"react\";\nimport type { DragState, DropPosition, TreeNodeData } from \"./TreeNav.types\";\n\n/** What changes as someone clicks, toggles and drags — the state rows subscribe to. */\nexport interface TreeState {\n selectedId?: string;\n expanded: ReadonlySet<string>;\n dragState: DragState;\n}\n\nexport interface TreeStore {\n get(): TreeState;\n set(next: TreeState): void;\n subscribe(listener: () => void): () => void;\n}\n\nexport function createTreeStore(initial: TreeState): TreeStore {\n let state = initial;\n const listeners = new Set<() => void>();\n return {\n get: () => state,\n set(next) {\n state = next;\n for (const listener of listeners) listener();\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n };\n}\n\n/**\n * Everything a row needs that does NOT change on a click. Handlers are stable\n * (they read the latest props and state when called), so this value only changes\n * when the tree's data or configuration does.\n *\n * Kept apart from the public `TreeNavContext` on purpose: that one still carries\n * live `selectedId` / `expandedIds` for `useTreeNav()` callers, and any component\n * reading it re-renders on every click. Rows no longer read it — which is the\n * whole fix: a click used to re-render every row in the tree.\n */\nexport interface TreeInternals {\n store: TreeStore;\n nodes: TreeNodeData[];\n select: (node: TreeNodeData) => void;\n toggle: (id: string) => void;\n expandNode: (id: string) => void;\n setDragState: (state: DragState) => void;\n contextMenu?: (e: React.MouseEvent, node: TreeNodeData) => void;\n moveNode?: (sourceId: string, targetId: string, position: DropPosition) => void;\n externalDrop?: (event: React.DragEvent, target: TreeNodeData, position: DropPosition) => void;\n indentSize: number;\n showIcons: boolean;\n draggable: boolean;\n dragCursor: \"grab\" | \"none\";\n acceptExternalDrops: boolean;\n /** Rows are rendered flat by a virtual list, so a row never renders its children. */\n flat: boolean;\n}\n\nexport const TreeInternalsContext = createContext<TreeInternals | null>(null);\n\nexport function useTreeInternals(): TreeInternals {\n const internals = useContext(TreeInternalsContext);\n if (!internals) throw new Error(\"TreeNav.Node must be used within a <TreeNav> component\");\n return internals;\n}\n\n/** Subscribe to ONE derived value; the row re-renders only when that value changes. */\nexport function useTreeSlice<T>(select: (state: TreeState) => T): T {\n const { store } = useTreeInternals();\n return useSyncExternalStore(\n store.subscribe,\n () => select(store.get()),\n () => select(store.get()),\n );\n}\n","import { memo, useRef, useEffect, useCallback } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useTreeInternals, useTreeSlice } from \"./tree-store\";\nimport type { TreeNodeProps, TreeNodeData, DropPosition } from \"./TreeNav.types\";\n\nconst EXT_COLORS: Record<string, string> = {\n ts: \"#3178c6\",\n tsx: \"#3178c6\",\n js: \"#f7df1e\",\n jsx: \"#f7df1e\",\n php: \"#777bb4\",\n html: \"#e34c26\",\n htm: \"#e34c26\",\n css: \"#264de4\",\n json: \"#a1a1aa\",\n md: \"#71717a\",\n yaml: \"#cb171e\",\n yml: \"#cb171e\",\n};\n\nfunction FileIcon({ ext }: { ext?: string }) {\n const color = (ext && EXT_COLORS[ext.toLowerCase()]) || \"#71717a\";\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M4 1h5.5L13 4.5V14a1 1 0 01-1 1H4a1 1 0 01-1-1V2a1 1 0 011-1z\" stroke={color} strokeWidth=\"1.2\" />\n <path d=\"M9 1v4h4\" stroke={color} strokeWidth=\"1.2\" />\n </svg>\n );\n}\n\nfunction FolderIcon({ open }: { open: boolean }) {\n if (open) {\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M1.5 3.5a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1V5H2.5V3.5z\" fill=\"#fbbf24\" />\n <path d=\"M1 6h13l-1.5 7.5H2.5L1 6z\" fill=\"#fbbf24\" opacity=\"0.7\" />\n </svg>\n );\n }\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M1.5 3a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1v8a1 1 0 01-1 1H2.5a1 1 0 01-1-1V3z\" fill=\"#fbbf24\" />\n </svg>\n );\n}\n\nfunction ChevronIcon({ open }: { open: boolean }) {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={cn(\"shrink-0 transition-transform duration-150\", open && \"rotate-90\")}\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\n// ---------------------------------------------------------------------------\n\nfunction isDescendantOf(\n nodes: TreeNodeData[],\n ancestorId: string,\n targetId: string,\n): boolean {\n function findNode(haystack: TreeNodeData[], id: string): TreeNodeData | undefined {\n for (const n of haystack) {\n if (n.id === id) return n;\n if (n.children) {\n const found = findNode(n.children, id);\n if (found) return found;\n }\n }\n return undefined;\n }\n\n function hasDescendant(node: TreeNodeData, id: string): boolean {\n if (!node.children) return false;\n for (const child of node.children) {\n if (child.id === id) return true;\n if (hasDescendant(child, id)) return true;\n }\n return false;\n }\n\n const ancestor = findNode(nodes, ancestorId);\n return ancestor ? hasDescendant(ancestor, targetId) : false;\n}\n\nfunction computeDropPosition(\n e: React.DragEvent,\n isFolder: boolean,\n): DropPosition {\n const rect = e.currentTarget.getBoundingClientRect();\n const offsetY = e.clientY - rect.top;\n const third = rect.height / 3;\n\n if (offsetY < third) return \"before\";\n if (offsetY > third * 2) return \"after\";\n return isFolder ? \"inside\" : \"after\";\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\n\n/**\n * One row (and, when nested, its expanded children).\n *\n * Reads what changes on a click — selection, expansion, drag — as per-row slices\n * of a store, so a click re-renders only the rows whose slice changed. It used to\n * read one context holding all of it, so every click re-rendered every row:\n * 175-267 ms per click on a 5,000-node tree. `memo` keeps a parent's re-render\n * from reaching rows whose `node` and `depth` did not change.\n */\nfunction TreeNodeImpl({ node, depth }: TreeNodeProps) {\n const {\n store, nodes, select, toggle, expandNode, setDragState, contextMenu, moveNode, externalDrop,\n indentSize, showIcons, draggable, dragCursor, acceptExternalDrops, flat,\n } = useTreeInternals();\n\n const isFolder = node.type === \"folder\" || (node.children && node.children.length > 0);\n const isExpanded = useTreeSlice((s) => s.expanded.has(node.id));\n const isSelected = useTreeSlice((s) => s.selectedId === node.id);\n const isDragging = useTreeSlice((s) => s.dragState.draggedNodeId === node.id);\n const dropPosition = useTreeSlice((s) =>\n s.dragState.dropTargetId === node.id ? s.dragState.dropPosition : null,\n );\n const isDropTarget = dropPosition !== null;\n const paddingLeft = depth * indentSize + 4;\n\n // Auto-expand timer for folders during drag\n const autoExpandTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearAutoExpand = useCallback(() => {\n if (autoExpandTimer.current) {\n clearTimeout(autoExpandTimer.current);\n autoExpandTimer.current = null;\n }\n }, []);\n\n useEffect(() => clearAutoExpand, [clearAutoExpand]);\n\n // ---------------------------------------------------------------------------\n // Click handlers\n // ---------------------------------------------------------------------------\n\n const handleClick = () => {\n if (node.disabled) return;\n if (isFolder) {\n toggle(node.id);\n }\n select(node);\n };\n\n const handleContextMenu = (e: React.MouseEvent) => {\n if (contextMenu) {\n e.preventDefault();\n contextMenu(e, node);\n }\n };\n\n // ---------------------------------------------------------------------------\n // Drag source handlers\n // ---------------------------------------------------------------------------\n\n const handleDragStart = useCallback((e: React.DragEvent) => {\n e.dataTransfer.setData(\"text/plain\", node.id);\n e.dataTransfer.effectAllowed = \"move\";\n // Set state async so the browser captures the element for the drag ghost first\n requestAnimationFrame(() => {\n setDragState({ draggedNodeId: node.id, dropTargetId: null, dropPosition: null });\n });\n }, [node.id, setDragState]);\n\n const handleDragEnd = useCallback(() => {\n clearAutoExpand();\n setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });\n }, [clearAutoExpand, setDragState]);\n\n // ---------------------------------------------------------------------------\n // Drop target handlers (on outer div). Drag state is read from the store when\n // the event fires, so a row does not re-render as a drag moves elsewhere.\n // ---------------------------------------------------------------------------\n\n const handleDragOver = useCallback((e: React.DragEvent) => {\n const { dragState } = store.get();\n const isExternalDrag = !dragState.draggedNodeId;\n if (isExternalDrag) {\n // External drop accepted only when host opted in.\n if (!acceptExternalDrops) return;\n } else {\n const sourceId = dragState.draggedNodeId!;\n if (sourceId === node.id) return;\n if (isDescendantOf(nodes, sourceId, node.id)) return;\n }\n\n e.preventDefault();\n e.stopPropagation();\n e.dataTransfer.dropEffect = isExternalDrag ? \"copy\" : \"move\";\n\n const position = computeDropPosition(e, !!isFolder);\n\n // Auto-expand collapsed folders when hovering \"inside\"\n if (isFolder && !store.get().expanded.has(node.id) && position === \"inside\") {\n if (!autoExpandTimer.current) {\n autoExpandTimer.current = setTimeout(() => {\n expandNode(node.id);\n autoExpandTimer.current = null;\n }, 500);\n }\n } else {\n clearAutoExpand();\n }\n\n if (dragState.dropTargetId !== node.id || dragState.dropPosition !== position) {\n setDragState({ draggedNodeId: dragState.draggedNodeId, dropTargetId: node.id, dropPosition: position });\n }\n }, [store, acceptExternalDrops, node.id, isFolder, nodes, setDragState, expandNode, clearAutoExpand]);\n\n const handleDragLeave = useCallback((e: React.DragEvent) => {\n if (!e.currentTarget.contains(e.relatedTarget as Node)) {\n clearAutoExpand();\n const { dragState } = store.get();\n if (dragState.dropTargetId === node.id) {\n setDragState({ ...dragState, dropTargetId: null, dropPosition: null });\n }\n }\n }, [store, node.id, setDragState, clearAutoExpand]);\n\n const handleDrop = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n clearAutoExpand();\n\n const { dragState } = store.get();\n const sourceId = dragState.draggedNodeId;\n const position = dragState.dropPosition ?? computeDropPosition(e, !!isFolder);\n\n if (sourceId) {\n // Internal node move.\n if (sourceId === node.id) return;\n if (isDescendantOf(nodes, sourceId, node.id)) return;\n moveNode?.(sourceId, node.id, position);\n } else if (acceptExternalDrops) {\n // External drop — files, items from other components, etc.\n externalDrop?.(e, node, position);\n }\n\n setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });\n }, [store, node, isFolder, nodes, moveNode, acceptExternalDrops, externalDrop, setDragState, clearAutoExpand]);\n\n // ---------------------------------------------------------------------------\n // Render\n // ---------------------------------------------------------------------------\n\n const canDrag = draggable && !node.disabled;\n const dropEnabled = draggable || acceptExternalDrops;\n\n return (\n <div\n data-react-fancy-tree-node=\"\"\n onDragOver={dropEnabled ? handleDragOver : undefined}\n onDragLeave={dropEnabled ? handleDragLeave : undefined}\n onDrop={dropEnabled ? handleDrop : undefined}\n >\n {/* Before drop indicator */}\n {isDropTarget && dropPosition === \"before\" && (\n <div\n data-react-fancy-tree-drop-indicator=\"before\"\n className=\"pointer-events-none h-0.5 rounded-full bg-blue-500\"\n style={{ marginLeft: paddingLeft }}\n />\n )}\n\n <button\n type=\"button\"\n draggable={canDrag}\n onDragStart={canDrag ? handleDragStart : undefined}\n onDragEnd={canDrag ? handleDragEnd : undefined}\n onClick={handleClick}\n onContextMenu={handleContextMenu}\n disabled={node.disabled}\n className={cn(\n \"flex w-full items-center gap-1 rounded-md py-0.5 text-left text-[13px] transition-colors\",\n isSelected\n ? \"bg-blue-500/15 text-blue-600 dark:text-blue-400\"\n : \"text-[color:var(--fancy-text-soft,var(--color-zinc-700))] hover:bg-[color:var(--fancy-surface-muted,var(--color-zinc-100))] dark:text-[color:var(--fancy-text-soft-dark,var(--color-zinc-300))] dark:hover:bg-[color:var(--fancy-surface-muted-dark,var(--color-zinc-800))]\",\n node.disabled && \"pointer-events-none opacity-40\",\n isDragging && \"opacity-50\",\n // Grab affordance. By default (`dragCursor=\"none\"`) the row keeps its\n // normal click cursor on hover — no open-hand \"move me\" cursor before\n // any drag — and only shows the closed-hand `grabbing` cursor while a\n // drag is actually underway (mousedown/drag). `dragCursor=\"grab\"` opts\n // back into the always-on grab cursor for whole-row drag handles (#14).\n canDrag &&\n (dragCursor === \"grab\" ? \"cursor-grab active:cursor-grabbing\" : \"active:cursor-grabbing\"),\n isDropTarget && dropPosition === \"inside\" && \"bg-blue-500/10 ring-1 ring-blue-500/30 ring-inset\",\n )}\n style={{ paddingLeft }}\n >\n {isFolder && <ChevronIcon open={isExpanded} />}\n {!isFolder && <span className=\"w-3.5 shrink-0\" />}\n {showIcons && (\n node.icon ?? (isFolder\n ? <FolderIcon open={isExpanded} />\n : <FileIcon ext={node.ext ?? node.label.split(\".\").pop()} />)\n )}\n <span className=\"truncate\">{node.label}</span>\n </button>\n\n {/* After drop indicator */}\n {isDropTarget && dropPosition === \"after\" && (\n <div\n data-react-fancy-tree-drop-indicator=\"after\"\n className=\"pointer-events-none h-0.5 rounded-full bg-blue-500\"\n style={{ marginLeft: paddingLeft }}\n />\n )}\n\n {/* A virtual list renders rows flat, so a row never renders its own children there. */}\n {!flat && isFolder && isExpanded && node.children && (\n <div data-react-fancy-tree-node-children=\"\">\n {node.children.map((child) => (\n <TreeNode key={child.id} node={child} depth={depth + 1} />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nexport const TreeNode = memo(TreeNodeImpl);\n\nTreeNode.displayName = \"TreeNode\";\n","import { useMemo, useCallback, useLayoutEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { VirtualList } from \"../VirtualList\";\nimport { TreeNavContext } from \"./TreeNav.context\";\nimport { TreeNode } from \"./TreeNode\";\nimport { createTreeStore, TreeInternalsContext, type TreeInternals } from \"./tree-store\";\nimport type { TreeNavProps, TreeNodeData, DragState } from \"./TreeNav.types\";\n\nfunction collectFolderIds(nodes: TreeNodeData[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n if (node.children && node.children.length > 0) {\n ids.push(node.id);\n ids.push(...collectFolderIds(node.children));\n }\n }\n return ids;\n}\n\nconst EMPTY_DRAG_STATE: DragState = {\n draggedNodeId: null,\n dropTargetId: null,\n dropPosition: null,\n};\n\ninterface FlatRow {\n id: string;\n node: TreeNodeData;\n depth: number;\n}\n\n/** The rows currently visible — every node, descending only into expanded folders. */\nfunction flattenVisible(nodes: TreeNodeData[], expanded: ReadonlySet<string>): FlatRow[] {\n const rows: FlatRow[] = [];\n const walk = (list: TreeNodeData[], depth: number) => {\n for (const node of list) {\n rows.push({ id: node.id, node, depth });\n const isFolder = node.type === \"folder\" || (node.children && node.children.length > 0);\n if (isFolder && node.children && expanded.has(node.id)) walk(node.children, depth + 1);\n }\n };\n walk(nodes, 0);\n return rows;\n}\n\nfunction TreeNavRoot({\n nodes,\n selectedId,\n onSelect,\n onNodeContextMenu,\n draggable = false,\n onNodeMove,\n acceptExternalDrops = false,\n onExternalDrop,\n expandedIds: controlledExpanded,\n defaultExpandedIds,\n onExpandedChange,\n defaultExpandAll = false,\n indentSize = 16,\n showIcons = true,\n dragCursor = \"none\",\n virtualize = false,\n height = 400,\n rowHeight = 26,\n className,\n}: TreeNavProps) {\n const [internalExpanded, setInternalExpanded] = useState<string[]>(() => {\n if (defaultExpandedIds) return defaultExpandedIds;\n if (defaultExpandAll) return collectFolderIds(nodes);\n return [];\n });\n\n const isControlled = controlledExpanded !== undefined;\n const expandedIds = isControlled ? controlledExpanded : internalExpanded;\n // One Set per change, so a row's \"am I expanded?\" is a lookup, not a scan.\n const expanded = useMemo(() => new Set(expandedIds), [expandedIds]);\n\n const [dragState, setDragState] = useState<DragState>(EMPTY_DRAG_STATE);\n\n /*\n * The latest props and state, for handlers that must stay STABLE. A handler\n * whose identity changes on every click would change the context rows read,\n * and re-render every row — the exact cost this component is avoiding.\n */\n const latest = useRef({ expandedIds, isControlled, onExpandedChange, onSelect, onNodeContextMenu, onNodeMove, onExternalDrop });\n latest.current = { expandedIds, isControlled, onExpandedChange, onSelect, onNodeContextMenu, onNodeMove, onExternalDrop };\n\n const setExpanded = useCallback((next: string[]) => {\n if (!latest.current.isControlled) setInternalExpanded(next);\n latest.current.onExpandedChange?.(next);\n }, []);\n\n const toggle = useCallback(\n (id: string) => {\n const current = latest.current.expandedIds;\n setExpanded(current.includes(id) ? current.filter((v) => v !== id) : [...current, id]);\n },\n [setExpanded],\n );\n\n const expandNode = useCallback(\n (id: string) => {\n const current = latest.current.expandedIds;\n if (!current.includes(id)) setExpanded([...current, id]);\n },\n [setExpanded],\n );\n\n const select = useCallback((node: TreeNodeData) => latest.current.onSelect?.(node.id, node), []);\n const contextMenu = useCallback(\n (e: React.MouseEvent, node: TreeNodeData) => latest.current.onNodeContextMenu?.(e, node),\n [],\n );\n const moveNode = useCallback(\n (sourceId: string, targetId: string, position: Parameters<NonNullable<TreeNavProps[\"onNodeMove\"]>>[2]) =>\n latest.current.onNodeMove?.(sourceId, targetId, position),\n [],\n );\n const externalDrop = useCallback(\n (event: React.DragEvent, target: TreeNodeData, position: Parameters<NonNullable<TreeNavProps[\"onExternalDrop\"]>>[2]) =>\n latest.current.onExternalDrop?.(event, target, position),\n [],\n );\n\n // What rows subscribe to. Seeded with this render's values so the first paint\n // is right; kept current after each render that changes them.\n const [store] = useState(() => createTreeStore({ selectedId, expanded, dragState }));\n useLayoutEffect(() => {\n const current = store.get();\n if (current.selectedId !== selectedId || current.expanded !== expanded || current.dragState !== dragState) {\n store.set({ selectedId, expanded, dragState });\n }\n }, [store, selectedId, expanded, dragState]);\n\n const hasContextMenu = onNodeContextMenu !== undefined;\n const internals = useMemo<TreeInternals>(\n () => ({\n store,\n nodes,\n select,\n toggle,\n expandNode,\n setDragState,\n // Present only when the host gave one: a row then claims the right-click.\n contextMenu: hasContextMenu ? contextMenu : undefined,\n moveNode,\n externalDrop,\n indentSize,\n showIcons,\n draggable,\n dragCursor,\n acceptExternalDrops,\n flat: virtualize,\n }),\n [store, nodes, select, toggle, expandNode, hasContextMenu, contextMenu, moveNode, externalDrop,\n indentSize, showIcons, draggable, dragCursor, acceptExternalDrops, virtualize],\n );\n\n // Unchanged public context, still live for `useTreeNav()` callers. Rows no\n // longer read it, so its changing on a click re-renders only those callers.\n const ctx = useMemo(\n () => ({\n selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\n draggable, dragCursor, dragState, setDragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode,\n }),\n [selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\n draggable, dragCursor, dragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode],\n );\n\n const rows = useMemo(\n () => (virtualize ? flattenVisible(nodes, expanded) : []),\n [virtualize, nodes, expanded],\n );\n\n const handleDragEnd = useCallback(() => {\n setDragState(EMPTY_DRAG_STATE);\n }, []);\n\n const dropEnabled = draggable || acceptExternalDrops;\n\n return (\n <TreeNavContext.Provider value={ctx}>\n <TreeInternalsContext.Provider value={internals}>\n <nav\n data-react-fancy-tree-nav=\"\"\n data-react-fancy-tree-nav-virtual={virtualize ? \"\" : undefined}\n className={cn(\"flex flex-col gap-0.5 py-1 text-sm\", className)}\n onDragEnd={dropEnabled ? handleDragEnd : undefined}\n >\n {virtualize ? (\n <VirtualList\n items={rows}\n height={height}\n estimateRowHeight={rowHeight}\n renderRow={(row) => <TreeNode node={row.node} depth={row.depth} />}\n />\n ) : (\n nodes.map((node) => <TreeNode key={node.id} node={node} depth={0} />)\n )}\n </nav>\n </TreeInternalsContext.Provider>\n </TreeNavContext.Provider>\n );\n}\n\nTreeNavRoot.displayName = \"TreeNav\";\n\nexport const TreeNav = Object.assign(TreeNavRoot, {\n Node: TreeNode,\n});\n"]}
@@ -193,12 +193,14 @@ function VirtualList({
193
193
  knownTop.current = next;
194
194
  const previous = scrollTop;
195
195
  setScrollTop(next);
196
- const bottom = Math.max(0, total - viewport);
196
+ const live = vp.clientHeight;
197
+ if (live > 0) setClientHeight(live);
198
+ const bottom = Math.max(0, total - (live || viewport));
197
199
  const wasProgrammatic = programmatic.current;
198
200
  programmatic.current = false;
199
201
  if (next <= bottom - BOTTOM_SLACK) {
200
202
  if (!wasProgrammatic && next < previous) askFollow(false);
201
- } else {
203
+ } else if (next > previous) {
202
204
  askFollow(true);
203
205
  }
204
206
  }, [scrollTop, total, viewport, askFollow]);
@@ -245,7 +247,7 @@ function VirtualList({
245
247
  role: "log",
246
248
  tabIndex: 0,
247
249
  className: chunkZU5Q77OT_cjs.cn("relative overflow-y-auto overflow-x-hidden outline-none", className),
248
- style: { height },
250
+ style: followTail ? { height, overflowAnchor: "none" } : { height },
249
251
  onScroll,
250
252
  onWheel,
251
253
  onKeyDown,
@@ -284,5 +286,5 @@ var TOUCH_SLOP = 4;
284
286
  var UPWARD_KEYS = /* @__PURE__ */ new Set(["ArrowUp", "PageUp", "Home"]);
285
287
 
286
288
  exports.VirtualList = VirtualList;
287
- //# sourceMappingURL=chunk-MF2DCMOA.cjs.map
288
- //# sourceMappingURL=chunk-MF2DCMOA.cjs.map
289
+ //# sourceMappingURL=chunk-2TLAZ5MX.cjs.map
290
+ //# sourceMappingURL=chunk-2TLAZ5MX.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/VirtualList/VirtualList.tsx"],"names":["useRef","useState","useCallback","useMemo","useLayoutEffect","useEffect","jsx","cn"],"mappings":"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","file":"chunk-2TLAZ5MX.cjs","sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport type { KeyboardEvent, TouchEvent, WheelEvent } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport type { VirtualListProps, VirtualListRow } from \"./VirtualList.types\";\n\n/**\n * A windowed list for ledgers that run to tens of thousands of rows — only the\n * visible slice is in the DOM, with **measured** variable row heights, deep\n * links by row id, and sticky-bottom tail-following.\n *\n * Built for an agent event stream, where every one of those three is load\n * bearing at once: message rows wrap and event rows do not, a link carries\n * `?at=<rowId>` to the exact row under discussion, and new rows arrive while\n * the human is reading older ones.\n *\n * ## Heights are measured, never predicted\n *\n * `estimateRowHeight` only seeds rows that have never been on screen. The\n * moment a row renders, its real height is measured and replaces the estimate,\n * and every offset after it moves. The alternative — a `rowHeight(item)`\n * callback — is a prediction about text wrapping, font loading and container\n * width, and when it is wrong the error accumulates down the list: rows draw in\n * the wrong place and `scrollToId` lands on a neighbour. Measuring is slower to\n * settle and correct when it does.\n *\n * ## Tail-follow breaks on INTENT, not on position\n *\n * Any upward gesture — wheel, drag, PageUp, Home — stops following\n * immediately, even when the list is still pinned to the bottom and\n * `scrollTop` has not changed yet. Position cannot tell \"the human scrolled\n * up\" from \"content grew downward\", so a position-only implementation keeps\n * following through a wheel-up and yanks the reader back on the next append.\n *\n * ## It is controlled\n *\n * `followTail` is the consumer's state. This component only ever *asks* via\n * `onFollowChange`, so the follow state is readable and writable by an agent\n * over a bridge, as the Human+ component contract requires. The `↓ n new` pill\n * is the consumer's to render, from `onUnseenChange`.\n *\n * @example\n * ```tsx\n * const [following, setFollowing] = useState(true);\n * const [unseen, setUnseen] = useState(0);\n *\n * <VirtualList\n * items={events}\n * renderRow={(e) => <StreamRow event={e} />}\n * height=\"100%\"\n * estimateRowHeight={28}\n * followTail={following}\n * onFollowChange={setFollowing}\n * onUnseenChange={setUnseen}\n * scrollToId={params.at}\n * />\n * {!following && unseen > 0 && (\n * <Button onClick={() => setFollowing(true)}>↓ {unseen} new</Button>\n * )}\n * ```\n */\nexport function VirtualList<T extends VirtualListRow>({\n items,\n renderRow,\n height = \"100%\",\n estimateRowHeight = 32,\n overscan = 6,\n followTail = false,\n onFollowChange,\n onUnseenChange,\n scrollToId = null,\n onScrollToIdResolved,\n className,\n ref,\n ...props\n}: VirtualListProps<T>) {\n const viewportRef = useRef<HTMLDivElement | null>(null);\n const windowRef = useRef<HTMLDivElement | null>(null);\n\n /** Measured heights by row id, so a height follows its row across a reindex. */\n const heights = useRef(new Map<string, number>());\n /** Bumped when a measurement actually changed, to recompute offsets. */\n const [measured, setMeasured] = useState(0);\n\n const [scrollTop, setScrollTop] = useState(0);\n const [clientHeight, setClientHeight] = useState(0);\n\n /**\n * Where the viewport is scrolled, as far as this list knows: the value from\n * the last `scroll` event, or the last position it set itself. Every change\n * to scrollTop raises a scroll event, so this is never wrong for longer than\n * one frame — and reading it, unlike reading `vp.scrollTop` inside a commit,\n * never forces a synchronous layout.\n */\n const knownTop = useRef(0);\n\n const attachViewport = useCallback(\n (el: HTMLDivElement | null) => {\n viewportRef.current = el;\n if (typeof ref === \"function\") ref(el);\n else if (ref) (ref as { current: HTMLDivElement | null }).current = el;\n },\n [ref],\n );\n\n // A number `height` IS the viewport height, and is usable before the element\n // has been measured. Without one, render a conservative first window so there\n // is something on screen for the measurement pass to work from.\n const viewport =\n clientHeight > 0 ? clientHeight : typeof height === \"number\" ? height : estimateRowHeight * 20;\n\n /**\n * Prefix sums of row heights: `offsets[i]` is where row `i` starts, and the\n * last entry is the total. Rebuilt whenever rows or measurements change —\n * O(n) once per change, against O(log n) per scroll from the binary search,\n * rather than O(n) on every scroll frame.\n */\n const { offsets, total } = useMemo(() => {\n const offs = new Float64Array(items.length + 1);\n for (let i = 0; i < items.length; i += 1) {\n offs[i + 1] = offs[i] + (heights.current.get(items[i].id) ?? estimateRowHeight);\n }\n\n return { offsets: offs, total: items.length === 0 ? 0 : offs[items.length] };\n }, [items, measured, estimateRowHeight]);\n\n /** First row whose span contains `y`. */\n const indexAt = useCallback(\n (y: number): number => {\n let lo = 0;\n let hi = items.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n if (offsets[mid] <= y) lo = mid;\n else hi = mid - 1;\n }\n\n return Math.max(0, lo);\n },\n [items.length, offsets],\n );\n\n const start = Math.max(0, indexAt(scrollTop) - overscan);\n const end = Math.min(items.length, indexAt(scrollTop + viewport) + 1 + overscan);\n const visible = useMemo(() => items.slice(start, end), [items, start, end]);\n\n // ---------------------------------------------------------------- measuring\n\n //\n // Every getBoundingClientRect() / clientHeight read after a commit forces a\n // synchronous layout, and this list re-renders on every appended row. So a\n // row is measured ONCE, when its element first appears, and from then on the\n // ResizeObserver reports when it changes, with the size in the entry: no read\n // that forces layout. An append costs one measurement; a render where nothing\n // changed costs none. (Measured in genie#1340: re-measuring every visible row\n // on every render was 472 forced row measurements/s with 11 agents streaming.)\n\n const observer = useRef<ResizeObserver | null>(null);\n /** Row elements currently observed, so each is observed once and released. */\n const observed = useRef(new Set<Element>());\n\n // A layout effect, declared before the measuring one, so the observer exists\n // by the time the first window is measured and the first rows are observed.\n useLayoutEffect(() => {\n // Absent in jsdom and in older runtimes. Without it the list measures every\n // visible row on every render instead, which is the only way it can notice\n // a row reflowing in place.\n if (typeof ResizeObserver === \"undefined\") return;\n\n const ro = new ResizeObserver((entries) => {\n let changed = false;\n for (const entry of entries) {\n const el = entry.target as HTMLElement;\n\n if (el === viewportRef.current) {\n // The viewport's own resize changes how many rows fit. Reading\n // clientHeight here is free: the browser has just done layout.\n const ch = el.clientHeight;\n if (ch > 0) setClientHeight(ch);\n continue;\n }\n\n const id = el.getAttribute(ROW_ID_ATTR);\n if (id === null) continue;\n\n const h = entryHeight(entry);\n if (h > 0 && heights.current.get(id) !== h) {\n heights.current.set(id, h);\n changed = true;\n }\n }\n\n if (changed) setMeasured((v) => v + 1);\n });\n observer.current = ro;\n\n const vp = viewportRef.current;\n if (vp) ro.observe(vp);\n\n const tracked = observed.current;\n\n return () => {\n ro.disconnect();\n observer.current = null;\n tracked.clear();\n };\n }, []);\n\n // No dependency array on purpose: every render looks at what is on screen.\n // That LOOK is cheap — walking the window's children reads no layout. Only a\n // row seen for the first time is measured (or, with no ResizeObserver, every\n // row, as nothing else would notice a reflow). A render that learns nothing\n // new sets no state, which is what stops it looping.\n useLayoutEffect(() => {\n const win = windowRef.current;\n if (!win) return;\n\n const ro = observer.current;\n const tracked = observed.current;\n const present = ro ? new Set<Element>() : null;\n\n let changed = false;\n let measuredAny = false;\n for (const el of Array.from(win.children) as HTMLElement[]) {\n const id = el.getAttribute(ROW_ID_ATTR);\n if (id === null) continue;\n\n if (ro && present) {\n present.add(el);\n // Already observed: the observer reports its resizes.\n if (tracked.has(el)) continue;\n\n ro.observe(el);\n tracked.add(el);\n // A row scrolling back into view: its height is known, and the\n // observer's first report for the new element corrects it if it moved.\n if (heights.current.has(id)) continue;\n }\n\n const h = el.getBoundingClientRect().height;\n measuredAny = true;\n // A zero height is \"not laid out yet\", not a 0px row. Recording it would\n // collapse the list and make every offset after it wrong.\n if (h > 0 && heights.current.get(id) !== h) {\n heights.current.set(id, h);\n changed = true;\n }\n }\n\n if (ro && present) {\n for (const el of tracked) {\n if (!present.has(el)) {\n ro.unobserve(el);\n tracked.delete(el);\n }\n }\n }\n\n // With an observer, the viewport's resizes arrive through it; read it here\n // only when this pass has already paid for a layout, so it is free.\n const vp = viewportRef.current;\n if (vp && (!ro || measuredAny) && vp.clientHeight > 0 && vp.clientHeight !== clientHeight) {\n setClientHeight(vp.clientHeight);\n }\n\n if (changed) setMeasured((v) => v + 1);\n });\n\n // ------------------------------------------------------------- deep linking\n\n /** The target this component last moved to, to detect that it has settled. */\n const anchored = useRef<number | null>(null);\n const resolved = useRef<string | null>(null);\n\n useEffect(() => {\n // A new id is a new request, even if the old one had resolved.\n anchored.current = null;\n resolved.current = null;\n }, [scrollToId]);\n\n useLayoutEffect(() => {\n if (scrollToId === null || resolved.current === scrollToId) return;\n\n const index = items.findIndex((item) => item.id === scrollToId);\n if (index < 0) {\n // A link to a row that is no longer in the list. Leaving the viewport\n // where it is and SAYING SO is the only honest outcome — scrolling to the\n // top would be indistinguishable from a successful landing.\n resolved.current = scrollToId;\n onScrollToIdResolved?.(scrollToId, false);\n\n return;\n }\n\n const vp = viewportRef.current;\n if (!vp) return;\n\n // Clamped the way the browser clamps: a row inside the final viewport-height\n // of the list cannot be brought to the top, and pretending otherwise would\n // leave this waiting for a position that can never be reached.\n const target = Math.max(0, Math.min(offsets[index], Math.max(0, total - viewport)));\n\n if (anchored.current !== target) {\n anchored.current = target;\n vp.scrollTop = target;\n knownTop.current = target;\n setScrollTop(target);\n\n return;\n }\n\n // The target stopped moving as rows above it were measured, and we are on\n // it. Only now is the deep link actually honoured.\n if (Math.abs(vp.scrollTop - target) <= 1) {\n resolved.current = scrollToId;\n onScrollToIdResolved?.(scrollToId, true);\n }\n });\n\n // ------------------------------------------------------------- tail-follow\n\n const reportedFollow = useRef(followTail);\n const programmatic = useRef(false);\n\n useEffect(() => {\n // The prop is the truth, so adopt it rather than remembering what we asked\n // for. Otherwise a consumer that declines an `onFollowChange(false)` would\n // never be asked again.\n reportedFollow.current = followTail;\n }, [followTail]);\n\n const askFollow = useCallback(\n (next: boolean) => {\n if (reportedFollow.current === next) return;\n reportedFollow.current = next;\n onFollowChange?.(next);\n },\n [onFollowChange],\n );\n\n useLayoutEffect(() => {\n if (!followTail) return;\n // A pending deep link wins while it resolves; otherwise the two would fight\n // over the same scrollTop and the link would lose.\n if (scrollToId !== null && resolved.current !== scrollToId) return;\n\n const vp = viewportRef.current;\n if (!vp) return;\n\n // This is the first layout read after most commits, so reading\n // `vp.scrollTop` here paid the forced synchronous layout on every append\n // (genie#1340 follow-up: 1.5 to 1.8 s per 120 s with 11 agents streaming).\n // Compare against the position the list already tracks instead, which only\n // differs from `bottom` when the bottom moved (rows, measurements or the\n // viewport changed) or someone scrolled — so a commit that moved nothing\n // neither reads nor writes. Without a ResizeObserver the measuring pass has\n // already paid for a layout this commit, so reading is free and keeps the\n // fallback exactly as it was.\n const current = observer.current ? knownTop.current : vp.scrollTop;\n const bottom = Math.max(0, total - viewport);\n if (Math.abs(current - bottom) > 0.5) {\n programmatic.current = true;\n vp.scrollTop = bottom;\n knownTop.current = bottom;\n setScrollTop(bottom);\n }\n });\n\n const onScroll = useCallback(() => {\n const vp = viewportRef.current;\n if (!vp) return;\n\n const next = vp.scrollTop;\n knownTop.current = next;\n const previous = scrollTop;\n setScrollTop(next);\n\n // The viewport as laid out NOW, not the `viewport` of the last commit. When it\n // grows, the browser clamps scrollTop to the new maximum, and it can deliver\n // that scroll event before the ResizeObserver callback for the same frame (it\n // does in Chromium once the page has seen a pointer event). Against the last\n // commit's height the clamp looks like an upward scroll; against this height it\n // is the bottom, which is what it is. Reading it costs no layout that reading\n // scrollTop above did not already pay. Zero means not laid out.\n //\n // Adopt it here too rather than waiting for the observer. This handler's own\n // setScrollTop commits first, and with the old height the follow effect would\n // write a position past the new maximum: the browser ignores it, no scroll event\n // comes back, and `programmatic` stays set to swallow the human's next scroll.\n const live = vp.clientHeight;\n if (live > 0) setClientHeight(live);\n const bottom = Math.max(0, total - (live || viewport));\n const wasProgrammatic = programmatic.current;\n programmatic.current = false;\n\n if (next <= bottom - BOTTOM_SLACK) {\n // Moved up, and not because we moved it.\n if (!wasProgrammatic && next < previous) askFollow(false);\n } else if (next > previous) {\n // Came down to the bottom. Being dragged UP to it by a clamp is not the\n // reader returning, so it does not resume a pause.\n askFollow(true);\n }\n }, [scrollTop, total, viewport, askFollow]);\n\n /** Any upward gesture is a statement of intent, whatever scrollTop says. */\n const onWheel = useCallback(\n (e: WheelEvent<HTMLDivElement>) => {\n if (e.deltaY < 0) askFollow(false);\n },\n [askFollow],\n );\n\n const onKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (UPWARD_KEYS.has(e.key)) askFollow(false);\n },\n [askFollow],\n );\n\n const touchStart = useRef<number | null>(null);\n\n const onTouchStart = useCallback((e: TouchEvent<HTMLDivElement>) => {\n touchStart.current = e.touches[0]?.clientY ?? null;\n }, []);\n\n const onTouchMove = useCallback(\n (e: TouchEvent<HTMLDivElement>) => {\n const from = touchStart.current;\n const y = e.touches[0]?.clientY;\n // Finger travelling DOWN drags the content down, which scrolls up.\n if (from !== null && y !== undefined && y - from > TOUCH_SLOP) askFollow(false);\n },\n [askFollow],\n );\n\n // ------------------------------------------------------------ unseen count\n\n const seen = useRef(items.length);\n const reportedUnseen = useRef<number | null>(null);\n\n useEffect(() => {\n const unseen = followTail ? 0 : Math.max(0, items.length - seen.current);\n if (followTail) seen.current = items.length;\n\n if (reportedUnseen.current === unseen) return;\n reportedUnseen.current = unseen;\n onUnseenChange?.(unseen);\n }, [items.length, followTail, onUnseenChange]);\n\n // ------------------------------------------------------------------ render\n\n return (\n <div\n ref={attachViewport}\n data-react-fancy-virtual-list=\"\"\n data-react-fancy-virtual-list-following={followTail ? \"true\" : \"false\"}\n data-react-fancy-virtual-list-rows={items.length}\n role=\"log\"\n tabIndex={0}\n className={cn(\"relative overflow-y-auto overflow-x-hidden outline-none\", className)}\n // While following, the list owns the position. Left on, scroll anchoring\n // answers rows reflowing above it by moving scrollTop up, which reaches\n // onScroll looking exactly like a scrollbar drag.\n style={followTail ? { height, overflowAnchor: \"none\" } : { height }}\n onScroll={onScroll}\n onWheel={onWheel}\n onKeyDown={onKeyDown}\n onTouchStart={onTouchStart}\n onTouchMove={onTouchMove}\n {...props}\n >\n {/* Holds the full scroll range so the scrollbar is honest from the first\n paint, instead of growing as rows are discovered. */}\n <div\n data-react-fancy-virtual-list-sizer=\"\"\n style={{ height: `${total}px`, position: \"relative\" }}\n >\n <div\n ref={windowRef}\n data-react-fancy-virtual-list-window=\"\"\n data-react-fancy-virtual-list-start={start}\n style={{ transform: `translateY(${offsets[start] ?? 0}px)` }}\n >\n {visible.map((item, i) => (\n <div key={item.id} data-react-fancy-virtual-row-id={item.id}>\n {renderRow(item, start + i)}\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\nconst ROW_ID_ATTR = \"data-react-fancy-virtual-row-id\";\n\n/**\n * A row's border-box height from a ResizeObserver entry. `borderBoxSize` is\n * what getBoundingClientRect() would report; older engines lack it (and early\n * Firefox gave a single object, not an array), so fall back to `contentRect`,\n * which equals it for the row wrapper, as that carries no padding or border.\n */\nfunction entryHeight(entry: ResizeObserverEntry): number {\n const box = entry.borderBoxSize as ReadonlyArray<ResizeObserverSize> | ResizeObserverSize | undefined;\n const first = Array.isArray(box) ? box[0] : (box as ResizeObserverSize | undefined);\n if (first && typeof first.blockSize === \"number\") return first.blockSize;\n\n return entry.contentRect?.height ?? 0;\n}\n/** Within this many px of the bottom counts as \"at the bottom\". */\nconst BOTTOM_SLACK = 4;\n/** Ignore a touch jitter smaller than this before calling it an upward drag. */\nconst TOUCH_SLOP = 4;\nconst UPWARD_KEYS = new Set([\"ArrowUp\", \"PageUp\", \"Home\"]);\n"]}
@@ -191,12 +191,14 @@ function VirtualList({
191
191
  knownTop.current = next;
192
192
  const previous = scrollTop;
193
193
  setScrollTop(next);
194
- const bottom = Math.max(0, total - viewport);
194
+ const live = vp.clientHeight;
195
+ if (live > 0) setClientHeight(live);
196
+ const bottom = Math.max(0, total - (live || viewport));
195
197
  const wasProgrammatic = programmatic.current;
196
198
  programmatic.current = false;
197
199
  if (next <= bottom - BOTTOM_SLACK) {
198
200
  if (!wasProgrammatic && next < previous) askFollow(false);
199
- } else {
201
+ } else if (next > previous) {
200
202
  askFollow(true);
201
203
  }
202
204
  }, [scrollTop, total, viewport, askFollow]);
@@ -243,7 +245,7 @@ function VirtualList({
243
245
  role: "log",
244
246
  tabIndex: 0,
245
247
  className: cn("relative overflow-y-auto overflow-x-hidden outline-none", className),
246
- style: { height },
248
+ style: followTail ? { height, overflowAnchor: "none" } : { height },
247
249
  onScroll,
248
250
  onWheel,
249
251
  onKeyDown,
@@ -282,5 +284,5 @@ var TOUCH_SLOP = 4;
282
284
  var UPWARD_KEYS = /* @__PURE__ */ new Set(["ArrowUp", "PageUp", "Home"]);
283
285
 
284
286
  export { VirtualList };
285
- //# sourceMappingURL=chunk-ARNFT23K.js.map
286
- //# sourceMappingURL=chunk-ARNFT23K.js.map
287
+ //# sourceMappingURL=chunk-5IXOQGPX.js.map
288
+ //# sourceMappingURL=chunk-5IXOQGPX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/VirtualList/VirtualList.tsx"],"names":[],"mappings":"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","file":"chunk-5IXOQGPX.js","sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport type { KeyboardEvent, TouchEvent, WheelEvent } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport type { VirtualListProps, VirtualListRow } from \"./VirtualList.types\";\n\n/**\n * A windowed list for ledgers that run to tens of thousands of rows — only the\n * visible slice is in the DOM, with **measured** variable row heights, deep\n * links by row id, and sticky-bottom tail-following.\n *\n * Built for an agent event stream, where every one of those three is load\n * bearing at once: message rows wrap and event rows do not, a link carries\n * `?at=<rowId>` to the exact row under discussion, and new rows arrive while\n * the human is reading older ones.\n *\n * ## Heights are measured, never predicted\n *\n * `estimateRowHeight` only seeds rows that have never been on screen. The\n * moment a row renders, its real height is measured and replaces the estimate,\n * and every offset after it moves. The alternative — a `rowHeight(item)`\n * callback — is a prediction about text wrapping, font loading and container\n * width, and when it is wrong the error accumulates down the list: rows draw in\n * the wrong place and `scrollToId` lands on a neighbour. Measuring is slower to\n * settle and correct when it does.\n *\n * ## Tail-follow breaks on INTENT, not on position\n *\n * Any upward gesture — wheel, drag, PageUp, Home — stops following\n * immediately, even when the list is still pinned to the bottom and\n * `scrollTop` has not changed yet. Position cannot tell \"the human scrolled\n * up\" from \"content grew downward\", so a position-only implementation keeps\n * following through a wheel-up and yanks the reader back on the next append.\n *\n * ## It is controlled\n *\n * `followTail` is the consumer's state. This component only ever *asks* via\n * `onFollowChange`, so the follow state is readable and writable by an agent\n * over a bridge, as the Human+ component contract requires. The `↓ n new` pill\n * is the consumer's to render, from `onUnseenChange`.\n *\n * @example\n * ```tsx\n * const [following, setFollowing] = useState(true);\n * const [unseen, setUnseen] = useState(0);\n *\n * <VirtualList\n * items={events}\n * renderRow={(e) => <StreamRow event={e} />}\n * height=\"100%\"\n * estimateRowHeight={28}\n * followTail={following}\n * onFollowChange={setFollowing}\n * onUnseenChange={setUnseen}\n * scrollToId={params.at}\n * />\n * {!following && unseen > 0 && (\n * <Button onClick={() => setFollowing(true)}>↓ {unseen} new</Button>\n * )}\n * ```\n */\nexport function VirtualList<T extends VirtualListRow>({\n items,\n renderRow,\n height = \"100%\",\n estimateRowHeight = 32,\n overscan = 6,\n followTail = false,\n onFollowChange,\n onUnseenChange,\n scrollToId = null,\n onScrollToIdResolved,\n className,\n ref,\n ...props\n}: VirtualListProps<T>) {\n const viewportRef = useRef<HTMLDivElement | null>(null);\n const windowRef = useRef<HTMLDivElement | null>(null);\n\n /** Measured heights by row id, so a height follows its row across a reindex. */\n const heights = useRef(new Map<string, number>());\n /** Bumped when a measurement actually changed, to recompute offsets. */\n const [measured, setMeasured] = useState(0);\n\n const [scrollTop, setScrollTop] = useState(0);\n const [clientHeight, setClientHeight] = useState(0);\n\n /**\n * Where the viewport is scrolled, as far as this list knows: the value from\n * the last `scroll` event, or the last position it set itself. Every change\n * to scrollTop raises a scroll event, so this is never wrong for longer than\n * one frame — and reading it, unlike reading `vp.scrollTop` inside a commit,\n * never forces a synchronous layout.\n */\n const knownTop = useRef(0);\n\n const attachViewport = useCallback(\n (el: HTMLDivElement | null) => {\n viewportRef.current = el;\n if (typeof ref === \"function\") ref(el);\n else if (ref) (ref as { current: HTMLDivElement | null }).current = el;\n },\n [ref],\n );\n\n // A number `height` IS the viewport height, and is usable before the element\n // has been measured. Without one, render a conservative first window so there\n // is something on screen for the measurement pass to work from.\n const viewport =\n clientHeight > 0 ? clientHeight : typeof height === \"number\" ? height : estimateRowHeight * 20;\n\n /**\n * Prefix sums of row heights: `offsets[i]` is where row `i` starts, and the\n * last entry is the total. Rebuilt whenever rows or measurements change —\n * O(n) once per change, against O(log n) per scroll from the binary search,\n * rather than O(n) on every scroll frame.\n */\n const { offsets, total } = useMemo(() => {\n const offs = new Float64Array(items.length + 1);\n for (let i = 0; i < items.length; i += 1) {\n offs[i + 1] = offs[i] + (heights.current.get(items[i].id) ?? estimateRowHeight);\n }\n\n return { offsets: offs, total: items.length === 0 ? 0 : offs[items.length] };\n }, [items, measured, estimateRowHeight]);\n\n /** First row whose span contains `y`. */\n const indexAt = useCallback(\n (y: number): number => {\n let lo = 0;\n let hi = items.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n if (offsets[mid] <= y) lo = mid;\n else hi = mid - 1;\n }\n\n return Math.max(0, lo);\n },\n [items.length, offsets],\n );\n\n const start = Math.max(0, indexAt(scrollTop) - overscan);\n const end = Math.min(items.length, indexAt(scrollTop + viewport) + 1 + overscan);\n const visible = useMemo(() => items.slice(start, end), [items, start, end]);\n\n // ---------------------------------------------------------------- measuring\n\n //\n // Every getBoundingClientRect() / clientHeight read after a commit forces a\n // synchronous layout, and this list re-renders on every appended row. So a\n // row is measured ONCE, when its element first appears, and from then on the\n // ResizeObserver reports when it changes, with the size in the entry: no read\n // that forces layout. An append costs one measurement; a render where nothing\n // changed costs none. (Measured in genie#1340: re-measuring every visible row\n // on every render was 472 forced row measurements/s with 11 agents streaming.)\n\n const observer = useRef<ResizeObserver | null>(null);\n /** Row elements currently observed, so each is observed once and released. */\n const observed = useRef(new Set<Element>());\n\n // A layout effect, declared before the measuring one, so the observer exists\n // by the time the first window is measured and the first rows are observed.\n useLayoutEffect(() => {\n // Absent in jsdom and in older runtimes. Without it the list measures every\n // visible row on every render instead, which is the only way it can notice\n // a row reflowing in place.\n if (typeof ResizeObserver === \"undefined\") return;\n\n const ro = new ResizeObserver((entries) => {\n let changed = false;\n for (const entry of entries) {\n const el = entry.target as HTMLElement;\n\n if (el === viewportRef.current) {\n // The viewport's own resize changes how many rows fit. Reading\n // clientHeight here is free: the browser has just done layout.\n const ch = el.clientHeight;\n if (ch > 0) setClientHeight(ch);\n continue;\n }\n\n const id = el.getAttribute(ROW_ID_ATTR);\n if (id === null) continue;\n\n const h = entryHeight(entry);\n if (h > 0 && heights.current.get(id) !== h) {\n heights.current.set(id, h);\n changed = true;\n }\n }\n\n if (changed) setMeasured((v) => v + 1);\n });\n observer.current = ro;\n\n const vp = viewportRef.current;\n if (vp) ro.observe(vp);\n\n const tracked = observed.current;\n\n return () => {\n ro.disconnect();\n observer.current = null;\n tracked.clear();\n };\n }, []);\n\n // No dependency array on purpose: every render looks at what is on screen.\n // That LOOK is cheap — walking the window's children reads no layout. Only a\n // row seen for the first time is measured (or, with no ResizeObserver, every\n // row, as nothing else would notice a reflow). A render that learns nothing\n // new sets no state, which is what stops it looping.\n useLayoutEffect(() => {\n const win = windowRef.current;\n if (!win) return;\n\n const ro = observer.current;\n const tracked = observed.current;\n const present = ro ? new Set<Element>() : null;\n\n let changed = false;\n let measuredAny = false;\n for (const el of Array.from(win.children) as HTMLElement[]) {\n const id = el.getAttribute(ROW_ID_ATTR);\n if (id === null) continue;\n\n if (ro && present) {\n present.add(el);\n // Already observed: the observer reports its resizes.\n if (tracked.has(el)) continue;\n\n ro.observe(el);\n tracked.add(el);\n // A row scrolling back into view: its height is known, and the\n // observer's first report for the new element corrects it if it moved.\n if (heights.current.has(id)) continue;\n }\n\n const h = el.getBoundingClientRect().height;\n measuredAny = true;\n // A zero height is \"not laid out yet\", not a 0px row. Recording it would\n // collapse the list and make every offset after it wrong.\n if (h > 0 && heights.current.get(id) !== h) {\n heights.current.set(id, h);\n changed = true;\n }\n }\n\n if (ro && present) {\n for (const el of tracked) {\n if (!present.has(el)) {\n ro.unobserve(el);\n tracked.delete(el);\n }\n }\n }\n\n // With an observer, the viewport's resizes arrive through it; read it here\n // only when this pass has already paid for a layout, so it is free.\n const vp = viewportRef.current;\n if (vp && (!ro || measuredAny) && vp.clientHeight > 0 && vp.clientHeight !== clientHeight) {\n setClientHeight(vp.clientHeight);\n }\n\n if (changed) setMeasured((v) => v + 1);\n });\n\n // ------------------------------------------------------------- deep linking\n\n /** The target this component last moved to, to detect that it has settled. */\n const anchored = useRef<number | null>(null);\n const resolved = useRef<string | null>(null);\n\n useEffect(() => {\n // A new id is a new request, even if the old one had resolved.\n anchored.current = null;\n resolved.current = null;\n }, [scrollToId]);\n\n useLayoutEffect(() => {\n if (scrollToId === null || resolved.current === scrollToId) return;\n\n const index = items.findIndex((item) => item.id === scrollToId);\n if (index < 0) {\n // A link to a row that is no longer in the list. Leaving the viewport\n // where it is and SAYING SO is the only honest outcome — scrolling to the\n // top would be indistinguishable from a successful landing.\n resolved.current = scrollToId;\n onScrollToIdResolved?.(scrollToId, false);\n\n return;\n }\n\n const vp = viewportRef.current;\n if (!vp) return;\n\n // Clamped the way the browser clamps: a row inside the final viewport-height\n // of the list cannot be brought to the top, and pretending otherwise would\n // leave this waiting for a position that can never be reached.\n const target = Math.max(0, Math.min(offsets[index], Math.max(0, total - viewport)));\n\n if (anchored.current !== target) {\n anchored.current = target;\n vp.scrollTop = target;\n knownTop.current = target;\n setScrollTop(target);\n\n return;\n }\n\n // The target stopped moving as rows above it were measured, and we are on\n // it. Only now is the deep link actually honoured.\n if (Math.abs(vp.scrollTop - target) <= 1) {\n resolved.current = scrollToId;\n onScrollToIdResolved?.(scrollToId, true);\n }\n });\n\n // ------------------------------------------------------------- tail-follow\n\n const reportedFollow = useRef(followTail);\n const programmatic = useRef(false);\n\n useEffect(() => {\n // The prop is the truth, so adopt it rather than remembering what we asked\n // for. Otherwise a consumer that declines an `onFollowChange(false)` would\n // never be asked again.\n reportedFollow.current = followTail;\n }, [followTail]);\n\n const askFollow = useCallback(\n (next: boolean) => {\n if (reportedFollow.current === next) return;\n reportedFollow.current = next;\n onFollowChange?.(next);\n },\n [onFollowChange],\n );\n\n useLayoutEffect(() => {\n if (!followTail) return;\n // A pending deep link wins while it resolves; otherwise the two would fight\n // over the same scrollTop and the link would lose.\n if (scrollToId !== null && resolved.current !== scrollToId) return;\n\n const vp = viewportRef.current;\n if (!vp) return;\n\n // This is the first layout read after most commits, so reading\n // `vp.scrollTop` here paid the forced synchronous layout on every append\n // (genie#1340 follow-up: 1.5 to 1.8 s per 120 s with 11 agents streaming).\n // Compare against the position the list already tracks instead, which only\n // differs from `bottom` when the bottom moved (rows, measurements or the\n // viewport changed) or someone scrolled — so a commit that moved nothing\n // neither reads nor writes. Without a ResizeObserver the measuring pass has\n // already paid for a layout this commit, so reading is free and keeps the\n // fallback exactly as it was.\n const current = observer.current ? knownTop.current : vp.scrollTop;\n const bottom = Math.max(0, total - viewport);\n if (Math.abs(current - bottom) > 0.5) {\n programmatic.current = true;\n vp.scrollTop = bottom;\n knownTop.current = bottom;\n setScrollTop(bottom);\n }\n });\n\n const onScroll = useCallback(() => {\n const vp = viewportRef.current;\n if (!vp) return;\n\n const next = vp.scrollTop;\n knownTop.current = next;\n const previous = scrollTop;\n setScrollTop(next);\n\n // The viewport as laid out NOW, not the `viewport` of the last commit. When it\n // grows, the browser clamps scrollTop to the new maximum, and it can deliver\n // that scroll event before the ResizeObserver callback for the same frame (it\n // does in Chromium once the page has seen a pointer event). Against the last\n // commit's height the clamp looks like an upward scroll; against this height it\n // is the bottom, which is what it is. Reading it costs no layout that reading\n // scrollTop above did not already pay. Zero means not laid out.\n //\n // Adopt it here too rather than waiting for the observer. This handler's own\n // setScrollTop commits first, and with the old height the follow effect would\n // write a position past the new maximum: the browser ignores it, no scroll event\n // comes back, and `programmatic` stays set to swallow the human's next scroll.\n const live = vp.clientHeight;\n if (live > 0) setClientHeight(live);\n const bottom = Math.max(0, total - (live || viewport));\n const wasProgrammatic = programmatic.current;\n programmatic.current = false;\n\n if (next <= bottom - BOTTOM_SLACK) {\n // Moved up, and not because we moved it.\n if (!wasProgrammatic && next < previous) askFollow(false);\n } else if (next > previous) {\n // Came down to the bottom. Being dragged UP to it by a clamp is not the\n // reader returning, so it does not resume a pause.\n askFollow(true);\n }\n }, [scrollTop, total, viewport, askFollow]);\n\n /** Any upward gesture is a statement of intent, whatever scrollTop says. */\n const onWheel = useCallback(\n (e: WheelEvent<HTMLDivElement>) => {\n if (e.deltaY < 0) askFollow(false);\n },\n [askFollow],\n );\n\n const onKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (UPWARD_KEYS.has(e.key)) askFollow(false);\n },\n [askFollow],\n );\n\n const touchStart = useRef<number | null>(null);\n\n const onTouchStart = useCallback((e: TouchEvent<HTMLDivElement>) => {\n touchStart.current = e.touches[0]?.clientY ?? null;\n }, []);\n\n const onTouchMove = useCallback(\n (e: TouchEvent<HTMLDivElement>) => {\n const from = touchStart.current;\n const y = e.touches[0]?.clientY;\n // Finger travelling DOWN drags the content down, which scrolls up.\n if (from !== null && y !== undefined && y - from > TOUCH_SLOP) askFollow(false);\n },\n [askFollow],\n );\n\n // ------------------------------------------------------------ unseen count\n\n const seen = useRef(items.length);\n const reportedUnseen = useRef<number | null>(null);\n\n useEffect(() => {\n const unseen = followTail ? 0 : Math.max(0, items.length - seen.current);\n if (followTail) seen.current = items.length;\n\n if (reportedUnseen.current === unseen) return;\n reportedUnseen.current = unseen;\n onUnseenChange?.(unseen);\n }, [items.length, followTail, onUnseenChange]);\n\n // ------------------------------------------------------------------ render\n\n return (\n <div\n ref={attachViewport}\n data-react-fancy-virtual-list=\"\"\n data-react-fancy-virtual-list-following={followTail ? \"true\" : \"false\"}\n data-react-fancy-virtual-list-rows={items.length}\n role=\"log\"\n tabIndex={0}\n className={cn(\"relative overflow-y-auto overflow-x-hidden outline-none\", className)}\n // While following, the list owns the position. Left on, scroll anchoring\n // answers rows reflowing above it by moving scrollTop up, which reaches\n // onScroll looking exactly like a scrollbar drag.\n style={followTail ? { height, overflowAnchor: \"none\" } : { height }}\n onScroll={onScroll}\n onWheel={onWheel}\n onKeyDown={onKeyDown}\n onTouchStart={onTouchStart}\n onTouchMove={onTouchMove}\n {...props}\n >\n {/* Holds the full scroll range so the scrollbar is honest from the first\n paint, instead of growing as rows are discovered. */}\n <div\n data-react-fancy-virtual-list-sizer=\"\"\n style={{ height: `${total}px`, position: \"relative\" }}\n >\n <div\n ref={windowRef}\n data-react-fancy-virtual-list-window=\"\"\n data-react-fancy-virtual-list-start={start}\n style={{ transform: `translateY(${offsets[start] ?? 0}px)` }}\n >\n {visible.map((item, i) => (\n <div key={item.id} data-react-fancy-virtual-row-id={item.id}>\n {renderRow(item, start + i)}\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\nconst ROW_ID_ATTR = \"data-react-fancy-virtual-row-id\";\n\n/**\n * A row's border-box height from a ResizeObserver entry. `borderBoxSize` is\n * what getBoundingClientRect() would report; older engines lack it (and early\n * Firefox gave a single object, not an array), so fall back to `contentRect`,\n * which equals it for the row wrapper, as that carries no padding or border.\n */\nfunction entryHeight(entry: ResizeObserverEntry): number {\n const box = entry.borderBoxSize as ReadonlyArray<ResizeObserverSize> | ResizeObserverSize | undefined;\n const first = Array.isArray(box) ? box[0] : (box as ResizeObserverSize | undefined);\n if (first && typeof first.blockSize === \"number\") return first.blockSize;\n\n return entry.contentRect?.height ?? 0;\n}\n/** Within this many px of the bottom counts as \"at the bottom\". */\nconst BOTTOM_SLACK = 4;\n/** Ignore a touch jitter smaller than this before calling it an upward drag. */\nconst TOUCH_SLOP = 4;\nconst UPWARD_KEYS = new Set([\"ArrowUp\", \"PageUp\", \"Home\"]);\n"]}
@@ -0,0 +1,3 @@
1
+
2
+ //# sourceMappingURL=chunk-5WMVBFBD.js.map
3
+ //# sourceMappingURL=chunk-5WMVBFBD.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-VXHFR5MV.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-5WMVBFBD.js"}