@jayalfredprufrock/mobx-toolbox 0.7.1 → 0.8.1
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/README.md +55 -18
- package/dist/form.mjs +2 -2
- package/dist/form.mjs.map +1 -1
- package/dist/{lazy-observable-DXAOlMUT.mjs → lazy-observable-8dTdgRqO.mjs} +22 -2
- package/dist/lazy-observable-8dTdgRqO.mjs.map +1 -0
- package/dist/lazy-observable-CSsG7nEB.d.mts.map +1 -1
- package/dist/lazy-observable.mjs +1 -1
- package/dist/model.mjs +1 -1
- package/dist/react-util.d.mts +14 -3
- package/dist/react-util.d.mts.map +1 -1
- package/dist/react-util.mjs +3 -24
- package/dist/react-util.mjs.map +1 -1
- package/dist/table.d.mts +619 -0
- package/dist/table.d.mts.map +1 -0
- package/dist/table.mjs +1318 -0
- package/dist/table.mjs.map +1 -0
- package/dist/useResize-BhJdvtef.mjs +38 -0
- package/dist/useResize-BhJdvtef.mjs.map +1 -0
- package/package.json +2 -1
- package/dist/lazy-observable-DXAOlMUT.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table.mjs","names":[],"sources":["../src/table/util.ts","../src/table/column.model.ts","../src/table/components/cell-slot.tsx","../src/table/components/cell-style.ts","../src/table/components/checkbox.tsx","../src/table/components/column-resizer.tsx","../src/table/table.context.ts","../src/table/components/table-body.tsx","../src/table/components/table-header.tsx","../src/table/components/selection.tsx","../src/table/components/table-empty.tsx","../src/table/components/table-expansion.tsx","../src/table/use-scroll.ts","../src/table/components/table-root.tsx","../src/table/components/namespace.tsx","../src/table/table.model.ts","../src/table/use-table.ts"],"sourcesContent":["import type { RowData } from \"./table.types\";\n\nexport const titleCase = (str: string): string => {\n return str\n .trim()\n .replace(/([a-z])([A-Z]+)/g, \"$1 $2\")\n .split(/[-_\\s]+/)\n .filter((s) => s.trim())\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())\n .join(\" \");\n};\n\n/**\n * Resolve a column key against a row: a direct property hit wins (so a literal \"a.b\" property\n * still works), otherwise the key is walked as a dot-path (\"owner.name\").\n */\nexport const getPath = (obj: unknown, path: string): unknown => {\n if (obj == null) return undefined;\n const direct = (obj as RowData)[path];\n if (direct !== undefined || !path.includes(\".\")) return direct;\n let current: unknown = obj;\n for (const segment of path.split(\".\")) {\n if (current == null) return undefined;\n current = (current as RowData)[segment];\n }\n return current;\n};\n\n// Values reaching the string fallback below are primitives in practice — numbers and Dates are\n// already handled, and a column whose values are plain objects has no meaningful order anyway. The\n// cast is what lets the type-aware linter accept stringifying an `unknown`.\nconst asString = (value: unknown): string => String(value as string);\n\n/**\n * Default sort comparator over extracted cell values — nullish first, numbers numerically, Dates\n * chronologically, everything else by locale string.\n */\nexport const compareValues = (a: unknown, b: unknown): number => {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();\n return asString(a).localeCompare(asString(b));\n};\n","import { action, computed, makeObservable, observable } from \"mobx\";\nimport type { TableModel } from \"./table.model\";\nimport type { BaseColumnDef, ColumnConfig, ColumnDef, RowData, SortDirection } from \"./table.types\";\nimport { compareValues, getPath, titleCase } from \"./util\";\n\nconst DEFAULT_MIN_WIDTH = 120;\n\n/** Key assigned to a selection column when its def doesn't supply one. */\nexport const SELECTION_COLUMN_KEY = \"__selection__\";\n\nexport class ColumnModel {\n readonly table: TableModel;\n readonly config: ColumnConfig;\n\n pinned: ColumnConfig[\"pinned\"] = false;\n\n // Hidden columns are excluded from layout/rendering (see TableModel.orderedColumns).\n hidden = false;\n\n // Manual override (e.g. drag-to-resize). When set the column is treated as fixed at this width\n // in the distribution; normally unset.\n manualWidth: number | undefined = undefined;\n\n /** Resolved pixel width — distributed across the viewport by the table (see `columnWidths`). */\n get width(): number {\n return this.table.columnWidths.get(this) ?? 0;\n }\n\n /** Fixed pixel width when the column isn't flexible (manual override or an explicit number). */\n get fixedWidth(): number | undefined {\n if (this.manualWidth !== undefined) return this.manualWidth;\n return typeof this.config.width === \"number\" ? this.config.width : undefined;\n }\n\n /** Flex weight (the `N` in `\"Nfr\"`); `0` when fixed. An unspecified width means `1fr`. */\n get grow(): number {\n if (this.fixedWidth !== undefined) return 0;\n if (typeof this.config.width === \"string\") {\n const n = Number.parseFloat(this.config.width);\n return Number.isFinite(n) && n > 0 ? n : 1;\n }\n return 1;\n }\n\n get minWidth(): number {\n return this.config.minWidth ?? DEFAULT_MIN_WIDTH;\n }\n\n get maxWidth(): number {\n return this.config.maxWidth ?? Number.POSITIVE_INFINITY;\n }\n\n get resizable(): boolean {\n return this.config.resizable !== false;\n }\n\n /** Whether header UIs should offer sorting on this column (selection columns never do). */\n get sortable(): boolean {\n return this.config.sortable !== false && !this.selection;\n }\n\n /** Whether this is the built-in row-selection column. */\n get selection(): boolean {\n return this.config.selection === true;\n }\n\n /**\n * True for the innermost pinned column on its side (the one bordering the scrollable area).\n * Consumers hang the pinned boundary shadow off `[data-pinned-edge]` so a group of pinned\n * columns shows a single shadow at the seam.\n */\n get isPinnedEdge(): boolean {\n const siblings = this.pinnedSiblings;\n return siblings ? siblings[siblings.length - 1] === this : false;\n }\n\n /**\n * True for the outermost pinned column on its side (the one at the viewport edge). Used by the\n * header to round its outer corners so a pinned column doesn't paint a square over the rounded\n * header background. Both rendered pinned arrays are ordered outer-edge-first.\n */\n get isPinnedOuterEdge(): boolean {\n const siblings = this.pinnedSiblings;\n return siblings ? siblings[0] === this : false;\n }\n\n get offset(): number {\n const colsMap = {\n left: this.table.leftPinnedRenderedColumns,\n right: this.table.rightPinnedRenderedColumns,\n unpinned: this.table.unpinnedColumns,\n };\n\n const cols = colsMap[this.pinned || \"unpinned\"];\n return cols.slice(0, cols.indexOf(this)).reduce((sum, c) => sum + c.width, 0);\n }\n\n get title(): string {\n return this.config.title ?? titleCase(this.config.key);\n }\n\n /** 1-based visual column position (pinned blocks at the edges) — the aria-colindex value. */\n get ariaColIndex(): number {\n return this.table.visualColumns.indexOf(this) + 1;\n }\n\n get key(): string {\n return this.config.key;\n }\n\n /** Active sort direction for this column, or undefined when it doesn't participate in the sort. */\n get sortDirection(): SortDirection | undefined {\n return this.table.sorts.find((s) => s.key === this.key)?.direction;\n }\n\n /** 1-based position in the sort priority (the \"1\"/\"2\" badge in multi-sort UIs); undefined when unsorted. */\n get sortIndex(): number | undefined {\n const index = this.table.sorts.findIndex((s) => s.key === this.key);\n return index >= 0 ? index + 1 : undefined;\n }\n\n // The rendered pinned block this column belongs to (outer-edge-first), or undefined when unpinned.\n private get pinnedSiblings(): ColumnModel[] | undefined {\n if (this.pinned === \"left\") return this.table.leftPinnedRenderedColumns;\n if (this.pinned === \"right\") return this.table.rightPinnedRenderedColumns;\n return undefined;\n }\n\n constructor(table: TableModel, config: ColumnConfig) {\n this.table = table;\n this.config = config;\n\n makeObservable(this, {\n pinned: observable,\n hidden: observable,\n manualWidth: observable,\n\n width: computed,\n fixedWidth: computed,\n grow: computed,\n isPinnedEdge: computed,\n isPinnedOuterEdge: computed,\n offset: computed,\n title: computed,\n ariaColIndex: computed,\n sortDirection: computed,\n sortIndex: computed,\n\n setPinned: action,\n setManualWidth: action,\n setHidden: action,\n });\n\n this.setPinned(config.pinned);\n }\n\n setPinned(pinned: ColumnConfig[\"pinned\"]): void {\n this.pinned = pinned;\n }\n\n setManualWidth(width: number | undefined): void {\n this.manualWidth = width;\n }\n\n setHidden(hidden: boolean): void {\n this.hidden = hidden;\n }\n\n /** Sort by this column — replaces the sort list unless `preserve: true` (see TableModel.setSort). */\n sortBy(direction: SortDirection, opts?: { preserve?: boolean }): void {\n this.table.setSort(this.key, direction, opts);\n }\n\n /** Remove this column from the sort; other columns' sorts are untouched. */\n clearSort(): void {\n this.table.clearSort(this.key);\n }\n\n /** Raw cell value for a row — what sorting compares and the default render displays. */\n getValue(row: RowData): unknown {\n return this.config.value(row);\n }\n\n /** Ascending comparison of two rows by this column's extracted values (`compare` def or the default). */\n compareRows(a: RowData, b: RowData): number {\n return (this.config.compare ?? compareValues)(this.getValue(a), this.getValue(b));\n }\n\n static fromDef(table: TableModel, def: ColumnDef<any>): ColumnModel {\n const normalizedDef = typeof def === \"string\" ? { key: def } : def;\n const { render, ...config } = normalizedDef as BaseColumnDef<any> & {\n key?: string;\n value?: (row: RowData) => unknown;\n selection?: boolean;\n };\n const key = config.key ?? (config.selection ? SELECTION_COLUMN_KEY : \"\");\n // the raw accessor: computed columns bring their own `value`; field columns resolve the key\n // as a (dot-)path. Selection columns have no value (rendered via <Table.SelectionCell>).\n const value =\n config.value ?? (config.selection ? (): null => null : (row: RowData) => getPath(row, key));\n\n return new ColumnModel(table, {\n ...config,\n key,\n value,\n // a custom render wins for display; sorting always goes through `value`\n render: render ?? value,\n });\n }\n}\n","import { observer } from \"mobx-react-lite\";\nimport type { ReactNode } from \"react\";\nimport type { ColumnModel } from \"../column.model\";\n\nexport type RenderColumn = (column: ColumnModel) => ReactNode;\n\n/**\n * Per-cell reactive boundary. `Table.Header`/`Table.Row` iterate the rendered columns and hand each\n * off to a `CellSlot` rather than calling the consumer's render function inline — so the render runs\n * inside *this* component's MobX reaction. The upshot: a cell re-renders only when the observables\n * *it* reads change (e.g. one field of one row), never because a sibling cell or the row did.\n *\n * It renders a transparent fragment, so whatever the consumer returns (a `<Table.Cell>`) lands\n * directly in the parent grid with no wrapper element.\n */\nexport const CellSlot = observer<{ column: ColumnModel; render: RenderColumn }>(\n ({ column, render }) => {\n return <>{render(column)}</>;\n },\n);\n","import type { CSSProperties } from \"react\";\nimport type { ColumnModel } from \"../column.model\";\n\n/**\n * Structural style shared by header and body cells: pinned cells stick to their edge at the\n * column's offset and must be opaque (they overlap scrolling cells). The opaque fill is a CSS var\n * so the consumer owns the color — set `--table-pinned-bg` once (and override it inside the header\n * to match a header background). `Canvas` is a theme-aware system default for zero-config use.\n *\n * These are the *only* styles the library forces on a cell; everything cosmetic (padding, font,\n * borders, hover) is left to the consumer's `className`/`style`.\n */\nexport const pinnedCellStyle = (column: ColumnModel): CSSProperties => {\n if (!column.pinned) return { position: \"relative\" };\n return {\n position: \"sticky\",\n [column.pinned]: column.offset,\n background: \"var(--table-pinned-bg, Canvas)\",\n // every cell is positioned, so paint order is DOM order — without this lift, the unpinned\n // cells that follow a left-pinned cell would paint over it while scrolling underneath\n zIndex: 1,\n };\n};\n","import { type FC, useEffect, useRef } from \"react\";\n\n/**\n * Props a selection-control component receives. Kept intentionally minimal so any checkbox — the\n * native input below, a Chakra `Checkbox`, a Tailwind one — can satisfy it.\n */\nexport interface TableCheckboxProps {\n checked: boolean;\n indeterminate?: boolean;\n onChange: () => void;\n \"aria-label\"?: string;\n}\n\n/**\n * Zero-config fallback used by `<Table.SelectionCell>` / `<Table.SelectAll>` when the consumer\n * neither registers a `checkbox` on `<Table.Root>` nor passes a render-prop. `indeterminate` is a\n * DOM-only property, so it's applied via ref rather than an attribute.\n */\nexport const NativeCheckbox: FC<TableCheckboxProps> = ({\n checked,\n indeterminate = false,\n onChange,\n ...rest\n}) => {\n const ref = useRef<HTMLInputElement>(null);\n useEffect(() => {\n if (ref.current) ref.current.indeterminate = indeterminate;\n }, [indeterminate]);\n return <input ref={ref} type=\"checkbox\" checked={checked} onChange={onChange} {...rest} />;\n};\n","import { observer } from \"mobx-react-lite\";\nimport { type CSSProperties, type FC, useEffect, useRef, useState } from \"react\";\nimport type { ColumnModel } from \"../column.model\";\n\nexport interface TableResizerProps {\n column: ColumnModel;\n}\n\n/**\n * Drag handle on a header cell's edge that resizes its column. Dragging sets the column's\n * `manualWidth` (treated as a fixed width in the distribution, so the remaining flex columns reflow\n * to fill); double-click resets it to auto. Right-pinned columns are anchored to the right, so their\n * handle sits on the left edge and the drag delta is inverted.\n *\n * Move/up listeners live on the window for the duration of the drag, so the resize tracks and ends\n * wherever the pointer is released — not only over the handle.\n */\nexport const TableResizer: FC<TableResizerProps> = observer(({ column }) => {\n const [resizing, setResizing] = useState(false);\n const drag = useRef<{ startX: number; startWidth: number } | null>(null);\n const raf = useRef<number | undefined>(undefined);\n const teardown = useRef<(() => void) | undefined>(undefined);\n\n const onLeftEdge = column.pinned === \"right\";\n\n // if we unmount mid-drag, drop the window listeners (no state update here — the component is gone)\n useEffect(() => () => teardown.current?.(), []);\n\n const beginResize = (e: React.PointerEvent): void => {\n e.preventDefault();\n e.stopPropagation(); // don't open the header menu\n drag.current = { startX: e.clientX, startWidth: column.width };\n setResizing(true);\n\n let latestX = e.clientX;\n const applyFrame = (): void => {\n raf.current = undefined;\n if (!drag.current) return;\n const delta = latestX - drag.current.startX;\n const next = drag.current.startWidth + (onLeftEdge ? -delta : delta);\n column.setManualWidth(Math.max(column.minWidth, next));\n };\n const onMove = (ev: PointerEvent): void => {\n latestX = ev.clientX;\n if (raf.current === undefined) raf.current = requestAnimationFrame(applyFrame);\n };\n const stop = (): void => {\n drag.current = null;\n setResizing(false);\n teardown.current?.();\n };\n\n teardown.current = (): void => {\n window.removeEventListener(\"pointermove\", onMove);\n window.removeEventListener(\"pointerup\", stop);\n window.removeEventListener(\"pointercancel\", stop);\n if (raf.current !== undefined) {\n cancelAnimationFrame(raf.current);\n raf.current = undefined;\n }\n document.body.style.userSelect = \"\";\n document.body.style.cursor = \"\";\n teardown.current = undefined;\n };\n\n window.addEventListener(\"pointermove\", onMove);\n window.addEventListener(\"pointerup\", stop);\n window.addEventListener(\"pointercancel\", stop);\n // keep the resize cursor and suppress text selection for the whole drag, not just over the handle\n document.body.style.userSelect = \"none\";\n document.body.style.cursor = \"col-resize\";\n };\n\n const resetWidth = (e: React.MouseEvent): void => {\n e.stopPropagation();\n column.setManualWidth(undefined);\n };\n\n const style: CSSProperties = {\n position: \"absolute\",\n top: 0,\n ...(onLeftEdge ? { left: 0 } : { right: 0 }),\n width: \"9px\",\n height: \"100%\",\n cursor: \"col-resize\",\n touchAction: \"none\",\n userSelect: \"none\",\n zIndex: 1,\n };\n\n return (\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n className=\"column-resizer\"\n data-resizing={resizing || undefined}\n onPointerDown={beginResize}\n onDoubleClick={resetWidth}\n style={style}\n />\n );\n});\n","import { createContext, type FC, useContext } from \"react\";\nimport type { TableCheckboxProps } from \"./components/checkbox\";\nimport { NativeCheckbox } from \"./components/checkbox\";\nimport type { TableModel } from \"./table.model\";\n\nexport const tableContext = createContext<TableModel | undefined>(undefined);\nexport const useTableContext = () => {\n const context = useContext(tableContext);\n if (!context) {\n throw new Error(\"Table context not available. Are you within the <Table.Root /> component?\");\n }\n return context;\n};\n\nexport const TableProvider = tableContext.Provider;\n\n/**\n * Slots let a consumer register defaults once on `<Table.Root>` (currently just the selection\n * `checkbox`) that the built-in parts fall back to. Defaults to a native checkbox so selection\n * works with zero wiring.\n */\nexport interface TableSlots {\n checkbox: FC<TableCheckboxProps>;\n}\n\nconst defaultSlots: TableSlots = { checkbox: NativeCheckbox };\n\nexport const slotsContext = createContext<TableSlots>(defaultSlots);\nexport const TableSlotsProvider = slotsContext.Provider;\nexport const useTableSlots = (): TableSlots => useContext(slotsContext);\n","import { observer } from \"mobx-react-lite\";\nimport {\n type CSSProperties,\n type FC,\n Fragment,\n type HTMLAttributes,\n memo,\n type ReactNode,\n} from \"react\";\nimport type { ColumnModel } from \"../column.model\";\nimport { useTableContext } from \"../table.context\";\nimport type { RowData } from \"../table.types\";\nimport { CellSlot, type RenderColumn } from \"./cell-slot\";\nimport { pinnedCellStyle } from \"./cell-style\";\n\nexport interface TableBodyProps {\n className?: string;\n style?: CSSProperties;\n /** Renders one row. Called once per *rendered* row; return a `<Table.Row>`. */\n children: (row: RowData) => ReactNode;\n}\n\n/**\n * The virtualized body. Owns the scroll-sized spacer and the `translate3d` window offset, then maps\n * the rendered slice of rows through the `children` render-prop. Rows are keyed by their row id\n * (see `rowIds`): by default the original index in the source array — stable under sort/filter/\n * scroll and across `appendRows` — or the consumer's `getRowId`, which stays stable even when a\n * refetch replaces the row objects.\n */\nexport const TableBody: FC<TableBodyProps> = observer(({ className, style, children }) => {\n const table = useTableContext();\n\n return (\n <div style={{ width: `${table.virtualWidth}px`, height: `${table.virtualHeight}px` }}>\n <div\n style={{\n position: \"absolute\",\n transform: `translate3d(0px, ${table.virtualOffsetY}px, 0px)`,\n }}\n >\n <div\n role=\"rowgroup\"\n className={className}\n style={{\n display: \"grid\",\n gridTemplateColumns: table.gridTemplateColumns,\n ...style,\n }}\n >\n {table.renderedRows.map((row) => (\n <Fragment key={table.rowIds.get(row)}>{children(row)}</Fragment>\n ))}\n </div>\n </div>\n </div>\n );\n});\n\nexport interface TableRowProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n row: RowData;\n /** Renders one body cell. Called once per rendered column; return a `<Table.Cell>` / `<Table.SelectionCell>`. */\n children: RenderColumn;\n}\n\n/**\n * A single body row. Mirrors the header's layout ownership (grid subgrid, pinned/spacer ordering)\n * and defers each cell to `children` via a per-cell `CellSlot`. Exposes `data-selected` so the\n * consumer can highlight selected rows in CSS. Reading selection here (not row field data) keeps\n * single-cell content updates from re-rendering the whole row — only a selection toggle does.\n */\nconst TableRowInner: FC<TableRowProps> = observer(\n ({ row, className, style, children, ...rest }) => {\n const table = useTableContext();\n const displayIndex = table.displayRowIndexMap.get(row);\n\n return (\n <div\n {...rest}\n role=\"row\"\n // 1-based, offset past the header row (aria-rowindex 1)\n aria-rowindex={displayIndex !== undefined ? displayIndex + 2 : undefined}\n aria-selected={table.selectable ? table.isRowSelected(row) : undefined}\n data-selected={table.isRowSelected(row) || undefined}\n data-expanded={table.isRowExpanded(row) || undefined}\n className={className}\n style={{\n height: `${table.rowHeight}px`,\n display: \"grid\",\n gridColumn: \"1 / -1\",\n gridTemplateColumns: \"subgrid\",\n alignItems: \"stretch\",\n textAlign: \"left\",\n ...style,\n }}\n >\n {table.leftPinnedRenderedColumns.map((col) => (\n <CellSlot key={col.key} column={col} render={children} />\n ))}\n <div role=\"presentation\" />\n {table.unpinnedRenderedColumns.map((col) => (\n <CellSlot key={col.key} column={col} render={children} />\n ))}\n {table.rightPinnedRenderedColumns.map((col) => (\n <CellSlot key={col.key} column={col} render={children} />\n ))}\n </div>\n );\n },\n);\n\n/**\n * Memoized on identity so scrolling (and filtering) only renders rows that actually entered/changed.\n * `children` (the per-column render-prop) gets a fresh closure on every `TableBody` render, so it is\n * *deliberately excluded* from the comparison — for a row still in the window that closure is\n * equivalent (it closes over the same `row` and reads row/column state live). Every other prop —\n * including pass-through DOM props like `onClick` — is compared shallowly. Layout changes still\n * flow through because the inner `observer` re-renders on the column/width observables it reads, and\n * per-cell data changes flow through each `CellSlot`'s own observer — neither is gated by this memo.\n * (Pass stable `className`/`style`/handlers, not fresh inline values, or the row re-renders every frame.)\n */\nexport const TableRow = memo(TableRowInner, (prev, next) => {\n const keys = new Set([...Object.keys(prev), ...Object.keys(next)]);\n for (const key of keys) {\n if (key === \"children\") continue;\n if (prev[key as keyof TableRowProps] !== next[key as keyof TableRowProps]) return false;\n }\n return true;\n});\n\nexport interface TableCellProps extends HTMLAttributes<HTMLDivElement> {\n column: ColumnModel;\n}\n\n/**\n * A single body cell. Owns pinning/offset/`data-pinned*`; cosmetics are the consumer's via\n * `className`/`style`, other DOM props pass through.\n */\nexport const TableCell: FC<TableCellProps> = observer(\n ({ column, children, className, style, ...rest }) => {\n return (\n <div\n {...rest}\n role=\"cell\"\n aria-colindex={column.ariaColIndex}\n data-pinned={column.pinned || undefined}\n data-pinned-corner={\n (column.pinned && column.isPinnedOuterEdge && column.pinned) || undefined\n }\n data-pinned-edge={column.isPinnedEdge || undefined}\n className={className}\n style={{ ...pinnedCellStyle(column), ...style }}\n >\n {children}\n </div>\n );\n },\n);\n","import { observer } from \"mobx-react-lite\";\nimport type { CSSProperties, FC, HTMLAttributes } from \"react\";\nimport type { ColumnModel } from \"../column.model\";\nimport { useTableContext } from \"../table.context\";\nimport { CellSlot, type RenderColumn } from \"./cell-slot\";\nimport { pinnedCellStyle } from \"./cell-style\";\n\nexport interface TableHeaderProps {\n className?: string;\n style?: CSSProperties;\n /**\n * Renders one header cell. Called once per *rendered* column (the library owns which columns are\n * live, their order, and the virtualization spacer); return a `<Table.ColumnHeader>` /\n * `<Table.SelectionHeaderCell>`.\n */\n children: RenderColumn;\n}\n\n/**\n * The sticky header row group. Owns layout — the grid track template, the left-pinned / spacer /\n * unpinned / right-pinned ordering — and defers each cell's content to the `children` render-prop\n * via a per-cell `CellSlot`.\n */\nexport const TableHeader: FC<TableHeaderProps> = observer(({ className, style, children }) => {\n const table = useTableContext();\n\n return (\n <div\n role=\"rowgroup\"\n className={[\"table-header\", className].filter(Boolean).join(\" \")}\n style={{\n position: \"sticky\",\n top: 0,\n zIndex: 20,\n width: `${table.virtualWidth}px`,\n display: \"grid\",\n gridTemplateColumns: table.gridTemplateColumns,\n ...style,\n }}\n >\n {/*\n * The rounded muted header background is a `.table-header::before` layer (consumer CSS). The\n * rowgroup is `virtualWidth` wide, so a background on it would put its corners at the ends of\n * the scrollable content — never both on screen. That layer is instead `position: sticky;\n * left: 0` with an explicit viewport width so both rounded corners stay visible at any scrollX.\n */}\n <div\n role=\"row\"\n aria-rowindex={1}\n style={{\n gridColumn: \"1 / -1\",\n gridRow: \"1\",\n height: `${table.rowHeight}px`,\n display: \"grid\",\n gridTemplateColumns: \"subgrid\",\n alignItems: \"stretch\",\n textAlign: \"left\",\n }}\n >\n {table.leftPinnedRenderedColumns.map((col) => (\n <CellSlot key={col.key} column={col} render={children} />\n ))}\n <div role=\"presentation\" />\n {table.unpinnedRenderedColumns.map((col) => (\n <CellSlot key={col.key} column={col} render={children} />\n ))}\n {table.rightPinnedRenderedColumns.map((col) => (\n <CellSlot key={col.key} column={col} render={children} />\n ))}\n </div>\n </div>\n );\n});\n\nexport interface TableColumnHeaderProps extends HTMLAttributes<HTMLDivElement> {\n column: ColumnModel;\n}\n\n/**\n * A single header cell. Owns the structural bits (sticky pinning, offset, `data-pinned*`) and stays\n * cosmetically open — the consumer's `className`/`style` add padding, font, borders, etc.; other\n * DOM props pass through.\n */\nexport const TableColumnHeader: FC<TableColumnHeaderProps> = observer(\n ({ column, children, className, style, ...rest }) => {\n return (\n <div\n {...rest}\n role=\"columnheader\"\n aria-colindex={column.ariaColIndex}\n aria-sort={\n column.sortDirection\n ? column.sortDirection === \"asc\"\n ? \"ascending\"\n : \"descending\"\n : undefined\n }\n data-pinned={column.pinned || undefined}\n data-pinned-edge={column.isPinnedEdge || undefined}\n data-pinned-corner={\n (column.pinned && column.isPinnedOuterEdge && column.pinned) || undefined\n }\n className={className}\n style={{\n ...pinnedCellStyle(column),\n scrollSnapAlign: column.pinned ? undefined : \"start\",\n ...style,\n }}\n >\n {children}\n </div>\n );\n },\n);\n","import { observer } from \"mobx-react-lite\";\nimport type { FC, ReactNode } from \"react\";\nimport type { ColumnModel } from \"../column.model\";\nimport { useTableContext, useTableSlots } from \"../table.context\";\nimport type { RowData } from \"../table.types\";\nimport type { TableCheckboxProps } from \"./checkbox\";\nimport { TableCell } from \"./table-body\";\nimport { TableColumnHeader } from \"./table-header\";\n\n// The selection state a render-prop receives — a subset of TableCheckboxProps.\ntype RowSelectState = Pick<TableCheckboxProps, \"checked\" | \"onChange\">;\ntype SelectAllState = Pick<TableCheckboxProps, \"checked\" | \"indeterminate\" | \"onChange\">;\n\n// centers the control both axes regardless of the consumer's cell CSS\nconst centerStyle = { display: \"flex\", alignItems: \"center\", justifyContent: \"center\" } as const;\n\nexport interface SelectionCellProps {\n column: ColumnModel;\n row: RowData;\n /** Custom control. Omit to use the checkbox registered on `<Table.Root>` (native by default). */\n children?: (state: RowSelectState) => ReactNode;\n}\n\n/** A body cell wired to per-row selection. Renders the registered checkbox unless given a render-prop. */\nexport const SelectionCell: FC<SelectionCellProps> = observer(({ column, row, children }) => {\n const table = useTableContext();\n const { checkbox: Checkbox } = useTableSlots();\n const state: RowSelectState = {\n checked: table.isRowSelected(row),\n onChange: () => table.toggleRow(row),\n };\n return (\n <TableCell column={column} style={centerStyle}>\n {children ? children(state) : <Checkbox {...state} aria-label=\"Select row\" />}\n </TableCell>\n );\n});\n\nexport interface SelectAllProps {\n /** Custom control. Omit to use the checkbox registered on `<Table.Root>` (native by default). */\n children?: (state: SelectAllState) => ReactNode;\n}\n\n/** The select-all control (checked / indeterminate / none). Place inside a header cell, or use `<Table.SelectionHeaderCell>`. */\nexport const SelectAll: FC<SelectAllProps> = observer(({ children }) => {\n const table = useTableContext();\n const { checkbox: Checkbox } = useTableSlots();\n const state: SelectAllState = {\n checked: table.allRowsSelected,\n indeterminate: table.someRowsSelected,\n onChange: () => table.toggleAllRows(),\n };\n return children ? <>{children(state)}</> : <Checkbox {...state} aria-label=\"Select all rows\" />;\n});\n\nexport interface SelectionHeaderCellProps {\n column: ColumnModel;\n children?: (state: SelectAllState) => ReactNode;\n}\n\n/** A header cell holding the centered select-all control — the header twin of `<Table.SelectionCell>`. */\nexport const SelectionHeaderCell: FC<SelectionHeaderCellProps> = observer(\n ({ column, children }) => {\n return (\n <TableColumnHeader column={column} style={centerStyle}>\n <SelectAll>{children}</SelectAll>\n </TableColumnHeader>\n );\n },\n);\n","import { observer } from \"mobx-react-lite\";\nimport type { FC, HTMLAttributes } from \"react\";\nimport { useTableContext } from \"../table.context\";\n\nexport type TableEmptyProps = HTMLAttributes<HTMLDivElement>;\n\n/**\n * The empty-state surface. Render it after `<Table.Body>`, gated by the consumer — e.g.\n * `table.displayRows.length === 0 && <Table.Empty>…</Table.Empty>` — the library never decides\n * what \"empty\" means or what to say about it (no rows vs. filtered-out are different stories,\n * and only the consumer knows the words and recovery actions).\n *\n * Owns placement only: fills the viewport below the sticky header (subtracting the theme-owned\n * header vars, falling back to the row height) and pins horizontally like the header pill, so\n * children center in the visible area at any horizontal scroll offset. Cosmetics are the\n * consumer's; `data-empty` is the styling hook.\n */\nexport const TableEmpty: FC<TableEmptyProps> = observer(\n ({ children, className, style, ...rest }) => {\n const table = useTableContext();\n return (\n <div\n {...rest}\n data-empty=\"\"\n className={className}\n style={{\n position: \"sticky\",\n left: 0,\n width: \"var(--table-viewport-width)\",\n height: `calc(${table.height}px - var(--table-header-height, ${table.rowHeight}px) - var(--table-header-gap, 0px))`,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n ...style,\n }}\n >\n {children}\n </div>\n );\n },\n);\n","import { observer } from \"mobx-react-lite\";\nimport { type CSSProperties, type FC, memo, type ReactNode } from \"react\";\nimport { useTableContext } from \"../table.context\";\nimport type { RowData } from \"../table.types\";\n\nexport interface TableExpansionProps {\n row: RowData;\n className?: string;\n style?: CSSProperties;\n children?: ReactNode;\n}\n\n/**\n * The detail panel below an expanded row. Render it as a sibling immediately after the row's\n * `<Table.Row>` inside `<Table.Body>`'s render-prop, gated on `table.isRowExpanded(row)`.\n *\n * Owns the geometry contract: the block is exactly `expansionHeight` tall (taller content scrolls\n * internally), and the cell pins to the viewport (`sticky` + explicit width — the same trick as\n * the header background) so horizontal scrolling moves the columns underneath the panel, not the\n * panel itself. Cosmetics are the consumer's via `className`/`style`.\n */\nconst TableExpansionInner: FC<TableExpansionProps> = observer(({ className, style, children }) => {\n const table = useTableContext();\n return (\n <div\n role=\"row\"\n data-expansion=\"\"\n style={{ gridColumn: \"1 / -1\", height: `${table.expansionHeight}px`, minWidth: 0 }}\n >\n <div\n role=\"cell\"\n data-expansion=\"\"\n className={className}\n style={{\n position: \"sticky\",\n left: 0,\n width: \"var(--table-viewport-width)\",\n height: \"100%\",\n overflowY: \"auto\",\n ...style,\n }}\n >\n {children}\n </div>\n </div>\n );\n});\n\n/**\n * Memoized on row identity like `TableRow`, with `children` deliberately excluded: the panel's\n * element tree is rebuilt by the body render-prop every window shift, but for the same row it is\n * equivalent. Panel content must derive from `row` (or be an observer reading live state) — not\n * from other values captured in the render-prop closure.\n */\nexport const TableExpansion = memo(\n TableExpansionInner,\n (prev, next) =>\n prev.row === next.row && prev.className === next.className && prev.style === next.style,\n);\n","import { useEffect, useRef } from \"react\";\n\n/**\n * Reports a scroll container's offsets on every scroll event.\n *\n * `onScroll` is read through a ref so an inline arrow doesn't re-subscribe on every render — the\n * table's root re-renders as the window shifts, and re-attaching the listener each time would be\n * pure waste.\n */\nexport const useScroll = (\n ref: React.RefObject<HTMLElement | null>,\n onScroll: (x: number, y: number) => void,\n): void => {\n const onScrollRef = useRef(onScroll);\n onScrollRef.current = onScroll;\n\n useEffect(() => {\n const scrollContainer = ref.current;\n if (!scrollContainer) {\n return;\n }\n\n // No rAF throttle: scroll events already fire once per frame in the rendering step (the same\n // tick as rAF), so coalescing them is redundant. Reading scrollLeft/Top is cheap and doesn't\n // force layout, and setScroll does near-zero work within a row (integer-bound windowing), so we\n // update synchronously — the change lands before paint with no extra scheduling hop or lag.\n const handleScroll = () =>\n onScrollRef.current(scrollContainer.scrollLeft, scrollContainer.scrollTop);\n\n scrollContainer.addEventListener(\"scroll\", handleScroll, { passive: true });\n return () => scrollContainer.removeEventListener(\"scroll\", handleScroll);\n }, [ref]);\n};\n","import { reaction } from \"mobx\";\nimport { observer } from \"mobx-react-lite\";\nimport { type FC, useEffect, useRef } from \"react\";\nimport { useResize } from \"../../react-util/useResize\";\nimport { TableProvider, TableSlotsProvider } from \"../table.context\";\nimport type { TableModel } from \"../table.model\";\nimport { useScroll } from \"../use-scroll\";\nimport { NativeCheckbox, type TableCheckboxProps } from \"./checkbox\";\n\nexport interface TableRootProps {\n table: TableModel;\n children?: React.ReactNode;\n style?: React.CSSProperties;\n className?: string;\n /**\n * Selection control used by `<Table.SelectionCell>` / `<Table.SelectAll>` when no render-prop is\n * given. Register it once here to capture your app's checkbox everywhere. Defaults to a native\n * `<input type=\"checkbox\">`.\n */\n checkbox?: FC<TableCheckboxProps>;\n}\n\n/**\n * The table skeleton: a non-scrolling viewport wrapper (owns measured size + the shared\n * `--table-row-height` var) around the scroll container that everything else renders into. This is\n * the only structural piece consumers mount directly; header/body compose inside it.\n */\nexport const TableRoot: FC<TableRootProps> = observer(\n ({ table, children, style, className, checkbox }) => {\n const viewportRef = useRef<HTMLDivElement>(null);\n const scrollContainerRef = useRef<HTMLDivElement>(null);\n\n useScroll(scrollContainerRef, (x, y) => table.setScroll(x, y));\n // Height comes from the non-scrolling outer container — measuring the scroll container's height\n // would feed back into its own maxHeight.\n useResize(viewportRef, (_width, height) => table.setHeight(height));\n // Width comes from the scroll container, whose content box excludes the vertical scrollbar (or a\n // reserved `scrollbar-gutter` strip) — so column widths fill the visible area with no phantom\n // horizontal scroll, whether or not the consumer reserves the gutter.\n useResize(scrollContainerRef, (width) => table.setWidth(width));\n\n // Execute programmatic scroll intents (scrollToRow/scrollToEnd). The sticky header's flow\n // height exactly offsets the content's start, so blockOffset values map 1:1 onto scrollTop.\n useEffect(\n () =>\n reaction(\n () => table.scrollRequest,\n (request) => {\n const container = scrollContainerRef.current;\n if (!request || !container) return;\n container.scrollTo({\n top: request.y === \"end\" ? container.scrollHeight : request.y,\n });\n table.clearScrollRequest();\n },\n ),\n [table, scrollContainerRef],\n );\n\n return (\n <TableProvider value={table}>\n <TableSlotsProvider value={{ checkbox: checkbox ?? NativeCheckbox }}>\n <div\n ref={viewportRef}\n className=\"table-viewport\"\n style={\n {\n width: \"100%\",\n height: \"100%\",\n position: \"relative\",\n \"--table-row-height\": `${table.rowHeight}px`,\n } as React.CSSProperties\n }\n >\n <div\n ref={scrollContainerRef}\n role=\"table\"\n // only a window of rows/columns is in the DOM, so assistive tech needs the true\n // extent (+1 row for the header) and each row/cell carries its absolute index\n aria-rowcount={table.displayRows.length + 1}\n aria-colcount={table.orderedColumns.length}\n aria-multiselectable={table.selectable || undefined}\n className={className}\n style={\n {\n position: \"relative\",\n overflow: \"auto\",\n // scroll-state query container: the documented pattern for pinned-edge\n // shadows (`@container scroll-state(scrollable: …)`) needs the container\n // declared here or those consumer rules silently never match\n containerType: \"scroll-state\",\n scrollSnapType: \"x proximity\",\n scrollPaddingLeft: `${table.leftPinnedRenderedColumns.reduce((sum, c) => sum + c.width, 0)}px`,\n width: \"100%\",\n maxHeight: `${table.height}px`,\n // visible content width (vertical scrollbar already excluded); consumed by the\n // rounded header background layer (`.table-header::before`)\n \"--table-viewport-width\": `${table.width}px`,\n ...style,\n } as React.CSSProperties\n }\n >\n {table.width > 0 && table.height > 0 && children}\n </div>\n </div>\n </TableSlotsProvider>\n </TableProvider>\n );\n },\n);\n","import { TableResizer } from \"./column-resizer\";\nimport { SelectAll, SelectionCell, SelectionHeaderCell } from \"./selection\";\nimport { TableBody, TableCell, TableRow } from \"./table-body\";\nimport { TableEmpty } from \"./table-empty\";\nimport { TableExpansion } from \"./table-expansion\";\nimport { TableColumnHeader, TableHeader } from \"./table-header\";\nimport { TableRoot } from \"./table-root\";\n\n/**\n * Compound namespace for the table skeleton. Consumers compose these into their own closed\n * component (styles + defaults captured once), e.g. `<Table.Root><Table.Header>…`.\n */\nexport const Table = {\n Root: TableRoot,\n Header: TableHeader,\n ColumnHeader: TableColumnHeader,\n Body: TableBody,\n Row: TableRow,\n Cell: TableCell,\n Empty: TableEmpty,\n Expansion: TableExpansion,\n Resizer: TableResizer,\n SelectionCell: SelectionCell,\n SelectionHeaderCell: SelectionHeaderCell,\n SelectAll: SelectAll,\n};\n","import {\n action,\n comparer,\n computed,\n type IReactionDisposer,\n makeObservable,\n observable,\n reaction,\n} from \"mobx\";\nimport { ColumnModel } from \"./column.model\";\nimport type {\n ColumnSort,\n ColumnState,\n FilterSource,\n RowData,\n RowId,\n SortDirection,\n TableConfig,\n TableState,\n} from \"./table.types\";\n\nexport class TableModel {\n readonly config?: TableConfig<any>;\n\n rows: RowData[] = [];\n\n columns = new Map<string, ColumnModel>();\n // column keys in display order; maintained by syncColumns and rearranged by moveColumn\n columnOrder: string[] = [];\n // client-side filter sources (AND-composed); each exposes a reactive `predicate`. See setFilter.\n filterSources: FilterSource[] = [];\n\n scrollX = 0;\n scrollY = 0;\n\n height = 0;\n width = 0;\n\n // active column sorts in priority order — earlier entries win, later ones break ties\n // (empty = original row order)\n sorts: ColumnSort[] = [];\n\n // Rows selected via the checkbox column, tracked by row id (see rowIds). Row-scoped state is\n // always stored as ids, never references — `selectedRows` derives the objects back.\n selectedIds = new Set<RowId>();\n\n // Rows expanded to show a detail panel, tracked by row id like selection. Ephemeral: reset by\n // setRows, preserved by appendRows, excluded from persisted TableState.\n expandedIds = new Set<RowId>();\n\n // A pending programmatic scroll. The model owns the intent; <Table.Root> executes it against\n // the scroll container and clears it. \"end\" resolves to the bottom of the content at\n // execution time (the live-tail follow position).\n scrollRequest: { y: number | \"end\" } | undefined = undefined;\n\n // The last snapshot given to applyState. Consulted whenever columns (re)sync, so state applied\n // before the columns exist (applyState before the first setRows, factory defs materializing on\n // first data) still lands. Never re-applied to columns that already exist — later user changes win.\n private appliedState: Partial<TableState> | undefined;\n\n private stateReactionDisposer: IReactionDisposer | undefined;\n\n get rowHeight(): number {\n return this.config?.rowHeight ?? 40;\n }\n\n get rowOverscan(): number {\n return this.config?.rowOverscan ?? 3;\n }\n\n get expansionHeight(): number {\n return this.config?.expansionHeight ?? 320;\n }\n\n get columnOverscan(): number {\n return this.config?.columnOverscan ?? 1;\n }\n\n // row → id, from config.getRowId; defaults to the row's index in the source array, which is\n // equivalent to reference identity for a static dataset and stable across appendRows\n get rowIds(): Map<RowData, RowId> {\n const getRowId = this.config?.getRowId;\n return new Map(this.rows.map((row, i) => [row, getRowId ? getRowId(row, i) : i]));\n }\n\n get allColumns(): ColumnModel[] {\n return this.columnOrder.flatMap((key) => {\n const col = this.columns.get(key);\n return col ? [col] : [];\n });\n }\n\n // hidden columns are excluded from layout and rendering\n get orderedColumns(): ColumnModel[] {\n return this.allColumns.filter((c) => !c.hidden);\n }\n\n /**\n * Resolved pixel width for every column, distributed across the viewport (`width`).\n * Fixed columns (explicit px or a manual override) claim their width; the rest are flex\n * (`\"Nfr\"`, default `1fr`) and share the remaining space by weight, clamped to\n * [minWidth, maxWidth] via a freeze-redistribute pass (a column that hits a clamp is frozen\n * and its share is re-split among the others). Any leftover slack — every flex column capped\n * at its max — is absorbed by the last column so the columns always fill the viewport (this\n * also soaks up sub-pixel rounding). When the minimums don't fit, the total exceeds the\n * viewport and the table scrolls horizontally.\n */\n get columnWidths(): Map<ColumnModel, number> {\n const cols = this.orderedColumns;\n const result = new Map<ColumnModel, number>();\n if (cols.length === 0) return result;\n\n const flex: ColumnModel[] = [];\n let fixedTotal = 0;\n for (const col of cols) {\n if (col.fixedWidth !== undefined) {\n result.set(col, col.fixedWidth);\n fixedTotal += col.fixedWidth;\n } else {\n flex.push(col);\n }\n }\n\n const free = this.width - fixedTotal;\n const frozen = new Set<ColumnModel>();\n\n while (frozen.size < flex.length) {\n const active = flex.filter((c) => !frozen.has(c));\n const frozenTotal = flex.reduce(\n (sum, c) => sum + (frozen.has(c) ? (result.get(c) ?? 0) : 0),\n 0,\n );\n const remaining = free - frozenTotal;\n const totalGrow = active.reduce((sum, c) => sum + c.grow, 0);\n\n if (totalGrow <= 0) {\n for (const c of active) result.set(c, c.minWidth);\n break;\n }\n\n let clamped = false;\n for (const c of active) {\n const share = (remaining * c.grow) / totalGrow;\n if (share < c.minWidth) {\n result.set(c, c.minWidth);\n frozen.add(c);\n clamped = true;\n } else if (share > c.maxWidth) {\n result.set(c, c.maxWidth);\n frozen.add(c);\n clamped = true;\n }\n }\n\n if (!clamped) {\n for (const c of active) result.set(c, (remaining * c.grow) / totalGrow);\n break;\n }\n }\n\n // no-gap: absorb any leftover (underfill) into the last column, even past its max\n const used = cols.reduce((sum, c) => sum + (result.get(c) ?? 0), 0);\n const slack = this.width - used;\n if (slack > 0) {\n const sink = cols[cols.length - 1]!;\n result.set(sink, (result.get(sink) ?? 0) + slack);\n }\n\n return result;\n }\n\n get virtualWidth(): number {\n return this.orderedColumns.reduce((sum, col) => sum + col.width, 0);\n }\n\n get virtualHeight(): number {\n return (\n this.filteredRows.length * this.rowHeight +\n this.expandedDisplayIndices.length * this.expansionHeight\n );\n }\n\n // Display indices of expanded rows, ascending. The expansion geometry below keys off this tiny\n // array (0–few entries), which is what keeps the block math effectively closed-form.\n get expandedDisplayIndices(): number[] {\n if (!this.expandedIds.size) return [];\n const indices: number[] = [];\n this.displayRows.forEach((row, i) => {\n const id = this.rowIds.get(row);\n if (id !== undefined && this.expandedIds.has(id)) indices.push(i);\n });\n return indices;\n }\n\n get unpinnedColumns(): ColumnModel[] {\n return this.orderedColumns.filter((c) => !c.pinned);\n }\n\n get firstUnpinnedRenderedIndex(): number {\n const firstVisibleIndex = this.unpinnedColumns.findIndex((col) => col.offset >= this.scrollX);\n return Math.max(0, firstVisibleIndex - this.columnOverscan);\n }\n\n get lastUnpinnedRenderedIndex(): number {\n const first = this.firstUnpinnedRenderedIndex;\n const maxOffset = this.scrollX + this.width;\n let lastVisibleIndex = this.unpinnedColumns.length - 1;\n for (let i = first; i < this.unpinnedColumns.length; i++) {\n if ((this.unpinnedColumns[i]?.offset ?? 0) >= maxOffset) {\n lastVisibleIndex = i;\n break;\n }\n }\n return Math.min(this.unpinnedColumns.length - 1, lastVisibleIndex + this.columnOverscan);\n }\n\n // Integer-bound like renderedRows, so horizontal scrolling only re-renders on column boundaries.\n get unpinnedRenderedColumns(): ColumnModel[] {\n return this.unpinnedColumns.slice(\n this.firstUnpinnedRenderedIndex,\n this.lastUnpinnedRenderedIndex + 1,\n );\n }\n\n get leftPinnedRenderedColumns(): ColumnModel[] {\n return this.orderedColumns.filter((c) => c.pinned === \"left\");\n }\n\n get rightPinnedRenderedColumns(): ColumnModel[] {\n return this.orderedColumns.filter((c) => c.pinned === \"right\").reverse();\n }\n\n get filteredRows(): RowData[] {\n // read each source's reactive predicate; skip sources with none (pass-through)\n const predicates = this.filterSources.flatMap((s) => (s.predicate ? [s.predicate] : []));\n if (!predicates.length) return this.rows;\n return this.rows.filter((r) => predicates.every((p) => p(r)));\n }\n\n // Rows in display order (filtered, then sorted by the active columns — first non-zero\n // comparison in priority order wins). Comparison goes through each column's value accessor\n // (dot-paths, computed `value` fns) and optional `compare` def — never a raw `row[key]` lookup.\n // Sort keys with no matching column are skipped.\n get displayRows(): RowData[] {\n const rows = this.filteredRows;\n // manual mode: sorts is reactive state for the consumer to serialize; rows arrive pre-sorted\n if (this.config?.sortMode === \"manual\") return rows;\n const active = this.sorts.flatMap(({ key, direction }) => {\n const col = this.columns.get(key);\n return col ? [{ col, dir: direction === \"desc\" ? -1 : 1 }] : [];\n });\n if (!active.length) return rows;\n return [...rows].sort((a, b) => {\n for (const { col, dir } of active) {\n const result = col.compareRows(a, b) * dir;\n if (result !== 0) return result;\n }\n return 0;\n });\n }\n\n get firstRenderedIndex(): number {\n return Math.max(0, this.indexAtOffset(this.scrollY) - this.rowOverscan);\n }\n\n get lastRenderedIndex(): number {\n const lastVisibleIndex = this.indexAtOffset(this.scrollY + this.height);\n return Math.min(this.displayRows.length - 1, lastVisibleIndex + this.rowOverscan);\n }\n\n // Windowed rows. Depends only on the integer slice bounds (which change once per crossed row\n // boundary), never on raw scrollY — so scrolling within a row does not invalidate this computed,\n // and the body re-renders per row boundary instead of on every scroll frame.\n get renderedRows(): RowData[] {\n return this.displayRows.slice(this.firstRenderedIndex, this.lastRenderedIndex + 1);\n }\n\n get virtualOffsetX(): number {\n return this.unpinnedRenderedColumns.at(0)?.offset ?? 0;\n }\n\n get virtualOffsetY(): number {\n return this.blockOffset(this.firstRenderedIndex);\n }\n\n get renderedColumns(): ColumnModel[] {\n return [\n ...this.leftPinnedRenderedColumns,\n ...this.unpinnedRenderedColumns,\n ...this.rightPinnedRenderedColumns,\n ];\n }\n\n // Visible columns in visual (left-to-right) order — pinned blocks at the edges regardless of\n // their position in columnOrder. Backs each column's ariaColIndex.\n get visualColumns(): ColumnModel[] {\n return [\n ...this.leftPinnedRenderedColumns,\n ...this.unpinnedColumns,\n ...this.rightPinnedRenderedColumns.slice().reverse(),\n ];\n }\n\n // row → display index (post filter/sort); backs each row's aria-rowindex\n get displayRowIndexMap(): Map<RowData, number> {\n return new Map(this.displayRows.map((row, i) => [row, i]));\n }\n\n /** Whether the table has a selection column (drives aria-multiselectable / aria-selected). */\n get selectable(): boolean {\n return this.allColumns.some((c) => c.selection);\n }\n\n get gridTemplateColumns(): string {\n const cols: string[] = [];\n cols.push(...this.leftPinnedRenderedColumns.map((c) => `${c.width}px`));\n cols.push(`${this.virtualOffsetX}px`);\n cols.push(...this.unpinnedRenderedColumns.map((c) => `${c.width}px`));\n cols.push(...this.rightPinnedRenderedColumns.map((c) => `${c.width}px`));\n return cols.join(\" \");\n }\n\n /** Whether the viewport is scrolled to (within one row of) the end of the content. */\n get atEnd(): boolean {\n return this.scrollY + this.height >= this.virtualHeight - this.rowHeight;\n }\n\n /** The selected row objects, in source order. Derived from `selectedIds`, so ids without a\n * matching row (possible only if a consumer mutates `selectedIds` directly) drop out. */\n get selectedRows(): RowData[] {\n const selected: RowData[] = [];\n for (const [row, id] of this.rowIds) {\n if (this.selectedIds.has(id)) selected.push(row);\n }\n return selected;\n }\n\n get allRowsSelected(): boolean {\n return this.filteredRows.length > 0 && this.selectedRows.length >= this.filteredRows.length;\n }\n\n get someRowsSelected(): boolean {\n return this.selectedRows.length > 0 && !this.allRowsSelected;\n }\n\n constructor(config?: TableConfig<any>) {\n this.config = config;\n\n makeObservable<this, \"syncColumns\">(this, {\n rows: observable.ref,\n columns: observable,\n columnOrder: observable.ref,\n filterSources: observable.ref,\n scrollX: observable,\n scrollY: observable,\n height: observable,\n width: observable,\n sorts: observable.ref,\n selectedIds: observable.shallow,\n expandedIds: observable.shallow,\n scrollRequest: observable.ref,\n\n rowIds: computed,\n allColumns: computed,\n orderedColumns: computed,\n columnWidths: computed,\n virtualWidth: computed,\n virtualHeight: computed,\n expandedDisplayIndices: computed,\n unpinnedColumns: computed,\n firstUnpinnedRenderedIndex: computed,\n lastUnpinnedRenderedIndex: computed,\n unpinnedRenderedColumns: computed,\n leftPinnedRenderedColumns: computed,\n rightPinnedRenderedColumns: computed,\n filteredRows: computed,\n displayRows: computed,\n firstRenderedIndex: computed,\n lastRenderedIndex: computed,\n renderedRows: computed,\n virtualOffsetX: computed,\n virtualOffsetY: computed,\n renderedColumns: computed,\n visualColumns: computed,\n displayRowIndexMap: computed,\n selectable: computed,\n gridTemplateColumns: computed,\n atEnd: computed,\n selectedRows: computed,\n allRowsSelected: computed,\n someRowsSelected: computed,\n\n applyState: action.bound,\n setFilter: action.bound,\n syncColumns: action,\n moveColumn: action.bound,\n setRows: action,\n appendRows: action.bound,\n setScroll: action.bound,\n scrollToRow: action.bound,\n scrollToEnd: action.bound,\n clearScrollRequest: action.bound,\n setWidth: action.bound,\n setHeight: action.bound,\n setSort: action.bound,\n setSorts: action.bound,\n clearSort: action.bound,\n toggleRow: action.bound,\n selectAllRows: action.bound,\n clearSelection: action.bound,\n toggleRowExpanded: action.bound,\n collapseAllRows: action.bound,\n toggleAllRows: action.bound,\n });\n\n if (config?.filter) {\n this.setFilter(config.filter);\n }\n if (config?.rows) {\n this.setRows(config.rows);\n }\n // registered after initial config so construction itself never fires; structural equality\n // suppresses echoes from unrelated observable churn\n this.activate();\n }\n\n /**\n * (Re)start the `onStateChange` reaction. Pairs with `dispose` — `useTable` calls both across\n * effect cycles, so a StrictMode dev remount (mount → cleanup → mount against the same model)\n * re-arms the reaction instead of leaving the surviving model deaf. No-op when already active\n * or when the config has no `onStateChange`.\n */\n activate(): void {\n const onStateChange = this.config?.onStateChange;\n if (onStateChange && !this.stateReactionDisposer) {\n this.stateReactionDisposer = reaction(() => this.getState(), onStateChange, {\n equals: comparer.structural,\n });\n }\n }\n\n /** Drop the `onStateChange` reaction. Pairs with `activate`. */\n dispose(): void {\n this.stateReactionDisposer?.();\n this.stateReactionDisposer = undefined;\n }\n\n rowId(row: RowData): RowId | undefined {\n return this.rowIds.get(row);\n }\n\n /** Snapshot of the user-curated arrangement (see `TableState`). JSON-serializable. */\n getState(): TableState {\n const columns: Record<string, ColumnState> = {};\n for (const col of this.allColumns) {\n const entry: ColumnState = { hidden: col.hidden, pinned: col.pinned || false };\n if (col.manualWidth !== undefined) entry.width = col.manualWidth;\n columns[col.key] = entry;\n }\n return {\n columnOrder: this.columnOrder.slice(),\n columns,\n sorts: this.sorts.map((s) => ({ ...s })),\n };\n }\n\n /**\n * Restore a (possibly partial) snapshot. Keys with no matching column are kept aside and land\n * when a matching column appears (see `appliedState`); columns the snapshot doesn't mention are\n * left as they are, ordered after the snapshot's columns.\n */\n applyState(state: Partial<TableState>): void {\n this.appliedState = { ...this.appliedState, ...state };\n if (state.columns) {\n for (const col of this.columns.values()) this.applyColumnState(col);\n }\n if (state.columnOrder) {\n this.columnOrder = this.mergedOrder(state.columnOrder);\n }\n // stale sort keys are harmless — displayRows skips sorts with no matching column\n if (state.sorts) {\n this.sorts = state.sorts.map((s) => ({ ...s }));\n }\n }\n\n /** Replace the client-side filter source(s). Pass `undefined` to clear. */\n setFilter(filter: FilterSource | FilterSource[] | undefined): void {\n this.filterSources = filter ? [filter].flat() : [];\n }\n\n /** Move a column to a new index in the display order. */\n moveColumn(key: string, toIndex: number): void {\n const from = this.columnOrder.indexOf(key);\n if (from < 0) return;\n const order = this.columnOrder.slice();\n order.splice(from, 1);\n order.splice(Math.max(0, Math.min(order.length, toIndex)), 0, key);\n this.columnOrder = order;\n }\n\n /** Replace the dataset. Row-keyed state (selection, expansion) is reset — ids from the old world\n * (indices by default) must not silently attach to new rows. Use `appendRows` to add without resetting. */\n setRows(rows: RowData[]): void {\n this.rows = rows;\n this.syncColumns();\n this.selectedIds.clear();\n this.expandedIds.clear();\n }\n\n /** Append rows without resetting row-keyed state — the \"load more\" path. Existing rows keep\n * their positions, so default (index) ids stay stable and selection survives. */\n appendRows(rows: RowData[]): void {\n this.rows = [...this.rows, ...rows];\n this.syncColumns();\n }\n\n setScroll(x: number, y: number): void {\n this.scrollX = x;\n this.scrollY = y;\n }\n\n /** Content offset of a display index's block top (row plus any expansion panels above it). */\n blockOffset(index: number): number {\n return index * this.rowHeight + this.expandedAbove(index) * this.expansionHeight;\n }\n\n /** Scroll so the row's block top lands at the viewport top, or its block end at the bottom. */\n scrollToRow(row: RowData, align: \"top\" | \"bottom\" = \"top\"): void {\n const index = this.displayRowIndexMap.get(row);\n if (index === undefined) return;\n if (align === \"top\") {\n this.scrollRequest = { y: this.blockOffset(index) };\n return;\n }\n const id = this.rowIds.get(row);\n const expanded = id !== undefined && this.expandedIds.has(id);\n const blockEnd =\n this.blockOffset(index) + this.rowHeight + (expanded ? this.expansionHeight : 0);\n this.scrollRequest = { y: Math.max(0, blockEnd - this.height) };\n }\n\n /** Scroll to the very end of the content. */\n scrollToEnd(): void {\n this.scrollRequest = { y: \"end\" };\n }\n\n clearScrollRequest(): void {\n this.scrollRequest = undefined;\n }\n\n setWidth(width: number): void {\n this.width = width;\n }\n\n setHeight(height: number): void {\n this.height = height;\n }\n\n /**\n * Set a column's sort. By default the whole sort list is replaced (single-sort behavior).\n * With `preserve: true` existing sorts are kept: a column already in the list changes\n * direction in place (keeping its priority), a new column is appended at the lowest priority.\n */\n setSort(key: string, direction: SortDirection, opts?: { preserve?: boolean }): void {\n if (!opts?.preserve) {\n this.sorts = [{ key, direction }];\n return;\n }\n const sorts = this.sorts.slice();\n const existing = sorts.findIndex((s) => s.key === key);\n if (existing >= 0) sorts[existing] = { key, direction };\n else sorts.push({ key, direction });\n this.sorts = sorts;\n }\n\n /** Replace the whole sort list at once (restoring a saved view); `setSort` covers per-column interactions. */\n setSorts(sorts: ColumnSort[]): void {\n this.sorts = sorts.map((s) => ({ ...s }));\n }\n\n /** Remove one column from the sort (later entries move up in priority), or all sorts when no key is given. */\n clearSort(key?: string): void {\n this.sorts = key === undefined ? [] : this.sorts.filter((s) => s.key !== key);\n }\n\n isRowSelected(row: RowData): boolean {\n const id = this.rowIds.get(row);\n return id !== undefined && this.selectedIds.has(id);\n }\n\n toggleRow(row: RowData): void {\n const id = this.rowIds.get(row);\n if (id === undefined) return;\n if (this.selectedIds.has(id)) this.selectedIds.delete(id);\n else this.selectedIds.add(id);\n }\n\n selectAllRows(): void {\n this.selectedIds.clear();\n for (const row of this.filteredRows) {\n const id = this.rowIds.get(row);\n if (id !== undefined) this.selectedIds.add(id);\n }\n }\n\n clearSelection(): void {\n this.selectedIds.clear();\n }\n\n toggleAllRows(): void {\n if (this.allRowsSelected) this.clearSelection();\n else this.selectAllRows();\n }\n\n isRowExpanded(row: RowData): boolean {\n const id = this.rowIds.get(row);\n return id !== undefined && this.expandedIds.has(id);\n }\n\n toggleRowExpanded(row: RowData): void {\n const id = this.rowIds.get(row);\n if (id === undefined) return;\n if (this.expandedIds.has(id)) {\n this.expandedIds.delete(id);\n } else {\n if (this.config?.expandMode === \"single\") this.expandedIds.clear();\n this.expandedIds.add(id);\n }\n }\n\n collapseAllRows(): void {\n this.expandedIds.clear();\n }\n\n private syncColumns(): void {\n const firstRow = this.rows?.at(0);\n\n // when no columns are specified, we use the object keys\n const columnsDef = this.config?.columns ?? Object.keys(firstRow ?? {});\n\n // the factory form allows for dynamic columns, which use the first\n // row of data to construct the column definition(s)\n const syncedColumns = columnsDef.flatMap((defOrFactory) => {\n if (typeof defOrFactory === \"function\") {\n if (!firstRow) return [];\n return [defOrFactory(firstRow)].flat().map((def) => ColumnModel.fromDef(this, def));\n }\n return ColumnModel.fromDef(this, defOrFactory);\n });\n\n const syncedKeys = new Set(syncedColumns.map((c) => c.config.key));\n\n // remove any stale\n for (const key of this.columns.keys()) {\n if (!syncedKeys.has(key)) {\n this.columns.delete(key);\n }\n }\n\n // add any new columns; freshly created ones pick up persisted state (applyState may have\n // run before they existed — e.g. before the first setRows)\n const firstSync = this.columns.size === 0;\n for (const column of syncedColumns) {\n if (!this.columns.has(column.config.key)) {\n this.columns.set(column.config.key, column);\n this.applyColumnState(column);\n }\n }\n\n // maintain display order: keep the current order for surviving columns, append new ones\n const keys = syncedColumns.map((c) => c.config.key);\n this.columnOrder = [\n ...this.columnOrder.filter((k) => keys.includes(k)),\n ...keys.filter((k) => !this.columnOrder.includes(k)),\n ];\n\n // on the first sync there is no user-made order yet, so the persisted order wins outright;\n // afterwards it is never re-applied — later user rearrangement beats the stored snapshot\n if (firstSync && this.appliedState?.columnOrder) {\n this.columnOrder = this.mergedOrder(this.appliedState.columnOrder);\n }\n }\n\n private applyColumnState(col: ColumnModel): void {\n const state = this.appliedState?.columns?.[col.key];\n if (!state) return;\n col.setHidden(state.hidden);\n col.setPinned(state.pinned);\n col.setManualWidth(state.width);\n }\n\n // snapshot order first (unknown keys dropped), then current columns the snapshot doesn't know\n private mergedOrder(order: string[]): string[] {\n const known = order.filter((k) => this.columns.has(k));\n const rest = this.columnOrder.filter((k) => !known.includes(k));\n return [...known, ...rest];\n }\n\n // number of expansion panels fully above the given display index\n private expandedAbove(index: number): number {\n let count = 0;\n for (const i of this.expandedDisplayIndices) {\n if (i < index) count++;\n else break;\n }\n return count;\n }\n\n /**\n * The display index of the row whose block (row + its expansion panel, if any) contains the\n * vertical content offset `y`. Walks the expanded indices accumulating their extra height —\n * a row scrolled past its own top stays \"at\" `y` while its panel is in view, so expanded rows\n * render as long as any part of their block does.\n */\n private indexAtOffset(y: number): number {\n const { rowHeight, expansionHeight } = this;\n let extra = 0;\n for (const i of this.expandedDisplayIndices) {\n const panelTop = (i + 1) * rowHeight + extra;\n if (panelTop > y) break;\n if (panelTop + expansionHeight > y) return i;\n extra += expansionHeight;\n }\n return Math.floor((y - extra) / rowHeight);\n }\n}\n","import { useEffect, useRef } from \"react\";\nimport { TableModel } from \"./table.model\";\nimport type { TableConfig } from \"./table.types\";\n\nexport const useTable = <T>(config?: TableConfig<T>): TableModel => {\n const tableRef = useRef<TableModel | undefined>(undefined);\n if (!tableRef.current) {\n tableRef.current = new TableModel(config);\n }\n\n // The model's onStateChange reaction must die with the component or it leaks past unmount.\n // activate/dispose as an effect pair (not dispose alone) because StrictMode dev remounts run\n // cleanup against a model the surviving ref will hand out again.\n useEffect(() => {\n tableRef.current?.activate();\n return () => tableRef.current?.dispose();\n }, []);\n\n return tableRef.current;\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/react-util/useResize.ts
|
|
4
|
+
/**
|
|
5
|
+
* Reports an element's **content-box** size whenever it changes, including the initial measurement.
|
|
6
|
+
*
|
|
7
|
+
* Content box means padding, border and scrollbars are all excluded — so a width fed back into
|
|
8
|
+
* layout math can't feed back into its own overflow, and a gutter appearing or disappearing is
|
|
9
|
+
* reported as a real size change. Values are fractional; round at the point of use if you need
|
|
10
|
+
* integers.
|
|
11
|
+
*
|
|
12
|
+
* `onResize` is read through a ref, so passing an inline arrow does not tear down and re-create the
|
|
13
|
+
* observer on every render.
|
|
14
|
+
*/
|
|
15
|
+
const useResize = (ref, onResize) => {
|
|
16
|
+
const onResizeRef = useRef(onResize);
|
|
17
|
+
onResizeRef.current = onResize;
|
|
18
|
+
useLayoutEffect(() => {
|
|
19
|
+
const el = ref.current;
|
|
20
|
+
if (!el) return;
|
|
21
|
+
if (typeof ResizeObserver !== "function") {
|
|
22
|
+
const handleResize = () => onResizeRef.current(el.clientWidth, el.clientHeight);
|
|
23
|
+
handleResize();
|
|
24
|
+
window.addEventListener("resize", handleResize);
|
|
25
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
26
|
+
}
|
|
27
|
+
const observer = new ResizeObserver((entries) => {
|
|
28
|
+
const entry = entries[0];
|
|
29
|
+
if (entry) onResizeRef.current(entry.contentRect.width, entry.contentRect.height);
|
|
30
|
+
});
|
|
31
|
+
observer.observe(el);
|
|
32
|
+
return () => observer.disconnect();
|
|
33
|
+
}, [ref]);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { useResize as t };
|
|
38
|
+
//# sourceMappingURL=useResize-BhJdvtef.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useResize-BhJdvtef.mjs","names":[],"sources":["../src/react-util/useResize.ts"],"sourcesContent":["import { useLayoutEffect, useRef } from \"react\";\n\n/**\n * Reports an element's **content-box** size whenever it changes, including the initial measurement.\n *\n * Content box means padding, border and scrollbars are all excluded — so a width fed back into\n * layout math can't feed back into its own overflow, and a gutter appearing or disappearing is\n * reported as a real size change. Values are fractional; round at the point of use if you need\n * integers.\n *\n * `onResize` is read through a ref, so passing an inline arrow does not tear down and re-create the\n * observer on every render.\n */\nexport const useResize = (\n ref: React.RefObject<HTMLElement | null>,\n onResize: (width: number, height: number) => void,\n): void => {\n const onResizeRef = useRef(onResize);\n onResizeRef.current = onResize;\n\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el) {\n return;\n }\n\n if (typeof ResizeObserver !== \"function\") {\n // Fallback for environments without ResizeObserver. `clientWidth`/`clientHeight` are the\n // padding box (scrollbars already excluded), so this over-reports by any padding — close\n // enough for a path no supported browser takes.\n const handleResize = () => onResizeRef.current(el.clientWidth, el.clientHeight);\n handleResize();\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }\n\n // The initial observation is delivered before the first paint, so no eager measure is needed.\n const observer = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (entry) {\n onResizeRef.current(entry.contentRect.width, entry.contentRect.height);\n }\n });\n\n observer.observe(el);\n return () => observer.disconnect();\n }, [ref]);\n};\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAa,aACX,KACA,aACS;CACT,MAAM,cAAc,OAAO,QAAQ;CACnC,YAAY,UAAU;CAEtB,sBAAsB;EACpB,MAAM,KAAK,IAAI;EACf,IAAI,CAAC,IACH;EAGF,IAAI,OAAO,mBAAmB,YAAY;GAIxC,MAAM,qBAAqB,YAAY,QAAQ,GAAG,aAAa,GAAG,YAAY;GAC9E,aAAa;GACb,OAAO,iBAAiB,UAAU,YAAY;GAC9C,aAAa,OAAO,oBAAoB,UAAU,YAAY;EAChE;EAGA,MAAM,WAAW,IAAI,gBAAgB,YAAY;GAC/C,MAAM,QAAQ,QAAQ;GACtB,IAAI,OACF,YAAY,QAAQ,MAAM,YAAY,OAAO,MAAM,YAAY,MAAM;EAEzE,CAAC;EAED,SAAS,QAAQ,EAAE;EACnB,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,GAAG,CAAC;AACV"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jayalfredprufrock/mobx-toolbox",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Misc mobx/react modules/utilities.",
|
|
5
5
|
"homepage": "https://github.com/jayalfredprufrock/mobx-toolbox#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"./model": "./dist/model.mjs",
|
|
23
23
|
"./react-util": "./dist/react-util.mjs",
|
|
24
24
|
"./router": "./dist/router.mjs",
|
|
25
|
+
"./table": "./dist/table.mjs",
|
|
25
26
|
"./util": "./dist/util.mjs",
|
|
26
27
|
"./package.json": "./package.json"
|
|
27
28
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-observable-DXAOlMUT.mjs","names":[],"sources":["../src/lazy-observable/lazy-observable.ts"],"sourcesContent":["import {\n _allowStateChanges,\n autorun,\n type IObservableArray,\n type IReactionDisposer,\n observable,\n onBecomeObserved,\n onBecomeUnobserved,\n} from \"mobx\";\n\nexport interface LazyObservable<T = any, TInitialValue = T | undefined> {\n value: TInitialValue;\n reset(): TInitialValue;\n getOrLoad(): Promise<T>;\n set(value: T): void;\n reload(): Promise<T>;\n status: \"init\" | \"loading\" | \"loaded\" | \"error\";\n error: unknown;\n loading: boolean;\n loaded: boolean;\n}\n\nexport interface LazyObservableOptions {\n shallow?: boolean;\n resetOnUnobserved?: \"never\" | \"always\" | number;\n debugName?: string;\n}\n\nexport interface LazyObservableOptionsWithInitialValue<\n TInitialValue,\n> extends LazyObservableOptions {\n initialValue?: TInitialValue;\n}\n\nexport function lazyObservable<T>(fetch: () => Promise<T>): LazyObservable<T>;\nexport function lazyObservable<T>(\n fetch: () => Promise<T>,\n options: LazyObservableOptions,\n): LazyObservable<T>;\nexport function lazyObservable<T, TInitialValue>(\n fetch: () => Promise<T>,\n options: LazyObservableOptionsWithInitialValue<TInitialValue>,\n): LazyObservable<T, TInitialValue>;\n\nexport function lazyObservable<T>(\n fetch: () => Promise<T>,\n options?: LazyObservableOptionsWithInitialValue<T>,\n): LazyObservable<T> {\n const value = observable.box<T>(options?.initialValue, { deep: !options?.shallow });\n const status = observable.box<LazyObservable<T>[\"status\"]>(\"init\");\n const error = observable.box<unknown>(undefined);\n\n let resetTimer: NodeJS.Timeout;\n let autorunDisposer: IReactionDisposer;\n\n let promise: Promise<T> | undefined;\n let promiseResolve: ((value: T) => void) | undefined;\n let promiseReject: ((value: unknown) => void) | undefined;\n\n const getOrCreatePromise = () => {\n if (promise && status.get() !== \"loaded\" && status.get() !== \"error\") {\n return promise;\n }\n promise = new Promise<T>((resolve, reject) => {\n promiseResolve = resolve;\n promiseReject = reject;\n });\n return promise;\n };\n\n const reset = () => {\n clearTimeout(resetTimer);\n autorunDisposer?.();\n _allowStateChanges(true, () => {\n value.set(options?.initialValue);\n status.set(\"init\");\n error.set(undefined);\n });\n return options?.initialValue;\n };\n\n const load = (): void => {\n if (status.get() === \"loading\") return;\n clearTimeout(resetTimer);\n\n // TODO: this disposer is unreliable while we are using\n // babel to convert all async function to generators\n autorunDisposer?.();\n autorunDisposer = autorun(() => {\n _allowStateChanges(true, () => {\n status.set(\"loading\");\n error.set(undefined);\n });\n\n let fetchPromise: Promise<T>;\n try {\n fetchPromise = fetch();\n } catch (e) {\n _allowStateChanges(true, () => {\n error.set(e);\n status.set(\"error\");\n promiseReject?.(e);\n });\n return;\n }\n\n fetchPromise\n .then((newValue) => {\n _allowStateChanges(true, () => {\n value.set(newValue);\n status.set(\"loaded\");\n promiseResolve?.(newValue);\n });\n })\n .catch((e) => {\n _allowStateChanges(true, () => {\n error.set(e);\n status.set(\"error\");\n promiseReject?.(e);\n });\n });\n });\n };\n\n // TODO: should this resolve any promises?\n const set = (val: T): void => {\n _allowStateChanges(true, () => {\n value.set(val);\n status.set(\"loaded\");\n });\n };\n\n let observedCount = 0;\n\n const onObserved = () => {\n observedCount = Math.min(2, observedCount + 1);\n\n // only consider observed the first time this handler gets called\n if (observedCount !== 1) return;\n\n if (options?.debugName) {\n console.log(`lazyObservable ${options.debugName}`, \"observed\");\n }\n clearTimeout(resetTimer);\n if (status.get() === \"error\" || status.get() === \"init\") {\n load();\n }\n };\n\n const onUnobserved = () => {\n observedCount = Math.max(0, observedCount - 1);\n\n // only consider unobserved when count reaches zero\n if (observedCount) return;\n\n if (options?.debugName) {\n console.log(`lazyObservable ${options.debugName}`, \"unobserved\");\n }\n\n // Errors are never cached regardless of resetOnUnobserved — failure state\n // shouldn't persist across mounts, only successfully loaded values should.\n if (status.get() === \"error\") {\n _allowStateChanges(true, () => {\n status.set(\"init\");\n error.set(undefined);\n });\n return;\n }\n\n if (options?.resetOnUnobserved === \"never\") {\n return;\n } else if (typeof options?.resetOnUnobserved === \"number\") {\n resetTimer = setTimeout(() => {\n reset();\n }, options?.resetOnUnobserved);\n } else {\n reset();\n }\n };\n\n onBecomeObserved(value, onObserved);\n onBecomeObserved(status, onObserved);\n onBecomeObserved(error, onObserved);\n\n onBecomeUnobserved(value, onUnobserved);\n onBecomeUnobserved(status, onUnobserved);\n onBecomeUnobserved(error, onUnobserved);\n\n return {\n get value() {\n return value.get();\n },\n get status() {\n return status.get();\n },\n get error() {\n return error.get();\n },\n get loading() {\n return status.get() === \"loading\";\n },\n get loaded() {\n return status.get() === \"loaded\";\n },\n reload() {\n const promise = getOrCreatePromise();\n load();\n return promise;\n },\n getOrLoad() {\n // TODO: this assertion shouldn't be necessary\n // consider fixing box/value type\n if (this.loaded) return Promise.resolve(value.get() as Promise<T>);\n return this.reload();\n },\n set,\n reset,\n };\n}\n\nexport interface LazyObservableArray<T = any> extends Omit<\n // value is never undefined: lazyObservableArray always seeds initialValue with []\n LazyObservable<IObservableArray<T>, IObservableArray<T>>,\n \"set\"\n> {\n set(value: T[]): void;\n}\n\nexport interface LazyObservableArrayOptions<T> extends LazyObservableOptions {\n initialValue?: T[];\n}\n\nexport function lazyObservableArray<T>(\n fetch: () => Promise<T[]>,\n options?: LazyObservableArrayOptions<T>,\n): LazyObservableArray<T> {\n return lazyObservable(fetch, {\n ...options,\n initialValue: options?.initialValue ?? [],\n }) as LazyObservableArray<T>;\n}\n\nexport type InferLazyObservable<O> =\n O extends LazyObservableArray<infer T> ? T[] : O extends LazyObservable<infer T> ? T : never;\n"],"mappings":"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"}
|