argus-table 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/argus-column-group.js +66 -0
- package/dist/argus-column-group.js.map +1 -0
- package/dist/argus-column.js +4 -25
- package/dist/argus-column.js.map +1 -1
- package/dist/argus-table.js +553 -200
- package/dist/argus-table.js.map +1 -1
- package/dist/attributes.js +21 -0
- package/dist/attributes.js.map +1 -0
- package/dist/cdn/argus-table.js +92 -57
- package/dist/cdn/argus-table.js.map +1 -1
- package/dist/column-registry.js +23 -0
- package/dist/column-registry.js.map +1 -0
- package/dist/controllers/auto-width.js +6 -5
- package/dist/controllers/auto-width.js.map +1 -1
- package/dist/controllers/column-children.js +32 -9
- package/dist/controllers/column-children.js.map +1 -1
- package/dist/controllers/column-window.js +0 -4
- package/dist/controllers/column-window.js.map +1 -1
- package/dist/controllers/plugins.js +25 -6
- package/dist/controllers/plugins.js.map +1 -1
- package/dist/controllers/reorder-drag.js +12 -9
- package/dist/controllers/reorder-drag.js.map +1 -1
- package/dist/controllers/row-measure.js +2 -1
- package/dist/controllers/row-measure.js.map +1 -1
- package/dist/controllers/virtual-controller.js +25 -32
- package/dist/controllers/virtual-controller.js.map +1 -1
- package/dist/core/src/columns/column-groups.js +129 -0
- package/dist/core/src/columns/column-groups.js.map +1 -0
- package/dist/core/src/columns/column-order.js +125 -31
- package/dist/core/src/columns/column-order.js.map +1 -1
- package/dist/core/src/columns/resolve-columns.js +0 -1
- package/dist/core/src/columns/resolve-columns.js.map +1 -1
- package/dist/core/src/columns/spread-width.js +43 -0
- package/dist/core/src/columns/spread-width.js.map +1 -0
- package/dist/core/src/filters/filter.js +6 -8
- package/dist/core/src/filters/filter.js.map +1 -1
- package/dist/core/src/grid/row-layout.js +19 -11
- package/dist/core/src/grid/row-layout.js.map +1 -1
- package/dist/core/src/state/selection.js +2 -1
- package/dist/core/src/state/selection.js.map +1 -1
- package/dist/core/src/table/create-table.js +57 -22
- package/dist/core/src/table/create-table.js.map +1 -1
- package/dist/core/src/table/sorted-row-model.js.map +1 -1
- package/dist/core/src/virtual/row-sizes.js +0 -4
- package/dist/core/src/virtual/row-sizes.js.map +1 -1
- package/dist/custom-elements.json +489 -245
- package/dist/events.js +16 -16
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +273 -127
- package/dist/index.js +3 -2
- package/dist/render/body.js +2 -2
- package/dist/render/body.js.map +1 -1
- package/dist/render/cells.js +11 -3
- package/dist/render/cells.js.map +1 -1
- package/dist/render/content-directive.js +1 -1
- package/dist/render/content-directive.js.map +1 -1
- package/dist/render/content.js +15 -3
- package/dist/render/content.js.map +1 -1
- package/dist/render/header.js +82 -19
- package/dist/render/header.js.map +1 -1
- package/dist/styles/base.js +1 -1
- package/dist/styles/base.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtual-controller.js","names":[],"sources":["../../src/controllers/virtual-controller.ts"],"sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit';\nimport { VirtualizerController } from '@tanstack/lit-virtual';\nimport { RowPool, RowSizes, ScrollScale, maxVisibleRows, steadyWindow } from '@argus-table/core';\n\n/** A rendered body row: its pool slot, its index in the row model and its offset. */\nexport interface WindowRow {\n slot: number;\n index: number;\n /** Offset in the rendered body, in px (scaled bodies: relative to the scroll position). */\n start: number;\n /** Measured row heights (`auto`): the row's height, the least it renders at. */\n size?: number;\n}\n\n/**\n * Engines place elements in a body taller than this (2^24 px) only to 2 px:\n * positions are 32-bit floats. Measured rows are whole px below it and an even\n * number of px above it, so every row starts on the grid and they stay flush.\n */\nconst COARSE_BODY = 16_000_000;\n\n/** How long scrolling has to be quiet before a held correction is written (see `#hold`). */\nconst HOLD_MS = 150;\n\nexport interface VirtualWindow {\n /** Rendered rows, in pool order (ADR 0001 addendum). */\n rows: WindowRow[];\n /** Rendered height of the body in px: all rows, or the cap. */\n totalSize: number;\n /** Visible rows, without overscan; `null` before the first layout. */\n range: { start: number; end: number } | null;\n}\n\n/** Measured row heights (`rowHeight: 'auto'`). */\nexport interface AutoRows {\n /** Changes identity whenever the row at an index may have changed (the model's `rowOrder`). */\n order: object;\n /** The index of a row id in the current order. */\n indexOf: (id: string) => number | undefined;\n}\n\nexport interface VirtualConfig {\n count: number;\n /** The row height; with `auto`, the least a row is and what an unmeasured row counts as. */\n rowHeight: number;\n overscan: number;\n /** Row id at an index of the row model; pool slots follow row ids. */\n rowIdAt: (index: number) => string;\n /**\n * The row to keep rendered outside the range (the one holding the grid's tab\n * stop), so its DOM and focus survive scrolling. `undefined`: none.\n */\n keep?: { id: string; index: number } | undefined;\n /** The tallest the body may render when it is `logical` px tall (`bodyCap()`); a taller one scrolls scaled. */\n cap: (logical: number) => number;\n /** Rows of different heights, measured as they render (`measureRows`). */\n auto?: AutoRows | undefined;\n}\n\n/**\n * Row virtualization (TanStack Virtual) plus the row pool: which rows render,\n * where, and which pool slot's DOM each one uses. The virtualizer registers itself\n * with the host and requests updates on scroll.\n *\n * Scroll scaling: engines limit an element's height, so a body taller than the\n * cap renders at the cap and its scroll position maps linearly onto the rows\n * (`ScrollScale`). The virtualizer works in logical offsets: it gets a stand-in\n * for the scroller whose `scrollTop`, `scrollHeight` and `scrollTo()` are\n * logical, and everything else passes through. Rows render relative to the\n * scroll position (`start` minus the difference between the logical and the\n * physical offset). Scroll through this controller (`scrollBy`, `reveal`,\n * `scrollToIndex`): physical px no longer match rows once the body is scaled.\n *\n * Rows of different heights (`auto`): TanStack's own measuring recomputes every\n * row below a measured one, too slow for a million rows, so in this mode the\n * virtualizer sits idle (no rows) and the controller does its part: the window\n * from `RowSizes` (heights by index, O(log n) offsets), a scroll listener that\n * re-renders when the window moves, and anchoring. Heights follow row ids, so\n * a sort or filter keeps them. A measured row above the top row would move\n * the rows in view: unscaled, the rows render shifted by the difference until\n * scrolling stops (`#hold`); scaled, the mapping pivots at the current offset.\n * Neither writes the scroll position while the user scrolls.\n */\nexport class VirtualController implements ReactiveController {\n readonly #virtual: VirtualizerController<HTMLElement, Element>;\n readonly #pool = new RowPool();\n readonly #scale = new ScrollScale();\n readonly #host: ReactiveControllerHost;\n readonly #getScrollElement: () => HTMLElement | null;\n #element: HTMLElement | undefined;\n #standIn: HTMLElement | undefined;\n #config: VirtualConfig = {\n count: 0,\n rowHeight: 40,\n overscan: 6,\n rowIdAt: String,\n cap: () => Infinity,\n };\n /** A logical offset to restore after the update, once the body has its new height. */\n #anchor: number | undefined;\n // Auto mode only.\n readonly #sizes = new RowSizes();\n /** Measured heights by row id (as read), so they follow rows through a sort or filter. */\n readonly #heights = new Map<string, number>();\n /** Rows are whole multiples of this many px (see `COARSE_BODY`). */\n #quantum = 1;\n /**\n * Unscaled: the logical offset less the scroller's. Rows measured above the\n * top row would move the rows in view; instead of writing the scroll position\n * (which cancels a wheel or touch scroll in progress, in WebKit at once) the\n * rows render shifted by this much, and it is written once scrolling has been\n * quiet for `HOLD_MS`.\n */\n #hold = 0;\n #holdTimer: ReturnType<typeof setTimeout> | undefined;\n /** The `order` the sizes are indexed for. */\n #order: object | undefined;\n /** The visible rows last rendered, and the logical offset they were rendered for. */\n #rendered: { start: number; end: number; top: number } | undefined;\n /** The scroller this controller listens to (auto mode). */\n #listening: HTMLElement | undefined;\n #resizeObserver: ResizeObserver | undefined;\n\n constructor(host: ReactiveControllerHost, getScrollElement: () => HTMLElement | null) {\n this.#host = host;\n this.#getScrollElement = getScrollElement;\n this.#virtual = new VirtualizerController<HTMLElement, Element>(host, {\n count: 0,\n getScrollElement: () => this.#scroller(),\n // The real element: a ResizeObserver can't observe the stand-in.\n observeElementRect: (_instance, cb) => {\n const element = this.#element;\n if (!element) return undefined;\n const report = () => {\n cb({ width: element.clientWidth, height: element.clientHeight });\n };\n report();\n const observer = new ResizeObserver(report);\n observer.observe(element);\n return () => {\n observer.disconnect();\n };\n },\n estimateSize: () => this.#config.rowHeight,\n overscan: this.#config.overscan,\n // One window size from the first render: the pool never grows while scrolling\n // (in WebKit, new rows' slots would cut a wheel scroll short; see steadyWindow).\n rangeExtractor: (range) =>\n steadyWindow(\n range,\n maxVisibleRows(this.#element?.clientHeight ?? 0, this.#config.rowHeight),\n ),\n });\n host.addController(this);\n }\n\n /** The scroller's stand-in for the virtualizer: vertical offsets and height in logical px. */\n #scroller(): HTMLElement | null {\n const element = this.#getScrollElement();\n if (!element) return null;\n if (this.#element !== element || !this.#standIn) {\n this.#element = element;\n this.#standIn = new Proxy(element, {\n get: (target, key) => {\n if (key === 'scrollTop') return this.#top(target);\n if (key === 'scrollHeight') return target.scrollHeight + this.#scale.excess;\n if (key === 'scrollTo') {\n return (options: ScrollToOptions) => {\n if (options.top === undefined) target.scrollTo(options);\n else this.#write(target, options.top, options.behavior);\n };\n }\n const value: unknown = Reflect.get(target, key);\n // Methods run on the element itself (DOM methods reject the proxy).\n return typeof value === 'function'\n ? (value as (...args: unknown[]) => unknown).bind(target)\n : value;\n },\n });\n }\n return this.#standIn;\n }\n\n /** The scroller's logical offset. */\n #top(element: HTMLElement): number {\n const top = element.scrollTop;\n const scale = this.#scale;\n return scale.scaled\n ? scale.toLogical(top, element.scrollHeight - element.clientHeight)\n : top + this.#hold;\n }\n\n /**\n * Scrolls to a logical offset. `round` rounds the physical one, so that a\n * scaled offset moves the scroller at all.\n */\n #write(\n element: HTMLElement,\n logical: number,\n behavior?: ScrollBehavior,\n round?: (px: number) => number,\n ): void {\n const scale = this.#scale;\n if (!scale.scaled) {\n this.#dropHold();\n element.scrollTo({ top: logical, behavior });\n return;\n }\n const range = element.scrollHeight - element.clientHeight;\n const target = Math.min(Math.max(logical, 0), range + scale.excess);\n const physical = scale.toPhysical(target, range);\n element.scrollTo({ top: round ? round(physical) : physical, behavior });\n if (behavior !== 'smooth') scale.land(target, element.scrollTop);\n }\n\n configure(next: VirtualConfig): void {\n const prev = this.#config;\n this.#config = next;\n if (next.auto) {\n this.#configureAuto(next.auto, prev);\n return;\n }\n if (prev.auto) {\n // Back to fixed heights: the virtualizer takes over again.\n this.#heights.clear();\n this.#sizes.reset(0, next.rowHeight);\n this.#order = undefined;\n this.#rendered = undefined;\n this.#dropHold();\n this.#virtual.setOptions({ count: next.count, overscan: next.overscan });\n this.#virtual.getVirtualizer().measure();\n } else if (\n next.count !== prev.count ||\n next.overscan !== prev.overscan ||\n next.rowHeight !== prev.rowHeight\n ) {\n this.#virtual.setOptions({ count: next.count, overscan: next.overscan });\n if (next.rowHeight !== prev.rowHeight) this.#virtual.getVirtualizer().measure();\n }\n // Not `scaled`: TypeScript would take it for unchanged after `configure()`.\n const wasScaled = this.#scale.excess > 0;\n const offset = this.#virtual.getVirtualizer().scrollOffset;\n const logical = next.count * next.rowHeight;\n if (this.#scale.configure(logical, next.cap(logical))) {\n // A new mapping would move the rows under a scaled scroll position (a data\n // source growing, a zoom change): keep the logical offset once the body\n // has its new height.\n if ((wasScaled || this.#scale.scaled) && offset !== null && offset > 0) {\n this.#anchor = offset;\n }\n }\n }\n\n #configureAuto(auto: AutoRows, prev: VirtualConfig): void {\n const next = this.#config;\n if (!prev.auto) this.#virtual.setOptions({ count: 0 });\n const sizes = this.#sizes;\n if (!prev.auto || next.rowHeight !== prev.rowHeight) {\n // A new minimum height (density, a theme): every row measures again.\n this.#heights.clear();\n this.#order = undefined;\n }\n const reset = () => {\n sizes.reset(next.count, this.#fit(0), this.#known(auto, next.count));\n };\n if (auto.order !== this.#order) {\n this.#order = auto.order;\n reset();\n } else if (next.count !== sizes.count) {\n // A data source counting more rows: they keep their indexes.\n sizes.resize(next.count);\n }\n // A body past 2^24 px needs even heights: fit the rows to the new grid.\n const quantum = this.#quantumFor(sizes.total);\n if (quantum !== this.#quantum) {\n this.#quantum = quantum;\n reset();\n }\n const element = this.#element;\n const wasScaled = this.#scale.excess > 0;\n const offset = element ? this.#top(element) : 0;\n const logical = sizes.total;\n if (this.#scale.configure(logical, next.cap(logical))) {\n if ((wasScaled || this.#scale.scaled) && offset > 0) this.#anchor = offset;\n }\n }\n\n /** The measured heights by index in the current order. Forgets rows long gone. */\n *#known(auto: AutoRows, count: number): Generator<[number, number]> {\n const heights = this.#heights;\n const prune = heights.size > 2 * count + 1000;\n for (const [id, px] of heights) {\n const index = auto.indexOf(id);\n if (index !== undefined && index < count) yield [index, this.#fit(px)];\n else if (prune) heights.delete(id);\n }\n }\n\n /** The px grid rows are on for a body `logical` px tall (see `COARSE_BODY`). */\n #quantumFor(logical: number): number {\n return Math.min(logical, this.#config.cap(logical)) > COARSE_BODY ? 2 : 1;\n }\n\n /** A measured height as a row renders it: on the grid, at least the row height. */\n #fit(px: number): number {\n const q = this.#quantum;\n const up = (value: number) => Math.ceil(value / q - 0.01) * q;\n return Math.max(up(px), up(this.#config.rowHeight));\n }\n\n hostUpdated(): void {\n this.#listen();\n const anchor = this.#anchor;\n if (anchor === undefined) return;\n this.#anchor = undefined;\n if (this.#element) this.#write(this.#element, anchor);\n // The logical offset is the same, so the virtualizer sees no change; the\n // rows still have to move to the new physical one.\n this.#host.requestUpdate();\n }\n\n hostDisconnected(): void {\n this.#unlisten();\n }\n\n /**\n * Auto mode: the virtualizer has no rows, so it reports neither scrolling nor\n * a new viewport size. Listen to the scroller here.\n */\n #listen(): void {\n const element = this.#config.auto ? this.#getScrollElement() : null;\n if (element === (this.#listening ?? null)) return;\n this.#unlisten();\n if (!element) return;\n this.#scroller();\n this.#listening = element;\n element.addEventListener('scroll', this.#onScroll, { passive: true });\n this.#resizeObserver ??= new ResizeObserver(this.#onScroll);\n // Reports the size once right away, which renders the first rows.\n this.#resizeObserver.observe(element);\n }\n\n #unlisten(): void {\n this.#listening?.removeEventListener('scroll', this.#onScroll);\n this.#resizeObserver?.disconnect();\n this.#listening = undefined;\n this.#dropHold();\n }\n\n #dropHold(): void {\n clearTimeout(this.#holdTimer);\n this.#holdTimer = undefined;\n this.#hold = 0;\n }\n\n /** Writes the held correction: the rows stay where they are on screen. */\n readonly #flushHold = () => {\n this.#holdTimer = undefined;\n const element = this.#element;\n if (!element || this.#hold === 0) return;\n const logical = this.#top(element);\n this.#hold = 0;\n element.scrollTo({ top: logical });\n this.#host.requestUpdate();\n };\n\n /** Re-renders when the visible rows have changed. */\n readonly #onScroll = () => {\n if (this.#holdTimer !== undefined) {\n clearTimeout(this.#holdTimer);\n this.#holdTimer = setTimeout(this.#flushHold, HOLD_MS);\n }\n const visible = this.#visible();\n const rendered = this.#rendered;\n if (visible?.start !== rendered?.start || visible?.end !== rendered?.end) {\n this.#host.requestUpdate();\n }\n };\n\n /** Auto mode: the visible rows (without overscan) and the logical offset, or `undefined` before layout. */\n #visible(): { start: number; end: number; top: number } | undefined {\n const element = this.#element;\n const sizes = this.#sizes;\n const view = element?.clientHeight ?? 0;\n if (!element || sizes.count === 0 || view <= 0) return undefined;\n const top = this.#top(element);\n const start = sizes.indexAt(top);\n // The last row that starts above the bottom edge.\n const end = Math.max(start, sizes.indexAt(top + view - 0.5));\n return { start, end, top };\n }\n\n /**\n * Computes the rendered window and updates the pool. Call once per render.\n */\n computeWindow(): VirtualWindow {\n if (this.#config.auto) return this.#autoWindow();\n const v = this.#virtual.getVirtualizer();\n const items = v.getVirtualItems();\n // Scaled: rows render relative to the scroll position. The physical offset is\n // read now, so rows for the virtualizer's offset fill the view even if the\n // scroller has moved on since its last scroll event.\n const scaled = this.#scale.scaled;\n const size = this.#scale.size;\n const shift = scaled ? (v.scrollOffset ?? 0) - (this.#element?.scrollTop ?? 0) : 0;\n const byId = new Map<string, { index: number; start: number }>();\n for (const item of items) {\n byId.set(this.#config.rowIdAt(item.index), { index: item.index, start: item.start - shift });\n }\n const { keep, rowHeight } = this.#config;\n this.#pool.pin(keep?.id);\n if (keep && !byId.has(keep.id)) {\n // Fixed row heights: the virtualizer would place it here too. Scaled, it is\n // out of view: keep it inside the body (or just above it, under the header),\n // so it doesn't stretch the scroll range past the cap.\n const start = keep.index * rowHeight - shift;\n byId.set(keep.id, {\n index: keep.index,\n start: scaled ? Math.min(Math.max(start, -rowHeight), size) : start,\n });\n }\n const rows: WindowRow[] = [];\n for (const { id, slot } of this.#pool.update(byId.keys())) {\n const item = byId.get(id);\n if (item) rows.push({ slot, ...item });\n }\n const range = v.range;\n return {\n rows,\n totalSize: scaled ? size : v.getTotalSize(),\n range: range ? { start: range.startIndex, end: range.endIndex } : null,\n };\n }\n\n /** `computeWindow()` for rows of different heights. */\n #autoWindow(): VirtualWindow {\n const { count, overscan, rowHeight, keep, rowIdAt } = this.#config;\n const sizes = this.#sizes;\n const scale = this.#scale;\n const element = this.#element;\n const visible = this.#visible();\n this.#rendered = visible;\n // Scaled: rows render relative to the scroll position. Unscaled: shifted by the held correction.\n const shift = visible && element ? visible.top - element.scrollTop : 0;\n const byId = new Map<string, { index: number; start: number; size?: number }>();\n if (visible && element) {\n const indexes = steadyWindow(\n { startIndex: visible.start, endIndex: visible.end, overscan, count },\n maxVisibleRows(element.clientHeight, rowHeight),\n );\n const first = indexes[0];\n let start = first === undefined ? 0 : sizes.startOf(first);\n for (const index of indexes) {\n const size = sizes.sizeOf(index);\n byId.set(rowIdAt(index), { index, start: start - shift, size });\n start += size;\n }\n }\n this.#pool.pin(keep?.id);\n if (keep && !byId.has(keep.id) && keep.index < count) {\n // Scaled, it is out of view: keep it inside the body (or just above it).\n const size = sizes.sizeOf(keep.index);\n const start = sizes.startOf(keep.index) - shift;\n byId.set(keep.id, {\n index: keep.index,\n start: scale.scaled ? Math.min(Math.max(start, -size), scale.size) : start,\n size,\n });\n }\n const rows: WindowRow[] = [];\n for (const { id, slot } of this.#pool.update(byId.keys())) {\n const item = byId.get(id);\n if (item) rows.push({ slot, ...item });\n }\n return {\n rows,\n totalSize: scale.scaled ? scale.size : sizes.total,\n range: visible ? { start: visible.start, end: visible.end } : null,\n };\n }\n\n /**\n * Auto mode: records the heights of rendered body rows (`[index, px]`), read\n * once their content has rendered. Heights under the row height count as it.\n * Returns whether any changed; the controller then requests the update that\n * places the rows. Rows above the top row keep the rows in view in place.\n */\n measureRows(measured: Iterable<readonly [number, number]>): boolean {\n const { auto, rowIdAt, cap } = this.#config;\n if (!auto) return false;\n const sizes = this.#sizes;\n const element = this.#element;\n const top = element ? this.#top(element) : 0;\n const anchor = sizes.indexAt(top);\n const within = top - sizes.startOf(anchor);\n let changed = false;\n for (const [index, px] of measured) {\n if (index < 0 || index >= sizes.count || !(px > 0)) continue;\n this.#heights.set(rowIdAt(index), px);\n if (sizes.set(index, this.#fit(px)) !== 0) changed = true;\n }\n if (!changed) return false;\n const target = sizes.startOf(anchor) + within;\n const scale = this.#scale;\n const wasScaled = scale.excess > 0;\n const logical = sizes.total;\n if (scale.configure(logical, cap(logical)) && element) {\n if (wasScaled && scale.scaled) {\n // Keep the rows in view where they are without moving the scroller.\n scale.pivot(element.scrollTop, target, element.scrollHeight - element.clientHeight);\n } else if (wasScaled !== scale.scaled) {\n // Once the body has its new height (the write would clamp to the old one).\n this.#anchor = target;\n } else if (Math.abs(target - top) > 0.01) {\n this.#hold += target - top;\n clearTimeout(this.#holdTimer);\n this.#holdTimer = setTimeout(this.#flushHold, HOLD_MS);\n }\n }\n this.#host.requestUpdate();\n return true;\n }\n\n /** The height of the row at `index` (auto mode: measured, or the row height). */\n sizeOf(index: number): number {\n return this.#config.auto ? this.#sizes.sizeOf(index) : this.#config.rowHeight;\n }\n\n /** The logical offset of the row at `index` in the body. */\n startOf(index: number): number {\n return this.#config.auto ? this.#sizes.startOf(index) : index * this.#config.rowHeight;\n }\n\n /**\n * Auto mode: how many rows a page moves from row `from`, the rows that fit in\n * `view` px below it (`down`) or above it. At least one.\n */\n pageRows(from: number, down: boolean, view: number): number {\n const sizes = this.#sizes;\n const start = sizes.startOf(from);\n if (down) return Math.max(1, sizes.indexAt(start + view + 0.01) - from);\n // The first row that starts at most `view` px above this one.\n let to = sizes.indexAt(start - view);\n if (sizes.startOf(to) < start - view - 0.01) to += 1;\n return Math.max(1, from - to);\n }\n\n /** Number of pool slots created so far: bounded by the most rows rendered at once. */\n get poolSize(): number {\n return this.#pool.size;\n }\n\n /** Whether the body is taller than the cap and scrolls scaled. */\n get scaled(): boolean {\n return this.#scale.scaled;\n }\n\n /** The index of the row at the top of the body (0 before the first layout). */\n get topRow(): number {\n const element = this.#element;\n const { count, rowHeight } = this.#config;\n if (!element || count === 0 || rowHeight <= 0) return 0;\n if (this.#config.auto) return this.#sizes.indexAt(this.#top(element) + 0.5);\n // Half a px of slack: engines round scroll offsets to device pixels.\n const row = Math.floor((this.#top(element) + 0.5) / rowHeight);\n return Math.min(Math.max(row, 0), count - 1);\n }\n\n /** `'start'` puts the row at the top; `'auto'` scrolls only as far as needed to show it. */\n scrollToIndex(index: number, align: 'start' | 'auto' = 'start'): void {\n if (this.#config.auto) {\n const element = this.#element;\n if (!element) return;\n if (align === 'auto') {\n this.reveal(index, 0, 0);\n } else {\n this.#write(element, this.#sizes.startOf(Math.min(Math.max(index, 0), this.#sizes.count)));\n }\n this.#host.requestUpdate();\n return;\n }\n this.#virtual.getVirtualizer().scrollToIndex(index, { align });\n }\n\n /** Scrolls by `delta` px of rows (logical px). */\n scrollBy(delta: number): void {\n const element = this.#element;\n if (element) this.#write(element, this.#top(element) + delta);\n }\n\n /** Scrolls to the start or the end of the body. */\n scrollToEdge(edge: 'start' | 'end'): void {\n const element = this.#element;\n // The end: #write clamps to the last logical offset.\n if (element) this.#write(element, edge === 'start' ? 0 : Number.MAX_SAFE_INTEGER);\n }\n\n /**\n * Scrolls the least distance that shows row `index` in full, between the\n * content above the body (`before` px: the header and top pinned rows) and\n * below it (`after` px). Returns whether the rows are yet to move there (with\n * the update after the scroll event): until then the row's DOM isn't where it\n * will be.\n */\n reveal(index: number, before: number, after: number): boolean {\n const element = this.#element;\n if (!element) return false;\n const auto = this.#config.auto !== undefined;\n const offset = this.#top(element);\n const view = element.clientHeight - before - after;\n const start = this.startOf(index);\n const size = this.sizeOf(index);\n let target: number;\n let round: ((px: number) => number) | undefined;\n // A row taller than the view shows its start.\n if (start < offset || (auto && size > view)) {\n target = start;\n round = Math.floor;\n } else if (start + size > offset + view) {\n target = start + size - view;\n round = Math.ceil;\n } else {\n target = offset;\n }\n if (target !== offset) this.#write(element, target, undefined, round);\n // Rendered for an offset the scroller has since left: they move with the\n // update after the scroll event.\n const rendered = auto ? this.#rendered?.top : this.#virtual.getVirtualizer().scrollOffset;\n return rendered !== this.#top(element);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"virtual-controller.js","names":[],"sources":["../../src/controllers/virtual-controller.ts"],"sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit';\nimport { VirtualizerController } from '@tanstack/lit-virtual';\nimport { RowPool, RowSizes, ScrollScale, maxVisibleRows, steadyWindow } from '@argus-table/core';\n\n/** A rendered body row: its pool slot, its index in the row model and its offset. */\nexport interface WindowRow {\n slot: number;\n index: number;\n /** Offset in the rendered body, in px (scaled bodies: relative to the scroll position). */\n start: number;\n /** Measured row heights (`auto`): the row's height, the least it renders at. */\n size?: number;\n}\n\n/**\n * Engines place elements in a body taller than this (2^24 px) only to 2 px:\n * positions are 32-bit floats. Measured rows are whole px below it and an even\n * number of px above it, so every row starts on the grid and they stay flush.\n */\nconst COARSE_BODY = 16_000_000;\n\n/** How long scrolling has to be quiet before a held correction is written (see `#hold`). */\nconst HOLD_MS = 150;\n\nexport interface VirtualWindow {\n /** Rendered rows, in pool order (ADR 0001 addendum). */\n rows: WindowRow[];\n /** Rendered height of the body in px: all rows, or the cap. */\n totalSize: number;\n /** Visible rows, without overscan; `null` before the first layout. */\n range: { start: number; end: number } | null;\n}\n\n/** Measured row heights (`rowHeight: 'auto'`). */\nexport interface AutoRows {\n /** Changes identity whenever the row at an index may have changed (the model's `rowOrder`). */\n order: object;\n /** The index of a row id in the current order. */\n indexOf: (id: string) => number | undefined;\n}\n\nexport interface VirtualConfig {\n count: number;\n /** The row height; with `auto`, the least a row is and what an unmeasured row counts as. */\n rowHeight: number;\n overscan: number;\n /** Row id at an index of the row model; pool slots follow row ids. */\n rowIdAt: (index: number) => string;\n /**\n * The row to keep rendered outside the range (the one holding the grid's tab\n * stop), so its DOM and focus survive scrolling. `undefined`: none.\n */\n keep?: { id: string; index: number } | undefined;\n /** The tallest the body may render when it is `logical` px tall (`bodyCap()`); a taller one scrolls scaled. */\n cap: (logical: number) => number;\n /** Rows of different heights, measured as they render (`measureRows`). */\n auto?: AutoRows | undefined;\n}\n\n/**\n * Row virtualization (TanStack Virtual) plus the row pool: which rows render,\n * where, and which pool slot's DOM each one uses. The virtualizer registers itself\n * with the host and requests updates on scroll.\n *\n * Scroll scaling: engines limit an element's height, so a body taller than the\n * cap renders at the cap and its scroll position maps linearly onto the rows\n * (`ScrollScale`). The virtualizer works in logical offsets: it gets a stand-in\n * for the scroller whose `scrollTop`, `scrollHeight` and `scrollTo()` are\n * logical, and everything else passes through. Rows render relative to the\n * scroll position (`start` minus the difference between the logical and the\n * physical offset). Scroll through this controller (`scrollBy`, `reveal`,\n * `scrollToIndex`): physical px no longer match rows once the body is scaled.\n *\n * Rows of different heights (`auto`): TanStack's own measuring recomputes every\n * row below a measured one, too slow for a million rows, so in this mode the\n * virtualizer sits idle (no rows) and the controller does its part: the window\n * from `RowSizes` (heights by index, O(log n) offsets), a scroll listener that\n * re-renders when the window moves, and anchoring. Heights follow row ids, so\n * a sort or filter keeps them. A measured row above the top row would move\n * the rows in view: unscaled, the rows render shifted by the difference until\n * scrolling stops (`#hold`); scaled, the mapping pivots at the current offset.\n * Neither writes the scroll position while the user scrolls.\n */\nexport class VirtualController implements ReactiveController {\n readonly #virtual: VirtualizerController<HTMLElement, Element>;\n readonly #pool = new RowPool();\n readonly #scale = new ScrollScale();\n readonly #host: ReactiveControllerHost;\n readonly #getScrollElement: () => HTMLElement | null;\n #element: HTMLElement | undefined;\n #standIn: HTMLElement | undefined;\n #config: VirtualConfig = {\n count: 0,\n rowHeight: 40,\n overscan: 6,\n rowIdAt: String,\n cap: () => Infinity,\n };\n /** A logical offset to restore after the update, once the body has its new height. */\n #anchor: number | undefined;\n // Auto mode only.\n readonly #sizes = new RowSizes();\n /** Measured heights by row id (as read), so they follow rows through a sort or filter. */\n readonly #heights = new Map<string, number>();\n /** Rows are whole multiples of this many px (see `COARSE_BODY`). */\n #quantum = 1;\n /**\n * Unscaled: the logical offset less the scroller's. Rows measured above the\n * top row would move the rows in view; instead of writing the scroll position\n * (which cancels a wheel or touch scroll in progress, in WebKit at once) the\n * rows render shifted by this much, and it is written once scrolling has been\n * quiet for `HOLD_MS`.\n */\n #hold = 0;\n #holdTimer: ReturnType<typeof setTimeout> | undefined;\n /** The `order` the sizes are indexed for. */\n #order: object | undefined;\n /** The visible rows last rendered, and the logical offset they were rendered for. */\n #rendered: { start: number; end: number; top: number } | undefined;\n /** The scroller this controller listens to (auto mode). */\n #listening: HTMLElement | undefined;\n #resizeObserver: ResizeObserver | undefined;\n\n constructor(host: ReactiveControllerHost, getScrollElement: () => HTMLElement | null) {\n this.#host = host;\n this.#getScrollElement = getScrollElement;\n this.#virtual = new VirtualizerController<HTMLElement, Element>(host, {\n count: 0,\n getScrollElement: () => this.#scroller(),\n // The real element: a ResizeObserver can't observe the stand-in.\n observeElementRect: (_instance, cb) => {\n const element = this.#element;\n if (!element) return undefined;\n const report = () => {\n cb({ width: element.clientWidth, height: element.clientHeight });\n };\n report();\n const observer = new ResizeObserver(report);\n observer.observe(element);\n return () => {\n observer.disconnect();\n };\n },\n estimateSize: () => this.#config.rowHeight,\n overscan: this.#config.overscan,\n // One window size from the first render: the pool never grows while scrolling\n // (in WebKit, new rows' slots would cut a wheel scroll short; see steadyWindow).\n rangeExtractor: (range) =>\n steadyWindow(\n range,\n maxVisibleRows(this.#element?.clientHeight ?? 0, this.#config.rowHeight),\n ),\n });\n host.addController(this);\n }\n\n /** The scroller's stand-in for the virtualizer: vertical offsets and height in logical px. */\n #scroller(): HTMLElement | null {\n const element = this.#getScrollElement();\n if (!element) return null;\n if (this.#element !== element || !this.#standIn) {\n this.#element = element;\n this.#standIn = new Proxy(element, {\n get: (target, key) => {\n if (key === 'scrollTop') return this.#top(target);\n if (key === 'scrollHeight') return target.scrollHeight + this.#scale.excess;\n if (key === 'scrollTo') {\n return (options: ScrollToOptions) => {\n if (options.top === undefined) target.scrollTo(options);\n else this.#write(target, options.top, options.behavior);\n };\n }\n const value: unknown = Reflect.get(target, key);\n // Methods run on the element itself (DOM methods reject the proxy).\n return typeof value === 'function'\n ? (value as (...args: unknown[]) => unknown).bind(target)\n : value;\n },\n });\n }\n return this.#standIn;\n }\n\n /** The scroller's logical offset. */\n #top(element: HTMLElement): number {\n const top = element.scrollTop;\n const scale = this.#scale;\n return scale.scaled\n ? scale.toLogical(top, element.scrollHeight - element.clientHeight)\n : top + this.#hold;\n }\n\n /**\n * Scrolls to a logical offset. `round` rounds the physical one, so that a\n * scaled offset moves the scroller at all.\n */\n #write(\n element: HTMLElement,\n logical: number,\n behavior?: ScrollBehavior,\n round?: (px: number) => number,\n ): void {\n const scale = this.#scale;\n if (!scale.scaled) {\n this.#dropHold();\n element.scrollTo({ top: logical, behavior });\n return;\n }\n const range = element.scrollHeight - element.clientHeight;\n const target = Math.min(Math.max(logical, 0), range + scale.excess);\n const physical = scale.toPhysical(target, range);\n element.scrollTo({ top: round ? round(physical) : physical, behavior });\n if (behavior !== 'smooth') scale.land(target, element.scrollTop);\n }\n\n configure(next: VirtualConfig): void {\n const prev = this.#config;\n this.#config = next;\n if (next.auto) {\n this.#configureAuto(next.auto, prev);\n return;\n }\n if (prev.auto) {\n // Back to fixed heights: the virtualizer takes over again.\n this.#heights.clear();\n this.#sizes.reset(0, next.rowHeight);\n this.#order = undefined;\n this.#rendered = undefined;\n this.#dropHold();\n this.#virtual.setOptions({ count: next.count, overscan: next.overscan });\n this.#virtual.getVirtualizer().measure();\n } else if (\n next.count !== prev.count ||\n next.overscan !== prev.overscan ||\n next.rowHeight !== prev.rowHeight\n ) {\n this.#virtual.setOptions({ count: next.count, overscan: next.overscan });\n if (next.rowHeight !== prev.rowHeight) this.#virtual.getVirtualizer().measure();\n }\n this.#rescale(next.count * next.rowHeight, this.#virtual.getVirtualizer().scrollOffset);\n }\n\n /**\n * A new logical body height. A new mapping would move the rows under a scaled\n * scroll position (a data source growing, a zoom change): keep the logical\n * offset (read before the new mapping) once the body has its new height.\n */\n #rescale(logical: number, offset: number | null): void {\n // Not `scaled`: TypeScript would take it for unchanged after `configure()`.\n const wasScaled = this.#scale.excess > 0;\n if (!this.#scale.configure(logical, this.#config.cap(logical))) return;\n if ((wasScaled || this.#scale.scaled) && offset !== null && offset > 0) this.#anchor = offset;\n }\n\n #configureAuto(auto: AutoRows, prev: VirtualConfig): void {\n const next = this.#config;\n if (!prev.auto) this.#virtual.setOptions({ count: 0 });\n const sizes = this.#sizes;\n if (!prev.auto || next.rowHeight !== prev.rowHeight) {\n // A new minimum height (density, a theme): every row measures again.\n this.#heights.clear();\n this.#order = undefined;\n }\n const reset = () => {\n sizes.reset(next.count, this.#fit(0), this.#known(auto, next.count));\n };\n if (auto.order !== this.#order) {\n this.#order = auto.order;\n reset();\n } else if (next.count !== sizes.count) {\n // A data source counting more rows: they keep their indexes.\n sizes.resize(next.count);\n }\n // A body past 2^24 px needs even heights: fit the rows to the new grid.\n const quantum = this.#quantumFor(sizes.total);\n if (quantum !== this.#quantum) {\n this.#quantum = quantum;\n reset();\n }\n const element = this.#element;\n this.#rescale(sizes.total, element ? this.#top(element) : 0);\n }\n\n /** The measured heights by index in the current order. Forgets rows long gone. */\n *#known(auto: AutoRows, count: number): Generator<[number, number]> {\n const heights = this.#heights;\n const prune = heights.size > 2 * count + 1000;\n for (const [id, px] of heights) {\n const index = auto.indexOf(id);\n if (index !== undefined && index < count) yield [index, this.#fit(px)];\n else if (prune) heights.delete(id);\n }\n }\n\n /** The px grid rows are on for a body `logical` px tall (see `COARSE_BODY`). */\n #quantumFor(logical: number): number {\n return Math.min(logical, this.#config.cap(logical)) > COARSE_BODY ? 2 : 1;\n }\n\n /** A measured height as a row renders it: on the grid, at least the row height. */\n #fit(px: number): number {\n const q = this.#quantum;\n const up = (value: number) => Math.ceil(value / q - 0.01) * q;\n return Math.max(up(px), up(this.#config.rowHeight));\n }\n\n hostUpdated(): void {\n this.#listen();\n const anchor = this.#anchor;\n if (anchor === undefined) return;\n this.#anchor = undefined;\n if (this.#element) this.#write(this.#element, anchor);\n // The logical offset is the same, so the virtualizer sees no change; the\n // rows still have to move to the new physical one.\n this.#host.requestUpdate();\n }\n\n hostDisconnected(): void {\n this.#unlisten();\n }\n\n /**\n * Auto mode: the virtualizer has no rows, so it reports neither scrolling nor\n * a new viewport size. Listen to the scroller here.\n */\n #listen(): void {\n const element = this.#config.auto ? this.#getScrollElement() : null;\n if (element === (this.#listening ?? null)) return;\n this.#unlisten();\n if (!element) return;\n this.#scroller();\n this.#listening = element;\n element.addEventListener('scroll', this.#onScroll, { passive: true });\n this.#resizeObserver ??= new ResizeObserver(this.#onScroll);\n // Reports the size once right away, which renders the first rows.\n this.#resizeObserver.observe(element);\n }\n\n #unlisten(): void {\n this.#listening?.removeEventListener('scroll', this.#onScroll);\n this.#resizeObserver?.disconnect();\n this.#listening = undefined;\n this.#dropHold();\n }\n\n #dropHold(): void {\n clearTimeout(this.#holdTimer);\n this.#holdTimer = undefined;\n this.#hold = 0;\n }\n\n /** Writes the held correction: the rows stay where they are on screen. */\n readonly #flushHold = () => {\n this.#holdTimer = undefined;\n const element = this.#element;\n if (!element || this.#hold === 0) return;\n const logical = this.#top(element);\n this.#hold = 0;\n element.scrollTo({ top: logical });\n this.#host.requestUpdate();\n };\n\n /** Re-renders when the visible rows have changed. */\n readonly #onScroll = () => {\n if (this.#holdTimer !== undefined) {\n clearTimeout(this.#holdTimer);\n this.#holdTimer = setTimeout(this.#flushHold, HOLD_MS);\n }\n const visible = this.#visible();\n const rendered = this.#rendered;\n if (visible?.start !== rendered?.start || visible?.end !== rendered?.end) {\n this.#host.requestUpdate();\n }\n };\n\n /** Auto mode: the visible rows (without overscan) and the logical offset, or `undefined` before layout. */\n #visible(): { start: number; end: number; top: number } | undefined {\n const element = this.#element;\n const sizes = this.#sizes;\n const view = element?.clientHeight ?? 0;\n if (!element || sizes.count === 0 || view <= 0) return undefined;\n const top = this.#top(element);\n const start = sizes.indexAt(top);\n // The last row that starts above the bottom edge.\n const end = Math.max(start, sizes.indexAt(top + view - 0.5));\n return { start, end, top };\n }\n\n /**\n * Computes the rendered window and updates the pool. Call once per render.\n */\n computeWindow(): VirtualWindow {\n if (this.#config.auto) return this.#autoWindow();\n const v = this.#virtual.getVirtualizer();\n const items = v.getVirtualItems();\n // Scaled: rows render relative to the scroll position. The physical offset is\n // read now, so rows for the virtualizer's offset fill the view even if the\n // scroller has moved on since its last scroll event.\n const scaled = this.#scale.scaled;\n const size = this.#scale.size;\n const shift = scaled ? (v.scrollOffset ?? 0) - (this.#element?.scrollTop ?? 0) : 0;\n const byId = new Map<string, { index: number; start: number }>();\n for (const item of items) {\n byId.set(this.#config.rowIdAt(item.index), { index: item.index, start: item.start - shift });\n }\n const { keep, rowHeight } = this.#config;\n this.#pool.pin(keep?.id);\n if (keep && !byId.has(keep.id)) {\n // Fixed row heights: the virtualizer would place it here too. Scaled, it is\n // out of view: keep it inside the body (or just above it, under the header),\n // so it doesn't stretch the scroll range past the cap.\n const start = keep.index * rowHeight - shift;\n byId.set(keep.id, {\n index: keep.index,\n start: scaled ? Math.min(Math.max(start, -rowHeight), size) : start,\n });\n }\n const rows = this.#pooled(byId);\n const range = v.range;\n return {\n rows,\n totalSize: scaled ? size : v.getTotalSize(),\n range: range ? { start: range.startIndex, end: range.endIndex } : null,\n };\n }\n\n /** The rows to render, in pool order: each holds its pool slot while in range. */\n #pooled(byId: Map<string, Omit<WindowRow, 'slot'>>): WindowRow[] {\n const rows: WindowRow[] = [];\n for (const { id, slot } of this.#pool.update(byId.keys())) {\n const item = byId.get(id);\n if (item) rows.push({ slot, ...item });\n }\n return rows;\n }\n\n /** `computeWindow()` for rows of different heights. */\n #autoWindow(): VirtualWindow {\n const { count, overscan, rowHeight, keep, rowIdAt } = this.#config;\n const sizes = this.#sizes;\n const scale = this.#scale;\n const element = this.#element;\n const visible = this.#visible();\n this.#rendered = visible;\n // Scaled: rows render relative to the scroll position. Unscaled: shifted by the held correction.\n const shift = visible && element ? visible.top - element.scrollTop : 0;\n const byId = new Map<string, { index: number; start: number; size?: number }>();\n if (visible && element) {\n const indexes = steadyWindow(\n { startIndex: visible.start, endIndex: visible.end, overscan, count },\n maxVisibleRows(element.clientHeight, rowHeight),\n );\n const first = indexes[0];\n let start = first === undefined ? 0 : sizes.startOf(first);\n for (const index of indexes) {\n const size = sizes.sizeOf(index);\n byId.set(rowIdAt(index), { index, start: start - shift, size });\n start += size;\n }\n }\n this.#pool.pin(keep?.id);\n if (keep && !byId.has(keep.id) && keep.index < count) {\n // Scaled, it is out of view: keep it inside the body (or just above it).\n const size = sizes.sizeOf(keep.index);\n const start = sizes.startOf(keep.index) - shift;\n byId.set(keep.id, {\n index: keep.index,\n start: scale.scaled ? Math.min(Math.max(start, -size), scale.size) : start,\n size,\n });\n }\n const rows = this.#pooled(byId);\n return {\n rows,\n totalSize: scale.scaled ? scale.size : sizes.total,\n range: visible ? { start: visible.start, end: visible.end } : null,\n };\n }\n\n /**\n * Auto mode: records the heights of rendered body rows (`[index, px]`), read\n * once their content has rendered. Heights under the row height count as it.\n * Returns whether any changed; the controller then requests the update that\n * places the rows. Rows above the top row keep the rows in view in place.\n */\n measureRows(measured: Iterable<readonly [number, number]>): boolean {\n const { auto, rowIdAt, cap } = this.#config;\n if (!auto) return false;\n const sizes = this.#sizes;\n const element = this.#element;\n const top = element ? this.#top(element) : 0;\n const anchor = sizes.indexAt(top);\n const within = top - sizes.startOf(anchor);\n let changed = false;\n for (const [index, px] of measured) {\n if (index < 0 || index >= sizes.count || !(px > 0)) continue;\n this.#heights.set(rowIdAt(index), px);\n if (sizes.set(index, this.#fit(px)) !== 0) changed = true;\n }\n if (!changed) return false;\n const target = sizes.startOf(anchor) + within;\n const scale = this.#scale;\n const wasScaled = scale.excess > 0;\n const logical = sizes.total;\n if (scale.configure(logical, cap(logical)) && element) {\n if (wasScaled && scale.scaled) {\n // Keep the rows in view where they are without moving the scroller.\n scale.pivot(element.scrollTop, target, element.scrollHeight - element.clientHeight);\n } else if (wasScaled !== scale.scaled) {\n // Once the body has its new height (the write would clamp to the old one).\n this.#anchor = target;\n } else if (Math.abs(target - top) > 0.01) {\n this.#hold += target - top;\n clearTimeout(this.#holdTimer);\n this.#holdTimer = setTimeout(this.#flushHold, HOLD_MS);\n }\n }\n this.#host.requestUpdate();\n return true;\n }\n\n /** The height of the row at `index` (auto mode: measured, or the row height). */\n sizeOf(index: number): number {\n return this.#config.auto ? this.#sizes.sizeOf(index) : this.#config.rowHeight;\n }\n\n /** The logical offset of the row at `index` in the body. */\n startOf(index: number): number {\n return this.#config.auto ? this.#sizes.startOf(index) : index * this.#config.rowHeight;\n }\n\n /**\n * Auto mode: how many rows a page moves from row `from`, the rows that fit in\n * `view` px below it (`down`) or above it. At least one.\n */\n pageRows(from: number, down: boolean, view: number): number {\n const sizes = this.#sizes;\n const start = sizes.startOf(from);\n if (down) return Math.max(1, sizes.indexAt(start + view + 0.01) - from);\n // The first row that starts at most `view` px above this one.\n let to = sizes.indexAt(start - view);\n if (sizes.startOf(to) < start - view - 0.01) to += 1;\n return Math.max(1, from - to);\n }\n\n /** Whether the body is taller than the cap and scrolls scaled. */\n get scaled(): boolean {\n return this.#scale.scaled;\n }\n\n /** The index of the row at the top of the body (0 before the first layout). */\n get topRow(): number {\n const element = this.#element;\n const { count, rowHeight } = this.#config;\n if (!element || count === 0 || rowHeight <= 0) return 0;\n if (this.#config.auto) return this.#sizes.indexAt(this.#top(element) + 0.5);\n // Half a px of slack: engines round scroll offsets to device pixels.\n const row = Math.floor((this.#top(element) + 0.5) / rowHeight);\n return Math.min(Math.max(row, 0), count - 1);\n }\n\n /** `'start'` puts the row at the top; `'auto'` scrolls only as far as needed to show it. */\n scrollToIndex(index: number, align: 'start' | 'auto' = 'start'): void {\n if (this.#config.auto) {\n const element = this.#element;\n if (!element) return;\n if (align === 'auto') {\n this.reveal(index, 0, 0);\n } else {\n this.#write(element, this.#sizes.startOf(Math.min(Math.max(index, 0), this.#sizes.count)));\n }\n this.#host.requestUpdate();\n return;\n }\n this.#virtual.getVirtualizer().scrollToIndex(index, { align });\n }\n\n /** Scrolls by `delta` px of rows (logical px). */\n scrollBy(delta: number): void {\n const element = this.#element;\n if (element) this.#write(element, this.#top(element) + delta);\n }\n\n /** Scrolls to the start or the end of the body. */\n scrollToEdge(edge: 'start' | 'end'): void {\n const element = this.#element;\n // The end: #write clamps to the last logical offset.\n if (element) this.#write(element, edge === 'start' ? 0 : Number.MAX_SAFE_INTEGER);\n }\n\n /**\n * Scrolls the least distance that shows row `index` in full, between the\n * content above the body (`before` px: the header and top pinned rows) and\n * below it (`after` px). Returns whether the rows are yet to move there (with\n * the update after the scroll event): until then the row's DOM isn't where it\n * will be.\n */\n reveal(index: number, before: number, after: number): boolean {\n const element = this.#element;\n if (!element) return false;\n const auto = this.#config.auto !== undefined;\n const offset = this.#top(element);\n const view = element.clientHeight - before - after;\n const start = this.startOf(index);\n const size = this.sizeOf(index);\n let target: number;\n let round: ((px: number) => number) | undefined;\n // A row taller than the view shows its start.\n if (start < offset || (auto && size > view)) {\n target = start;\n round = Math.floor;\n } else if (start + size > offset + view) {\n target = start + size - view;\n round = Math.ceil;\n } else {\n target = offset;\n }\n if (target !== offset) this.#write(element, target, undefined, round);\n // Rendered for an offset the scroller has since left: they move with the\n // update after the scroll event.\n const rendered = auto ? this.#rendered?.top : this.#virtual.getVirtualizer().scrollOffset;\n return rendered !== this.#top(element);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// ../core/src/columns/column-groups.ts
|
|
2
|
+
var NO_PATH = [];
|
|
3
|
+
/** A tree without groups. */
|
|
4
|
+
var EMPTY_TREE = {
|
|
5
|
+
groups: /* @__PURE__ */ new Map(),
|
|
6
|
+
pathOf: () => NO_PATH
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Resolves `columnGroups` against the known column ids. Throws on a group without
|
|
10
|
+
* an id or with an id used twice. Unknown column ids are ignored, a column listed
|
|
11
|
+
* twice belongs to the first group that lists it (depth first), and a group left
|
|
12
|
+
* without columns is dropped.
|
|
13
|
+
*/
|
|
14
|
+
function resolveColumnGroups(groups, columnIds) {
|
|
15
|
+
if (!groups?.length) return EMPTY_TREE;
|
|
16
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
18
|
+
const paths = /* @__PURE__ */ new Map();
|
|
19
|
+
let ordinal = 0;
|
|
20
|
+
const visit = (group, path) => {
|
|
21
|
+
if (typeof group.id !== "string" || group.id === "") throw new Error("argus-table: a column group needs a non-empty string id.");
|
|
22
|
+
if (seen.has(group.id)) throw new Error(`argus-table: column group id '${group.id}' is used more than once.`);
|
|
23
|
+
seen.add(group.id);
|
|
24
|
+
const node = {
|
|
25
|
+
id: group.id,
|
|
26
|
+
header: group.header,
|
|
27
|
+
parent: path.at(-1),
|
|
28
|
+
depth: path.length,
|
|
29
|
+
ordinal: ordinal++,
|
|
30
|
+
reorderable: group.reorderable !== false
|
|
31
|
+
};
|
|
32
|
+
const inner = [...path, group.id];
|
|
33
|
+
let kept = false;
|
|
34
|
+
const children = group.columns ?? [];
|
|
35
|
+
for (const child of children) if (typeof child === "string") {
|
|
36
|
+
if (!columnIds.has(child) || paths.has(child)) continue;
|
|
37
|
+
paths.set(child, inner);
|
|
38
|
+
kept = true;
|
|
39
|
+
} else if (child && typeof child === "object") kept = visit(child, inner) || kept;
|
|
40
|
+
if (kept) nodes.set(group.id, node);
|
|
41
|
+
return kept;
|
|
42
|
+
};
|
|
43
|
+
for (const group of groups) visit(group, NO_PATH);
|
|
44
|
+
if (!nodes.size) return EMPTY_TREE;
|
|
45
|
+
return {
|
|
46
|
+
groups: nodes,
|
|
47
|
+
pathOf: (id) => paths.get(id) ?? NO_PATH
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
var SIDES = {
|
|
51
|
+
start: 0,
|
|
52
|
+
center: 1,
|
|
53
|
+
end: 2
|
|
54
|
+
};
|
|
55
|
+
var sideOf = (pin) => pin ?? "center";
|
|
56
|
+
/** The `key` of a group's cell on one pin side. */
|
|
57
|
+
var groupKey = (groupId, pin) => `g:${sideOf(pin)}:${groupId}`;
|
|
58
|
+
/** Number of header rows above the column headers: the deepest nesting among the shown columns. */
|
|
59
|
+
function groupDepth(tree, columns) {
|
|
60
|
+
if (!tree.groups.size) return 0;
|
|
61
|
+
return columns.reduce((depth, c) => Math.max(depth, tree.pathOf(c.id).length), 0);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The group header rows over the shown columns (in visual order), top row first.
|
|
65
|
+
* In each row, consecutive columns of the same group and pin side share a cell; columns
|
|
66
|
+
* without a group at that level share a filler. Empty without groups.
|
|
67
|
+
*/
|
|
68
|
+
function headerGroupRows(tree, columns) {
|
|
69
|
+
const depth = groupDepth(tree, columns);
|
|
70
|
+
const rows = [];
|
|
71
|
+
for (let level = 0; level < depth; level++) {
|
|
72
|
+
const row = [];
|
|
73
|
+
let open;
|
|
74
|
+
const close = () => {
|
|
75
|
+
if (!open) return;
|
|
76
|
+
const { cell, members } = open;
|
|
77
|
+
const first = members[0];
|
|
78
|
+
const last = members.at(-1);
|
|
79
|
+
open = void 0;
|
|
80
|
+
if (!first || !last) return;
|
|
81
|
+
cell.columns = members.map((c) => c.id);
|
|
82
|
+
cell.span = members.length;
|
|
83
|
+
cell.offset = cell.pin === "end" ? last.offset : first.offset;
|
|
84
|
+
cell.pinEdge = cell.pin === "end" ? first.pinEdge : last.pinEdge;
|
|
85
|
+
cell.movable = cell.kind === "group" && cell.groupId !== void 0 && tree.groups.get(cell.groupId)?.reorderable === true && members.every((c) => c.kind === "data" && c.reorderable);
|
|
86
|
+
cell.resizable = cell.kind === "group" && members.some((c) => c.resizable);
|
|
87
|
+
row.push(cell);
|
|
88
|
+
};
|
|
89
|
+
for (const column of columns) {
|
|
90
|
+
const groupId = tree.pathOf(column.id)[level];
|
|
91
|
+
const side = sideOf(column.pin);
|
|
92
|
+
if (open && open.cell.groupId === groupId && sideOf(open.cell.pin) === side) {
|
|
93
|
+
open.members.push(column);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
close();
|
|
97
|
+
const node = groupId === void 0 ? void 0 : tree.groups.get(groupId);
|
|
98
|
+
open = {
|
|
99
|
+
members: [column],
|
|
100
|
+
cell: {
|
|
101
|
+
key: node ? groupKey(node.id, column.pin) : `f:${String(level)}:${column.id}`,
|
|
102
|
+
kind: node ? "group" : "filler",
|
|
103
|
+
groupId: node?.id,
|
|
104
|
+
header: node?.header,
|
|
105
|
+
level,
|
|
106
|
+
columns: [],
|
|
107
|
+
colIndex: column.colIndex,
|
|
108
|
+
span: 0,
|
|
109
|
+
pin: column.pin,
|
|
110
|
+
offset: 0,
|
|
111
|
+
pinEdge: false,
|
|
112
|
+
slot: node ? node.ordinal * 3 + SIDES[side] : -1,
|
|
113
|
+
movable: false,
|
|
114
|
+
resizable: false
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
close();
|
|
119
|
+
rows.push(row);
|
|
120
|
+
}
|
|
121
|
+
return rows;
|
|
122
|
+
}
|
|
123
|
+
/** The cell of a header row over a column, or `undefined`. */
|
|
124
|
+
function cellOver(rows, level, columnId) {
|
|
125
|
+
return rows[level]?.find((c) => c.columns.includes(columnId));
|
|
126
|
+
}
|
|
127
|
+
export { EMPTY_TREE, cellOver, groupDepth, groupKey, headerGroupRows, resolveColumnGroups };
|
|
128
|
+
|
|
129
|
+
//# sourceMappingURL=column-groups.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"column-groups.js","names":[],"sources":["../../../../../core/src/columns/column-groups.ts"],"sourcesContent":["import type { ColumnGroup, ColumnPin } from '../types.js';\n\n/** A group of `columnGroups`, resolved. */\nexport interface GroupNode<TContent> {\n id: string;\n header: ColumnGroup<TContent>['header'];\n /** The enclosing group's id, `undefined` at the top. */\n parent: string | undefined;\n /** Nesting depth: 0 for a top-level group. It is also the header row the group shows in. */\n depth: number;\n /** Position in a depth-first walk of `columnGroups`: a stable number for the group. */\n ordinal: number;\n /** Whether the group can move as a whole (`ColumnGroup.reorderable`). */\n reorderable: boolean;\n}\n\n/** `columnGroups` resolved against the columns. */\nexport interface GroupTree<TContent> {\n readonly groups: ReadonlyMap<string, GroupNode<TContent>>;\n /** The ids of the groups around a column, outermost first; empty for an ungrouped column. */\n pathOf(columnId: string): readonly string[];\n}\n\nconst NO_PATH: readonly string[] = [];\n\n/** A tree without groups. */\nexport const EMPTY_TREE: GroupTree<never> = {\n groups: new Map(),\n pathOf: () => NO_PATH,\n};\n\n/**\n * Resolves `columnGroups` against the known column ids. Throws on a group without\n * an id or with an id used twice. Unknown column ids are ignored, a column listed\n * twice belongs to the first group that lists it (depth first), and a group left\n * without columns is dropped.\n */\nexport function resolveColumnGroups<TContent>(\n groups: readonly ColumnGroup<TContent>[] | undefined,\n columnIds: ReadonlySet<string>,\n): GroupTree<TContent> {\n if (!groups?.length) return EMPTY_TREE;\n const seen = new Set<string>();\n const nodes = new Map<string, GroupNode<TContent>>();\n const paths = new Map<string, readonly string[]>();\n let ordinal = 0;\n // Returns whether the group kept any column.\n const visit = (group: ColumnGroup<TContent>, path: readonly string[]): boolean => {\n if (typeof group.id !== 'string' || group.id === '') {\n throw new Error('argus-table: a column group needs a non-empty string id.');\n }\n if (seen.has(group.id)) {\n throw new Error(`argus-table: column group id '${group.id}' is used more than once.`);\n }\n seen.add(group.id);\n const node: GroupNode<TContent> = {\n id: group.id,\n header: group.header,\n parent: path.at(-1),\n depth: path.length,\n ordinal: ordinal++,\n reorderable: group.reorderable !== false,\n };\n const inner = [...path, group.id];\n let kept = false;\n // In plain JavaScript, `columns` may be missing and hold anything.\n const children = (group.columns as readonly unknown[] | undefined) ?? [];\n for (const child of children) {\n if (typeof child === 'string') {\n if (!columnIds.has(child) || paths.has(child)) continue;\n paths.set(child, inner);\n kept = true;\n } else if (child && typeof child === 'object') {\n kept = visit(child as ColumnGroup<TContent>, inner) || kept;\n }\n }\n if (kept) nodes.set(group.id, node);\n return kept;\n };\n for (const group of groups) visit(group, NO_PATH);\n if (!nodes.size) return EMPTY_TREE;\n return { groups: nodes, pathOf: (id) => paths.get(id) ?? NO_PATH };\n}\n\n/** What header rows need to know about a shown column (a `ColumnView` fits). */\nexport interface GroupedColumn {\n id: string;\n kind: 'data' | 'selection';\n colIndex: number;\n pin: ColumnPin | undefined;\n offset: number;\n pinEdge: boolean;\n reorderable: boolean;\n resizable: boolean;\n}\n\n/** A cell of a group header row: a group over its columns, or a filler over ungrouped ones. */\nexport interface HeaderGroupCell<TContent> {\n /** Unique within the header: the group and its pin side, or the filler's first column. */\n key: string;\n kind: 'group' | 'filler';\n /** The group's id; `undefined` for a filler. */\n groupId: string | undefined;\n header: ColumnGroup<TContent>['header'];\n /** The header row, 0-based from the top. */\n level: number;\n /** The shown columns under the cell, in visual order. */\n columns: readonly string[];\n /** `aria-colindex` of the first column. */\n colIndex: number;\n /** Number of columns under the cell (`aria-colspan`). */\n span: number;\n pin: ColumnPin | undefined;\n /** Sticky inset in px, as `ColumnView.offset`: the start edge's for start-pinned cells, the end edge's for end-pinned ones. */\n offset: number;\n /** `true` when the cell ends at the edge of its pinned side, next to the scrolling area. */\n pinEdge: boolean;\n /** A number unique to the group and its pin side, stable while `columnGroups` is, for slot names. `-1` for a filler. */\n slot: number;\n /** `true` for a group that can move (`reorderable`) and whose shown columns can all be moved. */\n movable: boolean;\n /** `true` for a group with a column that can be resized: resizing the group resizes those. */\n resizable: boolean;\n}\n\nconst SIDES = { start: 0, center: 1, end: 2 } as const;\nconst sideOf = (pin: ColumnPin | undefined) => pin ?? 'center';\n\n/** The `key` of a group's cell on one pin side. */\nexport const groupKey = (groupId: string, pin: ColumnPin | undefined) =>\n `g:${sideOf(pin)}:${groupId}`;\n\n/** Number of header rows above the column headers: the deepest nesting among the shown columns. */\nexport function groupDepth(tree: GroupTree<unknown>, columns: readonly { id: string }[]): number {\n if (!tree.groups.size) return 0;\n return columns.reduce((depth, c) => Math.max(depth, tree.pathOf(c.id).length), 0);\n}\n\n/**\n * The group header rows over the shown columns (in visual order), top row first.\n * In each row, consecutive columns of the same group and pin side share a cell; columns\n * without a group at that level share a filler. Empty without groups.\n */\nexport function headerGroupRows<TContent>(\n tree: GroupTree<TContent>,\n columns: readonly GroupedColumn[],\n): HeaderGroupCell<TContent>[][] {\n const depth = groupDepth(tree, columns);\n const rows: HeaderGroupCell<TContent>[][] = [];\n for (let level = 0; level < depth; level++) {\n const row: HeaderGroupCell<TContent>[] = [];\n let open: { cell: HeaderGroupCell<TContent>; members: GroupedColumn[] } | undefined;\n const close = () => {\n if (!open) return;\n const { cell, members } = open;\n const first = members[0];\n const last = members.at(-1);\n open = undefined;\n if (!first || !last) return;\n cell.columns = members.map((c) => c.id);\n cell.span = members.length;\n cell.offset = cell.pin === 'end' ? last.offset : first.offset;\n cell.pinEdge = cell.pin === 'end' ? first.pinEdge : last.pinEdge;\n cell.movable =\n cell.kind === 'group' &&\n cell.groupId !== undefined &&\n tree.groups.get(cell.groupId)?.reorderable === true &&\n members.every((c) => c.kind === 'data' && c.reorderable);\n cell.resizable = cell.kind === 'group' && members.some((c) => c.resizable);\n row.push(cell);\n };\n for (const column of columns) {\n const groupId = tree.pathOf(column.id)[level];\n const side = sideOf(column.pin);\n if (open && open.cell.groupId === groupId && sideOf(open.cell.pin) === side) {\n open.members.push(column);\n continue;\n }\n close();\n const node = groupId === undefined ? undefined : tree.groups.get(groupId);\n open = {\n members: [column],\n cell: {\n key: node ? groupKey(node.id, column.pin) : `f:${String(level)}:${column.id}`,\n kind: node ? 'group' : 'filler',\n groupId: node?.id,\n header: node?.header,\n level,\n columns: [],\n colIndex: column.colIndex,\n span: 0,\n pin: column.pin,\n offset: 0,\n pinEdge: false,\n slot: node ? node.ordinal * 3 + SIDES[side] : -1,\n movable: false,\n resizable: false,\n },\n };\n }\n close();\n rows.push(row);\n }\n return rows;\n}\n\n/** The cell of a header row over a column, or `undefined`. */\nexport function cellOver<TContent>(\n rows: readonly (readonly HeaderGroupCell<TContent>[])[],\n level: number,\n columnId: string,\n): HeaderGroupCell<TContent> | undefined {\n return rows[level]?.find((c) => c.columns.includes(columnId));\n}\n"],"mappings":"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"}
|
|
@@ -1,14 +1,31 @@
|
|
|
1
|
+
import { EMPTY_TREE } from "./column-groups.js";
|
|
1
2
|
// ../core/src/columns/column-order.ts
|
|
2
|
-
var
|
|
3
|
+
var sideOf = (pin) => pin ?? "center";
|
|
4
|
+
/**
|
|
5
|
+
* Puts the columns of each group next to each other, nested groups within theirs:
|
|
6
|
+
* a group takes the place of its first column, and keeps its columns' order.
|
|
7
|
+
*/
|
|
8
|
+
function cluster(ids, tree, depth = 0) {
|
|
9
|
+
const units = /* @__PURE__ */ new Map();
|
|
10
|
+
for (const id of ids) {
|
|
11
|
+
const group = tree.pathOf(id)[depth];
|
|
12
|
+
const key = group === void 0 ? `c:${id}` : `g:${group}`;
|
|
13
|
+
let members = units.get(key);
|
|
14
|
+
if (!members) units.set(key, members = []);
|
|
15
|
+
members.push(id);
|
|
16
|
+
}
|
|
17
|
+
return [...units].flatMap(([key, members]) => key.startsWith("g:") ? cluster(members, tree, depth + 1) : members);
|
|
18
|
+
}
|
|
3
19
|
/**
|
|
4
20
|
* The order the table shows: start-pinned, scrolling, end-pinned columns, each
|
|
5
|
-
*
|
|
6
|
-
* the order doesn't list follow the listed ones of their
|
|
21
|
+
* side in the sequence of `order`. Unknown and repeated ids are dropped; columns
|
|
22
|
+
* the order doesn't list follow the listed ones of their side, in `columns` order.
|
|
23
|
+
* The columns of a group stay together on each side (see `columnGroups`).
|
|
7
24
|
*/
|
|
8
|
-
function normalizeOrder(order, columns) {
|
|
25
|
+
function normalizeOrder(order, columns, tree = EMPTY_TREE) {
|
|
9
26
|
const byId = new Map(columns.map((c) => [c.id, c]));
|
|
10
27
|
const listed = /* @__PURE__ */ new Set();
|
|
11
|
-
const
|
|
28
|
+
const sides = {
|
|
12
29
|
start: [],
|
|
13
30
|
center: [],
|
|
14
31
|
end: []
|
|
@@ -17,38 +34,115 @@ function normalizeOrder(order, columns) {
|
|
|
17
34
|
const column = byId.get(id);
|
|
18
35
|
if (!column || listed.has(id)) continue;
|
|
19
36
|
listed.add(id);
|
|
20
|
-
|
|
37
|
+
sides[sideOf(column.pin)].push(id);
|
|
21
38
|
}
|
|
22
|
-
for (const column of columns) if (!listed.has(column.id))
|
|
39
|
+
for (const column of columns) if (!listed.has(column.id)) sides[sideOf(column.pin)].push(column.id);
|
|
40
|
+
const { start, center, end } = sides;
|
|
41
|
+
if (!tree.groups.size) return [
|
|
42
|
+
...start,
|
|
43
|
+
...center,
|
|
44
|
+
...end
|
|
45
|
+
];
|
|
23
46
|
return [
|
|
24
|
-
...
|
|
25
|
-
...
|
|
26
|
-
...
|
|
47
|
+
...cluster(start, tree),
|
|
48
|
+
...cluster(center, tree),
|
|
49
|
+
...cluster(end, tree)
|
|
27
50
|
];
|
|
28
51
|
}
|
|
52
|
+
/** Whether a column belongs to a unit: is the column, or one of the group's on its side. */
|
|
53
|
+
function inUnit(unit, id, pinOf, tree) {
|
|
54
|
+
if (unit.kind === "column") return id === unit.id;
|
|
55
|
+
return pinOf.get(id) === sideOf(unit.pin) && tree.pathOf(id).includes(unit.id);
|
|
56
|
+
}
|
|
29
57
|
/**
|
|
30
|
-
* The
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* hidden columns keep their place among the others. `undefined` if the column can't
|
|
34
|
-
* move or isn't shown, `to` is outside the column's group, or nothing changes.
|
|
58
|
+
* The units a unit moves among, in visual order: the columns and groups with the
|
|
59
|
+
* same parent group (or none) on the same pin side, with their shown columns.
|
|
60
|
+
* Without groups, the shown columns of the side. Empty if the unit isn't shown.
|
|
35
61
|
*/
|
|
36
|
-
function
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
62
|
+
function siblingUnits(order, columns, tree, unit, shown = () => true) {
|
|
63
|
+
const pinOf = new Map(columns.map((c) => [c.id, sideOf(c.pin)]));
|
|
64
|
+
let side;
|
|
65
|
+
let parent;
|
|
66
|
+
if (unit.kind === "column") {
|
|
67
|
+
side = pinOf.get(unit.id);
|
|
68
|
+
parent = tree.pathOf(unit.id);
|
|
69
|
+
} else {
|
|
70
|
+
const node = tree.groups.get(unit.id);
|
|
71
|
+
if (!node) return [];
|
|
72
|
+
side = sideOf(unit.pin);
|
|
73
|
+
parent = [];
|
|
74
|
+
for (let id = node.parent; id !== void 0; id = tree.groups.get(id)?.parent) parent = [id, ...parent];
|
|
75
|
+
}
|
|
76
|
+
if (side === void 0) return [];
|
|
77
|
+
const pin = side === "center" ? void 0 : side;
|
|
78
|
+
const depth = parent.length;
|
|
79
|
+
const units = [];
|
|
80
|
+
let last;
|
|
81
|
+
for (const id of normalizeOrder(order, columns, tree)) {
|
|
82
|
+
if (pinOf.get(id) !== side || !shown(id)) continue;
|
|
83
|
+
const path = tree.pathOf(id);
|
|
84
|
+
if (path.length < depth || parent.some((g, i) => path[i] !== g)) continue;
|
|
85
|
+
const group = path[depth];
|
|
86
|
+
const key = group === void 0 ? `c:${id}` : `g:${group}`;
|
|
87
|
+
if (last?.key === key) {
|
|
88
|
+
last.columns.push(id);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
last = {
|
|
92
|
+
key,
|
|
93
|
+
columns: [id]
|
|
94
|
+
};
|
|
95
|
+
units.push({
|
|
96
|
+
unit: group === void 0 ? {
|
|
97
|
+
kind: "column",
|
|
98
|
+
id
|
|
99
|
+
} : {
|
|
100
|
+
kind: "group",
|
|
101
|
+
id: group,
|
|
102
|
+
pin
|
|
103
|
+
},
|
|
104
|
+
columns: last.columns
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return units.some((u) => sameUnit(u.unit, unit)) ? units : [];
|
|
108
|
+
}
|
|
109
|
+
/** Whether two units are the same column, or the same group on the same side. */
|
|
110
|
+
function sameUnit(a, b) {
|
|
111
|
+
if (a.kind !== b.kind || a.id !== b.id) return false;
|
|
112
|
+
return a.kind === "column" || sideOf(a.pin) === sideOf(b.pin);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The order after moving a unit (a column, or a group on one pin side) to position
|
|
116
|
+
* `to` among its siblings (see `siblingUnits`), counted after the move. It lands just
|
|
117
|
+
* before the sibling now at `to`, or after the last one; hidden columns keep their
|
|
118
|
+
* place among the others, and a group's hidden columns move with it. `undefined` if
|
|
119
|
+
* the unit can't move (a column with `reorderable: false`, a group with one), isn't
|
|
120
|
+
* shown, `to` is out of range, or nothing changes. A group with `reorderable: false`
|
|
121
|
+
* doesn't move either.
|
|
122
|
+
*/
|
|
123
|
+
function moveUnit(order, columns, tree, unit, to, shown = () => true) {
|
|
124
|
+
if (!Number.isInteger(to)) return void 0;
|
|
125
|
+
const siblings = siblingUnits(order, columns, tree, unit, shown);
|
|
126
|
+
const from = siblings.findIndex((s) => sameUnit(s.unit, unit));
|
|
127
|
+
const moving = siblings[from];
|
|
128
|
+
if (!moving || to === from || to < 0 || to >= siblings.length) return void 0;
|
|
129
|
+
const byId = new Map(columns.map((c) => [c.id, c]));
|
|
130
|
+
if (!moving.columns.every((id) => byId.get(id)?.reorderable)) return void 0;
|
|
131
|
+
if (unit.kind === "group" && tree.groups.get(unit.id)?.reorderable !== true) return void 0;
|
|
132
|
+
const pinOf = new Map(columns.map((c) => [c.id, sideOf(c.pin)]));
|
|
133
|
+
const current = normalizeOrder(order, columns, tree);
|
|
134
|
+
const block = current.filter((id) => inUnit(unit, id, pinOf, tree));
|
|
135
|
+
const next = current.filter((id) => !inUnit(unit, id, pinOf, tree));
|
|
136
|
+
const others = siblings.filter((_, i) => i !== from);
|
|
137
|
+
const before = others[to];
|
|
138
|
+
const after = others.at(-1);
|
|
139
|
+
let at = next.length;
|
|
140
|
+
if (before) at = next.findIndex((id) => inUnit(before.unit, id, pinOf, tree));
|
|
141
|
+
else if (after) at = next.findLastIndex((id) => inUnit(after.unit, id, pinOf, tree)) + 1;
|
|
142
|
+
next.splice(at, 0, ...block);
|
|
143
|
+
const result = normalizeOrder(next, columns, tree);
|
|
144
|
+
return result.every((id, i) => id === current[i]) ? void 0 : result;
|
|
51
145
|
}
|
|
52
|
-
export {
|
|
146
|
+
export { moveUnit, normalizeOrder, sameUnit, siblingUnits };
|
|
53
147
|
|
|
54
148
|
//# sourceMappingURL=column-order.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"column-order.js","names":[],"sources":["../../../../../core/src/columns/column-order.ts"],"sourcesContent":["import type { ColumnOrderState, ColumnPin } from '../types.js';\n\n/** What ordering needs to know about a column. */\nexport interface OrderableColumn {\n id: string;\n pin: ColumnPin | undefined;\n reorderable: boolean;\n}\n\ntype Group = ColumnPin | 'center';\n\nconst groupOf = (column: OrderableColumn): Group => column.pin ?? 'center';\n\n/**\n * The order the table shows: start-pinned, scrolling, end-pinned columns, each\n * group in the sequence of `order`. Unknown and repeated ids are dropped; columns\n * the order doesn't list follow the listed ones of their group, in `columns` order.\n */\nexport function normalizeOrder(\n order: readonly string[],\n columns: readonly OrderableColumn[],\n): ColumnOrderState {\n const byId = new Map(columns.map((c) => [c.id, c]));\n const listed = new Set<string>();\n const groups: Record<Group, string[]> = { start: [], center: [], end: [] };\n for (const id of order) {\n const column = byId.get(id);\n if (!column || listed.has(id)) continue;\n listed.add(id);\n groups[groupOf(column)].push(id);\n }\n for (const column of columns) {\n if (!listed.has(column.id)) groups[groupOf(column)].push(column.id);\n }\n return [...groups.start, ...groups.center, ...groups.end];\n}\n\n/**\n * The order after moving a column to position `to`: 0-based among the columns\n * `shown` accepts (hidden columns don't count), in the order the table shows them,\n * counted after the move. The column lands just before the shown column now at `to`,\n * hidden columns keep their place among the others. `undefined` if the column can't\n * move or isn't shown, `to` is outside the column's group, or nothing changes.\n */\nexport function moveColumn(\n order: readonly string[],\n columns: readonly OrderableColumn[],\n columnId: string,\n to: number,\n shown: (id: string) => boolean = () => true,\n): ColumnOrderState | undefined {\n const column = columns.find((c) => c.id === columnId);\n if (!column?.reorderable || !Number.isInteger(to) || !shown(columnId)) return undefined;\n const current = normalizeOrder(order, columns);\n const visible = current.filter(shown);\n const from = visible.indexOf(columnId);\n if (to === from) return undefined;\n const pinOf = new Map(columns.map((c) => [c.id, groupOf(c)]));\n const group = groupOf(column);\n const first = visible.findIndex((id) => pinOf.get(id) === group);\n const last = visible.findLastIndex((id) => pinOf.get(id) === group);\n if (to < first || to > last) return undefined;\n const next = current.filter((id) => id !== columnId);\n const before = visible.filter((id) => id !== columnId)[to];\n // Before the shown column now at `to`: one of the next group's, at the end of a\n // group, and normalizing puts the column back at the end of its own.\n next.splice(before === undefined ? next.length : next.indexOf(before), 0, columnId);\n return normalizeOrder(next, columns);\n}\n"],"mappings":";AAWA,IAAM,WAAW,WAAmC,OAAO,OAAO;;;;;;AAOlE,SAAgB,eACd,OACA,SACkB;CAClB,MAAM,OAAO,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CAClD,MAAM,yBAAS,IAAI,IAAY;CAC/B,MAAM,SAAkC;EAAE,OAAO,CAAC;EAAG,QAAQ,CAAC;EAAG,KAAK,CAAC;CAAE;CACzE,KAAK,MAAM,MAAM,OAAO;EACtB,MAAM,SAAS,KAAK,IAAI,EAAE;EAC1B,IAAI,CAAC,UAAU,OAAO,IAAI,EAAE,GAAG;EAC/B,OAAO,IAAI,EAAE;EACb,OAAO,QAAQ,MAAM,EAAE,CAAC,KAAK,EAAE;CACjC;CACA,KAAK,MAAM,UAAU,SACnB,IAAI,CAAC,OAAO,IAAI,OAAO,EAAE,GAAG,OAAO,QAAQ,MAAM,EAAE,CAAC,KAAK,OAAO,EAAE;CAEpE,OAAO;EAAC,GAAG,OAAO;EAAO,GAAG,OAAO;EAAQ,GAAG,OAAO;CAAG;AAC1D;;;;;;;;AASA,SAAgB,WACd,OACA,SACA,UACA,IACA,cAAuC,MACT;CAC9B,MAAM,SAAS,QAAQ,MAAM,MAAM,EAAE,OAAO,QAAQ;CACpD,IAAI,CAAC,QAAQ,eAAe,CAAC,OAAO,UAAU,EAAE,KAAK,CAAC,MAAM,QAAQ,GAAG,OAAO,KAAA;CAC9E,MAAM,UAAU,eAAe,OAAO,OAAO;CAC7C,MAAM,UAAU,QAAQ,OAAO,KAAK;CAEpC,IAAI,OADS,QAAQ,QAAQ,QAClB,GAAM,OAAO,KAAA;CACxB,MAAM,QAAQ,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,EAAE,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;CAC5D,MAAM,QAAQ,QAAQ,MAAM;CAC5B,MAAM,QAAQ,QAAQ,WAAW,OAAO,MAAM,IAAI,EAAE,MAAM,KAAK;CAC/D,MAAM,OAAO,QAAQ,eAAe,OAAO,MAAM,IAAI,EAAE,MAAM,KAAK;CAClE,IAAI,KAAK,SAAS,KAAK,MAAM,OAAO,KAAA;CACpC,MAAM,OAAO,QAAQ,QAAQ,OAAO,OAAO,QAAQ;CACnD,MAAM,SAAS,QAAQ,QAAQ,OAAO,OAAO,QAAQ,CAAC,CAAC;CAGvD,KAAK,OAAO,WAAW,KAAA,IAAY,KAAK,SAAS,KAAK,QAAQ,MAAM,GAAG,GAAG,QAAQ;CAClF,OAAO,eAAe,MAAM,OAAO;AACrC"}
|
|
1
|
+
{"version":3,"file":"column-order.js","names":[],"sources":["../../../../../core/src/columns/column-order.ts"],"sourcesContent":["import type { ColumnOrderState, ColumnPin } from '../types.js';\nimport { EMPTY_TREE, type GroupTree } from './column-groups.js';\n\n/** What ordering needs to know about a column. */\nexport interface OrderableColumn {\n id: string;\n pin: ColumnPin | undefined;\n reorderable: boolean;\n}\n\n/** What moves as one: a column, or a group's columns on one pin side. */\nexport type OrderUnit =\n { kind: 'column'; id: string } | { kind: 'group'; id: string; pin: ColumnPin | undefined };\n\n/** A unit among its siblings, with its shown columns in visual order. */\nexport interface SiblingUnit {\n unit: OrderUnit;\n columns: readonly string[];\n}\n\ntype Side = ColumnPin | 'center';\n\nconst sideOf = (pin: ColumnPin | undefined): Side => pin ?? 'center';\n\n/**\n * Puts the columns of each group next to each other, nested groups within theirs:\n * a group takes the place of its first column, and keeps its columns' order.\n */\nfunction cluster(ids: readonly string[], tree: GroupTree<unknown>, depth = 0): string[] {\n const units = new Map<string, string[]>();\n for (const id of ids) {\n const group = tree.pathOf(id)[depth];\n const key = group === undefined ? `c:${id}` : `g:${group}`;\n let members = units.get(key);\n if (!members) units.set(key, (members = []));\n members.push(id);\n }\n return [...units].flatMap(([key, members]) =>\n key.startsWith('g:') ? cluster(members, tree, depth + 1) : members,\n );\n}\n\n/**\n * The order the table shows: start-pinned, scrolling, end-pinned columns, each\n * side in the sequence of `order`. Unknown and repeated ids are dropped; columns\n * the order doesn't list follow the listed ones of their side, in `columns` order.\n * The columns of a group stay together on each side (see `columnGroups`).\n */\nexport function normalizeOrder(\n order: readonly string[],\n columns: readonly OrderableColumn[],\n tree: GroupTree<unknown> = EMPTY_TREE,\n): ColumnOrderState {\n const byId = new Map(columns.map((c) => [c.id, c]));\n const listed = new Set<string>();\n const sides: Record<Side, string[]> = { start: [], center: [], end: [] };\n for (const id of order) {\n const column = byId.get(id);\n if (!column || listed.has(id)) continue;\n listed.add(id);\n sides[sideOf(column.pin)].push(id);\n }\n for (const column of columns) {\n if (!listed.has(column.id)) sides[sideOf(column.pin)].push(column.id);\n }\n const { start, center, end } = sides;\n if (!tree.groups.size) return [...start, ...center, ...end];\n return [...cluster(start, tree), ...cluster(center, tree), ...cluster(end, tree)];\n}\n\n/** Whether a column belongs to a unit: is the column, or one of the group's on its side. */\nfunction inUnit(\n unit: OrderUnit,\n id: string,\n pinOf: ReadonlyMap<string, Side>,\n tree: GroupTree<unknown>,\n): boolean {\n if (unit.kind === 'column') return id === unit.id;\n return pinOf.get(id) === sideOf(unit.pin) && tree.pathOf(id).includes(unit.id);\n}\n\n/**\n * The units a unit moves among, in visual order: the columns and groups with the\n * same parent group (or none) on the same pin side, with their shown columns.\n * Without groups, the shown columns of the side. Empty if the unit isn't shown.\n */\nexport function siblingUnits(\n order: readonly string[],\n columns: readonly OrderableColumn[],\n tree: GroupTree<unknown>,\n unit: OrderUnit,\n shown: (id: string) => boolean = () => true,\n): SiblingUnit[] {\n const pinOf = new Map(columns.map((c) => [c.id, sideOf(c.pin)]));\n let side: Side | undefined;\n let parent: readonly string[];\n if (unit.kind === 'column') {\n side = pinOf.get(unit.id);\n parent = tree.pathOf(unit.id);\n } else {\n const node = tree.groups.get(unit.id);\n if (!node) return [];\n side = sideOf(unit.pin);\n parent = [];\n for (let id = node.parent; id !== undefined; id = tree.groups.get(id)?.parent) {\n parent = [id, ...parent];\n }\n }\n if (side === undefined) return [];\n const pin = side === 'center' ? undefined : side;\n const depth = parent.length;\n const units: SiblingUnit[] = [];\n let last: { key: string; columns: string[] } | undefined;\n for (const id of normalizeOrder(order, columns, tree)) {\n if (pinOf.get(id) !== side || !shown(id)) continue;\n const path = tree.pathOf(id);\n if (path.length < depth || parent.some((g, i) => path[i] !== g)) continue;\n const group = path[depth];\n const key = group === undefined ? `c:${id}` : `g:${group}`;\n if (last?.key === key) {\n last.columns.push(id);\n continue;\n }\n last = { key, columns: [id] };\n units.push({\n unit: group === undefined ? { kind: 'column', id } : { kind: 'group', id: group, pin },\n columns: last.columns,\n });\n }\n return units.some((u) => sameUnit(u.unit, unit)) ? units : [];\n}\n\n/** Whether two units are the same column, or the same group on the same side. */\nexport function sameUnit(a: OrderUnit, b: OrderUnit): boolean {\n if (a.kind !== b.kind || a.id !== b.id) return false;\n return a.kind === 'column' || sideOf(a.pin) === sideOf((b as typeof a).pin);\n}\n\n/**\n * The order after moving a unit (a column, or a group on one pin side) to position\n * `to` among its siblings (see `siblingUnits`), counted after the move. It lands just\n * before the sibling now at `to`, or after the last one; hidden columns keep their\n * place among the others, and a group's hidden columns move with it. `undefined` if\n * the unit can't move (a column with `reorderable: false`, a group with one), isn't\n * shown, `to` is out of range, or nothing changes. A group with `reorderable: false`\n * doesn't move either.\n */\nexport function moveUnit(\n order: readonly string[],\n columns: readonly OrderableColumn[],\n tree: GroupTree<unknown>,\n unit: OrderUnit,\n to: number,\n shown: (id: string) => boolean = () => true,\n): ColumnOrderState | undefined {\n if (!Number.isInteger(to)) return undefined;\n const siblings = siblingUnits(order, columns, tree, unit, shown);\n const from = siblings.findIndex((s) => sameUnit(s.unit, unit));\n const moving = siblings[from];\n if (!moving || to === from || to < 0 || to >= siblings.length) return undefined;\n const byId = new Map(columns.map((c) => [c.id, c]));\n if (!moving.columns.every((id) => byId.get(id)?.reorderable)) return undefined;\n if (unit.kind === 'group' && tree.groups.get(unit.id)?.reorderable !== true) return undefined;\n const pinOf = new Map(columns.map((c) => [c.id, sideOf(c.pin)]));\n const current = normalizeOrder(order, columns, tree);\n const block = current.filter((id) => inUnit(unit, id, pinOf, tree));\n const next = current.filter((id) => !inUnit(unit, id, pinOf, tree));\n const others = siblings.filter((_, i) => i !== from);\n const before = others[to];\n const after = others.at(-1);\n let at = next.length;\n if (before) at = next.findIndex((id) => inUnit(before.unit, id, pinOf, tree));\n else if (after) at = next.findLastIndex((id) => inUnit(after.unit, id, pinOf, tree)) + 1;\n next.splice(at, 0, ...block);\n const result = normalizeOrder(next, columns, tree);\n return result.every((id, i) => id === current[i]) ? undefined : result;\n}\n"],"mappings":"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"}
|
|
@@ -49,7 +49,6 @@ function resolveColumns(defs) {
|
|
|
49
49
|
const getValue = typeof accessor === "function" ? accessor : typeof accessor === "string" ? (row) => readPath(row, accessor) : () => void 0;
|
|
50
50
|
return {
|
|
51
51
|
id: def.id,
|
|
52
|
-
index,
|
|
53
52
|
header: def.header,
|
|
54
53
|
cell: def.cell,
|
|
55
54
|
getValue,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-columns.js","names":[],"sources":["../../../../../core/src/columns/resolve-columns.ts"],"sourcesContent":["import type { ColumnAlign, ColumnDef, ColumnPin, FilterKind } from '../types.js';\n\n/** Width used when a column sets none. */\nexport const DEFAULT_WIDTH = 150;\n/** Lower bound used when a column sets no `minWidth`. */\nexport const DEFAULT_MIN_WIDTH = 40;\n\n/** A column after defaults, bounds and the accessor have been resolved. */\nexport interface ResolvedColumn<TData, TContent> {\n id: string;\n
|
|
1
|
+
{"version":3,"file":"resolve-columns.js","names":[],"sources":["../../../../../core/src/columns/resolve-columns.ts"],"sourcesContent":["import type { ColumnAlign, ColumnDef, ColumnPin, FilterKind } from '../types.js';\n\n/** Width used when a column sets none. */\nexport const DEFAULT_WIDTH = 150;\n/** Lower bound used when a column sets no `minWidth`. */\nexport const DEFAULT_MIN_WIDTH = 40;\n\n/** A column after defaults, bounds and the accessor have been resolved. */\nexport interface ResolvedColumn<TData, TContent> {\n id: string;\n header: ColumnDef<TData, TContent>['header'];\n cell: ColumnDef<TData, TContent>['cell'];\n /** Reads the cell value; returns `undefined` when the column has no accessor. */\n getValue: (row: TData, index: number) => unknown;\n hasAccessor: boolean;\n /** Base width in px. For a flex column this is its minimum track size. */\n width: number;\n /**\n * Share of free space for `{ flex }` columns, otherwise `undefined`. Kept for pinned\n * columns too: the model drops it while the column is pinned (see `pinnedWidth`).\n */\n flex: number | undefined;\n /**\n * Width in px while the column is pinned. Pinned columns need a known width for their\n * sticky offsets, so a flex column pinned (by its definition or by state) gets the\n * default width, within its bounds. Equal to `width` for every other column.\n */\n pinnedWidth: number;\n /** `true` for `width: 'auto'`: the table measures the content and passes the width in. */\n auto: boolean;\n minWidth: number;\n maxWidth: number;\n /** The pin of the definition; `columnPinning` state can override it. */\n pin: ColumnPin | undefined;\n /** Hidden by the definition; `columnVisibility` state can override it. */\n hidden: boolean;\n sortable: boolean;\n resizable: boolean;\n reorderable: boolean;\n pinnable: boolean;\n hideable: boolean;\n /** The kind of filter users can set, or `undefined` when they can't filter the column. */\n filter: FilterKind | undefined;\n filterFn: ColumnDef<TData, TContent>['filterFn'];\n filterOptions: ColumnDef<TData, TContent>['filterOptions'];\n align: ColumnAlign;\n wrap: boolean;\n}\n\nconst FILTER_KINDS: readonly unknown[] = ['text', 'number', 'date', 'set'] satisfies FilterKind[];\n\nconst pathCache = new Map<string, readonly string[]>();\n\n/**\n * Reads a dotted property path such as `'customer.name'`. Returns `undefined`\n * as soon as a step is `null` or `undefined`, like optional chaining.\n */\nexport function readPath(source: unknown, path: string): unknown {\n let keys = pathCache.get(path);\n if (!keys) {\n keys = path.split('.');\n pathCache.set(path, keys);\n }\n let value: unknown = source;\n for (const key of keys) {\n if (value === null || value === undefined) return undefined;\n value = (value as Record<string, unknown>)[key];\n }\n return value;\n}\n\nfunction finite(value: number | undefined): number | undefined {\n return value !== undefined && Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * Normalizes `ColumnDef`s: checks ids, applies defaults and clamps widths.\n *\n * @throws if an id is empty or used twice, because state and events are keyed by id.\n */\nexport function resolveColumns<TData, TContent>(\n defs: readonly ColumnDef<TData, TContent>[],\n): ResolvedColumn<TData, TContent>[] {\n const seen = new Set<string>();\n return defs.map((def, index) => {\n if (typeof def.id !== 'string' || def.id === '') {\n throw new Error(`argus-table: column at index ${String(index)} needs a non-empty string id.`);\n }\n if (seen.has(def.id)) {\n throw new Error(`argus-table: column id '${def.id}' is used more than once.`);\n }\n seen.add(def.id);\n\n const minWidth = Math.max(0, finite(def.minWidth) ?? DEFAULT_MIN_WIDTH);\n const maxWidth = Math.max(minWidth, finite(def.maxWidth) ?? Number.POSITIVE_INFINITY);\n const clamp = (px: number) => Math.min(maxWidth, Math.max(minWidth, px));\n\n const pin = def.pin === 'start' || def.pin === 'end' ? def.pin : undefined;\n const { width } = def;\n const flex =\n typeof width === 'object' && Number.isFinite(width.flex) && width.flex > 0\n ? width.flex\n : undefined;\n const auto = width === 'auto';\n // An auto column shows the default width until it is measured.\n const fixed = typeof width === 'number' ? finite(width) : undefined;\n const pinnedWidth = clamp(fixed ?? DEFAULT_WIDTH);\n const base = flex !== undefined ? minWidth : pinnedWidth;\n\n const { accessor } = def;\n const getValue: ResolvedColumn<TData, TContent>['getValue'] =\n typeof accessor === 'function'\n ? accessor\n : typeof accessor === 'string'\n ? (row) => readPath(row, accessor)\n : () => undefined;\n\n return {\n id: def.id,\n header: def.header,\n cell: def.cell,\n getValue,\n hasAccessor: accessor !== undefined,\n width: base,\n flex,\n pinnedWidth,\n auto,\n minWidth,\n maxWidth,\n pin,\n hidden: def.hidden === true,\n sortable: def.sortable === true,\n resizable: def.resizable !== false,\n reorderable: def.reorderable !== false,\n pinnable: def.pinnable !== false,\n hideable: def.hideable !== false,\n filter: FILTER_KINDS.includes(def.filter) ? def.filter : undefined,\n filterFn: def.filterFn,\n filterOptions: def.filterOptions,\n align: def.align ?? 'start',\n wrap: def.wrap === true,\n };\n });\n}\n"],"mappings":"AAiDA,IAAM,eAAmC;CAAC;CAAQ;CAAU;CAAQ;AAAK;AAEzE,IAAM,4BAAY,IAAI,IAA+B;;;;;AAMrD,SAAgB,SAAS,QAAiB,MAAuB;CAC/D,IAAI,OAAO,UAAU,IAAI,IAAI;CAC7B,IAAI,CAAC,MAAM;EACT,OAAO,KAAK,MAAM,GAAG;EACrB,UAAU,IAAI,MAAM,IAAI;CAC1B;CACA,IAAI,QAAiB;CACrB,KAAK,MAAM,OAAO,MAAM;EACtB,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAA;EAClD,QAAS,MAAkC;CAC7C;CACA,OAAO;AACT;AAEA,SAAS,OAAO,OAA+C;CAC7D,OAAO,UAAU,KAAA,KAAa,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;AACjE;;;;;;AAOA,SAAgB,eACd,MACmC;CACnC,MAAM,uBAAO,IAAI,IAAY;CAC7B,OAAO,KAAK,KAAK,KAAK,UAAU;EAC9B,IAAI,OAAO,IAAI,OAAO,YAAY,IAAI,OAAO,IAC3C,MAAM,IAAI,MAAM,gCAAgC,OAAO,KAAK,EAAE,8BAA8B;EAE9F,IAAI,KAAK,IAAI,IAAI,EAAE,GACjB,MAAM,IAAI,MAAM,2BAA2B,IAAI,GAAG,0BAA0B;EAE9E,KAAK,IAAI,IAAI,EAAE;EAEf,MAAM,WAAW,KAAK,IAAI,GAAG,OAAO,IAAI,QAAQ,KAAA,EAAsB;EACtE,MAAM,WAAW,KAAK,IAAI,UAAU,OAAO,IAAI,QAAQ,KAAK,OAAO,iBAAiB;EACpF,MAAM,SAAS,OAAe,KAAK,IAAI,UAAU,KAAK,IAAI,UAAU,EAAE,CAAC;EAEvE,MAAM,MAAM,IAAI,QAAQ,WAAW,IAAI,QAAQ,QAAQ,IAAI,MAAM,KAAA;EACjE,MAAM,EAAE,UAAU;EAClB,MAAM,OACJ,OAAO,UAAU,YAAY,OAAO,SAAS,MAAM,IAAI,KAAK,MAAM,OAAO,IACrE,MAAM,OACN,KAAA;EACN,MAAM,OAAO,UAAU;EAGvB,MAAM,cAAc,OADN,OAAO,UAAU,WAAW,OAAO,KAAK,IAAI,KAAA,MAAA,GACV;EAChD,MAAM,OAAO,SAAS,KAAA,IAAY,WAAW;EAE7C,MAAM,EAAE,aAAa;EACrB,MAAM,WACJ,OAAO,aAAa,aAChB,WACA,OAAO,aAAa,YACjB,QAAQ,SAAS,KAAK,QAAQ,UACzB,KAAA;EAEd,OAAO;GACL,IAAI,IAAI;GACR,QAAQ,IAAI;GACZ,MAAM,IAAI;GACV;GACA,aAAa,aAAa,KAAA;GAC1B,OAAO;GACP;GACA;GACA;GACA;GACA;GACA;GACA,QAAQ,IAAI,WAAW;GACvB,UAAU,IAAI,aAAa;GAC3B,WAAW,IAAI,cAAc;GAC7B,aAAa,IAAI,gBAAgB;GACjC,UAAU,IAAI,aAAa;GAC3B,UAAU,IAAI,aAAa;GAC3B,QAAQ,aAAa,SAAS,IAAI,MAAM,IAAI,IAAI,SAAS,KAAA;GACzD,UAAU,IAAI;GACd,eAAe,IAAI;GACnB,OAAO,IAAI,SAAS;GACpB,MAAM,IAAI,SAAS;EACrB;CACF,CAAC;AACH"}
|