@gridkitjs/dom 0.0.0-dev-20261109133348 → 0.0.0-dev-20261109134250

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/dist/index.d.ts CHANGED
@@ -446,7 +446,7 @@ interface GridFocusController {
446
446
  * focused does nothing, which is what lets a cell's own `focus` event
447
447
  * report a click through here without a loop.
448
448
  */
449
- readonly focusCell: (rowIndex: number, columnIndex: number) => void;
449
+ readonly focusCell: (focus: GridFocus) => void;
450
450
  /**
451
451
  * Moves the tab stop as `key` does — the arrows, Home, End, the page keys,
452
452
  * with Ctrl for the grid's corners. Returns whether the key was one of
package/dist/index.js CHANGED
@@ -626,7 +626,7 @@ function observeElementWidth(element, onWidth) {
626
626
  // src/gridFocus.ts
627
627
  import {
628
628
  clampFocus,
629
- HEADER_ROW,
629
+ headerFocus,
630
630
  nextFocusForKey
631
631
  } from "@gridkitjs/core";
632
632
  var FALLBACK_PAGE = 10;
@@ -636,6 +636,12 @@ function cellIn(row, columnIndex) {
636
636
  }
637
637
  return row.cells[Math.min(columnIndex, row.cells.length - 1)] ?? null;
638
638
  }
639
+ function sameCell(a, b) {
640
+ if (a.area === "body" && b.area === "body") {
641
+ return a.rowIndex === b.rowIndex && a.columnIndex === b.columnIndex;
642
+ }
643
+ return a.area === b.area && a.columnIndex === b.columnIndex;
644
+ }
639
645
  function findBodyRow(bodyTable, rowIndex) {
640
646
  return bodyTable?.tBodies[0]?.querySelector(
641
647
  `:scope > tr[data-gridkit-row-index="${String(rowIndex)}"]`
@@ -653,7 +659,7 @@ function createGridFocus(initialOptions) {
653
659
  let options = initialOptions;
654
660
  let rows = null;
655
661
  const listeners = /* @__PURE__ */ new Set();
656
- let stored = { rowIndex: HEADER_ROW, columnIndex: 0 };
662
+ let stored = headerFocus(0);
657
663
  let snapshot = {
658
664
  stored,
659
665
  rowCount: options.rowCount,
@@ -691,14 +697,9 @@ function createGridFocus(initialOptions) {
691
697
  root.removeEventListener("pointerdown", dropPendingMove);
692
698
  };
693
699
  }
694
- function focusCell(rowIndex, columnIndex) {
695
- const next = clampFocus(
696
- { rowIndex, columnIndex },
697
- options.rowCount,
698
- options.columnCount
699
- );
700
- const current = getFocus();
701
- if (next.rowIndex === current.rowIndex && next.columnIndex === current.columnIndex) {
700
+ function focusCell(focus) {
701
+ const next = clampFocus(focus, options.rowCount, options.columnCount);
702
+ if (sameCell(next, getFocus())) {
702
703
  return;
703
704
  }
704
705
  pendingMove = next;
@@ -732,7 +733,7 @@ function createGridFocus(initialOptions) {
732
733
  if (next === null) {
733
734
  return false;
734
735
  }
735
- focusCell(next.rowIndex, next.columnIndex);
736
+ focusCell(next);
736
737
  return true;
737
738
  },
738
739
  attach(next, root) {
@@ -750,7 +751,7 @@ function createGridFocus(initialOptions) {
750
751
  return;
751
752
  }
752
753
  const cell = cellIn(
753
- drawn.rowIndex === HEADER_ROW ? rows.getHeaderRow() : rows.getBodyRow(drawn.rowIndex),
754
+ drawn.area === "header" ? rows.getHeaderRow() : rows.getBodyRow(drawn.rowIndex),
754
755
  drawn.columnIndex
755
756
  );
756
757
  if (cell !== null) {
@@ -759,7 +760,7 @@ function createGridFocus(initialOptions) {
759
760
  return;
760
761
  }
761
762
  const range = options.virtualRange;
762
- if (range != null && drawn.rowIndex !== HEADER_ROW && (drawn.rowIndex < range.startIndex || drawn.rowIndex > range.endIndex)) {
763
+ if (range != null && drawn.area === "body" && (drawn.rowIndex < range.startIndex || drawn.rowIndex > range.endIndex)) {
763
764
  range.scrollToIndex(drawn.rowIndex);
764
765
  return;
765
766
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/announcer.ts","../src/bodyTargets.ts","../src/checklist/attachChecklist.ts","../src/contextMenu/attachContextMenuTrigger.ts","../src/columnResize.ts","../src/pointerGesture.ts","../src/clipboard.ts","../src/download.ts","../src/dragReorder.ts","../src/dropTargets.ts","../src/elementWidth.ts","../src/gridFocus.ts","../src/infiniteScroll.ts","../src/menu/attachMenu.ts","../src/overlay/overlayStack.ts","../src/overlay/placeOverlay.ts","../src/overlay/attachOverlay.ts","../src/rowVirtualizer.ts","../src/toolbar/attachToolbar.ts","../src/toolbar/observeToolbarOverflow.ts","../src/print/printDocument.ts","../src/print/styles.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","export interface ChecklistOptions {\n /**\n * The item at `index` — its place in the whole list — asks to be checked\n * or unchecked: Space on it, or a click. The caller flips it in its own\n * state and draws the new `aria-checked`.\n */\n readonly onToggle: (index: number) => void;\n /** Focus moved to the item at `index`, by a key, a click or a script. */\n readonly onFocusChange?: ((index: number) => void) | undefined;\n /**\n * Bring the item at `index` into view, mounting it if the list's window\n * does not hold it. A virtualized renderer forwards this to its\n * virtualizer's `scrollToIndex`, with `{ block: \"nearest\" }` so a move of\n * one item scrolls by one item.\n */\n readonly scrollToIndex: (index: number) => void;\n}\n\nexport interface AttachedChecklist {\n /**\n * How many items the whole list holds, mounted or not. Starts at 0, so a\n * renderer calls this straight after attaching, and again whenever the\n * list changes — a search narrowing it, say. A focused place past the new\n * end moves back to the last item, and so does a move still waiting for\n * an item past it.\n */\n readonly setCount: (count: number) => void;\n /**\n * Focuses the item at `index`, clamped to the list, scrolling to it first.\n * An item outside the mounted window is focused once it mounts.\n */\n readonly focusItem: (index: number) => void;\n /**\n * Removes every listener and observer. The items keep their `tabindex`.\n * After it, `setCount` and `focusItem` do nothing: they neither scroll\n * nor focus, and a move still waiting for its item is dropped.\n */\n readonly detach: () => void;\n}\n\nconst ITEM = \"[data-gridkit-checklist-item]\";\nconst INDEX = \"data-gridkit-checklist-index\";\n\n/**\n * Where focus is going besides the focused place: nowhere, to an item a move\n * is waiting to see mounted, or nowhere ever again once detached.\n */\ntype ChecklistState =\n | { readonly kind: \"settled\" }\n | { readonly kind: \"awaiting-item\"; readonly index: number }\n | { readonly kind: \"detached\" };\n\nconst SETTLED: ChecklistState = { kind: \"settled\" };\nconst DETACHED: ChecklistState = { kind: \"detached\" };\n\nfunction indexOf(item: Element): number {\n return Number(item.getAttribute(INDEX));\n}\n\n/**\n * Makes `list` — a `role=\"listbox\"` the renderer drew, usually virtualized\n * so that only a window of a long list is mounted — behave as a\n * multi-select checklist with one tab stop: the arrow keys, Home, End,\n * PageUp and PageDown move focus among its items, and Space or a click asks\n * for the focused item to be toggled. It decides; the caller is told through\n * the callbacks and draws the outcome, `aria-checked` included.\n *\n * Items are the list's descendants with `data-gridkit-checklist-item` and a\n * `data-gridkit-checklist-index`: their place in the whole list rather than\n * in the window, since an item's place is all that survives it being\n * unmounted and mounted again. The list is the element that scrolls: PageUp\n * and PageDown move by as many items as it shows whole.\n *\n * One item at a time has `tabindex=\"0\"` — the focused one, or the first\n * mounted while the focused one is scrolled away — and the rest `-1`, kept\n * so as items mount by an observer of the list's children rather than by\n * the renderer. A move to an item outside the window asks `scrollToIndex`\n * for it and focuses it once it mounts, unless focus has gone somewhere\n * else in the meantime.\n *\n * Enter is not handled, so a form around the list hears it and applies;\n * Escape is the overlay stack's.\n */\nexport function attachChecklist(\n list: HTMLElement,\n options: ChecklistOptions,\n): AttachedChecklist {\n let count = 0;\n /** The focused place in the whole list: the tab stop, mounted or not. */\n let active = 0;\n let state: ChecklistState = SETTLED;\n\n function items(): HTMLElement[] {\n return Array.from(list.querySelectorAll<HTMLElement>(ITEM));\n }\n\n function itemAt(index: number): HTMLElement | null {\n return list.querySelector<HTMLElement>(\n `${ITEM}[${INDEX}=\"${String(index)}\"]`,\n );\n }\n\n function itemOf(target: EventTarget | null): HTMLElement | null {\n const item =\n target instanceof Element ? target.closest<HTMLElement>(ITEM) : null;\n return item !== null && list.contains(item) ? item : null;\n }\n\n function setTabStop(): void {\n const all = items();\n const stop = all.find((item) => indexOf(item) === active) ?? all[0];\n for (const item of all) {\n item.tabIndex = item === stop ? 0 : -1;\n }\n }\n\n function makeActive(index: number): void {\n if (index !== active) {\n active = index;\n options.onFocusChange?.(index);\n }\n }\n\n function moveTo(index: number): void {\n if (state.kind === \"detached\" || count === 0) {\n return;\n }\n const target = Math.max(0, Math.min(index, count - 1));\n makeActive(target);\n setTabStop();\n options.scrollToIndex(target);\n const item = itemAt(target);\n if (item === null) {\n state = { kind: \"awaiting-item\", index: target };\n } else {\n state = SETTLED;\n item.focus({ preventScroll: true });\n }\n }\n\n /** How many items the list shows whole, for PageUp and PageDown. */\n function pageSize(): number {\n const top = list.getBoundingClientRect().top + list.clientTop;\n const bottom = top + list.clientHeight;\n const whole = items().filter((item) => {\n const box = item.getBoundingClientRect();\n return box.top >= top && box.bottom <= bottom;\n }).length;\n return Math.max(1, whole);\n }\n\n // Every key it acts on is taken, so the list does not also scroll by\n // itself. A modified key is left for whatever shortcut it is. Moves count\n // from the focused place rather than the focused element, which lags\n // behind while a move waits for its item to mount.\n function onKeyDown(event: KeyboardEvent): void {\n const item = itemOf(event.target);\n if (\n item === null ||\n event.isComposing ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.shiftKey\n ) {\n return;\n }\n switch (event.key) {\n case \"ArrowDown\":\n moveTo(active + 1);\n break;\n case \"ArrowUp\":\n moveTo(active - 1);\n break;\n case \"Home\":\n moveTo(0);\n break;\n case \"End\":\n moveTo(count - 1);\n break;\n case \"PageDown\":\n moveTo(active + pageSize());\n break;\n case \"PageUp\":\n moveTo(active - pageSize());\n break;\n case \" \":\n // Held down, Space toggles once, as a checkbox does.\n if (!event.repeat) {\n options.onToggle(indexOf(item));\n }\n break;\n default:\n return;\n }\n event.preventDefault();\n }\n\n // However focus arrived — Tab, a click, a script — the tab stop follows it,\n // and a move still waiting for its item gives way.\n function onFocusIn(event: FocusEvent): void {\n const item = itemOf(event.target);\n if (item !== null) {\n state = SETTLED;\n makeActive(indexOf(item));\n setTabStop();\n }\n }\n\n function onClick(event: MouseEvent): void {\n const item = itemOf(event.target);\n if (item !== null) {\n options.onToggle(indexOf(item));\n item.focus({ preventScroll: true });\n }\n }\n\n // The item a move waits for takes focus only if focus is still in the\n // list, or dropped to nothing when the item it was on unmounted.\n function focusAwaitedItemIfMounted(): void {\n const item = state.kind === \"awaiting-item\" ? itemAt(state.index) : null;\n if (item === null) {\n return;\n }\n state = SETTLED;\n const focused = document.activeElement;\n if (\n focused === null ||\n focused === document.body ||\n list.contains(focused)\n ) {\n item.focus({ preventScroll: true });\n }\n }\n\n // Items mount and unmount as the window moves, and a kept element can take\n // another place when the list changes under it.\n const observer = new MutationObserver(() => {\n setTabStop();\n focusAwaitedItemIfMounted();\n });\n observer.observe(list, {\n childList: true,\n subtree: true,\n attributeFilter: [INDEX],\n });\n\n setTabStop();\n list.addEventListener(\"keydown\", onKeyDown);\n list.addEventListener(\"focusin\", onFocusIn);\n list.addEventListener(\"click\", onClick);\n\n return {\n setCount(next) {\n if (state.kind === \"detached\") {\n return;\n }\n count = next;\n active = Math.max(0, Math.min(active, count - 1));\n if (state.kind === \"awaiting-item\") {\n state =\n count === 0\n ? SETTLED\n : {\n kind: \"awaiting-item\",\n index: Math.min(state.index, count - 1),\n };\n }\n setTabStop();\n focusAwaitedItemIfMounted();\n },\n focusItem: moveTo,\n detach() {\n state = DETACHED;\n observer.disconnect();\n list.removeEventListener(\"keydown\", onKeyDown);\n list.removeEventListener(\"focusin\", onFocusIn);\n list.removeEventListener(\"click\", onClick);\n },\n };\n}\n","/**\n * How a context menu was asked for: by a pointer — a right-click, or a touch\n * held still — or by the keyboard.\n */\nexport type ContextMenuSource = \"pointer\" | \"keyboard\";\n\n/** A context menu asked for inside the root, as `onRequest` hears of it. */\nexport interface ContextMenuRequest {\n /** The element it was asked for on: the one under the pointer, or the one focused for a key. */\n readonly target: Element;\n /**\n * Where the pointer was, in viewport coordinates — where a menu opened by\n * it goes. `null` from the keyboard, which has no point: a menu it opens\n * goes beside the focused element instead.\n */\n readonly point: { readonly x: number; readonly y: number } | null;\n readonly source: ContextMenuSource;\n}\n\nexport interface ContextMenuTriggerOptions {\n /**\n * A context menu was asked for inside the root. Returns whether the caller\n * opened one of its own: if so the browser's menu is prevented, and if not\n * it shows as it would have.\n */\n readonly onRequest: (request: ContextMenuRequest) => boolean;\n}\n\n/** How long a touch rests on the screen before it asks for a menu. */\nconst LONG_PRESS_DELAY = 500;\n\n/** How far, in pixels, a touch may drift and still count as resting. */\nconst LONG_PRESS_SLOP = 10;\n\n/** Shift+F10 and the ContextMenu key: the keyboard's ways to ask for one. */\nfunction isContextMenuKey(event: KeyboardEvent): boolean {\n if (event.ctrlKey || event.altKey || event.metaKey) {\n return false;\n }\n return event.key === \"ContextMenu\" || (event.key === \"F10\" && event.shiftKey);\n}\n\n/** A touch pressing, until it asks, lifts or moves away. */\ninterface Press {\n readonly pointerId: number;\n readonly x: number;\n readonly y: number;\n readonly timer: number;\n}\n\n/**\n * Reports every context menu the user asks for inside `root` — a right-click,\n * Shift+F10, the ContextMenu key, or a touch held still for half a second —\n * through `onRequest`, and returns the function that stops it.\n *\n * Each gesture asks once. Pressing a key, or holding a touch, also makes some\n * browsers fire `contextmenu` of their own, and that event is taken, not\n * reported again, once the gesture opened a menu. The click a touch brings\n * as it lifts is taken too: the menu opened under the finger, and the click\n * would land in it.\n *\n * An event something already prevented is left alone, so a cell template\n * that shows a menu of its own keeps it. That is why the listeners are on the\n * document, in the bubble phase: a framework that handles events at its own\n * root container — React does — hears them before the document does, where\n * a listener on `root` itself would hear them first. Only events whose target\n * is inside `root` count.\n */\nexport function attachContextMenuTrigger(\n root: Element,\n options: ContextMenuTriggerOptions,\n): () => void {\n /**\n * Set once a key or a touch opened a menu, until the `contextmenu` event\n * the same gesture may still bring. A pointer or another key starts a new\n * gesture, so a browser that brings none does not cost the next\n * right-click.\n */\n let swallowContextMenu = false;\n /** Set once a touch opened a menu, until the click its lifting brings. */\n let swallowClick = false;\n let press: Press | null = null;\n\n /** Asks for a menu on what `event` happened to, unless something prevented it. */\n function ask(\n event: Event,\n point: ContextMenuRequest[\"point\"],\n source: ContextMenuSource,\n ): boolean {\n const { target } = event;\n return (\n !event.defaultPrevented &&\n target instanceof Element &&\n root.contains(target) &&\n options.onRequest({ target, point, source })\n );\n }\n\n /** `ask`, and the browser's own menu prevented when one opened. */\n function request(\n event: Event,\n point: ContextMenuRequest[\"point\"],\n source: ContextMenuSource,\n ): boolean {\n if (!ask(event, point, source)) {\n return false;\n }\n event.preventDefault();\n return true;\n }\n\n function cancelPress(): void {\n if (press !== null) {\n window.clearTimeout(press.timer);\n press = null;\n }\n }\n\n function onKeyDown(event: KeyboardEvent): void {\n swallowContextMenu = false;\n swallowClick = false;\n if (isContextMenuKey(event) && request(event, null, \"keyboard\")) {\n swallowContextMenu = true;\n }\n }\n\n /**\n * Every pointer starts a new gesture; a touch may become a long-press. It\n * is asked for once it has rested long enough, by which time the press\n * shows whether a template prevented it.\n */\n function onPointerDown(event: PointerEvent): void {\n swallowContextMenu = false;\n swallowClick = false;\n cancelPress();\n if (event.pointerType !== \"touch\" || !event.isPrimary) {\n return;\n }\n const point = { x: event.clientX, y: event.clientY };\n press = {\n pointerId: event.pointerId,\n ...point,\n timer: window.setTimeout(() => {\n press = null;\n if (ask(event, point, \"pointer\")) {\n swallowContextMenu = true;\n swallowClick = true;\n }\n }, LONG_PRESS_DELAY),\n };\n }\n\n function onPointerMove(event: PointerEvent): void {\n if (\n press?.pointerId === event.pointerId &&\n Math.hypot(event.clientX - press.x, event.clientY - press.y) >\n LONG_PRESS_SLOP\n ) {\n cancelPress();\n }\n }\n\n // Lifted, or taken over by the browser — to scroll, say.\n function onPointerEnd(event: PointerEvent): void {\n if (press?.pointerId === event.pointerId) {\n cancelPress();\n }\n }\n\n function onClick(event: MouseEvent): void {\n if (swallowClick) {\n swallowClick = false;\n event.preventDefault();\n event.stopPropagation();\n }\n }\n\n function onContextMenu(event: MouseEvent): void {\n if (swallowContextMenu) {\n swallowContextMenu = false;\n event.preventDefault();\n return;\n }\n // A browser that turns a held touch into `contextmenu` of its own asks\n // first, and the long-press need not.\n const touching = press !== null;\n cancelPress();\n if (\n request(event, { x: event.clientX, y: event.clientY }, \"pointer\") &&\n touching\n ) {\n swallowClick = true;\n }\n }\n\n document.addEventListener(\"keydown\", onKeyDown);\n document.addEventListener(\"pointerdown\", onPointerDown, true);\n document.addEventListener(\"pointermove\", onPointerMove, true);\n document.addEventListener(\"pointerup\", onPointerEnd, true);\n document.addEventListener(\"pointercancel\", onPointerEnd, true);\n // Captured, so the click is taken before anything under the finger hears it.\n document.addEventListener(\"click\", onClick, true);\n document.addEventListener(\"contextmenu\", onContextMenu);\n\n return () => {\n cancelPress();\n document.removeEventListener(\"keydown\", onKeyDown);\n document.removeEventListener(\"pointerdown\", onPointerDown, true);\n document.removeEventListener(\"pointermove\", onPointerMove, true);\n document.removeEventListener(\"pointerup\", onPointerEnd, true);\n document.removeEventListener(\"pointercancel\", onPointerEnd, true);\n document.removeEventListener(\"click\", onClick, true);\n document.removeEventListener(\"contextmenu\", onContextMenu);\n };\n}\n","import { applyColumnResize, type ColumnResizeSession } from \"@gridkitjs/core\";\nimport {\n mayStartGesture,\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, even when a\n * drag brought it back to where it started, or `null` when the width never\n * changed: a click on the handle — as each half of a double-click is — is\n * not a resize, and reporting one would fire a spurious resize on every\n * click.\n */\n readonly onEnd: (width: number | null) => void;\n /**\n * Escape, a cancelled pointer or an aborted `signal`: the column goes back\n * to `session.startWidth`.\n */\n readonly onCancel: () => void;\n /**\n * Cancels the resize when aborted, as Escape does. For a renderer whose\n * handle goes away mid-drag, so what it draws for the resize ends with it.\n * Already aborted, no resize starts.\n */\n readonly signal?: AbortSignal;\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, and not once\n * `handlers.signal` has aborted.\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 (!mayStartGesture(press, handlers.signal)) {\n return false;\n }\n press.preventDefault();\n\n let width = session.startWidth;\n let resized = false;\n startPointerGesture(\n handle,\n press.pointerId,\n {\n onMove(event) {\n width = applyColumnResize(session, event.clientX);\n resized ||= width !== session.startWidth;\n handlers.onMove(width);\n },\n onEnd() {\n handlers.onEnd(resized ? width : null);\n },\n onCancel() {\n handlers.onCancel();\n },\n },\n handlers.signal,\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, with `signal`, if any, not yet aborted. A right-click opens a\n * context menu and a second finger is not the gesture already under way, so\n * neither starts a drag or a resize; an aborted signal means the renderer has\n * already given up on what the press would drag.\n */\nexport function mayStartGesture(\n press: PointerPress,\n signal: AbortSignal | undefined,\n): boolean {\n return press.button === 0 && press.isPrimary && signal?.aborted !== true;\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 cancelling\n * on Escape or when `signal` aborts — leaving the caller's\n * `onMove`/`onEnd`/`onCancel` to own the gesture's own domain logic (and any\n * state reset that goes with it).\n *\n * Only the pointer that started the gesture moves or ends it: another finger\n * landing mid-drag fires its own events on the same element, and they are\n * ignored.\n */\nexport function startPointerGesture(\n element: HTMLElement,\n pointerId: number,\n handlers: PointerGestureHandlers,\n signal: AbortSignal | undefined,\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 signal?.removeEventListener(\"abort\", abandon);\n if (element.hasPointerCapture(pointerId)) {\n element.releasePointerCapture(pointerId);\n }\n }\n\n function move(event: PointerEvent): void {\n if (event.pointerId === pointerId) {\n handlers.onMove(event);\n }\n }\n\n function end(event: PointerEvent): void {\n if (event.pointerId === pointerId) {\n stop();\n handlers.onEnd();\n }\n }\n\n function cancel(event: PointerEvent): void {\n if (event.pointerId === pointerId) {\n abandon();\n }\n }\n\n function abandon(): void {\n stop();\n handlers.onCancel();\n }\n\n function onKeyDown(event: KeyboardEvent): void {\n if (event.key === \"Escape\") {\n abandon();\n }\n }\n\n element.addEventListener(\"pointermove\", move);\n element.addEventListener(\"pointerup\", end);\n element.addEventListener(\"pointercancel\", cancel);\n window.addEventListener(\"keydown\", onKeyDown);\n signal?.addEventListener(\"abort\", abandon);\n}\n","/** What `writeClipboard` puts on the clipboard: the same copy as plain text and as HTML. */\nexport interface ClipboardContent {\n readonly text: string;\n readonly html: string;\n}\n\n/**\n * The one legacy call, typed as GridKit uses it. `document.execCommand` is\n * deprecated and has no replacement for a browser without the async\n * Clipboard API — which is the only browser this is reached in.\n */\ninterface LegacyCopyDocument {\n execCommand(commandId: \"copy\"): boolean;\n}\n\n/** Writes both types through the async Clipboard API, where the browser has `ClipboardItem`. */\nasync function writeItem(\n clipboard: Clipboard,\n { text, html }: ClipboardContent,\n): Promise<void> {\n await clipboard.write([\n new ClipboardItem({\n \"text/plain\": new Blob([text], { type: \"text/plain\" }),\n \"text/html\": new Blob([html], { type: \"text/html\" }),\n }),\n ]);\n}\n\n/**\n * Copies through the `copy` event, the way pages did before the async API:\n * a one-shot listener sets both types on the event's `clipboardData`, and\n * `execCommand(\"copy\")` fires it. The command copies the focused selection,\n * so an off-screen field holding the text is focused and selected for it —\n * which also leaves the text on the clipboard should a browser give the\n * listener no `clipboardData` — and focus goes back where it was after.\n */\nfunction copyThroughEvent({ text, html }: ClipboardContent): boolean {\n const previous = document.activeElement;\n const field = document.createElement(\"textarea\");\n field.value = text;\n field.readOnly = true;\n field.setAttribute(\"aria-hidden\", \"true\");\n field.style.position = \"fixed\";\n field.style.top = \"0\";\n field.style.left = \"-9999px\";\n document.body.append(field);\n\n function onCopy(event: ClipboardEvent): void {\n if (event.clipboardData === null) {\n return;\n }\n event.clipboardData.setData(\"text/plain\", text);\n event.clipboardData.setData(\"text/html\", html);\n event.preventDefault();\n }\n\n document.addEventListener(\"copy\", onCopy, { capture: true, once: true });\n try {\n field.focus({ preventScroll: true });\n field.select();\n const legacy: LegacyCopyDocument = document;\n return legacy.execCommand(\"copy\");\n } finally {\n document.removeEventListener(\"copy\", onCopy, { capture: true });\n field.remove();\n if (previous instanceof HTMLElement) {\n previous.focus({ preventScroll: true });\n }\n }\n}\n\n/** Input types with no text of their own to copy, whose copy key the page may take. */\nconst TEXTLESS_INPUTS = new Set([\n \"button\",\n \"checkbox\",\n \"color\",\n \"file\",\n \"image\",\n \"radio\",\n \"range\",\n \"reset\",\n \"submit\",\n]);\n\nfunction isTextField(target: EventTarget | null): boolean {\n return (\n target instanceof HTMLTextAreaElement ||\n (target instanceof HTMLInputElement && !TEXTLESS_INPUTS.has(target.type)) ||\n (target instanceof HTMLElement && target.isContentEditable)\n );\n}\n\n/**\n * Whether a copy key pressed at `target` belongs to the browser: pressed in a\n * text field, or with text highlighted anywhere on the page. Either way the\n * user means the text in front of them, and a grid copying its own cell\n * instead would take that from them.\n */\nexport function isNativeCopy(target: EventTarget | null): boolean {\n const selection = document.getSelection();\n return isTextField(target) || (selection !== null && !selection.isCollapsed);\n}\n\n/**\n * Puts `content` on the clipboard as both `text/plain` and `text/html`, so a\n * paste into a spreadsheet splits into cells and one into a word processor\n * keeps its table. Call it from a user's gesture — a key press, a menu\n * choice — as browsers allow the clipboard only then.\n *\n * Tries each way a browser offers, best first: the async Clipboard API with\n * a `ClipboardItem` holding both types; then its `writeText`, plain text\n * alone, where `ClipboardItem` is missing or refused; then the legacy `copy`\n * event, for a page outside a secure context, where the async API is not\n * there at all. Rejects only when every one fails.\n */\nexport async function writeClipboard(content: ClipboardContent): Promise<void> {\n const attempts: (() => Promise<void>)[] = [];\n // Absent outside a secure context, whatever the DOM types say.\n const clipboard = navigator.clipboard as Clipboard | undefined;\n if (clipboard !== undefined) {\n if (typeof ClipboardItem === \"function\") {\n attempts.push(() => writeItem(clipboard, content));\n }\n attempts.push(() => clipboard.writeText(content.text));\n }\n let failure: unknown = null;\n for (const attempt of attempts) {\n try {\n await attempt();\n return;\n } catch (error) {\n failure = error;\n }\n }\n if (!copyThroughEvent(content)) {\n throw new Error(\"The browser refused to write to the clipboard.\", {\n cause: failure,\n });\n }\n}\n","/**\n * Saves `blob` as a file named `fileName` — the browser's own download, as a\n * link with a `download` attribute starts one, so nothing is opened or\n * navigated to.\n *\n * The link is put in the document for the click, as Firefox has needed, and\n * taken out straight after. Its object URL is revoked a task later rather\n * than at once: Safari starts the download from the URL after the click\n * returns, and a URL revoked synchronously is gone before it does.\n */\nexport function downloadBlob(blob: Blob, fileName: string): void {\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = fileName;\n link.hidden = true;\n document.body.append(link);\n link.click();\n link.remove();\n setTimeout(() => {\n URL.revokeObjectURL(url);\n }, 0);\n}\n","import {\n mayStartGesture,\n startPointerGesture,\n type PointerPress,\n} from \"./pointerGesture\";\n\n/**\n * How far the pointer travels, in any direction, before a press becomes a\n * drag. Below it the gesture is still a click, which is what leaves the\n * pressed element free to take one.\n */\nconst DRAG_THRESHOLD = 4;\n\n/**\n * Where a reorder gesture is: `pressed` until the pointer crosses the\n * threshold, then `dragging` over `target`, `null` for nowhere valid.\n */\ntype DragPhase<Target> =\n | { readonly kind: \"pressed\" }\n | { readonly kind: \"dragging\"; readonly target: Target | null };\n\n/**\n * What a renderer draws for a reorder gesture: nothing while `idle`, which\n * covers a press still short of the drag threshold, then while `dragging` the\n * dragged item, where it would land and the ghost trailing the pointer.\n */\nexport type DragReorderState<Target> =\n | { readonly kind: \"idle\" }\n | {\n readonly kind: \"dragging\";\n /** The item being dragged. */\n readonly draggedId: string;\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;\n };\n\n/** The state while nothing is dragged — the one a drag reports when it ends. */\nexport const NO_DRAG: DragReorderState<never> = { kind: \"idle\" };\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 * Cancels the drag when aborted, as Escape does: `onState(NO_DRAG)`, no\n * drop. For a renderer whose dragged element goes away mid-drag, so what\n * it draws for the drag ends with it. Already aborted, no drag starts.\n */\n readonly signal?: AbortSignal;\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 * and not once `handlers.signal` has aborted.\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, a cancelled\n * pointer or an aborted signal 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 (!mayStartGesture(press, handlers.signal)) {\n return false;\n }\n\n const { pointerId, clientX: startX, clientY: startY } = press;\n const grabX = startX - grabElement.getBoundingClientRect().left;\n let phase: DragPhase<Target> = { kind: \"pressed\" };\n\n startPointerGesture(\n grabElement,\n pointerId,\n {\n onMove(moveEvent) {\n const { clientX, clientY } = moveEvent;\n if (\n phase.kind === \"pressed\" &&\n Math.hypot(clientX - startX, clientY - startY) < DRAG_THRESHOLD\n ) {\n return;\n }\n phase = {\n kind: \"dragging\",\n target: handlers.resolveTarget(clientX, clientY, id),\n };\n handlers.onState({\n kind: \"dragging\",\n draggedId: id,\n dropTarget: phase.target,\n ghostTransform: `translate(${String(clientX - grabX)}px, ${String(clientY)}px)`,\n });\n },\n onEnd() {\n handlers.onState(NO_DRAG);\n if (phase.kind === \"dragging\" && phase.target !== null) {\n handlers.onDrop(phase.target, id);\n }\n },\n onCancel() {\n handlers.onState(NO_DRAG);\n },\n },\n handlers.signal,\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 * `root`, the element holding every row, lets the user take over from a\n * move still waiting for its row to be drawn: focus leaving `root`, or a\n * wheel or a press inside it, drops the move, so a row mounting later\n * neither scrolls back to itself nor pulls focus back into the grid.\n */\n readonly attach: (\n rows: GridFocusRows,\n root?: HTMLElement | null,\n ) => () => 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 * or once the user took over from it (see `attach`). 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 dropPendingMove(): void {\n pendingMove = null;\n }\n\n function listenForTakeover(root: HTMLElement): () => void {\n // Focus moving between cells stays in `root`, and focus lost to a cell\n // unmounting goes nowhere: only focus going somewhere else leaves it.\n function onFocusOut(event: FocusEvent): void {\n const to = event.relatedTarget;\n if (to instanceof Node && !root.contains(to)) {\n dropPendingMove();\n }\n }\n root.addEventListener(\"focusout\", onFocusOut);\n root.addEventListener(\"wheel\", dropPendingMove, { passive: true });\n root.addEventListener(\"pointerdown\", dropPendingMove);\n return () => {\n root.removeEventListener(\"focusout\", onFocusOut);\n root.removeEventListener(\"wheel\", dropPendingMove);\n root.removeEventListener(\"pointerdown\", dropPendingMove);\n };\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, root) {\n rows = next;\n const stopListening = root == null ? undefined : listenForTakeover(root);\n return () => {\n stopListening?.();\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. Not again until that load has\n * finished (`isLoadingMore` back to `false`), `hasMore` has changed, rows\n * have been added or removed in front of the sentinel, or the sentinel has\n * left `rootMargin` and come back.\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 a load finishing or `hasMore` changing re-checks at once.\n * While a load it asked for has not reported `isLoadingMore`, it measures\n * where the sentinel sits, to tell whether rows answered it.\n */\n readonly update: (options: InfiniteScrollOptions) => void;\n /** Stops watching for good: a later `update` does nothing. */\n readonly detach: () => void;\n}\n\n/**\n * Where the next load stands. `requested` is `onLoadMore` called with no\n * `isLoadingMore: true` seen since, which a load that starts nothing, or\n * finishes within one render, never leaves on its own. It keeps where the\n * sentinel sat when the load was asked for, so rows added in front of it\n * show as an answer even while it stays in range.\n */\ntype LoadState =\n | { readonly kind: \"idle\" }\n | { readonly kind: \"requested\"; readonly sentinelOffset: number }\n | { readonly kind: \"loading\" }\n | { readonly kind: \"detached\" };\n\nconst IDLE: LoadState = { kind: \"idle\" };\nconst LOADING: LoadState = { kind: \"loading\" };\nconst DETACHED: LoadState = { kind: \"detached\" };\n\n/**\n * The state after an `update`: `loading` while a load is in flight, and\n * `idle` again once one finishes or `hasMore` changes, either of which\n * means no load can still be pending.\n */\nfunction afterUpdate(\n state: LoadState,\n loading: boolean,\n hasMoreChanged: boolean,\n): LoadState {\n if (loading) {\n return LOADING;\n }\n return state.kind === \"loading\" || hasMoreChanged ? IDLE : state;\n}\n\n/**\n * How far into `root`'s scrolled content `sentinel` sits. Scrolling leaves\n * it alone; rows added or removed in front of the sentinel change it.\n */\nfunction contentOffset(root: Element, sentinel: Element): number {\n return (\n sentinel.getBoundingClientRect().top -\n root.getBoundingClientRect().top +\n root.scrollTop\n );\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 approach while no load is in\n * flight.\n *\n * Two facts drive it: whether the sentinel is intersecting (as of the\n * observer's last report), and the `LoadState`. Both outlive the sentinel\n * itself, which a renderer mounts and unmounts as `hasMore` changes, so they\n * live here rather than on any one observer.\n *\n * Off means off: with `hasMore` false, no sentinel, or once detached, no\n * observer exists.\n */\nexport function attachInfiniteScroll(\n root: Element,\n options: InfiniteScrollOptions,\n): InfiniteScroll {\n let latest = options;\n let intersecting = false;\n let state: LoadState = (options.isLoadingMore ?? false) ? LOADING : IDLE;\n let observer: IntersectionObserver | null = null;\n let observed: { sentinel: Element; rootMargin: string } | null = null;\n\n function maybeLoadMore(): void {\n if (\n state.kind !== \"idle\" ||\n !intersecting ||\n observed === null ||\n !latest.hasMore\n ) {\n return;\n }\n state = {\n kind: \"requested\",\n sentinelOffset: contentOffset(root, observed.sentinel),\n };\n latest.onLoadMore?.();\n }\n\n /**\n * Whether rows were added or removed in front of the sentinel since the\n * outstanding request: its answer, even when it left the sentinel in range,\n * where the observer, seeing no change, reports nothing. Less than a pixel\n * is a fractional scroll position rounding, not a row.\n */\n function requestAnswered(): boolean {\n if (state.kind !== \"requested\" || observed === null) {\n return false;\n }\n const moved = contentOffset(root, observed.sentinel) - state.sentinelOffset;\n return Math.abs(moved) >= 1;\n }\n\n function report(isIntersecting: boolean): void {\n intersecting = isIntersecting;\n // The sentinel left the range, so its next entry is a fresh approach.\n if (!intersecting && state.kind === \"requested\") {\n state = IDLE;\n }\n maybeLoadMore();\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 report(entry.isIntersecting);\n }\n },\n { root, rootMargin },\n );\n observer.observe(sentinel);\n observed = { sentinel, rootMargin };\n }\n\n observe();\n\n return {\n update(next) {\n if (state.kind === \"detached\") {\n return;\n }\n const hasMoreChanged = next.hasMore !== latest.hasMore;\n latest = next;\n state = afterUpdate(state, next.isLoadingMore ?? false, hasMoreChanged);\n observe();\n if (requestAnswered()) {\n // Where the sentinel is now is the observer's to say: a new one\n // reports at once, and the last report predates the new rows.\n state = IDLE;\n disconnect();\n observe();\n }\n // After a load finishes, or `hasMore` changes, the sentinel may still be\n // in range: then it never leaves and re-enters, and no observer report\n // would come to trigger the next load.\n maybeLoadMore();\n },\n detach() {\n disconnect();\n state = DETACHED;\n },\n };\n}\n","import { nextMenuIndex, typeaheadIndex, type MenuMove } from \"@gridkitjs/core\";\n\n/**\n * Where focus goes when a submenu opens: its first item, or nowhere — it\n * stays on the item that opened it, as when the pointer opened it.\n */\nexport type MenuSubmenuFocus = \"first\" | \"none\";\n\n/** Why a menu asked for every menu to close. */\nexport type MenuCloseReason = \"tab\";\n\nexport interface MenuOptions {\n /**\n * An enabled item that opens no submenu was chosen — Enter, Space or a\n * click. The caller runs it, and closes every menu.\n */\n readonly onActivate: (id: string) => void;\n /**\n * An enabled submenu item asks for its submenu: Enter, Space or ArrowRight\n * on it want its first item focused (`\"first\"`), a click or the pointer\n * resting on it want focus left where it is (`\"none\"`). It may come for a\n * submenu already open — ArrowRight on one the pointer opened — when a\n * `\"first\"` asks for focus to move into it (`focusItem(0)` on its menu).\n * Opening it closes any other submenu open from this menu.\n */\n readonly onOpenSubmenu?:\n | ((id: string, options: { readonly focus: MenuSubmenuFocus }) => void)\n | undefined;\n /**\n * The pointer came to rest on an item that opens no submenu, or a disabled\n * one: close whatever submenu is open from this menu, if any. Moving on to\n * another item is how a pointer user leaves a submenu they did not want.\n */\n readonly onCollapseSubmenu?: (() => void) | undefined;\n /**\n * ArrowLeft was pressed in this menu, which is a submenu: close it, and\n * focus goes back to the item that opened it. Passed only for a submenu —\n * in a menu without it, ArrowLeft does nothing.\n */\n readonly onCloseSubmenu?: (() => void) | undefined;\n /**\n * Tab or Shift+Tab was pressed: close every menu, so focus returns to where\n * the first one was opened from rather than moving on past it.\n */\n readonly onClose: (reason: MenuCloseReason) => void;\n}\n\nexport interface AttachedMenu {\n /**\n * Focuses an item, by its `data-gridkit-menu-item` id or its place among\n * the items. Does nothing for an item the menu does not have.\n */\n readonly focusItem: (item: string | number) => void;\n /** Removes every listener. The items keep the `tabindex` they had. */\n readonly detach: () => void;\n}\n\n/** Every element `attachMenu` treats as an item. Separators are not. */\nconst ITEM = [\n '[role=\"menuitem\"][data-gridkit-menu-item]',\n '[role=\"menuitemcheckbox\"][data-gridkit-menu-item]',\n '[role=\"menuitemradio\"][data-gridkit-menu-item]',\n].join(\", \");\n\n/** How long the pointer rests on a submenu item before it opens. */\nconst HOVER_DELAY = 150;\n\n/** How long after the last key typed the typeahead starts afresh. */\nconst TYPEAHEAD_TIMEOUT = 500;\n\n/** Each item, as `nextMenuIndex` reads it: the separators are not among them. */\nconst AS_ENTRY = { type: \"item\" } as const;\n\n/** The keys that move focus, and where to. */\nconst MOVES: Readonly<Record<string, MenuMove>> = {\n ArrowDown: \"next\",\n ArrowUp: \"previous\",\n Home: \"first\",\n End: \"last\",\n};\n\n/** Letters typed one after another, until `TYPEAHEAD_TIMEOUT` ends the run. */\ntype Typeahead =\n | { readonly kind: \"idle\" }\n | { readonly kind: \"typing\"; readonly typed: string; readonly timer: number };\n\n/**\n * The pointer over an item: `resting` until `HOVER_DELAY` has passed and\n * what the item opens or closes happens, then `rested` until it moves to\n * another. A key the menu acts on ends it, so the pointer has to move again\n * before it opens or closes anything.\n */\ntype Hover =\n | { readonly kind: \"away\" }\n | {\n readonly kind: \"resting\";\n readonly item: HTMLElement;\n readonly timer: number;\n }\n | { readonly kind: \"rested\"; readonly item: HTMLElement };\n\nconst IDLE: Typeahead = { kind: \"idle\" };\nconst AWAY: Hover = { kind: \"away\" };\n\nfunction idOf(item: HTMLElement): string {\n return item.getAttribute(\"data-gridkit-menu-item\") ?? \"\";\n}\n\nfunction isDisabled(item: HTMLElement): boolean {\n return item.getAttribute(\"aria-disabled\") === \"true\";\n}\n\nfunction opensSubmenu(item: HTMLElement): boolean {\n const popup = item.getAttribute(\"aria-haspopup\");\n return popup === \"menu\" || popup === \"true\";\n}\n\n/** What typeahead matches against: the item's `aria-label`, else its text. */\nfunction labelOf(item: HTMLElement): string {\n return (item.getAttribute(\"aria-label\") ?? item.textContent).trim();\n}\n\n/** A printable character, typed without a modifier that makes it a shortcut. */\nfunction isTypeahead(event: KeyboardEvent): boolean {\n return (\n /^\\S$/u.test(event.key) && !event.ctrlKey && !event.metaKey && !event.altKey\n );\n}\n\n/**\n * Makes `panel` — a `role=\"menu\"` element the renderer drew — behave as a\n * menu: the arrow keys, Home, End and typeahead move focus among its items,\n * Enter, Space and a click choose one, ArrowRight and ArrowLeft open and\n * close submenus, and the pointer moves focus to the item under it. It\n * decides; the caller is told through the callbacks and draws the outcome —\n * runs the item, opens or closes a submenu.\n *\n * Items are the panel's descendants with a menu item role (`menuitem`,\n * `menuitemcheckbox`, `menuitemradio`) and a `data-gridkit-menu-item` id,\n * read afresh on every key so items drawn since attaching count. A submenu\n * item says so with `aria-haspopup=\"menu\"`; an item with\n * `aria-disabled=\"true\"` takes focus, so a screen reader finds it, but is\n * never chosen. One item at a time has `tabindex=\"0\"` — the focused one, or\n * the first — and the rest `-1`: every item can take focus, and the first is\n * what an overlay's initial focus finds when the menu is attached before it.\n *\n * Escape is not handled: a menu is shown with `attachOverlay`, whose stack\n * gives Escape to the topmost overlay. A submenu is a menu of its own, in an\n * overlay of its own anchored to its item, attached when it opens.\n */\nexport function attachMenu(\n panel: HTMLElement,\n options: MenuOptions,\n): AttachedMenu {\n let typeahead: Typeahead = IDLE;\n let hover: Hover = AWAY;\n\n function items(): HTMLElement[] {\n return Array.from(panel.querySelectorAll<HTMLElement>(ITEM));\n }\n\n function itemOf(target: EventTarget | null): HTMLElement | null {\n const item =\n target instanceof Element ? target.closest<HTMLElement>(ITEM) : null;\n return item !== null && panel.contains(item) ? item : null;\n }\n\n function setTabStop(stop: HTMLElement | undefined): void {\n for (const item of items()) {\n item.tabIndex = item === stop ? 0 : -1;\n }\n }\n\n function focus(item: HTMLElement): void {\n setTabStop(item);\n item.focus();\n }\n\n function openSubmenu(item: HTMLElement, focusTo: MenuSubmenuFocus): void {\n if (!isDisabled(item)) {\n options.onOpenSubmenu?.(idOf(item), { focus: focusTo });\n }\n }\n\n function choose(item: HTMLElement, focusTo: MenuSubmenuFocus): void {\n if (opensSubmenu(item)) {\n openSubmenu(item, focusTo);\n } else if (!isDisabled(item)) {\n options.onActivate(idOf(item));\n }\n }\n\n function endTyping(): void {\n if (typeahead.kind === \"typing\") {\n window.clearTimeout(typeahead.timer);\n }\n typeahead = IDLE;\n }\n\n function endHover(): void {\n if (hover.kind === \"resting\") {\n window.clearTimeout(hover.timer);\n }\n hover = AWAY;\n }\n\n function type(key: string, all: HTMLElement[], from: number): void {\n const typed = (typeahead.kind === \"typing\" ? typeahead.typed : \"\") + key;\n endTyping();\n typeahead = {\n kind: \"typing\",\n typed,\n timer: window.setTimeout(() => {\n typeahead = IDLE;\n }, TYPEAHEAD_TIMEOUT),\n };\n const match = all[typeaheadIndex(all.map(labelOf), from, typed)];\n if (match !== undefined) {\n focus(match);\n }\n }\n\n function rest(item: HTMLElement): void {\n hover = {\n kind: \"resting\",\n item,\n timer: window.setTimeout(() => {\n hover = { kind: \"rested\", item };\n if (opensSubmenu(item) && !isDisabled(item)) {\n openSubmenu(item, \"none\");\n } else {\n options.onCollapseSubmenu?.();\n }\n }, HOVER_DELAY),\n };\n }\n\n // Every key it acts on is taken, the arrows included where they do\n // nothing: a menu has no caret for them to move, and the page scrolling\n // under it would dismiss its overlay. Any of them ends a rest of the\n // pointer, and any but a letter ends the typing.\n function onKeyDown(event: KeyboardEvent): void {\n if (event.isComposing || !actOnKey(event)) {\n return;\n }\n event.preventDefault();\n endHover();\n if (!isTypeahead(event)) {\n endTyping();\n }\n }\n\n /** Does what `event`'s key does in the menu; `false` for a key it ignores. */\n function actOnKey(event: KeyboardEvent): boolean {\n const all = items();\n const item = itemOf(event.target);\n const from = item === null ? -1 : all.indexOf(item);\n const move = MOVES[event.key];\n if (move !== undefined) {\n const next =\n all[\n nextMenuIndex(\n all.map(() => AS_ENTRY),\n from,\n move,\n )\n ];\n if (next !== undefined) {\n focus(next);\n }\n } else if (event.key === \"Enter\" || event.key === \" \") {\n if (item !== null) {\n choose(item, \"first\");\n }\n } else if (event.key === \"ArrowRight\") {\n if (item !== null && opensSubmenu(item)) {\n openSubmenu(item, \"first\");\n }\n } else if (event.key === \"ArrowLeft\") {\n options.onCloseSubmenu?.();\n } else if (event.key === \"Tab\") {\n options.onClose(\"tab\");\n } else if (isTypeahead(event)) {\n type(event.key, all, from);\n } else {\n return false;\n }\n return true;\n }\n\n // However focus arrived — the overlay's initial focus, a click, a script —\n // the tab stop follows it.\n function onFocusIn(event: FocusEvent): void {\n const item = itemOf(event.target);\n if (item !== null) {\n setTabStop(item);\n }\n }\n\n function onClick(event: MouseEvent): void {\n const item = itemOf(event.target);\n if (item !== null) {\n choose(item, \"none\");\n }\n }\n\n // A pointer moving over the menu moves focus with it at once, but what the\n // item under it opens or closes waits until it rests there: a pointer\n // crossing a sibling on its way into an open submenu leaves the submenu\n // open. Touch has no hover, so only its taps count, as clicks.\n function onPointerMove(event: PointerEvent): void {\n if (event.pointerType === \"touch\") {\n return;\n }\n const item = itemOf(event.target);\n if (item !== null && document.activeElement !== item) {\n focus(item);\n }\n if (hover.kind !== \"away\" && hover.item === item) {\n return;\n }\n endHover();\n if (item !== null) {\n rest(item);\n }\n }\n\n // Leaving the panel — into the submenu, usually — is not resting anywhere.\n function onPointerLeave(): void {\n endHover();\n }\n\n const focused = itemOf(document.activeElement);\n setTabStop(focused ?? items()[0]);\n\n panel.addEventListener(\"keydown\", onKeyDown);\n panel.addEventListener(\"focusin\", onFocusIn);\n panel.addEventListener(\"click\", onClick);\n panel.addEventListener(\"pointermove\", onPointerMove);\n panel.addEventListener(\"pointerleave\", onPointerLeave);\n\n return {\n focusItem(which) {\n const all = items();\n const item =\n typeof which === \"number\"\n ? all[which]\n : all.find((candidate) => idOf(candidate) === which);\n if (item !== undefined) {\n focus(item);\n }\n },\n detach() {\n endTyping();\n endHover();\n panel.removeEventListener(\"keydown\", onKeyDown);\n panel.removeEventListener(\"focusin\", onFocusIn);\n panel.removeEventListener(\"click\", onClick);\n panel.removeEventListener(\"pointermove\", onPointerMove);\n panel.removeEventListener(\"pointerleave\", onPointerLeave);\n },\n };\n}\n","/** What the stack needs of an attached overlay. */\nexport interface StackedOverlay {\n readonly panel: HTMLElement;\n /** Escape was pressed while this was the topmost overlay. */\n readonly onEscape: () => void;\n /** An overlay below this one detached, so this one has to go too. */\n readonly onParentDetached: () => void;\n}\n\n/**\n * Every attached overlay, in the order it attached: a submenu opened from a\n * menu sits above it. Module-level because nesting spans overlays that know\n * nothing of each other — a menu does not hold a reference to the submenu a\n * click inside it opened.\n */\nconst stack: StackedOverlay[] = [];\n\n/*\n * One listener for the whole stack, on `window` in the capture phase: the\n * first place a key event reaches, so neither React's root listener nor a\n * page's own Escape handler hears an Escape the topmost overlay took. An\n * Escape that ends an IME composition is the input method's, not ours.\n */\nfunction onKeyDown(event: KeyboardEvent): void {\n const topmost = stack.at(-1);\n if (event.key !== \"Escape\" || event.isComposing || topmost === undefined) {\n return;\n }\n event.preventDefault();\n event.stopPropagation();\n topmost.onEscape();\n}\n\n/** Puts `overlay` on top of the stack. */\nexport function pushOverlay(overlay: StackedOverlay): void {\n if (stack.length === 0) {\n window.addEventListener(\"keydown\", onKeyDown, true);\n }\n stack.push(overlay);\n}\n\n/**\n * Takes `overlay` off the stack, and every overlay above it with it — each\n * told through `onParentDetached`, topmost first, so a submenu never outlives\n * the menu it opened from. Does nothing for an overlay no longer on it.\n */\nexport function removeOverlay(overlay: StackedOverlay): void {\n const index = stack.indexOf(overlay);\n if (index === -1) {\n return;\n }\n const above = stack.splice(index).slice(1).reverse();\n for (const child of above) {\n child.onParentDetached();\n }\n if (stack.length === 0) {\n window.removeEventListener(\"keydown\", onKeyDown, true);\n }\n}\n\n/** Whether `target` is inside an overlay that sits above `overlay`. */\nexport function isInsideOverlayAbove(\n overlay: StackedOverlay,\n target: Node,\n): boolean {\n const index = stack.indexOf(overlay);\n return (\n index !== -1 &&\n stack.slice(index + 1).some((above) => above.panel.contains(target))\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 * Pixels to shift the overlay along the anchor's edge — down beside a\n * `left`/`right` side, right beside `top`/`bottom`; negative goes the other\n * way. A submenu opened `right-start` takes its own top padding, negated,\n * so its first item lines up with the item that opened it. The overlay is\n * still kept on screen. Defaults to `0`.\n */\n readonly offset?: 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 offset = options.offset ?? 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, shifted by the offset\n // before an edge is chosen, so the shifted overlay is what has to fit.\n const anchorStart = (vertical ? anchor.left : anchor.top) + offset;\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 isInsideOverlayAbove,\n pushOverlay,\n removeOverlay,\n type StackedOverlay,\n} from \"./overlayStack\";\nimport {\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/**\n * Where focus goes when an overlay attaches: its first focusable descendant,\n * the panel itself, nowhere — focus stays where it was — or a given element.\n */\nexport type OverlayInitialFocus = \"first\" | \"panel\" | \"none\" | HTMLElement;\n\n/** Why an overlay asked to close. */\nexport type OverlayDismissReason =\n \"escape\" | \"outside\" | \"scroll\" | \"resize\" | \"blur\" | \"parent\";\n\nexport interface OverlayOptions extends OverlayPlacementOptions {\n /**\n * The overlay asks to close: Escape was pressed while it was the topmost\n * overlay, a pointer went down outside it, something outside it scrolled,\n * the window resized or lost focus, focus moved outside it, or the overlay\n * it was opened above detached. The caller closes it — removes the panel\n * and detaches — or keeps it open by doing nothing, except after\n * `\"parent\"`, which detaches it regardless.\n */\n readonly onDismiss: (reason: OverlayDismissReason) => void;\n /**\n * Where focus goes on attaching. Defaults to `\"first\"`: a menu focuses its\n * first item. A panel whose first control is not where the user starts — a\n * filter editor's operator picker before its value box — passes that\n * element, or `\"panel\"` to land on the panel itself; `\"none\"` leaves focus\n * where it was, as a suggestion list does while typing goes on in the\n * field that opened it.\n */\n readonly initialFocus?: OverlayInitialFocus | undefined;\n /**\n * Where focus goes when the overlay detaches. Defaults to the element that\n * had focus when it attached; `null` leaves focus alone, unless\n * `returnFocusFallback` names somewhere.\n */\n readonly returnFocusTo?: HTMLElement | null | undefined;\n /**\n * Where focus goes when there is nothing to return it to as the overlay\n * detaches: `returnFocusTo` is `null`, or has left the page — the button a\n * menu opened from, taken away by what the menu ran. Called then, so it\n * can name what is on the page by that time; returning `null` leaves focus\n * alone. Like the return itself, it applies only while focus is still in\n * the panel, in an overlay above it, or nowhere.\n */\n readonly returnFocusFallback?: (() => HTMLElement | null) | undefined;\n /**\n * Whether a scroll outside the panel just after input inside it — an\n * `input` or `change` event, in the two animation frames that follow it —\n * leaves it open. A panel whose controls change the page under it opts\n * in: a column chooser hiding a column narrows the grid it opened from,\n * and the browser scrolls that grid back within its narrower content.\n * Defaults to `false`, where every scroll outside the panel dismisses it.\n */\n readonly ignoreScrollAfterInput?: boolean | undefined;\n /**\n * Whether Tab and Shift+Tab stay inside the panel: from its last tabbable\n * element Tab moves to its first, and Shift+Tab from its first to its last,\n * as a dialog keeps them. Defaults to `false`, where tabbing out of the\n * panel moves focus outside it, which dismisses it with `\"blur\"`. Only\n * the keys are kept: a pointer down outside still dismisses it, and so\n * does focus moved out by any other route.\n */\n readonly trapFocus?: boolean | 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\n/**\n * What Tab stops on in `panel`, in document order: what is focusable without\n * a negative `tabindex`, enabled and shown.\n */\nfunction tabbables(panel: HTMLElement): HTMLElement[] {\n return Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n (element) =>\n element.tabIndex >= 0 &&\n !element.matches(\":disabled\") &&\n element.checkVisibility({ visibilityProperty: true }),\n );\n}\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 * The element `initialFocus` names, or `null` for none. The panel stands in\n * for a first focusable descendant it does not have, given `tabindex=\"-1\"`\n * when it has no `tabindex` of its own so that it can take focus.\n */\nfunction initialFocusTarget(\n panel: HTMLElement,\n initialFocus: OverlayInitialFocus,\n): HTMLElement | null {\n if (initialFocus === \"none\") {\n return null;\n }\n if (initialFocus instanceof HTMLElement) {\n return initialFocus;\n }\n const first =\n initialFocus === \"first\"\n ? panel.querySelector<HTMLElement>(FOCUSABLE)\n : null;\n if (first !== null) {\n return first;\n }\n if (!panel.hasAttribute(\"tabindex\")) {\n panel.tabIndex = -1;\n }\n return panel;\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. Whenever the panel's size changes — a list\n * that shrinks under a search — it is placed again beside the anchor, so a\n * panel flipped above its anchor keeps its bottom edge there — or beside\n * where the anchor was, once it has left the page. Focus moves\n * where `initialFocus` says.\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 * Overlays stack in the order they attach. Escape belongs to the topmost\n * alone, which takes it: it closes a submenu without closing the menu under\n * it, and nothing else on the page — a grid that clears its selection on\n * Escape — hears it. An overlay counts everything in the overlays above it\n * as inside itself, so a click in a submenu keeps its menu open while a click\n * elsewhere in the menu closes only the submenu.\n *\n * Detaching removes every listener and returns focus, but only when focus is\n * still in the panel, in an overlay above it, or nowhere — a click that\n * dismissed it by landing on another control leaves focus on that control.\n * Focus goes to `returnFocusFallback` instead when the element it would\n * return to has left the page.\n * Any overlay still above it is dismissed with `\"parent\"` and detached first.\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 const stacked: StackedOverlay = {\n panel,\n onEscape: () => {\n onDismiss(\"escape\");\n },\n onParentDetached: () => {\n onDismiss(\"parent\");\n detach();\n },\n };\n\n /** In the panel, or in an overlay opened above it. */\n function inside(target: EventTarget | null): boolean {\n return (\n target instanceof Node &&\n (panel.contains(target) || isInsideOverlayAbove(stacked, target))\n );\n }\n\n function belongs(target: EventTarget | null): boolean {\n return (\n inside(target) ||\n (anchor instanceof Element &&\n target instanceof Node &&\n anchor.contains(target))\n );\n }\n\n function onPointerDown(event: PointerEvent): void {\n if (!belongs(event.target)) {\n onDismiss(\"outside\");\n }\n }\n\n // Set from input in the panel until two frames have passed: the scroll an\n // input causes is dispatched by the frame after its layout, which may\n // itself wait for the next frame.\n let settlingFrame: number | null = null;\n function onInput(): void {\n if (settlingFrame !== null) {\n cancelAnimationFrame(settlingFrame);\n }\n settlingFrame = requestAnimationFrame(() => {\n settlingFrame = requestAnimationFrame(() => {\n settlingFrame = null;\n });\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 // or one the panel's own input caused, when it asks for that.\n function onScroll(event: Event): void {\n if (!inside(event.target) && settlingFrame === null) {\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 // Only the moves that would leave the panel are made here — Tab with\n // nothing tabbable after the focused element, Shift+Tab with nothing before\n // it — and every other Tab is the browser's own, in its own order. What is\n // tabbable is read on each key, so controls drawn since attaching count; an\n // overlay above that sits inside the panel keeps its own Tab.\n function onTrapKeyDown(event: KeyboardEvent): void {\n if (\n event.key !== \"Tab\" ||\n event.defaultPrevented ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n !(event.target instanceof Node) ||\n isInsideOverlayAbove(stacked, event.target)\n ) {\n return;\n }\n const stops = tabbables(panel).filter(\n (element) => !isInsideOverlayAbove(stacked, element),\n );\n const first = stops[0];\n const last = stops.at(-1);\n if (first === undefined || last === undefined) {\n event.preventDefault();\n return;\n }\n const position = event.target.compareDocumentPosition(\n event.shiftKey ? first : last,\n );\n const leaving = event.shiftKey\n ? (position & Node.DOCUMENT_POSITION_PRECEDING) === 0\n : (position & Node.DOCUMENT_POSITION_FOLLOWING) === 0;\n if (leaving) {\n event.preventDefault();\n (event.shiftKey ? last : first).focus();\n }\n }\n\n function onWindowBlur(): void {\n onDismiss(\"blur\");\n }\n\n // An anchor taken out of the page has no box to read — the header of a\n // column the panel itself just hid — so the panel goes on being placed\n // beside where it last was, rather than in the viewport's corner.\n let lastAnchorBox: OverlayRect | null = null;\n function anchorBox(): OverlayRect {\n if (\n lastAnchorBox === null ||\n !(anchor instanceof Element) ||\n anchor.isConnected\n ) {\n lastAnchorBox = anchorRect(anchor);\n }\n return lastAnchorBox;\n }\n\n // Measured fixed at the viewport's top-left corner, shrink-wrapped and with\n // the whole viewport to grow into — in normal flow a block panel would\n // report the full width of the page — and from that corner every time, so\n // placing it again measures it the way the first placing did.\n function place(): void {\n panel.style.top = \"0px\";\n panel.style.left = \"0px\";\n const { width, height } = panel.getBoundingClientRect();\n const position = placeOverlay(\n anchorBox(),\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\n panel.style.position = \"fixed\";\n place();\n const resizeObserver = new ResizeObserver(place);\n resizeObserver.observe(panel);\n\n // On the stack before focus moves in, so the overlay below sees the focus\n // leaving it land inside an overlay above it rather than outside.\n pushOverlay(stacked);\n initialFocusTarget(panel, options.initialFocus ?? \"first\")?.focus({\n preventScroll: true,\n });\n\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 if (options.trapFocus === true) {\n panel.addEventListener(\"keydown\", onTrapKeyDown);\n }\n if (options.ignoreScrollAfterInput === true) {\n panel.addEventListener(\"input\", onInput);\n panel.addEventListener(\"change\", onInput);\n }\n\n let attached = true;\n function detach(): void {\n if (!attached) {\n return;\n }\n attached = false;\n // Read before the overlays above close and hand focus back to wherever\n // they took it from, which may already have left the page with this panel.\n const active = document.activeElement;\n const focusInside =\n active === null || active === document.body || inside(active);\n removeOverlay(stacked);\n resizeObserver.disconnect();\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 panel.removeEventListener(\"keydown\", onTrapKeyDown);\n panel.removeEventListener(\"input\", onInput);\n panel.removeEventListener(\"change\", onInput);\n if (settlingFrame !== null) {\n cancelAnimationFrame(settlingFrame);\n }\n\n if (!focusInside) {\n return;\n }\n const target =\n returnFocusTo?.isConnected === true\n ? returnFocusTo\n : options.returnFocusFallback?.();\n target?.focus({ preventScroll: true });\n }\n return detach;\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 /**\n * Forgets every measured height, for rows that are no longer the ones\n * measured. A `scrollToIndex` still waiting for its row is dropped too:\n * the row it asked for is no longer at that index.\n */\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. A later call\n * replaces one still waiting for its row; a row count that no longer holds\n * that row, or `resetHeights`, drops it.\n */\n readonly scrollToIndex: (\n index: number,\n options?: ScrollIntoViewOptions,\n ) => void;\n /**\n * Stops listening, measuring and scrolling, for good: every method does\n * nothing after it.\n */\n readonly destroy: () => void;\n}\n\n/**\n * Where the virtualizer is in its life: `ready`, `awaiting-row` while a\n * `scrollToIndex` waits for its row to mount to scroll to it properly, and\n * `destroyed` for good.\n */\ntype VirtualizerPhase =\n | { readonly kind: \"ready\" }\n | {\n readonly kind: \"awaiting-row\";\n readonly index: number;\n readonly options: ScrollIntoViewOptions | undefined;\n }\n | { readonly kind: \"destroyed\" };\n\nconst READY: VirtualizerPhase = { kind: \"ready\" };\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 phase: VirtualizerPhase = READY;\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 (phase.kind === \"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 if (phase.kind === \"awaiting-row\" && phase.index >= count) {\n phase = READY;\n }\n heights = Array.from({ length: count }, (_, index) => heights[index]);\n recompute();\n },\n resetHeights() {\n if (phase.kind === \"awaiting-row\") {\n phase = READY;\n }\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 || phase.kind === \"destroyed\") {\n return;\n }\n elements.set(index, element);\n indexByElement.set(element, index);\n rowObserver.observe(element);\n if (phase.kind === \"awaiting-row\" && phase.index === index) {\n element.scrollIntoView(phase.options);\n phase = READY;\n }\n };\n refCallbacks.set(index, callback);\n }\n return callback;\n },\n scrollToIndex(index, scrollOptions) {\n if (phase.kind === \"destroyed\" || 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 phase = READY;\n mounted.scrollIntoView(scrollOptions);\n return;\n }\n phase = { kind: \"awaiting-row\", index: clamped, options: scrollOptions };\n scrollElement.scrollTop = estimateOffsetBefore(clamped);\n },\n destroy() {\n phase = { kind: \"destroyed\" };\n scrollElement.removeEventListener(\"scroll\", handleScroll);\n viewportObserver.disconnect();\n rowObserver.disconnect();\n elements.clear();\n indexByElement.clear();\n },\n };\n}\n","import { nextMenuIndex, type MenuMove } from \"@gridkitjs/core\";\n\n/** Every element a toolbar is made of. Those with no control in them — separators, spacers — are passed over. */\nconst ENTRY = \"[data-gridkit-toolbar-item]\";\n\n/** What can take focus: an entry's control is the entry itself, or the first of these in it. */\nconst FOCUSABLE = [\n \"button:not(:disabled)\",\n \"input:not(:disabled)\",\n \"select:not(:disabled)\",\n \"textarea:not(:disabled)\",\n \"a[href]\",\n \"[tabindex]\",\n].join(\", \");\n\n/** The keys that move focus, and where to. */\nconst MOVES: Readonly<Record<string, MenuMove>> = {\n ArrowRight: \"next\",\n ArrowLeft: \"previous\",\n Home: \"first\",\n End: \"last\",\n};\n\n/** Each control, as `nextMenuIndex` reads it: no separators are among them. */\nconst AS_ENTRY = { type: \"item\" } as const;\n\n/** Input types with no caret, whose arrow keys the toolbar may take. */\nconst CARETLESS_INPUTS = new Set([\n \"button\",\n \"checkbox\",\n \"color\",\n \"file\",\n \"image\",\n \"radio\",\n \"range\",\n \"reset\",\n \"submit\",\n]);\n\ninterface Control {\n /** The `data-gridkit-toolbar-item` element. */\n readonly entry: HTMLElement;\n /** What takes focus for it. */\n readonly target: HTMLElement;\n}\n\nfunction targetOf(entry: HTMLElement): HTMLElement | null {\n return entry.matches(FOCUSABLE)\n ? entry\n : entry.querySelector<HTMLElement>(FOCUSABLE);\n}\n\n/** A field typed in, whose caret the arrow keys move. */\nfunction textFieldOf(\n target: EventTarget | null,\n): HTMLInputElement | HTMLTextAreaElement | null {\n return target instanceof HTMLTextAreaElement ||\n (target instanceof HTMLInputElement && !CARETLESS_INPUTS.has(target.type))\n ? target\n : null;\n}\n\n/**\n * Whether a key belongs to the text field it was pressed in rather than to\n * the toolbar: Home and End always do, and an arrow does unless the caret\n * sits at the end it points past — so the arrows still lead out of a field,\n * which would otherwise trap focus in it, the toolbar's one tab stop.\n */\nfunction belongsToField(target: EventTarget | null, move: MenuMove): boolean {\n if (target instanceof HTMLElement && target.isContentEditable) {\n return true;\n }\n const field = textFieldOf(target);\n if (field === null) {\n return false;\n }\n const { selectionStart, selectionEnd, value } = field;\n // A type that keeps its caret to itself — `email`, `number` — reports\n // none, so every key is left to it rather than taken from under a caret.\n if (\n move === \"first\" ||\n move === \"last\" ||\n selectionStart === null ||\n selectionStart !== selectionEnd\n ) {\n return true;\n }\n return move === \"previous\"\n ? selectionStart > 0\n : selectionStart < value.length;\n}\n\n/**\n * Makes `toolbar` — a `role=\"toolbar\"` element the renderer drew — one tab\n * stop that the arrow keys move within, following the WAI-ARIA toolbar\n * pattern: ArrowRight and ArrowLeft move focus to the next and previous\n * control, wrapping round the ends, and Home and End to the first and last.\n * Returns the function that stops it.\n *\n * The toolbar's entries are its descendants with a `data-gridkit-toolbar-item`,\n * and each entry's control is the entry itself when it can take focus, else\n * the first element in it that can; an entry with none — a separator, a\n * spacer — is passed over. They are read afresh on every key. One control at\n * a time has `tabindex=\"0\"` — the focused one, or the first — and the rest\n * `-1`, kept so as entries come and go; a control with\n * `aria-disabled=\"true\"` stays one of them, so a screen reader finds it.\n *\n * A text field keeps the keys it needs: Home and End, and an arrow while\n * the caret has text to move through in that direction. At the start or end\n * of its text, the arrow moves on to the next control.\n */\nexport function attachToolbar(toolbar: HTMLElement): () => void {\n /** The control with `tabindex=\"0\"`. */\n let stop: HTMLElement | undefined;\n\n function controls(): Control[] {\n return Array.from(toolbar.querySelectorAll<HTMLElement>(ENTRY)).flatMap(\n (entry) => {\n const target = targetOf(entry);\n return target === null ? [] : [{ entry, target }];\n },\n );\n }\n\n function indexOf(all: readonly Control[], node: EventTarget | null): number {\n return node instanceof Node\n ? all.findIndex((control) => control.entry.contains(node))\n : -1;\n }\n\n function setTabStop(next: HTMLElement | undefined): void {\n stop = next;\n for (const { target } of controls()) {\n target.tabIndex = target === next ? 0 : -1;\n }\n }\n\n function onKeyDown(event: KeyboardEvent): void {\n const move = MOVES[event.key];\n if (\n move === undefined ||\n event.isComposing ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.shiftKey ||\n belongsToField(event.target, move)\n ) {\n return;\n }\n const all = controls();\n const from = indexOf(all, event.target);\n if (from === -1) {\n return;\n }\n const next =\n all[\n nextMenuIndex(\n all.map(() => AS_ENTRY),\n from,\n move,\n )\n ];\n if (next !== undefined) {\n event.preventDefault();\n setTabStop(next.target);\n next.target.focus();\n }\n }\n\n // However focus arrived — a click, Tab, a script — the tab stop follows it.\n function onFocusIn(event: FocusEvent): void {\n const all = controls();\n const control = all[indexOf(all, event.target)];\n if (control !== undefined) {\n setTabStop(control.target);\n }\n }\n\n // A control drawn since — a button whose command became visible — would\n // otherwise come with the browser's own tab stop, and make two. The stop\n // stays where it was while its control is still drawn.\n const observer = new MutationObserver(() => {\n const all = controls();\n setTabStop(\n all.find(({ target }) => target === stop)?.target ?? all[0]?.target,\n );\n });\n\n const all = controls();\n setTabStop(\n all[indexOf(all, document.activeElement)]?.target ?? all[0]?.target,\n );\n toolbar.addEventListener(\"keydown\", onKeyDown);\n toolbar.addEventListener(\"focusin\", onFocusIn);\n observer.observe(toolbar, { childList: true, subtree: true });\n\n return () => {\n observer.disconnect();\n toolbar.removeEventListener(\"keydown\", onKeyDown);\n toolbar.removeEventListener(\"focusin\", onFocusIn);\n };\n}\n","import {\n fitToolbarItems,\n isPinnedToolbarItem,\n type ResolvedToolbarItem,\n} from \"@gridkitjs/core\";\n\n/**\n * A toolbar entry as the overflow reads it: its kind, the key its element\n * carries and, for a button or a dropdown, its label — a change to which,\n * while the entry is in \"More\" and not drawn, makes the width remembered for\n * it stale.\n */\nexport type ToolbarOverflowItem = Pick<ResolvedToolbarItem, \"type\" | \"key\"> & {\n readonly label?: string | undefined;\n};\n\nexport interface ToolbarOverflowOptions {\n /**\n * Every entry of the toolbar, in order — those drawn and those moved into\n * \"More\" alike — as `resolveToolbar` gave them.\n */\n readonly items: readonly ToolbarOverflowItem[];\n /**\n * How many entries fit, as `fitToolbarItems` counts them: called once\n * straight away, then whenever the count changes.\n */\n readonly onChange: (fit: number) => void;\n}\n\nexport interface ToolbarOverflow {\n /**\n * Call whenever the items change, with the current options: measures\n * again, and reports a changed count.\n */\n readonly update: (options: ToolbarOverflowOptions) => void;\n /** Stops observing. */\n readonly detach: () => void;\n}\n\nconst ENTRY_ATTRIBUTE = \"data-gridkit-toolbar-item\";\nconst MORE_ATTRIBUTE = \"data-gridkit-toolbar-more\";\n\nfunction pixels(value: string): number {\n return Number.parseFloat(value) || 0;\n}\n\n/** The room an element takes in its row: its box and its margins. */\nfunction outerWidth(element: HTMLElement): number {\n const style = getComputedStyle(element);\n return (\n element.getBoundingClientRect().width +\n pixels(style.marginLeft) +\n pixels(style.marginRight)\n );\n}\n\n/**\n * Measures how many of a toolbar's entries fit in it, reporting the count\n * through `onChange` straight away — so a renderer calling this before\n * paint draws the right entries from the first frame — and again whenever a\n * resize changes it. The arithmetic is @gridkitjs/core's `fitToolbarItems`:\n * the search box and custom entries always stay, and room for \"More\" is kept\n * only when something does not fit.\n *\n * Entries are found by their `data-gridkit-toolbar-item`, holding each\n * item's `key`, and the \"More\" button by `data-gridkit-toolbar-more`. An\n * entry moved into \"More\" is no longer drawn, so each width is remembered\n * by key from the last time it was; one never drawn yet counts as nothing,\n * which lets it be drawn, measured, and moved back out if it does not fit —\n * as one whose label changed while it was not drawn counts again.\n * A spacer counts as nothing too: its width is only the room left over. The\n * toolbar's padding and the `column-gap` between entries are allowed for.\n *\n * It measures again whenever the toolbar or an entry resizes and whenever\n * entries are drawn or taken away — the ones the count it reported let in,\n * or the \"More\" button — so a renderer only has to draw what it is told,\n * and to pass new items to `update`.\n */\nexport function observeToolbarOverflow(\n toolbar: HTMLElement,\n options: ToolbarOverflowOptions,\n): ToolbarOverflow {\n let current = options;\n const widths = new Map<string, number>();\n let moreWidth = 0;\n let reported: number | undefined;\n\n function drawnEntries(): HTMLElement[] {\n return Array.from(\n toolbar.querySelectorAll<HTMLElement>(`[${ENTRY_ATTRIBUTE}]`),\n );\n }\n\n function measure(): void {\n for (const element of drawnEntries()) {\n if (element.hasAttribute(MORE_ATTRIBUTE)) {\n moreWidth = outerWidth(element);\n } else {\n widths.set(\n element.getAttribute(ENTRY_ATTRIBUTE) ?? \"\",\n outerWidth(element),\n );\n }\n }\n const style = getComputedStyle(toolbar);\n // Every entry is counted with the gap after it, so the row gets one gap\n // more than it has — the one after its last entry — to share.\n const gap = pixels(style.columnGap);\n const fit = fitToolbarItems({\n widths: current.items.map(\n (item) =>\n (item.type === \"spacer\" ? 0 : (widths.get(item.key) ?? 0)) + gap,\n ),\n available:\n toolbar.clientWidth -\n pixels(style.paddingLeft) -\n pixels(style.paddingRight) +\n gap,\n moreWidth: moreWidth + gap,\n pinned: current.items.map(isPinnedToolbarItem),\n });\n if (fit !== reported) {\n reported = fit;\n current.onChange(fit);\n }\n }\n\n /** The toolbar for the room there is, and every entry drawn for a label that changed its width. */\n const resizes = new ResizeObserver(measure);\n\n function observeSizes(): void {\n resizes.disconnect();\n resizes.observe(toolbar);\n for (const element of drawnEntries()) {\n resizes.observe(element);\n }\n }\n\n const draws = new MutationObserver(() => {\n observeSizes();\n measure();\n });\n\n observeSizes();\n draws.observe(toolbar, { childList: true, subtree: true });\n measure();\n\n return {\n update(next) {\n // An entry whose label changed while it was in \"More\" is forgotten, so\n // it counts as never drawn: drawn, measured afresh, and moved back out\n // if it no longer fits. One drawn is measured again straight away.\n const labels = new Map(\n current.items.map((item) => [item.key, item.label]),\n );\n for (const item of next.items) {\n if (labels.has(item.key) && labels.get(item.key) !== item.label) {\n widths.delete(item.key);\n }\n }\n current = next;\n measure();\n },\n detach() {\n resizes.disconnect();\n draws.disconnect();\n },\n };\n}\n","/** What `printDocument` prints: a document's title, its body's markup and its stylesheet. */\nexport interface PrintDocumentOptions {\n /** The document's title, as text — what the browser puts in a page's header and names a saved PDF. */\n readonly title: string;\n /** The `<body>`'s markup, as `buildPrintDocument` writes it. */\n readonly bodyHtml: string;\n /** The document's whole stylesheet — `printStyles(page)`, and anything added after it. */\n readonly styles: string;\n}\n\n/**\n * What tells a print is over where `afterprint` never comes: the user\n * reaching for the page again, which a print dialog — modal while it is\n * open — allows only once it has closed.\n */\nconst RETURN_EVENTS = [\"pointerdown\", \"keydown\"] as const;\n\n/** How long a print waits for the frame's fonts before printing without them. */\nconst FONTS_TIMEOUT_MS = 3000;\n\n/** Whether a print is open, so a second request while it is can be refused. */\nlet printing = false;\n\n/**\n * A frame no one sees or reaches. It has no size rather than being hidden\n * with `display: none` or `visibility`, which some browsers then refuse to\n * print.\n */\nfunction createFrame(bodyHtml: string): HTMLIFrameElement {\n const frame = document.createElement(\"iframe\");\n frame.setAttribute(\"aria-hidden\", \"true\");\n frame.tabIndex = -1;\n frame.style.position = \"fixed\";\n frame.style.right = \"0\";\n frame.style.bottom = \"0\";\n frame.style.width = \"0\";\n frame.style.height = \"0\";\n frame.style.border = \"0\";\n frame.srcdoc = `<!doctype html><html><head><meta charset=\"utf-8\"></head><body>${bodyHtml}</body></html>`;\n return frame;\n}\n\n/**\n * Puts the frame in the page and waits for its document to load. Rejects\n * if the frame is taken out of the page first, as other code that clears\n * `document.body` can do: it would then never load.\n */\nfunction load(frame: HTMLIFrameElement): Promise<Window> {\n return new Promise((resolve, reject) => {\n const removal = new MutationObserver(() => {\n if (!frame.isConnected) {\n settle();\n reject(new Error(\"The print frame was removed before it loaded.\"));\n }\n });\n function loaded(): void {\n settle();\n if (frame.contentWindow === null) {\n reject(new Error(\"The print frame has no window.\"));\n } else {\n resolve(frame.contentWindow);\n }\n }\n function settle(): void {\n removal.disconnect();\n frame.removeEventListener(\"load\", loaded);\n }\n frame.addEventListener(\"load\", loaded);\n removal.observe(document.body, { childList: true });\n document.body.append(frame);\n });\n}\n\n/**\n * Gives the loaded document its title and stylesheet. Set through the DOM\n * rather than written into the markup, so neither needs escaping — a\n * `</style>` in the CSS, or a `<` in a title, stays what it is.\n */\nfunction fill(view: Window, { title, styles }: PrintDocumentOptions): void {\n const { document: frameDocument } = view;\n frameDocument.title = title;\n const style = frameDocument.createElement(\"style\");\n style.textContent = styles;\n frameDocument.head.append(style);\n}\n\n/**\n * Waits for the frame's fonts to load, for `FONTS_TIMEOUT_MS` at most: a\n * font whose request never settles would otherwise hold this print open,\n * and refuse every print after it. A font still missing then prints in the\n * next face of its stack. The timer is the page's rather than the frame's,\n * so it still fires if the frame is taken away meanwhile.\n */\nasync function fontsLoaded(view: Window): Promise<void> {\n let timer = 0;\n const timeout = new Promise<void>((resolve) => {\n timer = window.setTimeout(resolve, FONTS_TIMEOUT_MS);\n });\n try {\n await Promise.race([view.document.fonts.ready, timeout]);\n } finally {\n window.clearTimeout(timer);\n }\n}\n\n/**\n * Opens the print dialog for the frame and settles once it has closed: on\n * the frame's `afterprint`, which every current browser fires, or else the\n * next time the user presses a key or the pointer on the page. Not after a\n * set time: where `print()` returns with the dialog still open, a frame\n * taken away under it would print nothing.\n */\nasync function print(view: Window): Promise<void> {\n // A key comes to the frame, which has focus; the pointer to the page, as\n // the frame takes no room.\n const listeners: [Window, string][] = [\n [view, \"afterprint\"],\n ...RETURN_EVENTS.flatMap((type): [Window, string][] => [\n [view, type],\n [window, type],\n ]),\n ];\n let done = (): void => undefined;\n const closed = new Promise<void>((resolve) => {\n done = resolve;\n });\n for (const [target, type] of listeners) {\n target.addEventListener(type, done, { capture: true });\n }\n try {\n // Safari prints the page around a frame that does not have focus.\n view.focus();\n view.print();\n await closed;\n } finally {\n for (const [target, type] of listeners) {\n target.removeEventListener(type, done, { capture: true });\n }\n }\n}\n\n/**\n * Prints a document through a hidden frame: its own page, holding only\n * `bodyHtml` under `styles`, so what is printed is what was built — every\n * row, not the slice of them a virtualised grid has drawn — and nothing of\n * the page around it. The frame loads, waits for its fonts (a few seconds\n * at most), opens the print dialog, and is removed once the dialog closes, when the promise\n * resolves; focus goes back where it was. A frame rather than a new window,\n * which a popup blocker would stop.\n *\n * One print at a time: a call while a print is open resolves at once\n * without printing. Rejects, with the frame removed, if the frame is taken\n * out of the page before it loads or the dialog fails to open.\n */\nexport async function printDocument(\n options: PrintDocumentOptions,\n): Promise<void> {\n if (printing) {\n return;\n }\n printing = true;\n const previous = document.activeElement;\n const frame = createFrame(options.bodyHtml);\n try {\n const view = await load(frame);\n fill(view, options);\n await fontsLoaded(view);\n await print(view);\n } finally {\n frame.remove();\n printing = false;\n if (previous instanceof HTMLElement) {\n previous.focus({ preventScroll: true });\n }\n }\n}\n","import type { PrintPage } from \"@gridkitjs/core\";\n\n/** How far each level of group header steps in, after the cell's own padding. */\nconst GROUP_INDENT = \"12pt\";\n/** A cell's padding to either side, which a group header's indent adds to. */\nconst CELL_PADDING_X = \"5pt\";\n\n/**\n * The deepest group level given its own indent; any deeper shares it. A\n * stylesheet cannot read `data-level` as a length, so each level is a rule.\n */\nconst DEEPEST_INDENTED_LEVEL = 6;\n\nfunction pageRules({ paperSize, orientation, pageNumbers }: PrintPage): string {\n const numbers = pageNumbers\n ? `@bottom-right { content: counter(page) \" / \" counter(pages); font: 8pt system-ui, sans-serif; color: #555; }`\n : \"\";\n return `@page { size: ${paperSize} ${orientation}; margin: 12mm; ${numbers} }`;\n}\n\nfunction indentRule(selector: string, level: number): string {\n return `${selector} > td:first-child { padding-left: calc(${CELL_PADDING_X} + ${String(level)} * ${GROUP_INDENT}); }`;\n}\n\n/**\n * Each group header's label stepped in under its parent's. Every nested\n * header starts at the deepest indent, and the levels above it each step\n * back out to their own — a later rule of the same weight winning.\n */\nfunction groupIndentRules(): string {\n const rules = [\n indentRule('.export-group:not([data-level=\"0\"])', DEEPEST_INDENTED_LEVEL),\n ];\n for (let level = 1; level < DEEPEST_INDENTED_LEVEL; level++) {\n rules.push(\n indentRule(`.export-group[data-level=\"${String(level)}\"]`, level),\n );\n }\n return rules.join(\"\\n\");\n}\n\nconst BASE_RULES = `html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\nbody { margin: 0; background: #fff; color: #000; font: 10pt/1.35 system-ui, sans-serif; }\n.gridkit-print-header { margin-bottom: 8pt; }\n.print-title { margin: 0; font-size: 14pt; }\n.print-subtitle, .print-timestamp { margin: 2pt 0 0; color: #444; }\n.gridkit-print-footer { margin-top: 8pt; color: #444; }\n.gridkit-export-table { width: 100%; border-collapse: collapse; }\n.gridkit-export-table thead { display: table-header-group; }\n.gridkit-export-table tfoot { display: table-row-group; }\n.gridkit-export-table tr { break-inside: avoid; }\n.gridkit-export-table th, .gridkit-export-table td { border: 1px solid #bbb; padding: 3pt ${CELL_PADDING_X}; text-align: left; vertical-align: top; }\n.gridkit-export-table th { background: #eee; font-weight: 600; }\n.export-group > td { background: #f5f5f5; font-weight: 600; }\n.export-group-summary > td, .gridkit-export-table tfoot td { font-weight: 600; }\n.gridkit-export-table tfoot td { border-top: 2px solid #888; }`;\n\nconst FIT_TO_WIDTH_RULES = `.gridkit-export-table { table-layout: fixed; font-size: 8pt; }\n.gridkit-export-table th, .gridkit-export-table td { overflow-wrap: anywhere; }`;\n\n/**\n * The stylesheet a print of `buildPrintDocument`'s markup needs, for `page`.\n * A print frame sees none of the page's CSS, so this carries what makes the\n * table read on paper and nothing of a theme: the paper's size and\n * orientation; a header row repeated at the top of every page; rows kept\n * whole across a page break; neutral one-pixel borders; group headers\n * stepped in by depth; backgrounds printed as they are rather than dropped\n * to save ink; page numbers in the bottom-right margin, where the browser\n * draws `@page` margin boxes; and for `fitToWidth`, a smaller font in fixed\n * columns that wrap their text.\n *\n * The grand total prints once, after the last row — not repeated at the\n * foot of every page, where browsers repeat a `<tfoot>`, as a total of\n * rows the page does not hold would mislead. Add CSS after it to restyle.\n */\nexport function printStyles(page: PrintPage): string {\n return [\n pageRules(page),\n BASE_RULES,\n groupIndentRules(),\n page.fitToWidth ? FIT_TO_WIDTH_RULES : \"\",\n ]\n .filter((rules) => rules !== \"\")\n .join(\"\\n\");\n}\n"],"mappings":"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","names":["onKeyDown","onKeyDown","onKeyDown","DETACHED","ITEM","IDLE","onKeyDown","nextMenuIndex","FOCUSABLE","MOVES","AS_ENTRY","indexOf","all","onKeyDown"]}
1
+ {"version":3,"sources":["../src/announcer.ts","../src/bodyTargets.ts","../src/checklist/attachChecklist.ts","../src/contextMenu/attachContextMenuTrigger.ts","../src/columnResize.ts","../src/pointerGesture.ts","../src/clipboard.ts","../src/download.ts","../src/dragReorder.ts","../src/dropTargets.ts","../src/elementWidth.ts","../src/gridFocus.ts","../src/infiniteScroll.ts","../src/menu/attachMenu.ts","../src/overlay/overlayStack.ts","../src/overlay/placeOverlay.ts","../src/overlay/attachOverlay.ts","../src/rowVirtualizer.ts","../src/toolbar/attachToolbar.ts","../src/toolbar/observeToolbarOverflow.ts","../src/print/printDocument.ts","../src/print/styles.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","export interface ChecklistOptions {\n /**\n * The item at `index` — its place in the whole list — asks to be checked\n * or unchecked: Space on it, or a click. The caller flips it in its own\n * state and draws the new `aria-checked`.\n */\n readonly onToggle: (index: number) => void;\n /** Focus moved to the item at `index`, by a key, a click or a script. */\n readonly onFocusChange?: ((index: number) => void) | undefined;\n /**\n * Bring the item at `index` into view, mounting it if the list's window\n * does not hold it. A virtualized renderer forwards this to its\n * virtualizer's `scrollToIndex`, with `{ block: \"nearest\" }` so a move of\n * one item scrolls by one item.\n */\n readonly scrollToIndex: (index: number) => void;\n}\n\nexport interface AttachedChecklist {\n /**\n * How many items the whole list holds, mounted or not. Starts at 0, so a\n * renderer calls this straight after attaching, and again whenever the\n * list changes — a search narrowing it, say. A focused place past the new\n * end moves back to the last item, and so does a move still waiting for\n * an item past it.\n */\n readonly setCount: (count: number) => void;\n /**\n * Focuses the item at `index`, clamped to the list, scrolling to it first.\n * An item outside the mounted window is focused once it mounts.\n */\n readonly focusItem: (index: number) => void;\n /**\n * Removes every listener and observer. The items keep their `tabindex`.\n * After it, `setCount` and `focusItem` do nothing: they neither scroll\n * nor focus, and a move still waiting for its item is dropped.\n */\n readonly detach: () => void;\n}\n\nconst ITEM = \"[data-gridkit-checklist-item]\";\nconst INDEX = \"data-gridkit-checklist-index\";\n\n/**\n * Where focus is going besides the focused place: nowhere, to an item a move\n * is waiting to see mounted, or nowhere ever again once detached.\n */\ntype ChecklistState =\n | { readonly kind: \"settled\" }\n | { readonly kind: \"awaiting-item\"; readonly index: number }\n | { readonly kind: \"detached\" };\n\nconst SETTLED: ChecklistState = { kind: \"settled\" };\nconst DETACHED: ChecklistState = { kind: \"detached\" };\n\nfunction indexOf(item: Element): number {\n return Number(item.getAttribute(INDEX));\n}\n\n/**\n * Makes `list` — a `role=\"listbox\"` the renderer drew, usually virtualized\n * so that only a window of a long list is mounted — behave as a\n * multi-select checklist with one tab stop: the arrow keys, Home, End,\n * PageUp and PageDown move focus among its items, and Space or a click asks\n * for the focused item to be toggled. It decides; the caller is told through\n * the callbacks and draws the outcome, `aria-checked` included.\n *\n * Items are the list's descendants with `data-gridkit-checklist-item` and a\n * `data-gridkit-checklist-index`: their place in the whole list rather than\n * in the window, since an item's place is all that survives it being\n * unmounted and mounted again. The list is the element that scrolls: PageUp\n * and PageDown move by as many items as it shows whole.\n *\n * One item at a time has `tabindex=\"0\"` — the focused one, or the first\n * mounted while the focused one is scrolled away — and the rest `-1`, kept\n * so as items mount by an observer of the list's children rather than by\n * the renderer. A move to an item outside the window asks `scrollToIndex`\n * for it and focuses it once it mounts, unless focus has gone somewhere\n * else in the meantime.\n *\n * Enter is not handled, so a form around the list hears it and applies;\n * Escape is the overlay stack's.\n */\nexport function attachChecklist(\n list: HTMLElement,\n options: ChecklistOptions,\n): AttachedChecklist {\n let count = 0;\n /** The focused place in the whole list: the tab stop, mounted or not. */\n let active = 0;\n let state: ChecklistState = SETTLED;\n\n function items(): HTMLElement[] {\n return Array.from(list.querySelectorAll<HTMLElement>(ITEM));\n }\n\n function itemAt(index: number): HTMLElement | null {\n return list.querySelector<HTMLElement>(\n `${ITEM}[${INDEX}=\"${String(index)}\"]`,\n );\n }\n\n function itemOf(target: EventTarget | null): HTMLElement | null {\n const item =\n target instanceof Element ? target.closest<HTMLElement>(ITEM) : null;\n return item !== null && list.contains(item) ? item : null;\n }\n\n function setTabStop(): void {\n const all = items();\n const stop = all.find((item) => indexOf(item) === active) ?? all[0];\n for (const item of all) {\n item.tabIndex = item === stop ? 0 : -1;\n }\n }\n\n function makeActive(index: number): void {\n if (index !== active) {\n active = index;\n options.onFocusChange?.(index);\n }\n }\n\n function moveTo(index: number): void {\n if (state.kind === \"detached\" || count === 0) {\n return;\n }\n const target = Math.max(0, Math.min(index, count - 1));\n makeActive(target);\n setTabStop();\n options.scrollToIndex(target);\n const item = itemAt(target);\n if (item === null) {\n state = { kind: \"awaiting-item\", index: target };\n } else {\n state = SETTLED;\n item.focus({ preventScroll: true });\n }\n }\n\n /** How many items the list shows whole, for PageUp and PageDown. */\n function pageSize(): number {\n const top = list.getBoundingClientRect().top + list.clientTop;\n const bottom = top + list.clientHeight;\n const whole = items().filter((item) => {\n const box = item.getBoundingClientRect();\n return box.top >= top && box.bottom <= bottom;\n }).length;\n return Math.max(1, whole);\n }\n\n // Every key it acts on is taken, so the list does not also scroll by\n // itself. A modified key is left for whatever shortcut it is. Moves count\n // from the focused place rather than the focused element, which lags\n // behind while a move waits for its item to mount.\n function onKeyDown(event: KeyboardEvent): void {\n const item = itemOf(event.target);\n if (\n item === null ||\n event.isComposing ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.shiftKey\n ) {\n return;\n }\n switch (event.key) {\n case \"ArrowDown\":\n moveTo(active + 1);\n break;\n case \"ArrowUp\":\n moveTo(active - 1);\n break;\n case \"Home\":\n moveTo(0);\n break;\n case \"End\":\n moveTo(count - 1);\n break;\n case \"PageDown\":\n moveTo(active + pageSize());\n break;\n case \"PageUp\":\n moveTo(active - pageSize());\n break;\n case \" \":\n // Held down, Space toggles once, as a checkbox does.\n if (!event.repeat) {\n options.onToggle(indexOf(item));\n }\n break;\n default:\n return;\n }\n event.preventDefault();\n }\n\n // However focus arrived — Tab, a click, a script — the tab stop follows it,\n // and a move still waiting for its item gives way.\n function onFocusIn(event: FocusEvent): void {\n const item = itemOf(event.target);\n if (item !== null) {\n state = SETTLED;\n makeActive(indexOf(item));\n setTabStop();\n }\n }\n\n function onClick(event: MouseEvent): void {\n const item = itemOf(event.target);\n if (item !== null) {\n options.onToggle(indexOf(item));\n item.focus({ preventScroll: true });\n }\n }\n\n // The item a move waits for takes focus only if focus is still in the\n // list, or dropped to nothing when the item it was on unmounted.\n function focusAwaitedItemIfMounted(): void {\n const item = state.kind === \"awaiting-item\" ? itemAt(state.index) : null;\n if (item === null) {\n return;\n }\n state = SETTLED;\n const focused = document.activeElement;\n if (\n focused === null ||\n focused === document.body ||\n list.contains(focused)\n ) {\n item.focus({ preventScroll: true });\n }\n }\n\n // Items mount and unmount as the window moves, and a kept element can take\n // another place when the list changes under it.\n const observer = new MutationObserver(() => {\n setTabStop();\n focusAwaitedItemIfMounted();\n });\n observer.observe(list, {\n childList: true,\n subtree: true,\n attributeFilter: [INDEX],\n });\n\n setTabStop();\n list.addEventListener(\"keydown\", onKeyDown);\n list.addEventListener(\"focusin\", onFocusIn);\n list.addEventListener(\"click\", onClick);\n\n return {\n setCount(next) {\n if (state.kind === \"detached\") {\n return;\n }\n count = next;\n active = Math.max(0, Math.min(active, count - 1));\n if (state.kind === \"awaiting-item\") {\n state =\n count === 0\n ? SETTLED\n : {\n kind: \"awaiting-item\",\n index: Math.min(state.index, count - 1),\n };\n }\n setTabStop();\n focusAwaitedItemIfMounted();\n },\n focusItem: moveTo,\n detach() {\n state = DETACHED;\n observer.disconnect();\n list.removeEventListener(\"keydown\", onKeyDown);\n list.removeEventListener(\"focusin\", onFocusIn);\n list.removeEventListener(\"click\", onClick);\n },\n };\n}\n","/**\n * How a context menu was asked for: by a pointer — a right-click, or a touch\n * held still — or by the keyboard.\n */\nexport type ContextMenuSource = \"pointer\" | \"keyboard\";\n\n/** A context menu asked for inside the root, as `onRequest` hears of it. */\nexport interface ContextMenuRequest {\n /** The element it was asked for on: the one under the pointer, or the one focused for a key. */\n readonly target: Element;\n /**\n * Where the pointer was, in viewport coordinates — where a menu opened by\n * it goes. `null` from the keyboard, which has no point: a menu it opens\n * goes beside the focused element instead.\n */\n readonly point: { readonly x: number; readonly y: number } | null;\n readonly source: ContextMenuSource;\n}\n\nexport interface ContextMenuTriggerOptions {\n /**\n * A context menu was asked for inside the root. Returns whether the caller\n * opened one of its own: if so the browser's menu is prevented, and if not\n * it shows as it would have.\n */\n readonly onRequest: (request: ContextMenuRequest) => boolean;\n}\n\n/** How long a touch rests on the screen before it asks for a menu. */\nconst LONG_PRESS_DELAY = 500;\n\n/** How far, in pixels, a touch may drift and still count as resting. */\nconst LONG_PRESS_SLOP = 10;\n\n/** Shift+F10 and the ContextMenu key: the keyboard's ways to ask for one. */\nfunction isContextMenuKey(event: KeyboardEvent): boolean {\n if (event.ctrlKey || event.altKey || event.metaKey) {\n return false;\n }\n return event.key === \"ContextMenu\" || (event.key === \"F10\" && event.shiftKey);\n}\n\n/** A touch pressing, until it asks, lifts or moves away. */\ninterface Press {\n readonly pointerId: number;\n readonly x: number;\n readonly y: number;\n readonly timer: number;\n}\n\n/**\n * Reports every context menu the user asks for inside `root` — a right-click,\n * Shift+F10, the ContextMenu key, or a touch held still for half a second —\n * through `onRequest`, and returns the function that stops it.\n *\n * Each gesture asks once. Pressing a key, or holding a touch, also makes some\n * browsers fire `contextmenu` of their own, and that event is taken, not\n * reported again, once the gesture opened a menu. The click a touch brings\n * as it lifts is taken too: the menu opened under the finger, and the click\n * would land in it.\n *\n * An event something already prevented is left alone, so a cell template\n * that shows a menu of its own keeps it. That is why the listeners are on the\n * document, in the bubble phase: a framework that handles events at its own\n * root container — React does — hears them before the document does, where\n * a listener on `root` itself would hear them first. Only events whose target\n * is inside `root` count.\n */\nexport function attachContextMenuTrigger(\n root: Element,\n options: ContextMenuTriggerOptions,\n): () => void {\n /**\n * Set once a key or a touch opened a menu, until the `contextmenu` event\n * the same gesture may still bring. A pointer or another key starts a new\n * gesture, so a browser that brings none does not cost the next\n * right-click.\n */\n let swallowContextMenu = false;\n /** Set once a touch opened a menu, until the click its lifting brings. */\n let swallowClick = false;\n let press: Press | null = null;\n\n /** Asks for a menu on what `event` happened to, unless something prevented it. */\n function ask(\n event: Event,\n point: ContextMenuRequest[\"point\"],\n source: ContextMenuSource,\n ): boolean {\n const { target } = event;\n return (\n !event.defaultPrevented &&\n target instanceof Element &&\n root.contains(target) &&\n options.onRequest({ target, point, source })\n );\n }\n\n /** `ask`, and the browser's own menu prevented when one opened. */\n function request(\n event: Event,\n point: ContextMenuRequest[\"point\"],\n source: ContextMenuSource,\n ): boolean {\n if (!ask(event, point, source)) {\n return false;\n }\n event.preventDefault();\n return true;\n }\n\n function cancelPress(): void {\n if (press !== null) {\n window.clearTimeout(press.timer);\n press = null;\n }\n }\n\n function onKeyDown(event: KeyboardEvent): void {\n swallowContextMenu = false;\n swallowClick = false;\n if (isContextMenuKey(event) && request(event, null, \"keyboard\")) {\n swallowContextMenu = true;\n }\n }\n\n /**\n * Every pointer starts a new gesture; a touch may become a long-press. It\n * is asked for once it has rested long enough, by which time the press\n * shows whether a template prevented it.\n */\n function onPointerDown(event: PointerEvent): void {\n swallowContextMenu = false;\n swallowClick = false;\n cancelPress();\n if (event.pointerType !== \"touch\" || !event.isPrimary) {\n return;\n }\n const point = { x: event.clientX, y: event.clientY };\n press = {\n pointerId: event.pointerId,\n ...point,\n timer: window.setTimeout(() => {\n press = null;\n if (ask(event, point, \"pointer\")) {\n swallowContextMenu = true;\n swallowClick = true;\n }\n }, LONG_PRESS_DELAY),\n };\n }\n\n function onPointerMove(event: PointerEvent): void {\n if (\n press?.pointerId === event.pointerId &&\n Math.hypot(event.clientX - press.x, event.clientY - press.y) >\n LONG_PRESS_SLOP\n ) {\n cancelPress();\n }\n }\n\n // Lifted, or taken over by the browser — to scroll, say.\n function onPointerEnd(event: PointerEvent): void {\n if (press?.pointerId === event.pointerId) {\n cancelPress();\n }\n }\n\n function onClick(event: MouseEvent): void {\n if (swallowClick) {\n swallowClick = false;\n event.preventDefault();\n event.stopPropagation();\n }\n }\n\n function onContextMenu(event: MouseEvent): void {\n if (swallowContextMenu) {\n swallowContextMenu = false;\n event.preventDefault();\n return;\n }\n // A browser that turns a held touch into `contextmenu` of its own asks\n // first, and the long-press need not.\n const touching = press !== null;\n cancelPress();\n if (\n request(event, { x: event.clientX, y: event.clientY }, \"pointer\") &&\n touching\n ) {\n swallowClick = true;\n }\n }\n\n document.addEventListener(\"keydown\", onKeyDown);\n document.addEventListener(\"pointerdown\", onPointerDown, true);\n document.addEventListener(\"pointermove\", onPointerMove, true);\n document.addEventListener(\"pointerup\", onPointerEnd, true);\n document.addEventListener(\"pointercancel\", onPointerEnd, true);\n // Captured, so the click is taken before anything under the finger hears it.\n document.addEventListener(\"click\", onClick, true);\n document.addEventListener(\"contextmenu\", onContextMenu);\n\n return () => {\n cancelPress();\n document.removeEventListener(\"keydown\", onKeyDown);\n document.removeEventListener(\"pointerdown\", onPointerDown, true);\n document.removeEventListener(\"pointermove\", onPointerMove, true);\n document.removeEventListener(\"pointerup\", onPointerEnd, true);\n document.removeEventListener(\"pointercancel\", onPointerEnd, true);\n document.removeEventListener(\"click\", onClick, true);\n document.removeEventListener(\"contextmenu\", onContextMenu);\n };\n}\n","import { applyColumnResize, type ColumnResizeSession } from \"@gridkitjs/core\";\nimport {\n mayStartGesture,\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, even when a\n * drag brought it back to where it started, or `null` when the width never\n * changed: a click on the handle — as each half of a double-click is — is\n * not a resize, and reporting one would fire a spurious resize on every\n * click.\n */\n readonly onEnd: (width: number | null) => void;\n /**\n * Escape, a cancelled pointer or an aborted `signal`: the column goes back\n * to `session.startWidth`.\n */\n readonly onCancel: () => void;\n /**\n * Cancels the resize when aborted, as Escape does. For a renderer whose\n * handle goes away mid-drag, so what it draws for the resize ends with it.\n * Already aborted, no resize starts.\n */\n readonly signal?: AbortSignal;\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, and not once\n * `handlers.signal` has aborted.\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 (!mayStartGesture(press, handlers.signal)) {\n return false;\n }\n press.preventDefault();\n\n let width = session.startWidth;\n let resized = false;\n startPointerGesture(\n handle,\n press.pointerId,\n {\n onMove(event) {\n width = applyColumnResize(session, event.clientX);\n resized ||= width !== session.startWidth;\n handlers.onMove(width);\n },\n onEnd() {\n handlers.onEnd(resized ? width : null);\n },\n onCancel() {\n handlers.onCancel();\n },\n },\n handlers.signal,\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, with `signal`, if any, not yet aborted. A right-click opens a\n * context menu and a second finger is not the gesture already under way, so\n * neither starts a drag or a resize; an aborted signal means the renderer has\n * already given up on what the press would drag.\n */\nexport function mayStartGesture(\n press: PointerPress,\n signal: AbortSignal | undefined,\n): boolean {\n return press.button === 0 && press.isPrimary && signal?.aborted !== true;\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 cancelling\n * on Escape or when `signal` aborts — leaving the caller's\n * `onMove`/`onEnd`/`onCancel` to own the gesture's own domain logic (and any\n * state reset that goes with it).\n *\n * Only the pointer that started the gesture moves or ends it: another finger\n * landing mid-drag fires its own events on the same element, and they are\n * ignored.\n */\nexport function startPointerGesture(\n element: HTMLElement,\n pointerId: number,\n handlers: PointerGestureHandlers,\n signal: AbortSignal | undefined,\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 signal?.removeEventListener(\"abort\", abandon);\n if (element.hasPointerCapture(pointerId)) {\n element.releasePointerCapture(pointerId);\n }\n }\n\n function move(event: PointerEvent): void {\n if (event.pointerId === pointerId) {\n handlers.onMove(event);\n }\n }\n\n function end(event: PointerEvent): void {\n if (event.pointerId === pointerId) {\n stop();\n handlers.onEnd();\n }\n }\n\n function cancel(event: PointerEvent): void {\n if (event.pointerId === pointerId) {\n abandon();\n }\n }\n\n function abandon(): void {\n stop();\n handlers.onCancel();\n }\n\n function onKeyDown(event: KeyboardEvent): void {\n if (event.key === \"Escape\") {\n abandon();\n }\n }\n\n element.addEventListener(\"pointermove\", move);\n element.addEventListener(\"pointerup\", end);\n element.addEventListener(\"pointercancel\", cancel);\n window.addEventListener(\"keydown\", onKeyDown);\n signal?.addEventListener(\"abort\", abandon);\n}\n","/** What `writeClipboard` puts on the clipboard: the same copy as plain text and as HTML. */\nexport interface ClipboardContent {\n readonly text: string;\n readonly html: string;\n}\n\n/**\n * The one legacy call, typed as GridKit uses it. `document.execCommand` is\n * deprecated and has no replacement for a browser without the async\n * Clipboard API — which is the only browser this is reached in.\n */\ninterface LegacyCopyDocument {\n execCommand(commandId: \"copy\"): boolean;\n}\n\n/** Writes both types through the async Clipboard API, where the browser has `ClipboardItem`. */\nasync function writeItem(\n clipboard: Clipboard,\n { text, html }: ClipboardContent,\n): Promise<void> {\n await clipboard.write([\n new ClipboardItem({\n \"text/plain\": new Blob([text], { type: \"text/plain\" }),\n \"text/html\": new Blob([html], { type: \"text/html\" }),\n }),\n ]);\n}\n\n/**\n * Copies through the `copy` event, the way pages did before the async API:\n * a one-shot listener sets both types on the event's `clipboardData`, and\n * `execCommand(\"copy\")` fires it. The command copies the focused selection,\n * so an off-screen field holding the text is focused and selected for it —\n * which also leaves the text on the clipboard should a browser give the\n * listener no `clipboardData` — and focus goes back where it was after.\n */\nfunction copyThroughEvent({ text, html }: ClipboardContent): boolean {\n const previous = document.activeElement;\n const field = document.createElement(\"textarea\");\n field.value = text;\n field.readOnly = true;\n field.setAttribute(\"aria-hidden\", \"true\");\n field.style.position = \"fixed\";\n field.style.top = \"0\";\n field.style.left = \"-9999px\";\n document.body.append(field);\n\n function onCopy(event: ClipboardEvent): void {\n if (event.clipboardData === null) {\n return;\n }\n event.clipboardData.setData(\"text/plain\", text);\n event.clipboardData.setData(\"text/html\", html);\n event.preventDefault();\n }\n\n document.addEventListener(\"copy\", onCopy, { capture: true, once: true });\n try {\n field.focus({ preventScroll: true });\n field.select();\n const legacy: LegacyCopyDocument = document;\n return legacy.execCommand(\"copy\");\n } finally {\n document.removeEventListener(\"copy\", onCopy, { capture: true });\n field.remove();\n if (previous instanceof HTMLElement) {\n previous.focus({ preventScroll: true });\n }\n }\n}\n\n/** Input types with no text of their own to copy, whose copy key the page may take. */\nconst TEXTLESS_INPUTS = new Set([\n \"button\",\n \"checkbox\",\n \"color\",\n \"file\",\n \"image\",\n \"radio\",\n \"range\",\n \"reset\",\n \"submit\",\n]);\n\nfunction isTextField(target: EventTarget | null): boolean {\n return (\n target instanceof HTMLTextAreaElement ||\n (target instanceof HTMLInputElement && !TEXTLESS_INPUTS.has(target.type)) ||\n (target instanceof HTMLElement && target.isContentEditable)\n );\n}\n\n/**\n * Whether a copy key pressed at `target` belongs to the browser: pressed in a\n * text field, or with text highlighted anywhere on the page. Either way the\n * user means the text in front of them, and a grid copying its own cell\n * instead would take that from them.\n */\nexport function isNativeCopy(target: EventTarget | null): boolean {\n const selection = document.getSelection();\n return isTextField(target) || (selection !== null && !selection.isCollapsed);\n}\n\n/**\n * Puts `content` on the clipboard as both `text/plain` and `text/html`, so a\n * paste into a spreadsheet splits into cells and one into a word processor\n * keeps its table. Call it from a user's gesture — a key press, a menu\n * choice — as browsers allow the clipboard only then.\n *\n * Tries each way a browser offers, best first: the async Clipboard API with\n * a `ClipboardItem` holding both types; then its `writeText`, plain text\n * alone, where `ClipboardItem` is missing or refused; then the legacy `copy`\n * event, for a page outside a secure context, where the async API is not\n * there at all. Rejects only when every one fails.\n */\nexport async function writeClipboard(content: ClipboardContent): Promise<void> {\n const attempts: (() => Promise<void>)[] = [];\n // Absent outside a secure context, whatever the DOM types say.\n const clipboard = navigator.clipboard as Clipboard | undefined;\n if (clipboard !== undefined) {\n if (typeof ClipboardItem === \"function\") {\n attempts.push(() => writeItem(clipboard, content));\n }\n attempts.push(() => clipboard.writeText(content.text));\n }\n let failure: unknown = null;\n for (const attempt of attempts) {\n try {\n await attempt();\n return;\n } catch (error) {\n failure = error;\n }\n }\n if (!copyThroughEvent(content)) {\n throw new Error(\"The browser refused to write to the clipboard.\", {\n cause: failure,\n });\n }\n}\n","/**\n * Saves `blob` as a file named `fileName` — the browser's own download, as a\n * link with a `download` attribute starts one, so nothing is opened or\n * navigated to.\n *\n * The link is put in the document for the click, as Firefox has needed, and\n * taken out straight after. Its object URL is revoked a task later rather\n * than at once: Safari starts the download from the URL after the click\n * returns, and a URL revoked synchronously is gone before it does.\n */\nexport function downloadBlob(blob: Blob, fileName: string): void {\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = fileName;\n link.hidden = true;\n document.body.append(link);\n link.click();\n link.remove();\n setTimeout(() => {\n URL.revokeObjectURL(url);\n }, 0);\n}\n","import {\n mayStartGesture,\n startPointerGesture,\n type PointerPress,\n} from \"./pointerGesture\";\n\n/**\n * How far the pointer travels, in any direction, before a press becomes a\n * drag. Below it the gesture is still a click, which is what leaves the\n * pressed element free to take one.\n */\nconst DRAG_THRESHOLD = 4;\n\n/**\n * Where a reorder gesture is: `pressed` until the pointer crosses the\n * threshold, then `dragging` over `target`, `null` for nowhere valid.\n */\ntype DragPhase<Target> =\n | { readonly kind: \"pressed\" }\n | { readonly kind: \"dragging\"; readonly target: Target | null };\n\n/**\n * What a renderer draws for a reorder gesture: nothing while `idle`, which\n * covers a press still short of the drag threshold, then while `dragging` the\n * dragged item, where it would land and the ghost trailing the pointer.\n */\nexport type DragReorderState<Target> =\n | { readonly kind: \"idle\" }\n | {\n readonly kind: \"dragging\";\n /** The item being dragged. */\n readonly draggedId: string;\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;\n };\n\n/** The state while nothing is dragged — the one a drag reports when it ends. */\nexport const NO_DRAG: DragReorderState<never> = { kind: \"idle\" };\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 * Cancels the drag when aborted, as Escape does: `onState(NO_DRAG)`, no\n * drop. For a renderer whose dragged element goes away mid-drag, so what\n * it draws for the drag ends with it. Already aborted, no drag starts.\n */\n readonly signal?: AbortSignal;\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 * and not once `handlers.signal` has aborted.\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, a cancelled\n * pointer or an aborted signal 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 (!mayStartGesture(press, handlers.signal)) {\n return false;\n }\n\n const { pointerId, clientX: startX, clientY: startY } = press;\n const grabX = startX - grabElement.getBoundingClientRect().left;\n let phase: DragPhase<Target> = { kind: \"pressed\" };\n\n startPointerGesture(\n grabElement,\n pointerId,\n {\n onMove(moveEvent) {\n const { clientX, clientY } = moveEvent;\n if (\n phase.kind === \"pressed\" &&\n Math.hypot(clientX - startX, clientY - startY) < DRAG_THRESHOLD\n ) {\n return;\n }\n phase = {\n kind: \"dragging\",\n target: handlers.resolveTarget(clientX, clientY, id),\n };\n handlers.onState({\n kind: \"dragging\",\n draggedId: id,\n dropTarget: phase.target,\n ghostTransform: `translate(${String(clientX - grabX)}px, ${String(clientY)}px)`,\n });\n },\n onEnd() {\n handlers.onState(NO_DRAG);\n if (phase.kind === \"dragging\" && phase.target !== null) {\n handlers.onDrop(phase.target, id);\n }\n },\n onCancel() {\n handlers.onState(NO_DRAG);\n },\n },\n handlers.signal,\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 headerFocus,\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: (focus: GridFocus) => 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 * `root`, the element holding every row, lets the user take over from a\n * move still waiting for its row to be drawn: focus leaving `root`, or a\n * wheel or a press inside it, drops the move, so a row mounting later\n * neither scrolls back to itself nor pulls focus back into the grid.\n */\n readonly attach: (\n rows: GridFocusRows,\n root?: HTMLElement | null,\n ) => () => 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/** Whether `a` and `b` are the same cell. */\nfunction sameCell(a: GridFocus, b: GridFocus): boolean {\n if (a.area === \"body\" && b.area === \"body\") {\n return a.rowIndex === b.rowIndex && a.columnIndex === b.columnIndex;\n }\n return a.area === b.area && a.columnIndex === b.columnIndex;\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 = headerFocus(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 * or once the user took over from it (see `attach`). 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 dropPendingMove(): void {\n pendingMove = null;\n }\n\n function listenForTakeover(root: HTMLElement): () => void {\n // Focus moving between cells stays in `root`, and focus lost to a cell\n // unmounting goes nowhere: only focus going somewhere else leaves it.\n function onFocusOut(event: FocusEvent): void {\n const to = event.relatedTarget;\n if (to instanceof Node && !root.contains(to)) {\n dropPendingMove();\n }\n }\n root.addEventListener(\"focusout\", onFocusOut);\n root.addEventListener(\"wheel\", dropPendingMove, { passive: true });\n root.addEventListener(\"pointerdown\", dropPendingMove);\n return () => {\n root.removeEventListener(\"focusout\", onFocusOut);\n root.removeEventListener(\"wheel\", dropPendingMove);\n root.removeEventListener(\"pointerdown\", dropPendingMove);\n };\n }\n\n function focusCell(focus: GridFocus): void {\n const next = clampFocus(focus, options.rowCount, options.columnCount);\n if (sameCell(next, getFocus())) {\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);\n return true;\n },\n attach(next, root) {\n rows = next;\n const stopListening = root == null ? undefined : listenForTakeover(root);\n return () => {\n stopListening?.();\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.area === \"header\"\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. The header is\n // never virtualized.\n const range = options.virtualRange;\n if (\n range != null &&\n drawn.area === \"body\" &&\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. Not again until that load has\n * finished (`isLoadingMore` back to `false`), `hasMore` has changed, rows\n * have been added or removed in front of the sentinel, or the sentinel has\n * left `rootMargin` and come back.\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 a load finishing or `hasMore` changing re-checks at once.\n * While a load it asked for has not reported `isLoadingMore`, it measures\n * where the sentinel sits, to tell whether rows answered it.\n */\n readonly update: (options: InfiniteScrollOptions) => void;\n /** Stops watching for good: a later `update` does nothing. */\n readonly detach: () => void;\n}\n\n/**\n * Where the next load stands. `requested` is `onLoadMore` called with no\n * `isLoadingMore: true` seen since, which a load that starts nothing, or\n * finishes within one render, never leaves on its own. It keeps where the\n * sentinel sat when the load was asked for, so rows added in front of it\n * show as an answer even while it stays in range.\n */\ntype LoadState =\n | { readonly kind: \"idle\" }\n | { readonly kind: \"requested\"; readonly sentinelOffset: number }\n | { readonly kind: \"loading\" }\n | { readonly kind: \"detached\" };\n\nconst IDLE: LoadState = { kind: \"idle\" };\nconst LOADING: LoadState = { kind: \"loading\" };\nconst DETACHED: LoadState = { kind: \"detached\" };\n\n/**\n * The state after an `update`: `loading` while a load is in flight, and\n * `idle` again once one finishes or `hasMore` changes, either of which\n * means no load can still be pending.\n */\nfunction afterUpdate(\n state: LoadState,\n loading: boolean,\n hasMoreChanged: boolean,\n): LoadState {\n if (loading) {\n return LOADING;\n }\n return state.kind === \"loading\" || hasMoreChanged ? IDLE : state;\n}\n\n/**\n * How far into `root`'s scrolled content `sentinel` sits. Scrolling leaves\n * it alone; rows added or removed in front of the sentinel change it.\n */\nfunction contentOffset(root: Element, sentinel: Element): number {\n return (\n sentinel.getBoundingClientRect().top -\n root.getBoundingClientRect().top +\n root.scrollTop\n );\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 approach while no load is in\n * flight.\n *\n * Two facts drive it: whether the sentinel is intersecting (as of the\n * observer's last report), and the `LoadState`. Both outlive the sentinel\n * itself, which a renderer mounts and unmounts as `hasMore` changes, so they\n * live here rather than on any one observer.\n *\n * Off means off: with `hasMore` false, no sentinel, or once detached, no\n * observer exists.\n */\nexport function attachInfiniteScroll(\n root: Element,\n options: InfiniteScrollOptions,\n): InfiniteScroll {\n let latest = options;\n let intersecting = false;\n let state: LoadState = (options.isLoadingMore ?? false) ? LOADING : IDLE;\n let observer: IntersectionObserver | null = null;\n let observed: { sentinel: Element; rootMargin: string } | null = null;\n\n function maybeLoadMore(): void {\n if (\n state.kind !== \"idle\" ||\n !intersecting ||\n observed === null ||\n !latest.hasMore\n ) {\n return;\n }\n state = {\n kind: \"requested\",\n sentinelOffset: contentOffset(root, observed.sentinel),\n };\n latest.onLoadMore?.();\n }\n\n /**\n * Whether rows were added or removed in front of the sentinel since the\n * outstanding request: its answer, even when it left the sentinel in range,\n * where the observer, seeing no change, reports nothing. Less than a pixel\n * is a fractional scroll position rounding, not a row.\n */\n function requestAnswered(): boolean {\n if (state.kind !== \"requested\" || observed === null) {\n return false;\n }\n const moved = contentOffset(root, observed.sentinel) - state.sentinelOffset;\n return Math.abs(moved) >= 1;\n }\n\n function report(isIntersecting: boolean): void {\n intersecting = isIntersecting;\n // The sentinel left the range, so its next entry is a fresh approach.\n if (!intersecting && state.kind === \"requested\") {\n state = IDLE;\n }\n maybeLoadMore();\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 report(entry.isIntersecting);\n }\n },\n { root, rootMargin },\n );\n observer.observe(sentinel);\n observed = { sentinel, rootMargin };\n }\n\n observe();\n\n return {\n update(next) {\n if (state.kind === \"detached\") {\n return;\n }\n const hasMoreChanged = next.hasMore !== latest.hasMore;\n latest = next;\n state = afterUpdate(state, next.isLoadingMore ?? false, hasMoreChanged);\n observe();\n if (requestAnswered()) {\n // Where the sentinel is now is the observer's to say: a new one\n // reports at once, and the last report predates the new rows.\n state = IDLE;\n disconnect();\n observe();\n }\n // After a load finishes, or `hasMore` changes, the sentinel may still be\n // in range: then it never leaves and re-enters, and no observer report\n // would come to trigger the next load.\n maybeLoadMore();\n },\n detach() {\n disconnect();\n state = DETACHED;\n },\n };\n}\n","import { nextMenuIndex, typeaheadIndex, type MenuMove } from \"@gridkitjs/core\";\n\n/**\n * Where focus goes when a submenu opens: its first item, or nowhere — it\n * stays on the item that opened it, as when the pointer opened it.\n */\nexport type MenuSubmenuFocus = \"first\" | \"none\";\n\n/** Why a menu asked for every menu to close. */\nexport type MenuCloseReason = \"tab\";\n\nexport interface MenuOptions {\n /**\n * An enabled item that opens no submenu was chosen — Enter, Space or a\n * click. The caller runs it, and closes every menu.\n */\n readonly onActivate: (id: string) => void;\n /**\n * An enabled submenu item asks for its submenu: Enter, Space or ArrowRight\n * on it want its first item focused (`\"first\"`), a click or the pointer\n * resting on it want focus left where it is (`\"none\"`). It may come for a\n * submenu already open — ArrowRight on one the pointer opened — when a\n * `\"first\"` asks for focus to move into it (`focusItem(0)` on its menu).\n * Opening it closes any other submenu open from this menu.\n */\n readonly onOpenSubmenu?:\n | ((id: string, options: { readonly focus: MenuSubmenuFocus }) => void)\n | undefined;\n /**\n * The pointer came to rest on an item that opens no submenu, or a disabled\n * one: close whatever submenu is open from this menu, if any. Moving on to\n * another item is how a pointer user leaves a submenu they did not want.\n */\n readonly onCollapseSubmenu?: (() => void) | undefined;\n /**\n * ArrowLeft was pressed in this menu, which is a submenu: close it, and\n * focus goes back to the item that opened it. Passed only for a submenu —\n * in a menu without it, ArrowLeft does nothing.\n */\n readonly onCloseSubmenu?: (() => void) | undefined;\n /**\n * Tab or Shift+Tab was pressed: close every menu, so focus returns to where\n * the first one was opened from rather than moving on past it.\n */\n readonly onClose: (reason: MenuCloseReason) => void;\n}\n\nexport interface AttachedMenu {\n /**\n * Focuses an item, by its `data-gridkit-menu-item` id or its place among\n * the items. Does nothing for an item the menu does not have.\n */\n readonly focusItem: (item: string | number) => void;\n /** Removes every listener. The items keep the `tabindex` they had. */\n readonly detach: () => void;\n}\n\n/** Every element `attachMenu` treats as an item. Separators are not. */\nconst ITEM = [\n '[role=\"menuitem\"][data-gridkit-menu-item]',\n '[role=\"menuitemcheckbox\"][data-gridkit-menu-item]',\n '[role=\"menuitemradio\"][data-gridkit-menu-item]',\n].join(\", \");\n\n/** How long the pointer rests on a submenu item before it opens. */\nconst HOVER_DELAY = 150;\n\n/** How long after the last key typed the typeahead starts afresh. */\nconst TYPEAHEAD_TIMEOUT = 500;\n\n/** Each item, as `nextMenuIndex` reads it: the separators are not among them. */\nconst AS_ENTRY = { type: \"item\" } as const;\n\n/** The keys that move focus, and where to. */\nconst MOVES: Readonly<Record<string, MenuMove>> = {\n ArrowDown: \"next\",\n ArrowUp: \"previous\",\n Home: \"first\",\n End: \"last\",\n};\n\n/** Letters typed one after another, until `TYPEAHEAD_TIMEOUT` ends the run. */\ntype Typeahead =\n | { readonly kind: \"idle\" }\n | { readonly kind: \"typing\"; readonly typed: string; readonly timer: number };\n\n/**\n * The pointer over an item: `resting` until `HOVER_DELAY` has passed and\n * what the item opens or closes happens, then `rested` until it moves to\n * another. A key the menu acts on ends it, so the pointer has to move again\n * before it opens or closes anything.\n */\ntype Hover =\n | { readonly kind: \"away\" }\n | {\n readonly kind: \"resting\";\n readonly item: HTMLElement;\n readonly timer: number;\n }\n | { readonly kind: \"rested\"; readonly item: HTMLElement };\n\nconst IDLE: Typeahead = { kind: \"idle\" };\nconst AWAY: Hover = { kind: \"away\" };\n\nfunction idOf(item: HTMLElement): string {\n return item.getAttribute(\"data-gridkit-menu-item\") ?? \"\";\n}\n\nfunction isDisabled(item: HTMLElement): boolean {\n return item.getAttribute(\"aria-disabled\") === \"true\";\n}\n\nfunction opensSubmenu(item: HTMLElement): boolean {\n const popup = item.getAttribute(\"aria-haspopup\");\n return popup === \"menu\" || popup === \"true\";\n}\n\n/** What typeahead matches against: the item's `aria-label`, else its text. */\nfunction labelOf(item: HTMLElement): string {\n return (item.getAttribute(\"aria-label\") ?? item.textContent).trim();\n}\n\n/** A printable character, typed without a modifier that makes it a shortcut. */\nfunction isTypeahead(event: KeyboardEvent): boolean {\n return (\n /^\\S$/u.test(event.key) && !event.ctrlKey && !event.metaKey && !event.altKey\n );\n}\n\n/**\n * Makes `panel` — a `role=\"menu\"` element the renderer drew — behave as a\n * menu: the arrow keys, Home, End and typeahead move focus among its items,\n * Enter, Space and a click choose one, ArrowRight and ArrowLeft open and\n * close submenus, and the pointer moves focus to the item under it. It\n * decides; the caller is told through the callbacks and draws the outcome —\n * runs the item, opens or closes a submenu.\n *\n * Items are the panel's descendants with a menu item role (`menuitem`,\n * `menuitemcheckbox`, `menuitemradio`) and a `data-gridkit-menu-item` id,\n * read afresh on every key so items drawn since attaching count. A submenu\n * item says so with `aria-haspopup=\"menu\"`; an item with\n * `aria-disabled=\"true\"` takes focus, so a screen reader finds it, but is\n * never chosen. One item at a time has `tabindex=\"0\"` — the focused one, or\n * the first — and the rest `-1`: every item can take focus, and the first is\n * what an overlay's initial focus finds when the menu is attached before it.\n *\n * Escape is not handled: a menu is shown with `attachOverlay`, whose stack\n * gives Escape to the topmost overlay. A submenu is a menu of its own, in an\n * overlay of its own anchored to its item, attached when it opens.\n */\nexport function attachMenu(\n panel: HTMLElement,\n options: MenuOptions,\n): AttachedMenu {\n let typeahead: Typeahead = IDLE;\n let hover: Hover = AWAY;\n\n function items(): HTMLElement[] {\n return Array.from(panel.querySelectorAll<HTMLElement>(ITEM));\n }\n\n function itemOf(target: EventTarget | null): HTMLElement | null {\n const item =\n target instanceof Element ? target.closest<HTMLElement>(ITEM) : null;\n return item !== null && panel.contains(item) ? item : null;\n }\n\n function setTabStop(stop: HTMLElement | undefined): void {\n for (const item of items()) {\n item.tabIndex = item === stop ? 0 : -1;\n }\n }\n\n function focus(item: HTMLElement): void {\n setTabStop(item);\n item.focus();\n }\n\n function openSubmenu(item: HTMLElement, focusTo: MenuSubmenuFocus): void {\n if (!isDisabled(item)) {\n options.onOpenSubmenu?.(idOf(item), { focus: focusTo });\n }\n }\n\n function choose(item: HTMLElement, focusTo: MenuSubmenuFocus): void {\n if (opensSubmenu(item)) {\n openSubmenu(item, focusTo);\n } else if (!isDisabled(item)) {\n options.onActivate(idOf(item));\n }\n }\n\n function endTyping(): void {\n if (typeahead.kind === \"typing\") {\n window.clearTimeout(typeahead.timer);\n }\n typeahead = IDLE;\n }\n\n function endHover(): void {\n if (hover.kind === \"resting\") {\n window.clearTimeout(hover.timer);\n }\n hover = AWAY;\n }\n\n function type(key: string, all: HTMLElement[], from: number): void {\n const typed = (typeahead.kind === \"typing\" ? typeahead.typed : \"\") + key;\n endTyping();\n typeahead = {\n kind: \"typing\",\n typed,\n timer: window.setTimeout(() => {\n typeahead = IDLE;\n }, TYPEAHEAD_TIMEOUT),\n };\n const match = all[typeaheadIndex(all.map(labelOf), from, typed)];\n if (match !== undefined) {\n focus(match);\n }\n }\n\n function rest(item: HTMLElement): void {\n hover = {\n kind: \"resting\",\n item,\n timer: window.setTimeout(() => {\n hover = { kind: \"rested\", item };\n if (opensSubmenu(item) && !isDisabled(item)) {\n openSubmenu(item, \"none\");\n } else {\n options.onCollapseSubmenu?.();\n }\n }, HOVER_DELAY),\n };\n }\n\n // Every key it acts on is taken, the arrows included where they do\n // nothing: a menu has no caret for them to move, and the page scrolling\n // under it would dismiss its overlay. Any of them ends a rest of the\n // pointer, and any but a letter ends the typing.\n function onKeyDown(event: KeyboardEvent): void {\n if (event.isComposing || !actOnKey(event)) {\n return;\n }\n event.preventDefault();\n endHover();\n if (!isTypeahead(event)) {\n endTyping();\n }\n }\n\n /** Does what `event`'s key does in the menu; `false` for a key it ignores. */\n function actOnKey(event: KeyboardEvent): boolean {\n const all = items();\n const item = itemOf(event.target);\n const from = item === null ? -1 : all.indexOf(item);\n const move = MOVES[event.key];\n if (move !== undefined) {\n const next =\n all[\n nextMenuIndex(\n all.map(() => AS_ENTRY),\n from,\n move,\n )\n ];\n if (next !== undefined) {\n focus(next);\n }\n } else if (event.key === \"Enter\" || event.key === \" \") {\n if (item !== null) {\n choose(item, \"first\");\n }\n } else if (event.key === \"ArrowRight\") {\n if (item !== null && opensSubmenu(item)) {\n openSubmenu(item, \"first\");\n }\n } else if (event.key === \"ArrowLeft\") {\n options.onCloseSubmenu?.();\n } else if (event.key === \"Tab\") {\n options.onClose(\"tab\");\n } else if (isTypeahead(event)) {\n type(event.key, all, from);\n } else {\n return false;\n }\n return true;\n }\n\n // However focus arrived — the overlay's initial focus, a click, a script —\n // the tab stop follows it.\n function onFocusIn(event: FocusEvent): void {\n const item = itemOf(event.target);\n if (item !== null) {\n setTabStop(item);\n }\n }\n\n function onClick(event: MouseEvent): void {\n const item = itemOf(event.target);\n if (item !== null) {\n choose(item, \"none\");\n }\n }\n\n // A pointer moving over the menu moves focus with it at once, but what the\n // item under it opens or closes waits until it rests there: a pointer\n // crossing a sibling on its way into an open submenu leaves the submenu\n // open. Touch has no hover, so only its taps count, as clicks.\n function onPointerMove(event: PointerEvent): void {\n if (event.pointerType === \"touch\") {\n return;\n }\n const item = itemOf(event.target);\n if (item !== null && document.activeElement !== item) {\n focus(item);\n }\n if (hover.kind !== \"away\" && hover.item === item) {\n return;\n }\n endHover();\n if (item !== null) {\n rest(item);\n }\n }\n\n // Leaving the panel — into the submenu, usually — is not resting anywhere.\n function onPointerLeave(): void {\n endHover();\n }\n\n const focused = itemOf(document.activeElement);\n setTabStop(focused ?? items()[0]);\n\n panel.addEventListener(\"keydown\", onKeyDown);\n panel.addEventListener(\"focusin\", onFocusIn);\n panel.addEventListener(\"click\", onClick);\n panel.addEventListener(\"pointermove\", onPointerMove);\n panel.addEventListener(\"pointerleave\", onPointerLeave);\n\n return {\n focusItem(which) {\n const all = items();\n const item =\n typeof which === \"number\"\n ? all[which]\n : all.find((candidate) => idOf(candidate) === which);\n if (item !== undefined) {\n focus(item);\n }\n },\n detach() {\n endTyping();\n endHover();\n panel.removeEventListener(\"keydown\", onKeyDown);\n panel.removeEventListener(\"focusin\", onFocusIn);\n panel.removeEventListener(\"click\", onClick);\n panel.removeEventListener(\"pointermove\", onPointerMove);\n panel.removeEventListener(\"pointerleave\", onPointerLeave);\n },\n };\n}\n","/** What the stack needs of an attached overlay. */\nexport interface StackedOverlay {\n readonly panel: HTMLElement;\n /** Escape was pressed while this was the topmost overlay. */\n readonly onEscape: () => void;\n /** An overlay below this one detached, so this one has to go too. */\n readonly onParentDetached: () => void;\n}\n\n/**\n * Every attached overlay, in the order it attached: a submenu opened from a\n * menu sits above it. Module-level because nesting spans overlays that know\n * nothing of each other — a menu does not hold a reference to the submenu a\n * click inside it opened.\n */\nconst stack: StackedOverlay[] = [];\n\n/*\n * One listener for the whole stack, on `window` in the capture phase: the\n * first place a key event reaches, so neither React's root listener nor a\n * page's own Escape handler hears an Escape the topmost overlay took. An\n * Escape that ends an IME composition is the input method's, not ours.\n */\nfunction onKeyDown(event: KeyboardEvent): void {\n const topmost = stack.at(-1);\n if (event.key !== \"Escape\" || event.isComposing || topmost === undefined) {\n return;\n }\n event.preventDefault();\n event.stopPropagation();\n topmost.onEscape();\n}\n\n/** Puts `overlay` on top of the stack. */\nexport function pushOverlay(overlay: StackedOverlay): void {\n if (stack.length === 0) {\n window.addEventListener(\"keydown\", onKeyDown, true);\n }\n stack.push(overlay);\n}\n\n/**\n * Takes `overlay` off the stack, and every overlay above it with it — each\n * told through `onParentDetached`, topmost first, so a submenu never outlives\n * the menu it opened from. Does nothing for an overlay no longer on it.\n */\nexport function removeOverlay(overlay: StackedOverlay): void {\n const index = stack.indexOf(overlay);\n if (index === -1) {\n return;\n }\n const above = stack.splice(index).slice(1).reverse();\n for (const child of above) {\n child.onParentDetached();\n }\n if (stack.length === 0) {\n window.removeEventListener(\"keydown\", onKeyDown, true);\n }\n}\n\n/** Whether `target` is inside an overlay that sits above `overlay`. */\nexport function isInsideOverlayAbove(\n overlay: StackedOverlay,\n target: Node,\n): boolean {\n const index = stack.indexOf(overlay);\n return (\n index !== -1 &&\n stack.slice(index + 1).some((above) => above.panel.contains(target))\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 * Pixels to shift the overlay along the anchor's edge — down beside a\n * `left`/`right` side, right beside `top`/`bottom`; negative goes the other\n * way. A submenu opened `right-start` takes its own top padding, negated,\n * so its first item lines up with the item that opened it. The overlay is\n * still kept on screen. Defaults to `0`.\n */\n readonly offset?: 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 offset = options.offset ?? 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, shifted by the offset\n // before an edge is chosen, so the shifted overlay is what has to fit.\n const anchorStart = (vertical ? anchor.left : anchor.top) + offset;\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 isInsideOverlayAbove,\n pushOverlay,\n removeOverlay,\n type StackedOverlay,\n} from \"./overlayStack\";\nimport {\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/**\n * Where focus goes when an overlay attaches: its first focusable descendant,\n * the panel itself, nowhere — focus stays where it was — or a given element.\n */\nexport type OverlayInitialFocus = \"first\" | \"panel\" | \"none\" | HTMLElement;\n\n/** Why an overlay asked to close. */\nexport type OverlayDismissReason =\n \"escape\" | \"outside\" | \"scroll\" | \"resize\" | \"blur\" | \"parent\";\n\nexport interface OverlayOptions extends OverlayPlacementOptions {\n /**\n * The overlay asks to close: Escape was pressed while it was the topmost\n * overlay, a pointer went down outside it, something outside it scrolled,\n * the window resized or lost focus, focus moved outside it, or the overlay\n * it was opened above detached. The caller closes it — removes the panel\n * and detaches — or keeps it open by doing nothing, except after\n * `\"parent\"`, which detaches it regardless.\n */\n readonly onDismiss: (reason: OverlayDismissReason) => void;\n /**\n * Where focus goes on attaching. Defaults to `\"first\"`: a menu focuses its\n * first item. A panel whose first control is not where the user starts — a\n * filter editor's operator picker before its value box — passes that\n * element, or `\"panel\"` to land on the panel itself; `\"none\"` leaves focus\n * where it was, as a suggestion list does while typing goes on in the\n * field that opened it.\n */\n readonly initialFocus?: OverlayInitialFocus | undefined;\n /**\n * Where focus goes when the overlay detaches. Defaults to the element that\n * had focus when it attached; `null` leaves focus alone, unless\n * `returnFocusFallback` names somewhere.\n */\n readonly returnFocusTo?: HTMLElement | null | undefined;\n /**\n * Where focus goes when there is nothing to return it to as the overlay\n * detaches: `returnFocusTo` is `null`, or has left the page — the button a\n * menu opened from, taken away by what the menu ran. Called then, so it\n * can name what is on the page by that time; returning `null` leaves focus\n * alone. Like the return itself, it applies only while focus is still in\n * the panel, in an overlay above it, or nowhere.\n */\n readonly returnFocusFallback?: (() => HTMLElement | null) | undefined;\n /**\n * Whether a scroll outside the panel just after input inside it — an\n * `input` or `change` event, in the two animation frames that follow it —\n * leaves it open. A panel whose controls change the page under it opts\n * in: a column chooser hiding a column narrows the grid it opened from,\n * and the browser scrolls that grid back within its narrower content.\n * Defaults to `false`, where every scroll outside the panel dismisses it.\n */\n readonly ignoreScrollAfterInput?: boolean | undefined;\n /**\n * Whether Tab and Shift+Tab stay inside the panel: from its last tabbable\n * element Tab moves to its first, and Shift+Tab from its first to its last,\n * as a dialog keeps them. Defaults to `false`, where tabbing out of the\n * panel moves focus outside it, which dismisses it with `\"blur\"`. Only\n * the keys are kept: a pointer down outside still dismisses it, and so\n * does focus moved out by any other route.\n */\n readonly trapFocus?: boolean | 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\n/**\n * What Tab stops on in `panel`, in document order: what is focusable without\n * a negative `tabindex`, enabled and shown.\n */\nfunction tabbables(panel: HTMLElement): HTMLElement[] {\n return Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n (element) =>\n element.tabIndex >= 0 &&\n !element.matches(\":disabled\") &&\n element.checkVisibility({ visibilityProperty: true }),\n );\n}\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 * The element `initialFocus` names, or `null` for none. The panel stands in\n * for a first focusable descendant it does not have, given `tabindex=\"-1\"`\n * when it has no `tabindex` of its own so that it can take focus.\n */\nfunction initialFocusTarget(\n panel: HTMLElement,\n initialFocus: OverlayInitialFocus,\n): HTMLElement | null {\n if (initialFocus === \"none\") {\n return null;\n }\n if (initialFocus instanceof HTMLElement) {\n return initialFocus;\n }\n const first =\n initialFocus === \"first\"\n ? panel.querySelector<HTMLElement>(FOCUSABLE)\n : null;\n if (first !== null) {\n return first;\n }\n if (!panel.hasAttribute(\"tabindex\")) {\n panel.tabIndex = -1;\n }\n return panel;\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. Whenever the panel's size changes — a list\n * that shrinks under a search — it is placed again beside the anchor, so a\n * panel flipped above its anchor keeps its bottom edge there — or beside\n * where the anchor was, once it has left the page. Focus moves\n * where `initialFocus` says.\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 * Overlays stack in the order they attach. Escape belongs to the topmost\n * alone, which takes it: it closes a submenu without closing the menu under\n * it, and nothing else on the page — a grid that clears its selection on\n * Escape — hears it. An overlay counts everything in the overlays above it\n * as inside itself, so a click in a submenu keeps its menu open while a click\n * elsewhere in the menu closes only the submenu.\n *\n * Detaching removes every listener and returns focus, but only when focus is\n * still in the panel, in an overlay above it, or nowhere — a click that\n * dismissed it by landing on another control leaves focus on that control.\n * Focus goes to `returnFocusFallback` instead when the element it would\n * return to has left the page.\n * Any overlay still above it is dismissed with `\"parent\"` and detached first.\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 const stacked: StackedOverlay = {\n panel,\n onEscape: () => {\n onDismiss(\"escape\");\n },\n onParentDetached: () => {\n onDismiss(\"parent\");\n detach();\n },\n };\n\n /** In the panel, or in an overlay opened above it. */\n function inside(target: EventTarget | null): boolean {\n return (\n target instanceof Node &&\n (panel.contains(target) || isInsideOverlayAbove(stacked, target))\n );\n }\n\n function belongs(target: EventTarget | null): boolean {\n return (\n inside(target) ||\n (anchor instanceof Element &&\n target instanceof Node &&\n anchor.contains(target))\n );\n }\n\n function onPointerDown(event: PointerEvent): void {\n if (!belongs(event.target)) {\n onDismiss(\"outside\");\n }\n }\n\n // Set from input in the panel until two frames have passed: the scroll an\n // input causes is dispatched by the frame after its layout, which may\n // itself wait for the next frame.\n let settlingFrame: number | null = null;\n function onInput(): void {\n if (settlingFrame !== null) {\n cancelAnimationFrame(settlingFrame);\n }\n settlingFrame = requestAnimationFrame(() => {\n settlingFrame = requestAnimationFrame(() => {\n settlingFrame = null;\n });\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 // or one the panel's own input caused, when it asks for that.\n function onScroll(event: Event): void {\n if (!inside(event.target) && settlingFrame === null) {\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 // Only the moves that would leave the panel are made here — Tab with\n // nothing tabbable after the focused element, Shift+Tab with nothing before\n // it — and every other Tab is the browser's own, in its own order. What is\n // tabbable is read on each key, so controls drawn since attaching count; an\n // overlay above that sits inside the panel keeps its own Tab.\n function onTrapKeyDown(event: KeyboardEvent): void {\n if (\n event.key !== \"Tab\" ||\n event.defaultPrevented ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n !(event.target instanceof Node) ||\n isInsideOverlayAbove(stacked, event.target)\n ) {\n return;\n }\n const stops = tabbables(panel).filter(\n (element) => !isInsideOverlayAbove(stacked, element),\n );\n const first = stops[0];\n const last = stops.at(-1);\n if (first === undefined || last === undefined) {\n event.preventDefault();\n return;\n }\n const position = event.target.compareDocumentPosition(\n event.shiftKey ? first : last,\n );\n const leaving = event.shiftKey\n ? (position & Node.DOCUMENT_POSITION_PRECEDING) === 0\n : (position & Node.DOCUMENT_POSITION_FOLLOWING) === 0;\n if (leaving) {\n event.preventDefault();\n (event.shiftKey ? last : first).focus();\n }\n }\n\n function onWindowBlur(): void {\n onDismiss(\"blur\");\n }\n\n // An anchor taken out of the page has no box to read — the header of a\n // column the panel itself just hid — so the panel goes on being placed\n // beside where it last was, rather than in the viewport's corner.\n let lastAnchorBox: OverlayRect | null = null;\n function anchorBox(): OverlayRect {\n if (\n lastAnchorBox === null ||\n !(anchor instanceof Element) ||\n anchor.isConnected\n ) {\n lastAnchorBox = anchorRect(anchor);\n }\n return lastAnchorBox;\n }\n\n // Measured fixed at the viewport's top-left corner, shrink-wrapped and with\n // the whole viewport to grow into — in normal flow a block panel would\n // report the full width of the page — and from that corner every time, so\n // placing it again measures it the way the first placing did.\n function place(): void {\n panel.style.top = \"0px\";\n panel.style.left = \"0px\";\n const { width, height } = panel.getBoundingClientRect();\n const position = placeOverlay(\n anchorBox(),\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\n panel.style.position = \"fixed\";\n place();\n const resizeObserver = new ResizeObserver(place);\n resizeObserver.observe(panel);\n\n // On the stack before focus moves in, so the overlay below sees the focus\n // leaving it land inside an overlay above it rather than outside.\n pushOverlay(stacked);\n initialFocusTarget(panel, options.initialFocus ?? \"first\")?.focus({\n preventScroll: true,\n });\n\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 if (options.trapFocus === true) {\n panel.addEventListener(\"keydown\", onTrapKeyDown);\n }\n if (options.ignoreScrollAfterInput === true) {\n panel.addEventListener(\"input\", onInput);\n panel.addEventListener(\"change\", onInput);\n }\n\n let attached = true;\n function detach(): void {\n if (!attached) {\n return;\n }\n attached = false;\n // Read before the overlays above close and hand focus back to wherever\n // they took it from, which may already have left the page with this panel.\n const active = document.activeElement;\n const focusInside =\n active === null || active === document.body || inside(active);\n removeOverlay(stacked);\n resizeObserver.disconnect();\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 panel.removeEventListener(\"keydown\", onTrapKeyDown);\n panel.removeEventListener(\"input\", onInput);\n panel.removeEventListener(\"change\", onInput);\n if (settlingFrame !== null) {\n cancelAnimationFrame(settlingFrame);\n }\n\n if (!focusInside) {\n return;\n }\n const target =\n returnFocusTo?.isConnected === true\n ? returnFocusTo\n : options.returnFocusFallback?.();\n target?.focus({ preventScroll: true });\n }\n return detach;\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 /**\n * Forgets every measured height, for rows that are no longer the ones\n * measured. A `scrollToIndex` still waiting for its row is dropped too:\n * the row it asked for is no longer at that index.\n */\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. A later call\n * replaces one still waiting for its row; a row count that no longer holds\n * that row, or `resetHeights`, drops it.\n */\n readonly scrollToIndex: (\n index: number,\n options?: ScrollIntoViewOptions,\n ) => void;\n /**\n * Stops listening, measuring and scrolling, for good: every method does\n * nothing after it.\n */\n readonly destroy: () => void;\n}\n\n/**\n * Where the virtualizer is in its life: `ready`, `awaiting-row` while a\n * `scrollToIndex` waits for its row to mount to scroll to it properly, and\n * `destroyed` for good.\n */\ntype VirtualizerPhase =\n | { readonly kind: \"ready\" }\n | {\n readonly kind: \"awaiting-row\";\n readonly index: number;\n readonly options: ScrollIntoViewOptions | undefined;\n }\n | { readonly kind: \"destroyed\" };\n\nconst READY: VirtualizerPhase = { kind: \"ready\" };\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 phase: VirtualizerPhase = READY;\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 (phase.kind === \"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 if (phase.kind === \"awaiting-row\" && phase.index >= count) {\n phase = READY;\n }\n heights = Array.from({ length: count }, (_, index) => heights[index]);\n recompute();\n },\n resetHeights() {\n if (phase.kind === \"awaiting-row\") {\n phase = READY;\n }\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 || phase.kind === \"destroyed\") {\n return;\n }\n elements.set(index, element);\n indexByElement.set(element, index);\n rowObserver.observe(element);\n if (phase.kind === \"awaiting-row\" && phase.index === index) {\n element.scrollIntoView(phase.options);\n phase = READY;\n }\n };\n refCallbacks.set(index, callback);\n }\n return callback;\n },\n scrollToIndex(index, scrollOptions) {\n if (phase.kind === \"destroyed\" || 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 phase = READY;\n mounted.scrollIntoView(scrollOptions);\n return;\n }\n phase = { kind: \"awaiting-row\", index: clamped, options: scrollOptions };\n scrollElement.scrollTop = estimateOffsetBefore(clamped);\n },\n destroy() {\n phase = { kind: \"destroyed\" };\n scrollElement.removeEventListener(\"scroll\", handleScroll);\n viewportObserver.disconnect();\n rowObserver.disconnect();\n elements.clear();\n indexByElement.clear();\n },\n };\n}\n","import { nextMenuIndex, type MenuMove } from \"@gridkitjs/core\";\n\n/** Every element a toolbar is made of. Those with no control in them — separators, spacers — are passed over. */\nconst ENTRY = \"[data-gridkit-toolbar-item]\";\n\n/** What can take focus: an entry's control is the entry itself, or the first of these in it. */\nconst FOCUSABLE = [\n \"button:not(:disabled)\",\n \"input:not(:disabled)\",\n \"select:not(:disabled)\",\n \"textarea:not(:disabled)\",\n \"a[href]\",\n \"[tabindex]\",\n].join(\", \");\n\n/** The keys that move focus, and where to. */\nconst MOVES: Readonly<Record<string, MenuMove>> = {\n ArrowRight: \"next\",\n ArrowLeft: \"previous\",\n Home: \"first\",\n End: \"last\",\n};\n\n/** Each control, as `nextMenuIndex` reads it: no separators are among them. */\nconst AS_ENTRY = { type: \"item\" } as const;\n\n/** Input types with no caret, whose arrow keys the toolbar may take. */\nconst CARETLESS_INPUTS = new Set([\n \"button\",\n \"checkbox\",\n \"color\",\n \"file\",\n \"image\",\n \"radio\",\n \"range\",\n \"reset\",\n \"submit\",\n]);\n\ninterface Control {\n /** The `data-gridkit-toolbar-item` element. */\n readonly entry: HTMLElement;\n /** What takes focus for it. */\n readonly target: HTMLElement;\n}\n\nfunction targetOf(entry: HTMLElement): HTMLElement | null {\n return entry.matches(FOCUSABLE)\n ? entry\n : entry.querySelector<HTMLElement>(FOCUSABLE);\n}\n\n/** A field typed in, whose caret the arrow keys move. */\nfunction textFieldOf(\n target: EventTarget | null,\n): HTMLInputElement | HTMLTextAreaElement | null {\n return target instanceof HTMLTextAreaElement ||\n (target instanceof HTMLInputElement && !CARETLESS_INPUTS.has(target.type))\n ? target\n : null;\n}\n\n/**\n * Whether a key belongs to the text field it was pressed in rather than to\n * the toolbar: Home and End always do, and an arrow does unless the caret\n * sits at the end it points past — so the arrows still lead out of a field,\n * which would otherwise trap focus in it, the toolbar's one tab stop.\n */\nfunction belongsToField(target: EventTarget | null, move: MenuMove): boolean {\n if (target instanceof HTMLElement && target.isContentEditable) {\n return true;\n }\n const field = textFieldOf(target);\n if (field === null) {\n return false;\n }\n const { selectionStart, selectionEnd, value } = field;\n // A type that keeps its caret to itself — `email`, `number` — reports\n // none, so every key is left to it rather than taken from under a caret.\n if (\n move === \"first\" ||\n move === \"last\" ||\n selectionStart === null ||\n selectionStart !== selectionEnd\n ) {\n return true;\n }\n return move === \"previous\"\n ? selectionStart > 0\n : selectionStart < value.length;\n}\n\n/**\n * Makes `toolbar` — a `role=\"toolbar\"` element the renderer drew — one tab\n * stop that the arrow keys move within, following the WAI-ARIA toolbar\n * pattern: ArrowRight and ArrowLeft move focus to the next and previous\n * control, wrapping round the ends, and Home and End to the first and last.\n * Returns the function that stops it.\n *\n * The toolbar's entries are its descendants with a `data-gridkit-toolbar-item`,\n * and each entry's control is the entry itself when it can take focus, else\n * the first element in it that can; an entry with none — a separator, a\n * spacer — is passed over. They are read afresh on every key. One control at\n * a time has `tabindex=\"0\"` — the focused one, or the first — and the rest\n * `-1`, kept so as entries come and go; a control with\n * `aria-disabled=\"true\"` stays one of them, so a screen reader finds it.\n *\n * A text field keeps the keys it needs: Home and End, and an arrow while\n * the caret has text to move through in that direction. At the start or end\n * of its text, the arrow moves on to the next control.\n */\nexport function attachToolbar(toolbar: HTMLElement): () => void {\n /** The control with `tabindex=\"0\"`. */\n let stop: HTMLElement | undefined;\n\n function controls(): Control[] {\n return Array.from(toolbar.querySelectorAll<HTMLElement>(ENTRY)).flatMap(\n (entry) => {\n const target = targetOf(entry);\n return target === null ? [] : [{ entry, target }];\n },\n );\n }\n\n function indexOf(all: readonly Control[], node: EventTarget | null): number {\n return node instanceof Node\n ? all.findIndex((control) => control.entry.contains(node))\n : -1;\n }\n\n function setTabStop(next: HTMLElement | undefined): void {\n stop = next;\n for (const { target } of controls()) {\n target.tabIndex = target === next ? 0 : -1;\n }\n }\n\n function onKeyDown(event: KeyboardEvent): void {\n const move = MOVES[event.key];\n if (\n move === undefined ||\n event.isComposing ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.shiftKey ||\n belongsToField(event.target, move)\n ) {\n return;\n }\n const all = controls();\n const from = indexOf(all, event.target);\n if (from === -1) {\n return;\n }\n const next =\n all[\n nextMenuIndex(\n all.map(() => AS_ENTRY),\n from,\n move,\n )\n ];\n if (next !== undefined) {\n event.preventDefault();\n setTabStop(next.target);\n next.target.focus();\n }\n }\n\n // However focus arrived — a click, Tab, a script — the tab stop follows it.\n function onFocusIn(event: FocusEvent): void {\n const all = controls();\n const control = all[indexOf(all, event.target)];\n if (control !== undefined) {\n setTabStop(control.target);\n }\n }\n\n // A control drawn since — a button whose command became visible — would\n // otherwise come with the browser's own tab stop, and make two. The stop\n // stays where it was while its control is still drawn.\n const observer = new MutationObserver(() => {\n const all = controls();\n setTabStop(\n all.find(({ target }) => target === stop)?.target ?? all[0]?.target,\n );\n });\n\n const all = controls();\n setTabStop(\n all[indexOf(all, document.activeElement)]?.target ?? all[0]?.target,\n );\n toolbar.addEventListener(\"keydown\", onKeyDown);\n toolbar.addEventListener(\"focusin\", onFocusIn);\n observer.observe(toolbar, { childList: true, subtree: true });\n\n return () => {\n observer.disconnect();\n toolbar.removeEventListener(\"keydown\", onKeyDown);\n toolbar.removeEventListener(\"focusin\", onFocusIn);\n };\n}\n","import {\n fitToolbarItems,\n isPinnedToolbarItem,\n type ResolvedToolbarItem,\n} from \"@gridkitjs/core\";\n\n/**\n * A toolbar entry as the overflow reads it: its kind, the key its element\n * carries and, for a button or a dropdown, its label — a change to which,\n * while the entry is in \"More\" and not drawn, makes the width remembered for\n * it stale.\n */\nexport type ToolbarOverflowItem = Pick<ResolvedToolbarItem, \"type\" | \"key\"> & {\n readonly label?: string | undefined;\n};\n\nexport interface ToolbarOverflowOptions {\n /**\n * Every entry of the toolbar, in order — those drawn and those moved into\n * \"More\" alike — as `resolveToolbar` gave them.\n */\n readonly items: readonly ToolbarOverflowItem[];\n /**\n * How many entries fit, as `fitToolbarItems` counts them: called once\n * straight away, then whenever the count changes.\n */\n readonly onChange: (fit: number) => void;\n}\n\nexport interface ToolbarOverflow {\n /**\n * Call whenever the items change, with the current options: measures\n * again, and reports a changed count.\n */\n readonly update: (options: ToolbarOverflowOptions) => void;\n /** Stops observing. */\n readonly detach: () => void;\n}\n\nconst ENTRY_ATTRIBUTE = \"data-gridkit-toolbar-item\";\nconst MORE_ATTRIBUTE = \"data-gridkit-toolbar-more\";\n\nfunction pixels(value: string): number {\n return Number.parseFloat(value) || 0;\n}\n\n/** The room an element takes in its row: its box and its margins. */\nfunction outerWidth(element: HTMLElement): number {\n const style = getComputedStyle(element);\n return (\n element.getBoundingClientRect().width +\n pixels(style.marginLeft) +\n pixels(style.marginRight)\n );\n}\n\n/**\n * Measures how many of a toolbar's entries fit in it, reporting the count\n * through `onChange` straight away — so a renderer calling this before\n * paint draws the right entries from the first frame — and again whenever a\n * resize changes it. The arithmetic is @gridkitjs/core's `fitToolbarItems`:\n * the search box and custom entries always stay, and room for \"More\" is kept\n * only when something does not fit.\n *\n * Entries are found by their `data-gridkit-toolbar-item`, holding each\n * item's `key`, and the \"More\" button by `data-gridkit-toolbar-more`. An\n * entry moved into \"More\" is no longer drawn, so each width is remembered\n * by key from the last time it was; one never drawn yet counts as nothing,\n * which lets it be drawn, measured, and moved back out if it does not fit —\n * as one whose label changed while it was not drawn counts again.\n * A spacer counts as nothing too: its width is only the room left over. The\n * toolbar's padding and the `column-gap` between entries are allowed for.\n *\n * It measures again whenever the toolbar or an entry resizes and whenever\n * entries are drawn or taken away — the ones the count it reported let in,\n * or the \"More\" button — so a renderer only has to draw what it is told,\n * and to pass new items to `update`.\n */\nexport function observeToolbarOverflow(\n toolbar: HTMLElement,\n options: ToolbarOverflowOptions,\n): ToolbarOverflow {\n let current = options;\n const widths = new Map<string, number>();\n let moreWidth = 0;\n let reported: number | undefined;\n\n function drawnEntries(): HTMLElement[] {\n return Array.from(\n toolbar.querySelectorAll<HTMLElement>(`[${ENTRY_ATTRIBUTE}]`),\n );\n }\n\n function measure(): void {\n for (const element of drawnEntries()) {\n if (element.hasAttribute(MORE_ATTRIBUTE)) {\n moreWidth = outerWidth(element);\n } else {\n widths.set(\n element.getAttribute(ENTRY_ATTRIBUTE) ?? \"\",\n outerWidth(element),\n );\n }\n }\n const style = getComputedStyle(toolbar);\n // Every entry is counted with the gap after it, so the row gets one gap\n // more than it has — the one after its last entry — to share.\n const gap = pixels(style.columnGap);\n const fit = fitToolbarItems({\n widths: current.items.map(\n (item) =>\n (item.type === \"spacer\" ? 0 : (widths.get(item.key) ?? 0)) + gap,\n ),\n available:\n toolbar.clientWidth -\n pixels(style.paddingLeft) -\n pixels(style.paddingRight) +\n gap,\n moreWidth: moreWidth + gap,\n pinned: current.items.map(isPinnedToolbarItem),\n });\n if (fit !== reported) {\n reported = fit;\n current.onChange(fit);\n }\n }\n\n /** The toolbar for the room there is, and every entry drawn for a label that changed its width. */\n const resizes = new ResizeObserver(measure);\n\n function observeSizes(): void {\n resizes.disconnect();\n resizes.observe(toolbar);\n for (const element of drawnEntries()) {\n resizes.observe(element);\n }\n }\n\n const draws = new MutationObserver(() => {\n observeSizes();\n measure();\n });\n\n observeSizes();\n draws.observe(toolbar, { childList: true, subtree: true });\n measure();\n\n return {\n update(next) {\n // An entry whose label changed while it was in \"More\" is forgotten, so\n // it counts as never drawn: drawn, measured afresh, and moved back out\n // if it no longer fits. One drawn is measured again straight away.\n const labels = new Map(\n current.items.map((item) => [item.key, item.label]),\n );\n for (const item of next.items) {\n if (labels.has(item.key) && labels.get(item.key) !== item.label) {\n widths.delete(item.key);\n }\n }\n current = next;\n measure();\n },\n detach() {\n resizes.disconnect();\n draws.disconnect();\n },\n };\n}\n","/** What `printDocument` prints: a document's title, its body's markup and its stylesheet. */\nexport interface PrintDocumentOptions {\n /** The document's title, as text — what the browser puts in a page's header and names a saved PDF. */\n readonly title: string;\n /** The `<body>`'s markup, as `buildPrintDocument` writes it. */\n readonly bodyHtml: string;\n /** The document's whole stylesheet — `printStyles(page)`, and anything added after it. */\n readonly styles: string;\n}\n\n/**\n * What tells a print is over where `afterprint` never comes: the user\n * reaching for the page again, which a print dialog — modal while it is\n * open — allows only once it has closed.\n */\nconst RETURN_EVENTS = [\"pointerdown\", \"keydown\"] as const;\n\n/** How long a print waits for the frame's fonts before printing without them. */\nconst FONTS_TIMEOUT_MS = 3000;\n\n/** Whether a print is open, so a second request while it is can be refused. */\nlet printing = false;\n\n/**\n * A frame no one sees or reaches. It has no size rather than being hidden\n * with `display: none` or `visibility`, which some browsers then refuse to\n * print.\n */\nfunction createFrame(bodyHtml: string): HTMLIFrameElement {\n const frame = document.createElement(\"iframe\");\n frame.setAttribute(\"aria-hidden\", \"true\");\n frame.tabIndex = -1;\n frame.style.position = \"fixed\";\n frame.style.right = \"0\";\n frame.style.bottom = \"0\";\n frame.style.width = \"0\";\n frame.style.height = \"0\";\n frame.style.border = \"0\";\n frame.srcdoc = `<!doctype html><html><head><meta charset=\"utf-8\"></head><body>${bodyHtml}</body></html>`;\n return frame;\n}\n\n/**\n * Puts the frame in the page and waits for its document to load. Rejects\n * if the frame is taken out of the page first, as other code that clears\n * `document.body` can do: it would then never load.\n */\nfunction load(frame: HTMLIFrameElement): Promise<Window> {\n return new Promise((resolve, reject) => {\n const removal = new MutationObserver(() => {\n if (!frame.isConnected) {\n settle();\n reject(new Error(\"The print frame was removed before it loaded.\"));\n }\n });\n function loaded(): void {\n settle();\n if (frame.contentWindow === null) {\n reject(new Error(\"The print frame has no window.\"));\n } else {\n resolve(frame.contentWindow);\n }\n }\n function settle(): void {\n removal.disconnect();\n frame.removeEventListener(\"load\", loaded);\n }\n frame.addEventListener(\"load\", loaded);\n removal.observe(document.body, { childList: true });\n document.body.append(frame);\n });\n}\n\n/**\n * Gives the loaded document its title and stylesheet. Set through the DOM\n * rather than written into the markup, so neither needs escaping — a\n * `</style>` in the CSS, or a `<` in a title, stays what it is.\n */\nfunction fill(view: Window, { title, styles }: PrintDocumentOptions): void {\n const { document: frameDocument } = view;\n frameDocument.title = title;\n const style = frameDocument.createElement(\"style\");\n style.textContent = styles;\n frameDocument.head.append(style);\n}\n\n/**\n * Waits for the frame's fonts to load, for `FONTS_TIMEOUT_MS` at most: a\n * font whose request never settles would otherwise hold this print open,\n * and refuse every print after it. A font still missing then prints in the\n * next face of its stack. The timer is the page's rather than the frame's,\n * so it still fires if the frame is taken away meanwhile.\n */\nasync function fontsLoaded(view: Window): Promise<void> {\n let timer = 0;\n const timeout = new Promise<void>((resolve) => {\n timer = window.setTimeout(resolve, FONTS_TIMEOUT_MS);\n });\n try {\n await Promise.race([view.document.fonts.ready, timeout]);\n } finally {\n window.clearTimeout(timer);\n }\n}\n\n/**\n * Opens the print dialog for the frame and settles once it has closed: on\n * the frame's `afterprint`, which every current browser fires, or else the\n * next time the user presses a key or the pointer on the page. Not after a\n * set time: where `print()` returns with the dialog still open, a frame\n * taken away under it would print nothing.\n */\nasync function print(view: Window): Promise<void> {\n // A key comes to the frame, which has focus; the pointer to the page, as\n // the frame takes no room.\n const listeners: [Window, string][] = [\n [view, \"afterprint\"],\n ...RETURN_EVENTS.flatMap((type): [Window, string][] => [\n [view, type],\n [window, type],\n ]),\n ];\n let done = (): void => undefined;\n const closed = new Promise<void>((resolve) => {\n done = resolve;\n });\n for (const [target, type] of listeners) {\n target.addEventListener(type, done, { capture: true });\n }\n try {\n // Safari prints the page around a frame that does not have focus.\n view.focus();\n view.print();\n await closed;\n } finally {\n for (const [target, type] of listeners) {\n target.removeEventListener(type, done, { capture: true });\n }\n }\n}\n\n/**\n * Prints a document through a hidden frame: its own page, holding only\n * `bodyHtml` under `styles`, so what is printed is what was built — every\n * row, not the slice of them a virtualised grid has drawn — and nothing of\n * the page around it. The frame loads, waits for its fonts (a few seconds\n * at most), opens the print dialog, and is removed once the dialog closes, when the promise\n * resolves; focus goes back where it was. A frame rather than a new window,\n * which a popup blocker would stop.\n *\n * One print at a time: a call while a print is open resolves at once\n * without printing. Rejects, with the frame removed, if the frame is taken\n * out of the page before it loads or the dialog fails to open.\n */\nexport async function printDocument(\n options: PrintDocumentOptions,\n): Promise<void> {\n if (printing) {\n return;\n }\n printing = true;\n const previous = document.activeElement;\n const frame = createFrame(options.bodyHtml);\n try {\n const view = await load(frame);\n fill(view, options);\n await fontsLoaded(view);\n await print(view);\n } finally {\n frame.remove();\n printing = false;\n if (previous instanceof HTMLElement) {\n previous.focus({ preventScroll: true });\n }\n }\n}\n","import type { PrintPage } from \"@gridkitjs/core\";\n\n/** How far each level of group header steps in, after the cell's own padding. */\nconst GROUP_INDENT = \"12pt\";\n/** A cell's padding to either side, which a group header's indent adds to. */\nconst CELL_PADDING_X = \"5pt\";\n\n/**\n * The deepest group level given its own indent; any deeper shares it. A\n * stylesheet cannot read `data-level` as a length, so each level is a rule.\n */\nconst DEEPEST_INDENTED_LEVEL = 6;\n\nfunction pageRules({ paperSize, orientation, pageNumbers }: PrintPage): string {\n const numbers = pageNumbers\n ? `@bottom-right { content: counter(page) \" / \" counter(pages); font: 8pt system-ui, sans-serif; color: #555; }`\n : \"\";\n return `@page { size: ${paperSize} ${orientation}; margin: 12mm; ${numbers} }`;\n}\n\nfunction indentRule(selector: string, level: number): string {\n return `${selector} > td:first-child { padding-left: calc(${CELL_PADDING_X} + ${String(level)} * ${GROUP_INDENT}); }`;\n}\n\n/**\n * Each group header's label stepped in under its parent's. Every nested\n * header starts at the deepest indent, and the levels above it each step\n * back out to their own — a later rule of the same weight winning.\n */\nfunction groupIndentRules(): string {\n const rules = [\n indentRule('.export-group:not([data-level=\"0\"])', DEEPEST_INDENTED_LEVEL),\n ];\n for (let level = 1; level < DEEPEST_INDENTED_LEVEL; level++) {\n rules.push(\n indentRule(`.export-group[data-level=\"${String(level)}\"]`, level),\n );\n }\n return rules.join(\"\\n\");\n}\n\nconst BASE_RULES = `html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\nbody { margin: 0; background: #fff; color: #000; font: 10pt/1.35 system-ui, sans-serif; }\n.gridkit-print-header { margin-bottom: 8pt; }\n.print-title { margin: 0; font-size: 14pt; }\n.print-subtitle, .print-timestamp { margin: 2pt 0 0; color: #444; }\n.gridkit-print-footer { margin-top: 8pt; color: #444; }\n.gridkit-export-table { width: 100%; border-collapse: collapse; }\n.gridkit-export-table thead { display: table-header-group; }\n.gridkit-export-table tfoot { display: table-row-group; }\n.gridkit-export-table tr { break-inside: avoid; }\n.gridkit-export-table th, .gridkit-export-table td { border: 1px solid #bbb; padding: 3pt ${CELL_PADDING_X}; text-align: left; vertical-align: top; }\n.gridkit-export-table th { background: #eee; font-weight: 600; }\n.export-group > td { background: #f5f5f5; font-weight: 600; }\n.export-group-summary > td, .gridkit-export-table tfoot td { font-weight: 600; }\n.gridkit-export-table tfoot td { border-top: 2px solid #888; }`;\n\nconst FIT_TO_WIDTH_RULES = `.gridkit-export-table { table-layout: fixed; font-size: 8pt; }\n.gridkit-export-table th, .gridkit-export-table td { overflow-wrap: anywhere; }`;\n\n/**\n * The stylesheet a print of `buildPrintDocument`'s markup needs, for `page`.\n * A print frame sees none of the page's CSS, so this carries what makes the\n * table read on paper and nothing of a theme: the paper's size and\n * orientation; a header row repeated at the top of every page; rows kept\n * whole across a page break; neutral one-pixel borders; group headers\n * stepped in by depth; backgrounds printed as they are rather than dropped\n * to save ink; page numbers in the bottom-right margin, where the browser\n * draws `@page` margin boxes; and for `fitToWidth`, a smaller font in fixed\n * columns that wrap their text.\n *\n * The grand total prints once, after the last row — not repeated at the\n * foot of every page, where browsers repeat a `<tfoot>`, as a total of\n * rows the page does not hold would mislead. Add CSS after it to restyle.\n */\nexport function printStyles(page: PrintPage): string {\n return [\n pageRules(page),\n BASE_RULES,\n groupIndentRules(),\n page.fitToWidth ? FIT_TO_WIDTH_RULES : \"\",\n ]\n .filter((rules) => rules !== \"\")\n .join(\"\\n\");\n}\n"],"mappings":"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","names":["onKeyDown","onKeyDown","onKeyDown","DETACHED","ITEM","IDLE","onKeyDown","nextMenuIndex","FOCUSABLE","MOVES","AS_ENTRY","indexOf","all","onKeyDown"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gridkitjs/dom",
3
- "version": "0.0.0-dev-20261109133348",
3
+ "version": "0.0.0-dev-20261109134250",
4
4
  "description": "Framework-free browser behaviours for GridKit renderers: gestures, focus, observers, virtualization, overlays and announcements.",
5
5
  "license": "MIT",
6
6
  "author": "Blagoja Blazhevski",
@@ -37,7 +37,7 @@
37
37
  "README.md"
38
38
  ],
39
39
  "dependencies": {
40
- "@gridkitjs/core": "0.0.0-dev-20261109133348"
40
+ "@gridkitjs/core": "0.0.0-dev-20261109134250"
41
41
  },
42
42
  "devDependencies": {
43
43
  "tsup": "^8.5.1",