@gridkitjs/dom 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +54 -2
- package/dist/index.d.ts +515 -0
- package/dist/index.js +739 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/announcer.ts","../src/bodyTargets.ts","../src/columnResize.ts","../src/pointerGesture.ts","../src/dragReorder.ts","../src/dropTargets.ts","../src/elementWidth.ts","../src/gridFocus.ts","../src/infiniteScroll.ts","../src/overlay/placeOverlay.ts","../src/overlay/attachOverlay.ts","../src/rowVirtualizer.ts"],"sourcesContent":["/** A live region to report changes through. */\nexport interface LiveAnnouncer {\n /**\n * Has assistive technology read `text` out, politely — after whatever it is\n * already saying. The same text twice in a row is read twice.\n */\n readonly announce: (text: string) => void;\n /** Removes the live region. */\n readonly destroy: () => void;\n}\n\n/** Whitespace to a screen reader, but a change to the text it is watching. */\nconst NO_BREAK_SPACE = String.fromCodePoint(0xa0);\n\n/**\n * Creates the one live region a grid reports through — a reorder, a resize, a\n * bulk selection, anything no visual cue can carry to a screen reader — as\n * the last child of `container`.\n *\n * Polite rather than assertive, so an announcement waits for a pause instead\n * of cutting across what the user is already hearing. The element carries\n * `gridkit-sr-only`, which the theme clips to nothing while leaving it in the\n * accessibility tree.\n *\n * A live region speaks only when its text changes, so a message identical to\n * the last would go unheard. A no-break space, added and dropped in turn,\n * makes the repeat a change without changing what is read.\n */\nexport function createLiveAnnouncer(\n container: HTMLElement = document.body,\n): LiveAnnouncer {\n const region = document.createElement(\"div\");\n region.className = \"gridkit-sr-only\";\n region.setAttribute(\"role\", \"status\");\n region.setAttribute(\"aria-live\", \"polite\");\n container.append(region);\n\n let last = \"\";\n let padded = false;\n\n return {\n announce(text) {\n padded = text === last && !padded;\n last = text;\n region.textContent = padded ? text + NO_BREAK_SPACE : text;\n },\n destroy() {\n region.remove();\n },\n };\n}\n","/** The data cell an event happened in. */\nexport interface BodyCellTarget {\n /** The row's place among every body row, from its `data-gridkit-row-index`. */\n readonly rowIndex: number;\n /** The cell's place in its row, off the table's own indices. */\n readonly columnIndex: number;\n readonly columnId: string;\n}\n\n/** The group-header row an event happened in. */\nexport interface GroupRowTarget {\n readonly rowIndex: number;\n readonly groupId: string;\n}\n\n/** The body row an event happened in — a data row, or a group's header or summary. */\nexport interface BodyRowTarget {\n readonly rowIndex: number;\n /** The column of the cell it happened in, `null` when it was in none. */\n readonly columnId: string | null;\n}\n\n/**\n * A row's place among every body row, off the `data-gridkit-row-index` each\n * row carries — not its DOM position, which under virtualization is off by\n * the rows not mounted. `null` without the attribute: a spacer row, or no row\n * at all.\n */\nfunction rowIndexOf(row: Element): number | null {\n const attribute = row.getAttribute(\"data-gridkit-row-index\");\n return attribute === null ? null : Number(attribute);\n}\n\n/**\n * The data cell `target` is in — what a renderer delegating its body's\n * events to one listener asks of each event. `closest` is what makes the\n * whole cell the target: a click on a template's own markup resolves to the\n * cell holding it.\n *\n * Never matches inside a group-header row: that row's single `<td>` carries\n * no `data-gridkit-column` (it spans every column at once, so no one column\n * id applies), which keeps this to data rows without a check of its own.\n */\nexport function bodyCellFrom(\n target: EventTarget | null,\n): BodyCellTarget | null {\n if (!(target instanceof Element)) {\n return null;\n }\n const cell = target.closest(\"td[data-gridkit-column]\");\n const row = cell?.parentElement;\n const columnId = cell?.getAttribute(\"data-gridkit-column\");\n const rowIndex = row === null || row === undefined ? null : rowIndexOf(row);\n if (\n !(cell instanceof HTMLTableCellElement) ||\n !(row instanceof HTMLTableRowElement) ||\n columnId === null ||\n columnId === undefined ||\n rowIndex === null\n ) {\n return null;\n }\n return { rowIndex, columnIndex: cell.cellIndex, columnId };\n}\n\n/** The group-header row (`tr[data-gridkit-group]`) `target` is in. */\nexport function groupRowFrom(\n target: EventTarget | null,\n): GroupRowTarget | null {\n if (!(target instanceof Element)) {\n return null;\n }\n const row = target.closest(\"tr[data-gridkit-group]\");\n const groupId = row?.getAttribute(\"data-gridkit-group\");\n const rowIndex = row === null ? null : rowIndexOf(row);\n if (\n !(row instanceof HTMLTableRowElement) ||\n groupId === null ||\n groupId === undefined ||\n rowIndex === null\n ) {\n return null;\n }\n return { rowIndex, groupId };\n}\n\n/**\n * The row `target` is in, cell or not — all a row click needs. The column is\n * read only off a cell of that very row, so a table inside a cell template\n * cannot lend it one of its own.\n */\nexport function bodyRowFrom(target: EventTarget | null): BodyRowTarget | null {\n if (!(target instanceof Element)) {\n return null;\n }\n const row = target.closest(\"tr[data-gridkit-row-index]\");\n const rowIndex = row === null ? null : rowIndexOf(row);\n if (rowIndex === null) {\n return null;\n }\n const cell = target.closest(\"td[data-gridkit-column]\");\n return {\n rowIndex,\n columnId:\n cell?.parentElement === row\n ? cell.getAttribute(\"data-gridkit-column\")\n : null,\n };\n}\n","import { applyColumnResize, type ColumnResizeSession } from \"@gridkitjs/core\";\nimport {\n isPrimaryPress,\n startPointerGesture,\n type PointerPress,\n} from \"./pointerGesture\";\n\nexport interface ColumnResizeGestureHandlers {\n /** Every pointer move, with the width the column takes there. */\n readonly onMove: (width: number) => void;\n /**\n * The pointer was released. `width` is the width to commit, or `null` when\n * it ended where it started: a click on the handle — as each half of a\n * double-click is — is not a resize, and reporting one would fire a\n * spurious resize on every click.\n */\n readonly onEnd: (width: number | null) => void;\n /** Escape, or a cancelled pointer: the column goes back to `session.startWidth`. */\n readonly onCancel: () => void;\n}\n\n/**\n * Drags a column's edge from `press` on its resize `handle`, turning pointer\n * positions into widths through `@gridkitjs/core`'s `session` — begun with\n * `beginColumnResize` — and reporting them. Returns whether a resize began:\n * only the primary button of the primary pointer starts one.\n *\n * Captures the pointer on `handle`, so the drag keeps reporting wherever the\n * pointer goes, and prevents the press's default, so it does not select the\n * header text it started on. No minimum distance, unlike a reorder drag: the\n * handle does nothing else a press could have meant.\n */\nexport function startColumnResizeGesture(\n handle: HTMLElement,\n press: PointerPress,\n session: ColumnResizeSession,\n handlers: ColumnResizeGestureHandlers,\n): boolean {\n if (!isPrimaryPress(press)) {\n return false;\n }\n press.preventDefault();\n\n let width = session.startWidth;\n startPointerGesture(handle, press.pointerId, {\n onMove(event) {\n width = applyColumnResize(session, event.clientX);\n handlers.onMove(width);\n },\n onEnd() {\n handlers.onEnd(width === session.startWidth ? null : width);\n },\n onCancel() {\n handlers.onCancel();\n },\n });\n return true;\n}\n\n/**\n * The width the widest cell in `columnId` needs to show its content in full,\n * or `null` when the column has no cells — what double-clicking a resize\n * handle sizes the column to, through `sizeColumnToContent`.\n *\n * Measured with a `Range` over each cell's contents rather than the cell's own\n * `scrollWidth`. A range shrink-wraps what is actually laid out, so a column\n * far wider than its content reports the content width and can be sized *down*;\n * `scrollWidth` never reports less than the cell it is read from and so can\n * only ever grow a column.\n *\n * Only rendered rows are measured — on a paged or virtualized grid, the ones\n * on screen.\n */\nexport function measureColumnContent(\n table: HTMLTableElement,\n columnId: string,\n): number | null {\n const cells = table.querySelectorAll<HTMLElement>(\n `td[data-gridkit-column=\"${CSS.escape(columnId)}\"]`,\n );\n const range = document.createRange();\n let widest: number | null = null;\n\n for (const cell of cells) {\n range.selectNodeContents(cell);\n const style = getComputedStyle(cell);\n // The range covers the content box only, so the cell's own spacing has to\n // be added back or the column would clip by exactly its padding.\n const width =\n range.getBoundingClientRect().width +\n parseFloat(style.paddingLeft) +\n parseFloat(style.paddingRight) +\n parseFloat(style.borderLeftWidth) +\n parseFloat(style.borderRightWidth);\n\n if (widest === null || width > widest) {\n widest = width;\n }\n }\n\n return widest;\n}\n","/**\n * The `pointerdown` a gesture starts from. A native `PointerEvent` is one,\n * and so is a framework's wrapper around it, such as React's synthetic\n * event, so a renderer passes whichever its handler received.\n */\nexport interface PointerPress {\n readonly pointerId: number;\n readonly clientX: number;\n readonly clientY: number;\n readonly button: number;\n readonly isPrimary: boolean;\n preventDefault(): void;\n}\n\n/**\n * Whether `press` may start a gesture: the main button of the primary\n * pointer. A right-click opens a context menu and a second finger is not the\n * gesture already under way, so neither starts a drag or a resize.\n */\nexport function isPrimaryPress(press: PointerPress): boolean {\n return press.button === 0 && press.isPrimary;\n}\n\ninterface PointerGestureHandlers {\n onMove: (event: PointerEvent) => void;\n onEnd: () => void;\n onCancel: () => void;\n}\n\n/**\n * Wires up a pointer-driven gesture — capture, move tracking, and an\n * Escape-to-cancel — leaving the caller's `onMove`/`onEnd`/`onCancel` to own\n * the gesture's own domain logic (and any state reset that goes with it).\n */\nexport function startPointerGesture(\n element: HTMLElement,\n pointerId: number,\n handlers: PointerGestureHandlers,\n): void {\n element.setPointerCapture(pointerId);\n\n function stop(): void {\n element.removeEventListener(\"pointermove\", move);\n element.removeEventListener(\"pointerup\", end);\n element.removeEventListener(\"pointercancel\", cancel);\n window.removeEventListener(\"keydown\", onKeyDown);\n if (element.hasPointerCapture(pointerId)) {\n element.releasePointerCapture(pointerId);\n }\n }\n\n function move(event: PointerEvent): void {\n handlers.onMove(event);\n }\n\n function end(): void {\n stop();\n handlers.onEnd();\n }\n\n function cancel(): void {\n stop();\n handlers.onCancel();\n }\n\n function onKeyDown(event: KeyboardEvent): void {\n if (event.key === \"Escape\") {\n cancel();\n }\n }\n\n element.addEventListener(\"pointermove\", move);\n element.addEventListener(\"pointerup\", end);\n element.addEventListener(\"pointercancel\", cancel);\n window.addEventListener(\"keydown\", onKeyDown);\n}\n","import {\n isPrimaryPress,\n startPointerGesture,\n type PointerPress,\n} from \"./pointerGesture\";\n\n/**\n * How far the pointer travels before a press becomes a drag. Below it the\n * gesture is still a click, which is what leaves the pressed element free to\n * take one.\n */\nconst DRAG_THRESHOLD = 4;\n\n/** What a renderer draws while a reorder drag is open. */\nexport interface DragReorderState<Target> {\n /** The item being dragged, once the press has become a drag. */\n readonly draggedId: string | null;\n /** Where it would land if released now, `null` for nowhere valid. */\n readonly dropTarget: Target | null;\n /** Where the element trailing the pointer sits, as a CSS `transform`. */\n readonly ghostTransform: string | null;\n}\n\n/** The state while nothing is dragged — the one a drag reports when it ends. */\nexport const NO_DRAG: DragReorderState<never> = {\n draggedId: null,\n dropTarget: null,\n ghostTransform: null,\n};\n\nexport interface DragReorderHandlers<Target> {\n /**\n * Where a drop at `(clientX, clientY)` would land, or `null` for nowhere\n * valid — including a gap `id` already occupies, so a drop target only\n * ever promises a move that happens. Called on every pointer move once the\n * drag has opened. `resolveColumnDropTarget` and\n * `resolveGroupByDropTarget` are the grid's own.\n */\n readonly resolveTarget: (\n clientX: number,\n clientY: number,\n id: string,\n ) => Target | null;\n /** Every change to what the renderer draws, `NO_DRAG` once the drag ends. */\n readonly onState: (state: DragReorderState<Target>) => void;\n /** The pointer was released over a valid target. */\n readonly onDrop: (target: Target, id: string) => void;\n}\n\n/**\n * The pointer mechanics behind every drag-to-reorder gesture in the grid — a\n * column header dragged among the others or into the group-by bar, a\n * group-by chip dragged within it: capture, the drag threshold, and the ghost\n * trailing the pointer, holding the spot it was picked up by. Returns whether\n * a gesture began; only the main button of the primary pointer starts one.\n *\n * The press is left to its default, so the pressed element still takes focus;\n * until the pointer has travelled `DRAG_THRESHOLD` pixels nothing is\n * reported, so a press that never moves stays a click. Escape, or a cancelled\n * pointer, leaves the order as it was.\n *\n * Pointer events rather than HTML5 drag-and-drop: `dragenter`/`dragleave`\n * fire per descendant, so a dragged item's own inner markup would each report\n * the target leaving and re-entering as the pointer crossed them.\n */\nexport function startDragReorder<Target>(\n id: string,\n grabElement: HTMLElement,\n press: PointerPress,\n handlers: DragReorderHandlers<Target>,\n): boolean {\n if (!isPrimaryPress(press)) {\n return false;\n }\n\n const { pointerId, clientX: startX } = press;\n const grabX = startX - grabElement.getBoundingClientRect().left;\n let dragging = false;\n let target: Target | null = null;\n\n startPointerGesture(grabElement, pointerId, {\n onMove(moveEvent) {\n const { clientX, clientY } = moveEvent;\n if (!dragging) {\n if (Math.abs(clientX - startX) < DRAG_THRESHOLD) {\n return;\n }\n dragging = true;\n }\n target = handlers.resolveTarget(clientX, clientY, id);\n handlers.onState({\n draggedId: id,\n dropTarget: target,\n ghostTransform: `translate(${String(clientX - grabX)}px, ${String(clientY)}px)`,\n });\n },\n onEnd() {\n const dropped = target;\n handlers.onState(NO_DRAG);\n if (dragging && dropped !== null) {\n handlers.onDrop(dropped, id);\n }\n },\n onCancel() {\n target = null;\n handlers.onState(NO_DRAG);\n },\n });\n return true;\n}\n","import {\n movesColumn,\n movesGroupBy,\n resolveDropBefore,\n type ColumnOrderState,\n type DropSide,\n type GroupByState,\n} from \"@gridkitjs/core\";\n\n/** A drop into the group-by bar, in front of `beforeColumnId` — `null` to append. */\nexport interface GroupByDropTarget {\n readonly kind: \"group-by\";\n readonly beforeColumnId: string | null;\n}\n\n/**\n * Where a dragged column header would land: among the other headers, in\n * front of `beforeId` (`null` for the end), or in the group-by bar.\n */\nexport type ColumnDropTarget =\n | { readonly kind: \"column-order\"; readonly beforeId: string | null }\n | GroupByDropTarget;\n\nexport interface ColumnDropContext {\n /** The ids of the columns on screen, in order — what a drop is resolved among. */\n readonly order: ColumnOrderState;\n /** What a drop onto the group-by bar is resolved against. */\n readonly groupBy: GroupByState;\n /** Column ids whose header may be dragged into the group-by bar at all. */\n readonly groupByDraggableIds: ReadonlySet<string>;\n}\n\n/**\n * The closest ancestor of (or the element itself at) `(x, y)` matching\n * `selector`. `closest` is what makes the whole matched element the target:\n * a hit on a descendant (a header's label, a chip's remove button) resolves\n * to it.\n *\n * Hit-tested rather than read from an event's target, which pointer capture\n * pins to the element the drag opened on.\n */\nfunction elementAt(x: number, y: number, selector: string): Element | null {\n return document.elementFromPoint(x, y)?.closest(selector) ?? null;\n}\n\n/**\n * Which half of `element` `x` falls in. Read fresh on every move, so a width\n * changed mid-drag cannot leave the midpoint stale.\n */\nfunction sideOf(element: Element, x: number): DropSide {\n const rect = element.getBoundingClientRect();\n return x < rect.left + rect.width / 2 ? \"before\" : \"after\";\n}\n\n/**\n * Where a header or chip dropped at `(clientX, clientY)` would land within\n * the group-by bar (`[data-gridkit-group-by-bar]`, its chips\n * `[data-gridkit-group-chip]`), or `null` when the pointer isn't over the bar\n * at all, or when the drop would change nothing. Shared by a header dragged\n * in from outside the bar and a chip dragged within it, so the two can never\n * disagree about where a drop lands.\n */\nexport function resolveGroupByDropTarget(\n clientX: number,\n clientY: number,\n movedId: string,\n groupBy: GroupByState,\n): GroupByDropTarget | null {\n const overBar = elementAt(clientX, clientY, \"[data-gridkit-group-by-bar]\");\n if (overBar === null) {\n return null;\n }\n\n const overChip = elementAt(clientX, clientY, \"[data-gridkit-group-chip]\");\n const chipId = overChip?.getAttribute(\"data-gridkit-group-chip\");\n const beforeColumnId =\n overChip && chipId\n ? resolveDropBefore(\n groupBy.map((entry) => entry.columnId),\n chipId,\n sideOf(overChip, clientX),\n )\n : null; // Over the (possibly empty) bar itself, past every chip: append.\n\n return movesGroupBy(groupBy, movedId, beforeColumnId)\n ? { kind: \"group-by\", beforeColumnId }\n : null;\n}\n\n/**\n * Where the header of `movedId` dropped at `(clientX, clientY)` would land:\n * before or after another header (`th[data-gridkit-column]`), by which half\n * of it the pointer is over, or else in the group-by bar — or `null` for\n * nowhere a drop would change anything.\n *\n * A column already grouped never targets the bar: reordering it there is its\n * chip's job, not its header's.\n */\nexport function resolveColumnDropTarget(\n clientX: number,\n clientY: number,\n movedId: string,\n { order, groupBy, groupByDraggableIds }: ColumnDropContext,\n): ColumnDropTarget | null {\n const over = elementAt(clientX, clientY, \"th[data-gridkit-column]\");\n const overId = over?.getAttribute(\"data-gridkit-column\");\n const beforeId =\n over && overId\n ? resolveDropBefore(order, overId, sideOf(over, clientX))\n : undefined;\n\n if (beforeId !== undefined) {\n // A gap the column already sits in would promise a move that\n // `moveColumnBefore` then declines to make.\n return movesColumn(order, movedId, beforeId)\n ? { kind: \"column-order\", beforeId }\n : null;\n }\n\n const alreadyGrouped = groupBy.some((entry) => entry.columnId === movedId);\n return groupByDraggableIds.has(movedId) && !alreadyGrouped\n ? resolveGroupByDropTarget(clientX, clientY, movedId, groupBy)\n : null;\n}\n","/**\n * Reports `element`'s inner width now and on every change after, until the\n * returned function disconnects it — what `resizeMode: \"fit\"` fills.\n *\n * The first report is synchronous, so a renderer calling this before paint\n * lays its columns out at the right width from the first frame rather than\n * visibly jumping once the observer's first callback arrives.\n *\n * Reads `clientWidth` rather than the observer entry's `contentRect` so the\n * first report and every later one come from the same, already-rounded,\n * source.\n */\nexport function observeElementWidth(\n element: HTMLElement,\n onWidth: (width: number) => void,\n): () => void {\n onWidth(element.clientWidth);\n\n const observer = new ResizeObserver(() => {\n onWidth(element.clientWidth);\n });\n observer.observe(element);\n\n return () => {\n observer.disconnect();\n };\n}\n","import {\n clampFocus,\n HEADER_ROW,\n nextFocusForKey,\n type GridFocus,\n type NavigationModifiers,\n} from \"@gridkitjs/core\";\n\n/** Rows a page key moves when the viewport cannot be measured. */\nconst FALLBACK_PAGE = 10;\n\n/**\n * The window of rows a row virtualizer has drawn, and a way to bring a row\n * into it — what a focus move needs when its target row may not be mounted.\n */\nexport interface FocusVirtualRange {\n readonly startIndex: number;\n readonly endIndex: number;\n readonly scrollToIndex: (index: number) => void;\n}\n\n/** How to find the rendered rows the browser's focus moves among. */\nexport interface GridFocusRows {\n /** The header row's element, `null` while none is rendered. */\n readonly getHeaderRow: () => HTMLTableRowElement | null;\n /** Body row `rowIndex`'s element — `findBodyRow` finds it — `null` while it is not rendered. */\n readonly getBodyRow: (rowIndex: number) => HTMLTableRowElement | null;\n}\n\nexport interface GridFocusOptions {\n /** Body rows, counting group headers and summaries — every row a key could step through. */\n readonly rowCount: number;\n readonly columnCount: number;\n /**\n * Rows the vertical keys step over rather than stop on — a group's own\n * summary row, which takes a row slot but is not a stop of its own.\n */\n readonly isSkippableRow?: ((rowIndex: number) => boolean) | undefined;\n /**\n * The window the renderer drew, when rows are virtualized. A move can\n * target a row outside it — `ArrowDown` past its end, or `Ctrl+End` — that\n * no lookup would find until it is scrolled into range.\n */\n readonly virtualRange?: FocusVirtualRange | null | undefined;\n}\n\nexport interface GridFocusController {\n /**\n * The cell holding the grid's single tab stop, clamped into the current\n * row and column counts. The same object until it changes, so it can serve\n * as a renderer's snapshot.\n */\n readonly getFocus: () => GridFocus;\n /** Calls `listener` whenever the focused cell moves. Returns the unsubscribe. */\n readonly subscribe: (listener: () => void) => () => void;\n /**\n * Replaces the options, as a renderer does on every render with its\n * current counts and window. Calls no listener: the renderer calling it is\n * already drawing.\n */\n readonly setOptions: (options: GridFocusOptions) => void;\n /**\n * Moves the tab stop, and the browser's focus with it once the renderer\n * has drawn the move (see `focusDrawnCell`). Moving to the cell already\n * focused does nothing, which is what lets a cell's own `focus` event\n * report a click through here without a loop.\n */\n readonly focusCell: (rowIndex: number, columnIndex: number) => void;\n /**\n * Moves the tab stop as `key` does — the arrows, Home, End, the page keys,\n * with Ctrl for the grid's corners. Returns whether the key was one of\n * them, in which case the caller prevents its default: taken even when the\n * move lands nowhere, or an arrow at the last row would scroll the page\n * out from under the grid.\n */\n readonly handleKey: (\n key: string,\n modifiers: NavigationModifiers,\n pageSize: number,\n ) => boolean;\n /**\n * Connects the tab stop to the rendered rows, which the browser's focus\n * then follows it among. Returns the function that disconnects them.\n */\n readonly attach: (rows: GridFocusRows) => () => void;\n /**\n * Called by the renderer once it has drawn `drawn` — a value `getFocus`\n * returned — so the browser's focus follows a move to the cell now in the\n * markup. Does nothing unless a move to exactly that drawing is pending.\n */\n readonly focusDrawnCell: (drawn: GridFocus) => void;\n}\n\n/**\n * The cell at a coordinate. `columnIndex` is clamped to the row's own last\n * cell rather than read directly: a group-header row renders one\n * `<td colSpan>` regardless of the column count, so a focus arriving from a\n * data row further right would otherwise address a cell that row doesn't\n * have.\n */\nfunction cellIn(\n row: HTMLTableRowElement | null,\n columnIndex: number,\n): HTMLElement | null {\n if (row === null) {\n return null;\n }\n return row.cells[Math.min(columnIndex, row.cells.length - 1)] ?? null;\n}\n\n/**\n * Body row `rowIndex` in `bodyTable`, by its `data-gridkit-row-index` rather\n * than by its position in `tBodies[0].rows`: a virtualized body holds only a\n * slice of the rows, behind a leading spacer `<tr>`, so once the window\n * scrolls a DOM position no longer matches the row's index in the full array\n * and addresses some other mounted row, or none.\n *\n * The attribute holds a plain integer, so the selector needs no escaping,\n * unlike one built from a consumer's row id.\n */\nexport function findBodyRow(\n bodyTable: HTMLTableElement | null,\n rowIndex: number,\n): HTMLTableRowElement | null {\n return (\n bodyTable?.tBodies[0]?.querySelector<HTMLTableRowElement>(\n `:scope > tr[data-gridkit-row-index=\"${String(rowIndex)}\"]`,\n ) ?? null\n );\n}\n\n/**\n * How many rows fit `bodyTable`'s scroll wrapper — its parent, the row area\n * alone, not the header, footer and pager around it — which is what a page\n * key moves. Reads the first rendered row's height, and falls back to ten\n * rows when nothing can be measured.\n */\nexport function measurePageSize(bodyTable: HTMLTableElement | null): number {\n const firstRow = bodyTable?.tBodies[0]?.rows[0];\n const viewport = bodyTable?.parentElement;\n if (\n firstRow === undefined ||\n viewport === null ||\n viewport === undefined ||\n firstRow.offsetHeight === 0\n ) {\n return FALLBACK_PAGE;\n }\n return Math.max(1, Math.floor(viewport.clientHeight / firstRow.offsetHeight));\n}\n\n/**\n * The grid's single tab stop and the keys that move it, which is what\n * `role=\"grid\"` obliges: arrow keys travel cell to cell and Tab passes the\n * whole grid by. The renderer attaches its rows, draws `getFocus()` —\n * `tabindex=\"0\"` on that cell, `-1` on every other — re-reads it when\n * `subscribe` calls, and calls `focusDrawnCell` after drawing.\n *\n * Separate from selection, because focus and selection are different states:\n * a cell is focused while travelling before anything is selected, and every\n * cell of a range is selected without being focused. Navigation also has to\n * work with selection off entirely.\n *\n * Starts on the header row's first cell.\n */\nexport function createGridFocus(\n initialOptions: GridFocusOptions,\n): GridFocusController {\n let options = initialOptions;\n let rows: GridFocusRows | null = null;\n const listeners = new Set<() => void>();\n\n /**\n * Clamped on the way out rather than written back, so a coordinate pushed\n * out of range by a change to the data is restored when the data comes\n * back.\n */\n let stored: GridFocus = { rowIndex: HEADER_ROW, columnIndex: 0 };\n let snapshot = {\n stored,\n rowCount: options.rowCount,\n columnCount: options.columnCount,\n focus: clampFocus(stored, options.rowCount, options.columnCount),\n };\n\n /**\n * The move the browser's focus still has to follow, or `null` once it has.\n * Without it every drawing would pull focus into the grid, the first\n * included.\n *\n * Holds the very `stored` object `focusCell` wrote rather than a flag, so\n * only a drawing made from that move can act on it. Under virtualization a\n * scroll can commit a drawing just before a key press, and a renderer may\n * run that older drawing's after-draw step only once the press has marked\n * a new move pending. With a flag, that stale step found its own (still\n * mounted) cell, focused it and cleared the flag, so the new move's own\n * step returned early and never scrolled its row into range.\n */\n let pendingMove: GridFocus | null = null;\n\n function getFocus(): GridFocus {\n if (\n snapshot.stored !== stored ||\n snapshot.rowCount !== options.rowCount ||\n snapshot.columnCount !== options.columnCount\n ) {\n snapshot = {\n stored,\n rowCount: options.rowCount,\n columnCount: options.columnCount,\n focus: clampFocus(stored, options.rowCount, options.columnCount),\n };\n }\n return snapshot.focus;\n }\n\n function focusCell(rowIndex: number, columnIndex: number): void {\n const next = clampFocus(\n { rowIndex, columnIndex },\n options.rowCount,\n options.columnCount,\n );\n const current = getFocus();\n if (\n next.rowIndex === current.rowIndex &&\n next.columnIndex === current.columnIndex\n ) {\n return;\n }\n pendingMove = next;\n stored = next;\n for (const listener of listeners) {\n listener();\n }\n }\n\n return {\n getFocus,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n setOptions(next) {\n options = next;\n },\n focusCell,\n handleKey(key, modifiers, pageSize) {\n const next = nextFocusForKey(\n key,\n modifiers,\n getFocus(),\n options.rowCount,\n options.columnCount,\n pageSize,\n options.isSkippableRow,\n );\n if (next === null) {\n return false;\n }\n focusCell(next.rowIndex, next.columnIndex);\n return true;\n },\n attach(next) {\n rows = next;\n return () => {\n if (rows === next) {\n rows = null;\n }\n };\n },\n focusDrawnCell(drawn) {\n if (\n rows === null ||\n pendingMove === null ||\n snapshot.stored !== pendingMove ||\n snapshot.focus !== drawn\n ) {\n return;\n }\n const cell = cellIn(\n drawn.rowIndex === HEADER_ROW\n ? rows.getHeaderRow()\n : rows.getBodyRow(drawn.rowIndex),\n drawn.columnIndex,\n );\n if (cell !== null) {\n pendingMove = null;\n // Focusing the cell the browser already sits on is a no-op, which is\n // what makes one method serve both a key press and a cell reporting\n // a click.\n cell.focus();\n return;\n }\n\n // The target row isn't mounted — under virtualization, that means it's\n // outside the drawn window rather than an actual failure. Ask for it to\n // be brought into range; the move stays pending, so the next drawing\n // retries once the row mounts. Without virtualization (or for a row\n // out of bounds even for it) there is nothing further to try, so the\n // move is dropped rather than left to retry forever.\n const range = options.virtualRange;\n if (\n range != null &&\n drawn.rowIndex !== HEADER_ROW &&\n (drawn.rowIndex < range.startIndex || drawn.rowIndex > range.endIndex)\n ) {\n range.scrollToIndex(drawn.rowIndex);\n return;\n }\n pendingMove = null;\n },\n };\n}\n","export interface InfiniteScrollOptions {\n /**\n * The element whose approach triggers a load — the always-last row of the\n * body — or `null` while none is rendered.\n */\n readonly sentinel: Element | null;\n /** Whether a further load could still return more rows. `false` stops watching altogether. */\n readonly hasMore: boolean;\n /**\n * Called at most once per approach: when the sentinel comes within\n * `rootMargin` of the root's visible area, and not again until\n * `isLoadingMore` has gone from `true` back to `false`.\n */\n readonly onLoadMore?: (() => void) | undefined;\n /** Whether a load `onLoadMore` started is still in flight. */\n readonly isLoadingMore?: boolean | undefined;\n /** How far ahead of the sentinel scrolling into view a load starts, as CSS margins. Defaults to `\"200px\"`. */\n readonly rootMargin?: string | undefined;\n}\n\nexport interface InfiniteScroll {\n /**\n * Takes the current options. Cheap when nothing changed, so a renderer may\n * call it after every render; a new sentinel or `rootMargin` restarts the\n * observer, and `isLoadingMore` going back to `false` re-checks at once.\n */\n readonly update: (options: InfiniteScrollOptions) => void;\n /** Stops watching for good. */\n readonly detach: () => void;\n}\n\n/**\n * Turns a sentinel's approach to the bottom of the scrolling `root` into\n * `onLoadMore` calls, no more than one per \"the sentinel is near and no load\n * is in flight since the last one finished\".\n *\n * Three facts drive it: whether the sentinel is intersecting (as of the\n * observer's last report), whether `onLoadMore` has fired since the last\n * load finished, and whether a load was in flight at the last update. They\n * outlive the sentinel itself, which a renderer mounts and unmounts as\n * `hasMore` changes, so they live here rather than on any one observer.\n *\n * Off means off: with `hasMore` false, or no sentinel, no observer exists.\n */\nexport function attachInfiniteScroll(\n root: Element,\n options: InfiniteScrollOptions,\n): InfiniteScroll {\n let latest = options;\n let intersecting = false;\n /** Set once `onLoadMore` fires, cleared only when a load finishes. */\n let fired = false;\n let wasLoading = options.isLoadingMore ?? false;\n let observer: IntersectionObserver | null = null;\n let observed: { sentinel: Element; rootMargin: string } | null = null;\n\n function maybeLoadMore(): void {\n if (\n !intersecting ||\n !latest.hasMore ||\n (latest.isLoadingMore ?? false) ||\n fired\n ) {\n return;\n }\n fired = true;\n latest.onLoadMore?.();\n }\n\n function disconnect(): void {\n observer?.disconnect();\n observer = null;\n observed = null;\n intersecting = false;\n }\n\n function observe(): void {\n const sentinel = latest.hasMore ? latest.sentinel : null;\n const rootMargin = latest.rootMargin ?? \"200px\";\n if (\n observed !== null &&\n observed.sentinel === sentinel &&\n observed.rootMargin === rootMargin\n ) {\n return;\n }\n disconnect();\n if (sentinel === null) {\n return;\n }\n observer = new IntersectionObserver(\n (entries) => {\n const entry = entries[entries.length - 1];\n if (entry === undefined) {\n return;\n }\n intersecting = entry.isIntersecting;\n maybeLoadMore();\n },\n { root, rootMargin },\n );\n observer.observe(sentinel);\n observed = { sentinel, rootMargin };\n }\n\n observe();\n\n return {\n update(next) {\n latest = next;\n const loading = next.isLoadingMore ?? false;\n if (wasLoading && !loading) {\n fired = false;\n // The rows a finished load appended may not have pushed the sentinel\n // out of `rootMargin` at all, in which case it never leaves and\n // re-enters — no observer report would come to trigger the next load.\n maybeLoadMore();\n }\n wasLoading = loading;\n observe();\n },\n detach: disconnect,\n };\n}\n","/** Which side of its anchor an overlay opens on. */\nexport type OverlaySide = \"top\" | \"bottom\" | \"left\" | \"right\";\n\n/**\n * Which edge of the anchor the overlay lines up with along that side: its\n * left edge (`start`) or right edge (`end`) for `top`/`bottom`, its top or\n * bottom edge for `left`/`right`.\n */\nexport type OverlayAlign = \"start\" | \"end\";\n\nexport type OverlayPlacement = `${OverlaySide}-${OverlayAlign}`;\n\n/** A box in viewport coordinates, as `getBoundingClientRect` reports one. */\nexport interface OverlayRect {\n readonly top: number;\n readonly left: number;\n readonly width: number;\n readonly height: number;\n}\n\nexport interface OverlaySize {\n readonly width: number;\n readonly height: number;\n}\n\nexport interface OverlayPlacementOptions {\n /** Where the overlay would rather open. Defaults to `\"bottom-start\"`. */\n readonly placement?: OverlayPlacement | undefined;\n /** Space between the anchor and the overlay, in pixels. Defaults to `0`. */\n readonly gap?: number | undefined;\n}\n\n/** Where an overlay goes: its top-left corner, and the placement it ended up with. */\nexport interface OverlayPosition {\n readonly top: number;\n readonly left: number;\n readonly placement: OverlayPlacement;\n}\n\nconst OPPOSITE_SIDE: Readonly<Record<OverlaySide, OverlaySide>> = {\n top: \"bottom\",\n bottom: \"top\",\n left: \"right\",\n right: \"left\",\n};\n\nfunction isVertical(side: OverlaySide): boolean {\n return side === \"top\" || side === \"bottom\";\n}\n\n/** How much room `side` of `anchor` leaves before the viewport's edge. */\nfunction roomBeside(\n side: OverlaySide,\n anchor: OverlayRect,\n viewport: OverlaySize,\n): number {\n switch (side) {\n case \"top\":\n return anchor.top;\n case \"bottom\":\n return viewport.height - (anchor.top + anchor.height);\n case \"left\":\n return anchor.left;\n case \"right\":\n return viewport.width - (anchor.left + anchor.width);\n }\n}\n\n/**\n * The preferred side if the overlay fits there, the opposite one if it fits\n * there instead, and otherwise whichever has more room — a menu too tall for\n * either still shows as much of itself as it can.\n */\nfunction chooseSide(\n preferred: OverlaySide,\n needed: number,\n anchor: OverlayRect,\n viewport: OverlaySize,\n): OverlaySide {\n const opposite = OPPOSITE_SIDE[preferred];\n const room = roomBeside(preferred, anchor, viewport);\n const oppositeRoom = roomBeside(opposite, anchor, viewport);\n if (room >= needed) {\n return preferred;\n }\n return oppositeRoom >= needed || oppositeRoom > room ? opposite : preferred;\n}\n\n/** The preferred edge unless only the other one keeps the overlay on screen. */\nfunction chooseAlign(\n preferred: OverlayAlign,\n fitsAtStart: boolean,\n fitsAtEnd: boolean,\n): OverlayAlign {\n if (preferred === \"start\") {\n return fitsAtStart || !fitsAtEnd ? \"start\" : \"end\";\n }\n return fitsAtEnd || !fitsAtStart ? \"end\" : \"start\";\n}\n\n/** The overlay's offset away from the anchor: its top for `top`/`bottom`, its left otherwise. */\nfunction offsetBeside(\n side: OverlaySide,\n anchor: OverlayRect,\n panel: OverlaySize,\n gap: number,\n): number {\n switch (side) {\n case \"top\":\n return anchor.top - gap - panel.height;\n case \"bottom\":\n return anchor.top + anchor.height + gap;\n case \"left\":\n return anchor.left - gap - panel.width;\n case \"right\":\n return anchor.left + anchor.width + gap;\n }\n}\n\n/** Keeps `value` within `[0, limit]`, pinning to `0` when even that fails. */\nfunction clampIntoView(value: number, limit: number): number {\n return Math.max(0, Math.min(value, limit));\n}\n\n/**\n * Where an overlay of size `panel` goes beside `anchor` so that it stays in\n * `viewport`: on the preferred side unless only the opposite one has room,\n * lined up with the preferred edge unless that runs it off screen and the\n * other edge does not, and finally pushed back inside if it still overflows.\n * Pure, so the rules are testable without a browser; `attachOverlay` feeds it\n * the measurements.\n *\n * A point — a context menu opened where the pointer is — is an anchor with\n * no width or height, which is what makes `start`/`end` mean \"after\" and\n * \"before\" it.\n */\nexport function placeOverlay(\n anchor: OverlayRect,\n panel: OverlaySize,\n viewport: OverlaySize,\n options: OverlayPlacementOptions = {},\n): OverlayPosition {\n const [preferredSide, preferredAlign] = (\n options.placement ?? \"bottom-start\"\n ).split(\"-\") as [OverlaySide, OverlayAlign];\n const gap = options.gap ?? 0;\n const vertical = isVertical(preferredSide);\n\n const side = chooseSide(\n preferredSide,\n (vertical ? panel.height : panel.width) + gap,\n anchor,\n viewport,\n );\n\n // The axis the overlay runs along beside the anchor.\n const anchorStart = vertical ? anchor.left : anchor.top;\n const anchorEnd = anchorStart + (vertical ? anchor.width : anchor.height);\n const extent = vertical ? panel.width : panel.height;\n const room = vertical ? viewport.width : viewport.height;\n const align = chooseAlign(\n preferredAlign,\n anchorStart + extent <= room,\n anchorEnd - extent >= 0,\n );\n const along = align === \"start\" ? anchorStart : anchorEnd - extent;\n const away = offsetBeside(side, anchor, panel, gap);\n\n return {\n top: clampIntoView(vertical ? away : along, viewport.height - panel.height),\n left: clampIntoView(vertical ? along : away, viewport.width - panel.width),\n placement: `${side}-${align}`,\n };\n}\n","import {\n placeOverlay,\n type OverlayPlacementOptions,\n type OverlayRect,\n} from \"./placeOverlay\";\n\n/** What an overlay opens beside: an element, or a point in viewport coordinates. */\nexport type OverlayAnchor =\n Element | { readonly x: number; readonly y: number };\n\n/** Why an overlay asked to close. */\nexport type OverlayDismissReason =\n \"escape\" | \"outside\" | \"scroll\" | \"resize\" | \"blur\";\n\nexport interface OverlayOptions extends OverlayPlacementOptions {\n /**\n * The overlay asks to close: Escape was pressed, a pointer went down\n * outside it, something outside it scrolled, the window resized or lost\n * focus, or focus moved outside it. The caller closes it — removes the\n * panel and detaches — or keeps it open by doing nothing.\n */\n readonly onDismiss: (reason: OverlayDismissReason) => void;\n /**\n * Where focus goes when the overlay detaches. Defaults to the element that\n * had focus when it attached; `null` leaves focus alone.\n */\n readonly returnFocusTo?: HTMLElement | null | undefined;\n}\n\n/** Everything focusable by script, so a menu's `tabindex=\"-1\"` items count. */\nconst FOCUSABLE =\n \"a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]\";\n\nfunction anchorRect(anchor: OverlayAnchor): OverlayRect {\n if (anchor instanceof Element) {\n const { top, left, width, height } = anchor.getBoundingClientRect();\n return { top, left, width, height };\n }\n return { top: anchor.y, left: anchor.x, width: 0, height: 0 };\n}\n\n/**\n * Shows `panel` as an overlay beside `anchor` — a menu, a popover, a filter\n * editor — and returns the function that ends it. The caller renders the\n * panel, usually as a child of `document.body` so no scrolling ancestor clips\n * it; this positions it, moves focus into it, and reports through\n * `onDismiss` when the user is done with it.\n *\n * The panel is positioned `fixed`, at the place `placeOverlay` picks from the\n * anchor's and the panel's measured boxes, and carries that placement in\n * `data-gridkit-placement` (`\"top-start\"` once flipped above, say) for a\n * stylesheet to point an arrow by. Focus moves to the panel's first focusable\n * descendant, or to the panel itself — given `tabindex=\"-1\"` if it has no\n * `tabindex` — when there is none.\n *\n * Nothing that happens on or in the anchor dismisses the overlay, so a button\n * that toggles it can close it with its own click rather than having the\n * overlay close and the click reopen it.\n *\n * Detaching removes every listener and returns focus, but only when focus is\n * still in the panel or nowhere — a click that dismissed it by landing on\n * another control leaves focus on that control.\n */\nexport function attachOverlay(\n anchor: OverlayAnchor,\n panel: HTMLElement,\n options: OverlayOptions,\n): () => void {\n const { onDismiss } = options;\n const returnFocusTo =\n options.returnFocusTo === undefined\n ? document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null\n : options.returnFocusTo;\n\n function belongs(target: EventTarget | null): boolean {\n return (\n target instanceof Node &&\n (panel.contains(target) ||\n (anchor instanceof Element && anchor.contains(target)))\n );\n }\n\n function onKeyDown(event: KeyboardEvent): void {\n if (event.key === \"Escape\") {\n onDismiss(\"escape\");\n }\n }\n\n function onPointerDown(event: PointerEvent): void {\n if (!belongs(event.target)) {\n onDismiss(\"outside\");\n }\n }\n\n // The overlay is placed in viewport coordinates, so any scroll that moves\n // its anchor leaves it behind — except a scroll inside the panel itself.\n function onScroll(event: Event): void {\n if (!(event.target instanceof Node && panel.contains(event.target))) {\n onDismiss(\"scroll\");\n }\n }\n\n function onResize(): void {\n onDismiss(\"resize\");\n }\n\n // Only a move to somewhere definite counts: focus dropping to nothing, as\n // when the focused item re-renders away, is not the user leaving.\n function onFocusOut(event: FocusEvent): void {\n if (event.relatedTarget !== null && !belongs(event.relatedTarget)) {\n onDismiss(\"blur\");\n }\n }\n\n function onWindowBlur(): void {\n onDismiss(\"blur\");\n }\n\n // Shrink-wrapped before it is measured: in normal flow a block panel\n // would report the full width of the page.\n panel.style.position = \"fixed\";\n panel.style.top = \"0px\";\n panel.style.left = \"0px\";\n const { width, height } = panel.getBoundingClientRect();\n const position = placeOverlay(\n anchorRect(anchor),\n { width, height },\n {\n width: document.documentElement.clientWidth,\n height: document.documentElement.clientHeight,\n },\n options,\n );\n panel.style.top = `${String(position.top)}px`;\n panel.style.left = `${String(position.left)}px`;\n panel.setAttribute(\"data-gridkit-placement\", position.placement);\n\n const first = panel.querySelector<HTMLElement>(FOCUSABLE);\n if (first === null && !panel.hasAttribute(\"tabindex\")) {\n panel.tabIndex = -1;\n }\n (first ?? panel).focus({ preventScroll: true });\n\n document.addEventListener(\"keydown\", onKeyDown, true);\n document.addEventListener(\"pointerdown\", onPointerDown, true);\n window.addEventListener(\"scroll\", onScroll, { capture: true, passive: true });\n window.addEventListener(\"resize\", onResize);\n window.addEventListener(\"blur\", onWindowBlur);\n panel.addEventListener(\"focusout\", onFocusOut);\n\n let attached = true;\n return () => {\n if (!attached) {\n return;\n }\n attached = false;\n document.removeEventListener(\"keydown\", onKeyDown, true);\n document.removeEventListener(\"pointerdown\", onPointerDown, true);\n window.removeEventListener(\"scroll\", onScroll, { capture: true });\n window.removeEventListener(\"resize\", onResize);\n window.removeEventListener(\"blur\", onWindowBlur);\n panel.removeEventListener(\"focusout\", onFocusOut);\n\n const active = document.activeElement;\n if (\n returnFocusTo !== null &&\n (active === null || active === document.body || panel.contains(active))\n ) {\n returnFocusTo.focus({ preventScroll: true });\n }\n };\n}\n","import { visibleRange, type VisibleRange } from \"@gridkitjs/core\";\n\nexport interface RowVirtualizerOptions {\n /** The height assumed for a row not yet measured. */\n readonly estimatedRowHeight: number;\n /** Rows rendered beyond each edge of the visible area. */\n readonly overscan: number;\n /**\n * The window of rows to render moved: called whenever a scroll, a resize\n * of the scroll element, a measured row height or a change of rows moves\n * it, and never with a window equal to the last.\n */\n readonly onRange: (range: VisibleRange) => void;\n}\n\nexport interface RowVirtualizer {\n /** How many rows there are. Measured heights of the rows that remain are kept. */\n readonly setRowCount: (count: number) => void;\n /** Forgets every measured height, for rows that are no longer the ones measured. */\n readonly resetHeights: () => void;\n /**\n * A ref callback for the element of row `index` (its place among all the\n * rows, not among those rendered): measured while mounted, forgotten once\n * called with `null`. The same function for the same index every time, so\n * a renderer re-attaching it on every render costs nothing.\n */\n readonly measureRow: (index: number) => (element: HTMLElement | null) => void;\n /**\n * Scrolls so row `index` is in view. A mounted row is scrolled to directly;\n * one outside the window is jumped to at its estimated offset, then\n * scrolled to properly once it mounts and can be measured.\n */\n readonly scrollToIndex: (\n index: number,\n options?: ScrollIntoViewOptions,\n ) => void;\n /** Stops listening and measuring, for good. */\n readonly destroy: () => void;\n}\n\nfunction sameRange(a: VisibleRange, b: VisibleRange): boolean {\n return (\n a.startIndex === b.startIndex &&\n a.endIndex === b.endIndex &&\n a.offsetBefore === b.offsetBefore &&\n a.offsetAfter === b.offsetAfter\n );\n}\n\n/**\n * Windows a long list of rows inside `scrollElement` down to those near its\n * scroll position, measuring each rendered row's real height rather than\n * assuming a uniform one — rows are not uniform (`.is-wrapped` cells, group\n * header and summary rows), so a fixed-height virtualizer would either break\n * wrapping or rule it out. The renderer draws `onRange`'s window, with\n * spacers of `offsetBefore`/`offsetAfter` around it, and hands each row's\n * element to `measureRow`.\n *\n * The heights are a mutable array, not a structure rebuilt per measurement:\n * one can arrive many times a second while scrolling. The pure arithmetic\n * over them is `@gridkitjs/core`'s `visibleRange`; this owns only the\n * mutability around it and the listeners that feed it — the scroll position,\n * the scroll element's height, and one `ResizeObserver` shared by every\n * mounted row.\n */\nexport function createRowVirtualizer(\n scrollElement: HTMLElement,\n options: RowVirtualizerOptions,\n): RowVirtualizer {\n const { estimatedRowHeight, overscan, onRange } = options;\n\n let heights: (number | undefined)[] = [];\n let scrollTop = scrollElement.scrollTop;\n let viewportHeight = scrollElement.clientHeight;\n let range: VisibleRange | null = null;\n let destroyed = false;\n /** The row `scrollToIndex` is still waiting to mount, to scroll to it properly. */\n let pendingScroll:\n { index: number; options: ScrollIntoViewOptions | undefined } | undefined;\n\n const elements = new Map<number, HTMLElement>();\n const indexByElement = new Map<Element, number>();\n const refCallbacks = new Map<number, (element: HTMLElement | null) => void>();\n\n function recompute(): void {\n if (destroyed) {\n return;\n }\n const next = visibleRange(\n heights,\n estimatedRowHeight,\n scrollTop,\n viewportHeight,\n overscan,\n );\n if (range !== null && sameRange(range, next)) {\n return;\n }\n range = next;\n onRange(next);\n }\n\n const rowObserver = new ResizeObserver((entries) => {\n let changed = false;\n for (const entry of entries) {\n const index = indexByElement.get(entry.target);\n if (index === undefined) continue;\n const height = entry.target.getBoundingClientRect().height;\n if (heights[index] !== height) {\n heights[index] = height;\n changed = true;\n }\n }\n if (changed) {\n recompute();\n }\n });\n\n // Kept current here rather than read on each recompute: a scroll can fire\n // many times a second, and each one only needs to feed the next window.\n const viewportObserver = new ResizeObserver(() => {\n viewportHeight = scrollElement.clientHeight;\n recompute();\n });\n viewportObserver.observe(scrollElement);\n\n function handleScroll(): void {\n scrollTop = scrollElement.scrollTop;\n recompute();\n }\n scrollElement.addEventListener(\"scroll\", handleScroll, { passive: true });\n\n function estimateOffsetBefore(index: number): number {\n let total = 0;\n for (let current = 0; current < index; current++) {\n total += heights[current] ?? estimatedRowHeight;\n }\n return total;\n }\n\n recompute();\n\n return {\n setRowCount(count) {\n heights = Array.from({ length: count }, (_, index) => heights[index]);\n recompute();\n },\n resetHeights() {\n heights = new Array<number | undefined>(heights.length).fill(undefined);\n recompute();\n },\n measureRow(index) {\n let callback = refCallbacks.get(index);\n if (callback === undefined) {\n callback = (element) => {\n const previous = elements.get(index);\n if (previous !== undefined) {\n rowObserver.unobserve(previous);\n indexByElement.delete(previous);\n elements.delete(index);\n }\n if (element === null || destroyed) {\n return;\n }\n elements.set(index, element);\n indexByElement.set(element, index);\n rowObserver.observe(element);\n if (pendingScroll?.index === index) {\n element.scrollIntoView(pendingScroll.options);\n pendingScroll = undefined;\n }\n };\n refCallbacks.set(index, callback);\n }\n return callback;\n },\n scrollToIndex(index, scrollOptions) {\n if (heights.length === 0) {\n return;\n }\n const clamped = Math.max(0, Math.min(index, heights.length - 1));\n const mounted = elements.get(clamped);\n if (mounted !== undefined) {\n mounted.scrollIntoView(scrollOptions);\n return;\n }\n pendingScroll = { index: clamped, options: scrollOptions };\n scrollElement.scrollTop = estimateOffsetBefore(clamped);\n },\n destroy() {\n destroyed = true;\n pendingScroll = undefined;\n scrollElement.removeEventListener(\"scroll\", handleScroll);\n viewportObserver.disconnect();\n rowObserver.disconnect();\n elements.clear();\n indexByElement.clear();\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gridkitjs/dom",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-free browser behaviours for GridKit renderers: gestures, focus, observers, virtualization, overlays and announcements.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Blagoja Blazhevski",
|
|
7
|
+
"homepage": "https://github.com/blagojablazhevski/gridkit#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/blagojablazhevski/gridkit.git",
|
|
11
|
+
"directory": "packages/dom"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"datagrid",
|
|
15
|
+
"grid",
|
|
16
|
+
"table",
|
|
17
|
+
"dom",
|
|
18
|
+
"headless",
|
|
19
|
+
"typescript",
|
|
20
|
+
"framework-agnostic"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=22"
|
|
25
|
+
},
|
|
26
|
+
"main": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@gridkitjs/core": "0.11.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"tsup": "^8.5.1",
|
|
44
|
+
"typescript": "^6.0.3",
|
|
45
|
+
"vitest": "^4.1.10"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"lint": "eslint src",
|
|
51
|
+
"test": "vitest run --passWithNoTests"
|
|
52
|
+
}
|
|
6
53
|
}
|