argus-table 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +25 -2
  2. package/dist/announce.js +27 -0
  3. package/dist/announce.js.map +1 -0
  4. package/dist/argus-column.js +121 -0
  5. package/dist/argus-column.js.map +1 -0
  6. package/dist/argus-table.js +585 -83
  7. package/dist/argus-table.js.map +1 -1
  8. package/dist/cdn/argus-table.js +79 -4533
  9. package/dist/cdn/argus-table.js.map +1 -1
  10. package/dist/controllers/auto-width.js +89 -0
  11. package/dist/controllers/auto-width.js.map +1 -0
  12. package/dist/controllers/column-children.js +41 -0
  13. package/dist/controllers/column-children.js.map +1 -0
  14. package/dist/controllers/data-controller.js +177 -0
  15. package/dist/controllers/data-controller.js.map +1 -0
  16. package/dist/controllers/reorder-drag.js +160 -0
  17. package/dist/controllers/reorder-drag.js.map +1 -0
  18. package/dist/controllers/resize-drag.js +3 -0
  19. package/dist/controllers/resize-drag.js.map +1 -1
  20. package/dist/controllers/table-controller.js +2 -1
  21. package/dist/controllers/table-controller.js.map +1 -1
  22. package/dist/controllers/virtual-controller.js +146 -9
  23. package/dist/controllers/virtual-controller.js.map +1 -1
  24. package/dist/core/src/columns/column-order.js +50 -0
  25. package/dist/core/src/columns/column-order.js.map +1 -0
  26. package/dist/core/src/columns/resolve-columns.js +5 -1
  27. package/dist/core/src/columns/resolve-columns.js.map +1 -1
  28. package/dist/core/src/data/page-cache.js +143 -0
  29. package/dist/core/src/data/page-cache.js.map +1 -0
  30. package/dist/core/src/grid/row-layout.js +58 -0
  31. package/dist/core/src/grid/row-layout.js.map +1 -0
  32. package/dist/core/src/table/create-table.js +112 -14
  33. package/dist/core/src/table/create-table.js.map +1 -1
  34. package/dist/core/src/table/sorted-row-model.js +86 -0
  35. package/dist/core/src/table/sorted-row-model.js.map +1 -0
  36. package/dist/core/src/virtual/scroll-scale.js +74 -0
  37. package/dist/core/src/virtual/scroll-scale.js.map +1 -0
  38. package/dist/custom-elements.json +1463 -62
  39. package/dist/events.js +5 -1
  40. package/dist/events.js.map +1 -1
  41. package/dist/index.d.ts +252 -27
  42. package/dist/index.js +3 -2
  43. package/dist/max-height.js +68 -0
  44. package/dist/max-height.js.map +1 -0
  45. package/dist/render/body.js +53 -20
  46. package/dist/render/body.js.map +1 -1
  47. package/dist/render/cells.js +21 -4
  48. package/dist/render/cells.js.map +1 -1
  49. package/dist/render/content.js +12 -5
  50. package/dist/render/content.js.map +1 -1
  51. package/dist/render/header.js +21 -4
  52. package/dist/render/header.js.map +1 -1
  53. package/dist/render/pinned-rows.js +29 -0
  54. package/dist/render/pinned-rows.js.map +1 -0
  55. package/dist/styles/base.js +1 -1
  56. package/dist/styles/base.js.map +1 -1
  57. package/package.json +2 -1
@@ -1,5 +1,7 @@
1
+ import { moveColumn, normalizeOrder } from "../columns/column-order.js";
1
2
  import { resolveColumns } from "../columns/resolve-columns.js";
2
- import { columnPinningFeature, columnSizingFeature, constructTable, createSortedRowModel, rowSortingFeature, sortFn_alphanumeric, sortFn_basic, sortFn_datetime, sortFn_text, tableFeatures } from "@tanstack/table-core";
3
+ import { sortedRowModel } from "./sorted-row-model.js";
4
+ import { columnPinningFeature, columnSizingFeature, constructTable, rowSortingFeature, sortFn_alphanumeric, sortFn_basic, sortFn_datetime, sortFn_text, tableFeatures } from "@tanstack/table-core";
3
5
  import { storeReactivityBindings } from "@tanstack/table-core/store-reactivity-bindings";
4
6
  // ../core/src/table/create-table.ts
5
7
  var features = tableFeatures({
@@ -7,7 +9,7 @@ var features = tableFeatures({
7
9
  columnPinningFeature,
8
10
  columnSizingFeature,
9
11
  rowSortingFeature,
10
- sortedRowModel: createSortedRowModel(),
12
+ sortedRowModel: sortedRowModel(),
11
13
  sortFns: {
12
14
  alphanumeric: sortFn_alphanumeric,
13
15
  basic: sortFn_basic,
@@ -28,30 +30,61 @@ function toTanStack(column) {
28
30
  };
29
31
  }
30
32
  /**
31
- * Creates the table model over TanStack Table. It owns no state: sorting and sizing
32
- * come in through `update()`, and `nextSorting()`/`nextSizing()` only propose
33
+ * Creates the table model over TanStack Table. It owns no state: sorting, sizing and order
34
+ * come in through `update()`, and `nextSorting()`, `nextSizing()` and `nextOrder()` only propose
33
35
  * changes, so the caller decides (controlled or uncontrolled) whether to apply them.
34
36
  */
35
37
  function createTableModel(initial) {
36
38
  let options = initial;
37
39
  let resolved = resolveColumns(options.columns);
38
40
  let byId = new Map(resolved.map((c) => [c.id, c]));
39
- let tanStackColumns = resolved.map(toTanStack);
41
+ let tanStackById = new Map(resolved.map((c) => [c.id, toTanStack(c)]));
40
42
  let views;
43
+ let sizing;
44
+ const sizingState = () => {
45
+ if (sizing) return sizing;
46
+ const auto = options.autoWidths;
47
+ if (!auto) return sizing = options.columnSizing;
48
+ const merged = {};
49
+ for (const column of resolved) {
50
+ const px = auto[column.id];
51
+ if (!column.auto || px === void 0 || !Number.isFinite(px)) continue;
52
+ merged[column.id] = Math.min(column.maxWidth, Math.max(column.minWidth, px));
53
+ }
54
+ return sizing = {
55
+ ...merged,
56
+ ...options.columnSizing
57
+ };
58
+ };
59
+ let order;
60
+ const orderState = () => order ??= normalizeOrder(options.columnOrder ?? [], resolved);
41
61
  const rowViews = /* @__PURE__ */ new WeakMap();
42
62
  let indexById;
63
+ let ordered;
64
+ const orderedColumns = () => {
65
+ const current = orderState();
66
+ if (ordered?.order !== current) {
67
+ const pinOf = (id) => byId.get(id)?.pin;
68
+ ordered = {
69
+ order: current,
70
+ columns: current.flatMap((id) => tanStackById.get(id) ?? []),
71
+ pinning: {
72
+ start: current.filter((id) => pinOf(id) === "start"),
73
+ end: current.filter((id) => pinOf(id) === "end")
74
+ }
75
+ };
76
+ }
77
+ return ordered;
78
+ };
43
79
  const tableOptions = () => ({
44
80
  features,
45
- columns: tanStackColumns,
46
- data: options.data,
81
+ columns: orderedColumns().columns,
82
+ data: options.remote ? [] : options.data,
47
83
  getRowId: options.getRowId ?? defaultRowId,
48
84
  state: {
49
85
  sorting: options.sorting,
50
- columnSizing: options.columnSizing,
51
- columnPinning: {
52
- start: resolved.filter((c) => c.pin === "start").map((c) => c.id),
53
- end: resolved.filter((c) => c.pin === "end").map((c) => c.id)
54
- }
86
+ columnSizing: sizingState(),
87
+ columnPinning: orderedColumns().pinning
55
88
  },
56
89
  onSortingChange: () => void 0,
57
90
  onColumnSizingChange: () => void 0,
@@ -59,6 +92,7 @@ function createTableModel(initial) {
59
92
  });
60
93
  const table = constructTable(tableOptions());
61
94
  const rows = () => table.getRowModel().rows;
95
+ const pinned = (side) => options.pinnedRows?.[side] ?? [];
62
96
  const buildViews = () => {
63
97
  const start = table.getStartLeafColumns();
64
98
  const center = table.getCenterLeafColumns();
@@ -76,10 +110,12 @@ function createTableModel(initial) {
76
110
  pinEdge,
77
111
  width: column.getSize(),
78
112
  flex: options.columnSizing[def.id] === void 0 ? def.flex : void 0,
113
+ auto: def.auto && options.columnSizing[def.id] === void 0,
79
114
  minWidth: def.minWidth,
80
115
  maxWidth: def.maxWidth,
81
116
  sortable: def.sortable,
82
117
  resizable: def.resizable,
118
+ reorderable: def.reorderable,
83
119
  align: def.align,
84
120
  sorted: sort ? sort.desc ? "desc" : "asc" : false,
85
121
  header: def.header,
@@ -103,8 +139,10 @@ function createTableModel(initial) {
103
139
  if (next.columns && next.columns !== options.columns) {
104
140
  resolved = resolveColumns(next.columns);
105
141
  byId = new Map(resolved.map((c) => [c.id, c]));
106
- tanStackColumns = resolved.map(toTanStack);
142
+ tanStackById = new Map(resolved.map((c) => [c.id, toTanStack(c)]));
107
143
  }
144
+ if (merged.columns !== options.columns || merged.columnSizing !== options.columnSizing || merged.autoWidths !== options.autoWidths) sizing = void 0;
145
+ if (merged.columns !== options.columns || merged.columnOrder !== options.columnOrder) order = void 0;
108
146
  options = merged;
109
147
  views = void 0;
110
148
  table.setOptions((prev) => ({
@@ -116,12 +154,20 @@ function createTableModel(initial) {
116
154
  return views ??= buildViews();
117
155
  },
118
156
  get rowCount() {
119
- return rows().length;
157
+ return options.remote?.rowCount ?? rows().length;
120
158
  },
121
159
  get totalWidth() {
122
160
  return table.getTotalSize();
123
161
  },
124
162
  rowAt(index) {
163
+ if (options.remote) {
164
+ const entry = options.remote.entryAt(index);
165
+ return entry && {
166
+ id: entry.id,
167
+ index,
168
+ original: entry.row
169
+ };
170
+ }
125
171
  const row = rows()[index];
126
172
  if (!row) return void 0;
127
173
  let view = rowViews.get(row);
@@ -136,6 +182,7 @@ function createTableModel(initial) {
136
182
  return view;
137
183
  },
138
184
  indexOf(rowId) {
185
+ if (options.remote) return options.remote.indexOf(rowId);
139
186
  const current = rows();
140
187
  if (indexById?.rows !== current) indexById = {
141
188
  rows: current,
@@ -144,9 +191,25 @@ function createTableModel(initial) {
144
191
  return indexById.map.get(rowId);
145
192
  },
146
193
  valueAt(index, columnId) {
194
+ if (options.remote) {
195
+ const entry = options.remote.entryAt(index);
196
+ return entry && byId.get(columnId)?.getValue(entry.row, index);
197
+ }
147
198
  return rows()[index]?.getValue(columnId);
148
199
  },
149
200
  cellContext(index, columnId) {
201
+ if (options.remote) {
202
+ const entry = options.remote.entryAt(index);
203
+ const column = byId.get(columnId);
204
+ if (!entry || !column) return void 0;
205
+ return {
206
+ row: entry.row,
207
+ rowId: entry.id,
208
+ index,
209
+ columnId,
210
+ value: column.getValue(entry.row, index)
211
+ };
212
+ }
150
213
  const row = rows()[index];
151
214
  if (!row || !byId.has(columnId)) return void 0;
152
215
  return {
@@ -177,6 +240,41 @@ function createTableModel(initial) {
177
240
  ...options.columnSizing,
178
241
  [columnId]: clamped
179
242
  };
243
+ },
244
+ orderOf(base) {
245
+ return base === void 0 ? orderState() : normalizeOrder(base, resolved);
246
+ },
247
+ nextOrder(columnId, to, base) {
248
+ return moveColumn(base ?? orderState(), resolved, columnId, to);
249
+ },
250
+ pinnedCount(side) {
251
+ return pinned(side).length;
252
+ },
253
+ pinnedRowAt(side, index) {
254
+ if (index < 0 || index >= pinned(side).length) return void 0;
255
+ const row = pinned(side)[index];
256
+ return {
257
+ id: options.getRowId ? options.getRowId(row, index) : `${side}-${String(index)}`,
258
+ index,
259
+ original: row
260
+ };
261
+ },
262
+ pinnedValueAt(side, index, columnId) {
263
+ if (index < 0 || index >= pinned(side).length) return void 0;
264
+ return byId.get(columnId)?.getValue(pinned(side)[index], index);
265
+ },
266
+ pinnedCellContext(side, index, columnId) {
267
+ const row = this.pinnedRowAt(side, index);
268
+ const column = byId.get(columnId);
269
+ if (!row || !column) return void 0;
270
+ return {
271
+ row: row.original,
272
+ rowId: row.id,
273
+ index,
274
+ columnId,
275
+ value: column.getValue(row.original, index),
276
+ pinned: side
277
+ };
180
278
  }
181
279
  };
182
280
  }
@@ -1 +1 @@
1
- {"version":3,"file":"create-table.js","names":[],"sources":["../../../../../core/src/table/create-table.ts"],"sourcesContent":["import {\n columnPinningFeature,\n columnSizingFeature,\n constructTable,\n createSortedRowModel,\n rowSortingFeature,\n sortFn_alphanumeric,\n sortFn_basic,\n sortFn_datetime,\n sortFn_text,\n tableFeatures,\n type Column,\n type ColumnDef as TanStackColumnDef,\n type Row,\n type Table,\n type TableOptions,\n} from '@tanstack/table-core';\nimport { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings';\nimport { resolveColumns, type ResolvedColumn } from '../columns/resolve-columns.js';\nimport type {\n CellContext,\n ColumnAlign,\n ColumnDef,\n ColumnPin,\n ColumnSizingState,\n SortState,\n} from '../types.js';\n\n// The TanStack adapter. Everything TanStack stays in this module: callers only see\n// the view types below, so TanStack can be upgraded or replaced without touching\n// `argus-table`'s public API.\n\nconst features = tableFeatures({\n // Vanilla (framework-free) reactivity; required by `constructTable`.\n coreReactivityFeature: storeReactivityBindings(),\n columnPinningFeature,\n columnSizingFeature,\n rowSortingFeature,\n sortedRowModel: createSortedRowModel(),\n // Registered by name so `sortFn: 'auto'` can pick one by value type.\n sortFns: {\n alphanumeric: sortFn_alphanumeric,\n basic: sortFn_basic,\n datetime: sortFn_datetime,\n text: sortFn_text,\n },\n});\ntype Features = typeof features;\ntype AnyRow = Record<string, unknown>;\n\n/** Sort direction of a column, or `false` when it isn't sorted. */\nexport type SortDirection = 'asc' | 'desc' | false;\n\n/** A column as the renderer sees it, in visual order: start-pinned, scrolling, end-pinned. */\nexport interface ColumnView<TData, TContent> {\n id: string;\n /** 1-based position in visual order, for `aria-colindex`. */\n colIndex: number;\n pin: ColumnPin | undefined;\n /**\n * Sticky inset in px: from the start edge for `'start'` columns, from the end edge\n * for `'end'` columns. For scrolling columns, the start within the scrolling area.\n */\n offset: number;\n /** `true` on the pinned column next to the scrolling area (one per pinned side). */\n pinEdge: boolean;\n /** Current width in px (a resized width wins over the column's own). */\n width: number;\n /** Share of free space, or `undefined` for a fixed width. A resized column stops flexing. */\n flex: number | undefined;\n minWidth: number;\n maxWidth: number;\n sortable: boolean;\n resizable: boolean;\n align: ColumnAlign;\n sorted: SortDirection;\n header: ColumnDef<TData, TContent>['header'];\n cell: ColumnDef<TData, TContent>['cell'];\n hasAccessor: boolean;\n}\n\n/** A row of the current (sorted) row model. */\nexport interface RowView<TData> {\n id: string;\n /** Index in the current row model, 0-based. */\n index: number;\n original: TData;\n}\n\nexport interface TableModelOptions<TData, TContent> {\n columns: readonly ColumnDef<TData, TContent>[];\n data: readonly TData[];\n /** Row id function. Default: the row's index in `data`, as a string. */\n getRowId: ((row: TData, index: number) => string) | undefined;\n /** Current sort state; its owner is the caller (see `controllable`). */\n sorting: SortState;\n /** Current resized widths; its owner is the caller. */\n columnSizing: ColumnSizingState;\n}\n\nexport interface TableModel<TData, TContent> {\n /** Updates any subset of the options. Unchanged references are ignored. */\n update(options: Partial<TableModelOptions<TData, TContent>>): void;\n /** Columns in visual order. */\n readonly columns: readonly ColumnView<TData, TContent>[];\n /** Number of rows in the current row model. */\n readonly rowCount: number;\n /** Sum of all column widths in px (flex columns count their minimum). */\n readonly totalWidth: number;\n /** The row at an index of the current row model. */\n rowAt(index: number): RowView<TData> | undefined;\n /** The index of a row id in the current row model (after sorting), or `undefined`. */\n indexOf(rowId: string): number | undefined;\n /** The value a column reads from the row at `index`. */\n valueAt(index: number, columnId: string): unknown;\n /** What a column's `cell` function receives for the row at `index`. */\n cellContext(index: number, columnId: string): CellContext<TData> | undefined;\n /**\n * The sort state that clicking this column's header would produce:\n * asc → desc → unsorted, one column at a time. `undefined` if it can't sort.\n */\n nextSorting(columnId: string): SortState | undefined;\n /**\n * The sizing state after setting a column to `width` px, clamped to its bounds.\n * `undefined` if the column can't be resized.\n */\n nextSizing(columnId: string, width: number): ColumnSizingState | undefined;\n}\n\nconst defaultRowId = (_row: unknown, index: number) => String(index);\n\nfunction toTanStack<TData, TContent>(\n column: ResolvedColumn<TData, TContent>,\n): TanStackColumnDef<Features, AnyRow> {\n return {\n id: column.id,\n accessorFn: column.getValue as (row: AnyRow, index: number) => unknown,\n size: column.width,\n minSize: column.minWidth,\n maxSize: column.maxWidth,\n enableSorting: column.sortable,\n sortUndefined: 'last',\n };\n}\n\n/**\n * Creates the table model over TanStack Table. It owns no state: sorting and sizing\n * come in through `update()`, and `nextSorting()`/`nextSizing()` only propose\n * changes, so the caller decides (controlled or uncontrolled) whether to apply them.\n */\nexport function createTableModel<TData, TContent>(\n initial: TableModelOptions<TData, TContent>,\n): TableModel<TData, TContent> {\n let options = initial;\n let resolved = resolveColumns(options.columns);\n let byId = new Map(resolved.map((c) => [c.id, c]));\n // Kept stable between updates: a new array would make TanStack rebuild columns and rows.\n let tanStackColumns = resolved.map(toTanStack);\n let views: ColumnView<TData, TContent>[] | undefined;\n const rowViews = new WeakMap<Row<Features, AnyRow>, RowView<TData>>();\n // Built lazily per row model (sorting or new data gives a new array).\n let indexById: { rows: readonly Row<Features, AnyRow>[]; map: Map<string, number> } | undefined;\n\n const tableOptions = (): TableOptions<Features, AnyRow> => ({\n features,\n columns: tanStackColumns,\n data: options.data as AnyRow[],\n getRowId: (options.getRowId ?? defaultRowId) as (row: AnyRow, index: number) => string,\n state: {\n sorting: options.sorting,\n columnSizing: options.columnSizing,\n columnPinning: {\n start: resolved.filter((c) => c.pin === 'start').map((c) => c.id),\n end: resolved.filter((c) => c.pin === 'end').map((c) => c.id),\n },\n },\n // State is owned by the caller; TanStack's own setters must not change it.\n onSortingChange: () => undefined,\n onColumnSizingChange: () => undefined,\n onColumnPinningChange: () => undefined,\n });\n\n const table: Table<Features, AnyRow> = constructTable(tableOptions());\n\n const rows = () => table.getRowModel().rows;\n\n const buildViews = (): ColumnView<TData, TContent>[] => {\n const start = table.getStartLeafColumns();\n const center = table.getCenterLeafColumns();\n const end = table.getEndLeafColumns();\n const sorting = options.sorting;\n const view = (\n column: Column<Features, AnyRow>,\n colIndex: number,\n offset: number,\n pinEdge: boolean,\n ): ColumnView<TData, TContent> => {\n const def = byId.get(column.id);\n if (!def) throw new Error(`argus-table: unknown column '${column.id}'.`);\n const sort = sorting.find((s) => s.id === column.id);\n return {\n id: def.id,\n colIndex,\n pin: def.pin,\n offset,\n pinEdge,\n width: column.getSize(),\n flex: options.columnSizing[def.id] === undefined ? def.flex : undefined,\n minWidth: def.minWidth,\n maxWidth: def.maxWidth,\n sortable: def.sortable,\n resizable: def.resizable,\n align: def.align,\n sorted: sort ? (sort.desc ? 'desc' : 'asc') : false,\n header: def.header,\n cell: def.cell,\n hasAccessor: def.hasAccessor,\n };\n };\n return [\n ...start.map((c, i) => view(c, i + 1, c.getStart('start'), i === start.length - 1)),\n ...center.map((c, i) => view(c, start.length + i + 1, c.getStart('center'), false)),\n ...end.map((c, i) =>\n view(c, start.length + center.length + i + 1, c.getAfter('end'), i === 0),\n ),\n ];\n };\n\n return {\n update(next) {\n const merged = { ...options, ...next };\n const changed = (Object.keys(next) as (keyof typeof next)[]).some(\n (key) => next[key] !== options[key],\n );\n if (!changed) return;\n if (next.columns && next.columns !== options.columns) {\n resolved = resolveColumns(next.columns);\n byId = new Map(resolved.map((c) => [c.id, c]));\n tanStackColumns = resolved.map(toTanStack);\n }\n options = merged;\n views = undefined;\n table.setOptions((prev) => ({ ...prev, ...tableOptions() }));\n },\n\n get columns() {\n return (views ??= buildViews());\n },\n\n get rowCount() {\n return rows().length;\n },\n\n get totalWidth() {\n return table.getTotalSize();\n },\n\n rowAt(index) {\n const row = rows()[index];\n if (!row) return undefined;\n let view = rowViews.get(row);\n if (view?.index !== index) {\n view = { id: row.id, index, original: row.original as TData };\n rowViews.set(row, view);\n }\n return view;\n },\n\n indexOf(rowId) {\n const current = rows();\n if (indexById?.rows !== current) {\n indexById = { rows: current, map: new Map(current.map((r, i) => [r.id, i])) };\n }\n return indexById.map.get(rowId);\n },\n\n valueAt(index, columnId) {\n return rows()[index]?.getValue(columnId);\n },\n\n cellContext(index, columnId) {\n const row = rows()[index];\n if (!row || !byId.has(columnId)) return undefined;\n return {\n row: row.original as TData,\n rowId: row.id,\n index,\n columnId,\n value: row.getValue(columnId),\n };\n },\n\n nextSorting(columnId) {\n if (byId.get(columnId)?.sortable !== true) return undefined;\n const current = options.sorting.find((s) => s.id === columnId);\n if (!current) return [{ id: columnId, desc: false }];\n return current.desc ? [] : [{ id: columnId, desc: true }];\n },\n\n nextSizing(columnId, width) {\n const column = byId.get(columnId);\n if (!column?.resizable || !Number.isFinite(width)) return undefined;\n const clamped = Math.round(Math.min(column.maxWidth, Math.max(column.minWidth, width)));\n return { ...options.columnSizing, [columnId]: clamped };\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"create-table.js","names":[],"sources":["../../../../../core/src/table/create-table.ts"],"sourcesContent":["import {\n columnPinningFeature,\n columnSizingFeature,\n constructTable,\n rowSortingFeature,\n sortFn_alphanumeric,\n sortFn_basic,\n sortFn_datetime,\n sortFn_text,\n tableFeatures,\n type Column,\n type ColumnDef as TanStackColumnDef,\n type Row,\n type Table,\n type TableOptions,\n} from '@tanstack/table-core';\nimport { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings';\nimport { moveColumn, normalizeOrder } from '../columns/column-order.js';\nimport { resolveColumns, type ResolvedColumn } from '../columns/resolve-columns.js';\nimport { sortedRowModel } from './sorted-row-model.js';\nimport type {\n CellContext,\n ColumnAlign,\n ColumnDef,\n ColumnOrderState,\n ColumnPin,\n ColumnSizingState,\n PinnedRows,\n RowPin,\n SortState,\n} from '../types.js';\n\n// The TanStack adapter. Everything TanStack stays in this module: callers only see\n// the view types below, so TanStack can be upgraded or replaced without touching\n// `argus-table`'s public API.\n\nconst features = tableFeatures({\n // Vanilla (framework-free) reactivity; required by `constructTable`.\n coreReactivityFeature: storeReactivityBindings(),\n columnPinningFeature,\n columnSizingFeature,\n rowSortingFeature,\n // Our own: TanStack's is very slow in Firefox at scale (see sorted-row-model.ts).\n sortedRowModel: sortedRowModel(),\n // Registered by name so `sortFn: 'auto'` can pick one by value type.\n sortFns: {\n alphanumeric: sortFn_alphanumeric,\n basic: sortFn_basic,\n datetime: sortFn_datetime,\n text: sortFn_text,\n },\n});\ntype Features = typeof features;\ntype AnyRow = Record<string, unknown>;\n\n/** Sort direction of a column, or `false` when it isn't sorted. */\nexport type SortDirection = 'asc' | 'desc' | false;\n\n/** A column as the renderer sees it, in visual order: start-pinned, scrolling, end-pinned. */\nexport interface ColumnView<TData, TContent> {\n id: string;\n /** 1-based position in visual order, for `aria-colindex`. */\n colIndex: number;\n pin: ColumnPin | undefined;\n /**\n * Sticky inset in px: from the start edge for `'start'` columns, from the end edge\n * for `'end'` columns. For scrolling columns, the start within the scrolling area.\n */\n offset: number;\n /** `true` on the pinned column next to the scrolling area (one per pinned side). */\n pinEdge: boolean;\n /** Current width in px (a resized width wins over the column's own). */\n width: number;\n /** Share of free space, or `undefined` for a fixed width. A resized column stops flexing. */\n flex: number | undefined;\n /**\n * `true` for a `width: 'auto'` column that hasn't been resized: the table measures\n * its content and passes the width in as `autoWidths`.\n */\n auto: boolean;\n minWidth: number;\n maxWidth: number;\n sortable: boolean;\n resizable: boolean;\n reorderable: boolean;\n align: ColumnAlign;\n sorted: SortDirection;\n header: ColumnDef<TData, TContent>['header'];\n cell: ColumnDef<TData, TContent>['cell'];\n hasAccessor: boolean;\n}\n\n/** A row of the current (sorted) row model. */\nexport interface RowView<TData> {\n id: string;\n /** Index in the current row model, 0-based. */\n index: number;\n original: TData;\n}\n\n/**\n * Rows that live outside the model, loaded page by page (`dataSource`). The\n * source sorts them, so the model reads them as they are. `PageCache` implements it.\n */\nexport interface RemoteRows<TData> {\n readonly rowCount: number;\n /** The loaded row at an index, with its id; `undefined` while it isn't loaded. */\n entryAt(index: number): { id: string; row: TData } | undefined;\n /** The index of a loaded row's id, or `undefined`. */\n indexOf(rowId: string): number | undefined;\n}\n\nexport interface TableModelOptions<TData, TContent> {\n columns: readonly ColumnDef<TData, TContent>[];\n data: readonly TData[];\n /**\n * Remote rows. When set, rows come from here instead of `data`, and the model\n * doesn't sort them (the source does).\n */\n remote?: RemoteRows<TData> | undefined;\n /** Row id function. Default: the row's index in `data`, as a string. */\n getRowId: ((row: TData, index: number) => string) | undefined;\n /** Current sort state; its owner is the caller (see `controllable`). */\n sorting: SortState;\n /** Current resized widths; its owner is the caller. */\n columnSizing: ColumnSizingState;\n /**\n * Current column order; its owner is the caller. Each pin group follows it; an\n * empty array (the default) keeps the order of `columns`.\n */\n columnOrder?: ColumnOrderState | undefined;\n /**\n * Measured widths of `width: 'auto'` columns, in px by id. Clamped to the column's\n * bounds; a resized width wins. Ids of other columns are ignored.\n */\n autoWidths?: ColumnSizingState | undefined;\n /** Rows fixed above or below the body. They stay out of the row model: never sorted, not counted. */\n pinnedRows?: PinnedRows<TData> | undefined;\n}\n\nexport interface TableModel<TData, TContent> {\n /** Updates any subset of the options. Unchanged references are ignored. */\n update(options: Partial<TableModelOptions<TData, TContent>>): void;\n /** Columns in visual order. */\n readonly columns: readonly ColumnView<TData, TContent>[];\n /** Number of rows in the current row model. */\n readonly rowCount: number;\n /** Sum of all column widths in px (flex columns count their minimum). */\n readonly totalWidth: number;\n /** The row at an index of the current row model; `undefined` for a remote row not loaded yet. */\n rowAt(index: number): RowView<TData> | undefined;\n /** The index of a row id in the current row model (after sorting), or `undefined`. */\n indexOf(rowId: string): number | undefined;\n /** The value a column reads from the row at `index`. */\n valueAt(index: number, columnId: string): unknown;\n /** What a column's `cell` function receives for the row at `index`. */\n cellContext(index: number, columnId: string): CellContext<TData> | undefined;\n /**\n * The sort state that clicking this column's header would produce:\n * asc → desc → unsorted, one column at a time. `undefined` if it can't sort.\n */\n nextSorting(columnId: string): SortState | undefined;\n /**\n * The sizing state after setting a column to `width` px, clamped to its bounds.\n * `undefined` if the column can't be resized.\n */\n nextSizing(columnId: string, width: number): ColumnSizingState | undefined;\n /**\n * The full column order as the table shows it: start-pinned, scrolling and\n * end-pinned ids. `base` defaults to the current `columnOrder`.\n */\n orderOf(base?: readonly string[]): ColumnOrderState;\n /**\n * The order after moving a column to position `to` (0-based, in the whole order,\n * counted after the move), starting from `base` (default: the current order).\n * `undefined` if the column can't move, `to` is outside its pin group, or nothing changes.\n */\n nextOrder(columnId: string, to: number, base?: readonly string[]): ColumnOrderState | undefined;\n /** Number of rows pinned to a side. */\n pinnedCount(side: RowPin): number;\n /**\n * A pinned row, `index` counted within its side. Its id comes from `getRowId`,\n * else it is `'top-{index}'` or `'bottom-{index}'`, apart from the data rows' index ids.\n */\n pinnedRowAt(side: RowPin, index: number): RowView<TData> | undefined;\n /** The value a column reads from a pinned row. */\n pinnedValueAt(side: RowPin, index: number, columnId: string): unknown;\n /** What a column's `cell` function receives for a pinned row (with `pinned` set). */\n pinnedCellContext(side: RowPin, index: number, columnId: string): CellContext<TData> | undefined;\n}\n\nconst defaultRowId = (_row: unknown, index: number) => String(index);\n\nfunction toTanStack<TData, TContent>(\n column: ResolvedColumn<TData, TContent>,\n): TanStackColumnDef<Features, AnyRow> {\n return {\n id: column.id,\n accessorFn: column.getValue as (row: AnyRow, index: number) => unknown,\n size: column.width,\n minSize: column.minWidth,\n maxSize: column.maxWidth,\n enableSorting: column.sortable,\n sortUndefined: 'last',\n };\n}\n\n/**\n * Creates the table model over TanStack Table. It owns no state: sorting, sizing and order\n * come in through `update()`, and `nextSorting()`, `nextSizing()` and `nextOrder()` only propose\n * changes, so the caller decides (controlled or uncontrolled) whether to apply them.\n */\nexport function createTableModel<TData, TContent>(\n initial: TableModelOptions<TData, TContent>,\n): TableModel<TData, TContent> {\n let options = initial;\n let resolved = resolveColumns(options.columns);\n let byId = new Map(resolved.map((c) => [c.id, c]));\n // TanStack column defs by id, kept between updates that don't change `columns`.\n let tanStackById = new Map(resolved.map((c) => [c.id, toTanStack(c)]));\n let views: ColumnView<TData, TContent>[] | undefined;\n // TanStack's sizing state: measured auto widths under the resized ones. Kept stable\n // between updates that don't touch either.\n let sizing: ColumnSizingState | undefined;\n const sizingState = (): ColumnSizingState => {\n if (sizing) return sizing;\n const auto = options.autoWidths;\n if (!auto) return (sizing = options.columnSizing);\n const merged: ColumnSizingState = {};\n for (const column of resolved) {\n const px = auto[column.id];\n if (!column.auto || px === undefined || !Number.isFinite(px)) continue;\n merged[column.id] = Math.min(column.maxWidth, Math.max(column.minWidth, px));\n }\n return (sizing = { ...merged, ...options.columnSizing });\n };\n // The normalized order, kept stable between updates that touch neither columns nor order.\n let order: ColumnOrderState | undefined;\n const orderState = (): ColumnOrderState =>\n (order ??= normalizeOrder(options.columnOrder ?? [], resolved));\n const rowViews = new WeakMap<Row<Features, AnyRow>, RowView<TData>>();\n // Built lazily per row model (sorting or new data gives a new array).\n let indexById: { rows: readonly Row<Features, AnyRow>[]; map: Map<string, number> } | undefined;\n\n // The order reaches TanStack as the order of its column defs (the scrolling\n // columns follow it) and of the pinning arrays (the pinned ones do). Rebuilt only\n // when the order changes: a new columns array makes TanStack rebuild its columns,\n // though not its row models, which depend on data and sorting alone.\n let ordered:\n | {\n order: ColumnOrderState;\n columns: TanStackColumnDef<Features, AnyRow>[];\n pinning: { start: string[]; end: string[] };\n }\n | undefined;\n const orderedColumns = () => {\n const current = orderState();\n if (ordered?.order !== current) {\n const pinOf = (id: string) => byId.get(id)?.pin;\n ordered = {\n order: current,\n columns: current.flatMap((id) => tanStackById.get(id) ?? []),\n pinning: {\n start: current.filter((id) => pinOf(id) === 'start'),\n end: current.filter((id) => pinOf(id) === 'end'),\n },\n };\n }\n return ordered;\n };\n\n const tableOptions = (): TableOptions<Features, AnyRow> => ({\n features,\n columns: orderedColumns().columns,\n // Remote rows bypass TanStack's row models; it still handles columns and state.\n data: (options.remote ? [] : options.data) as AnyRow[],\n getRowId: (options.getRowId ?? defaultRowId) as (row: AnyRow, index: number) => string,\n state: {\n sorting: options.sorting,\n columnSizing: sizingState(),\n columnPinning: orderedColumns().pinning,\n },\n // State is owned by the caller; TanStack's own setters must not change it.\n onSortingChange: () => undefined,\n onColumnSizingChange: () => undefined,\n onColumnPinningChange: () => undefined,\n });\n\n const table: Table<Features, AnyRow> = constructTable(tableOptions());\n\n const rows = () => table.getRowModel().rows;\n const pinned = (side: RowPin): readonly TData[] => options.pinnedRows?.[side] ?? [];\n\n const buildViews = (): ColumnView<TData, TContent>[] => {\n const start = table.getStartLeafColumns();\n const center = table.getCenterLeafColumns();\n const end = table.getEndLeafColumns();\n const sorting = options.sorting;\n const view = (\n column: Column<Features, AnyRow>,\n colIndex: number,\n offset: number,\n pinEdge: boolean,\n ): ColumnView<TData, TContent> => {\n const def = byId.get(column.id);\n if (!def) throw new Error(`argus-table: unknown column '${column.id}'.`);\n const sort = sorting.find((s) => s.id === column.id);\n return {\n id: def.id,\n colIndex,\n pin: def.pin,\n offset,\n pinEdge,\n width: column.getSize(),\n flex: options.columnSizing[def.id] === undefined ? def.flex : undefined,\n auto: def.auto && options.columnSizing[def.id] === undefined,\n minWidth: def.minWidth,\n maxWidth: def.maxWidth,\n sortable: def.sortable,\n resizable: def.resizable,\n reorderable: def.reorderable,\n align: def.align,\n sorted: sort ? (sort.desc ? 'desc' : 'asc') : false,\n header: def.header,\n cell: def.cell,\n hasAccessor: def.hasAccessor,\n };\n };\n return [\n ...start.map((c, i) => view(c, i + 1, c.getStart('start'), i === start.length - 1)),\n ...center.map((c, i) => view(c, start.length + i + 1, c.getStart('center'), false)),\n ...end.map((c, i) =>\n view(c, start.length + center.length + i + 1, c.getAfter('end'), i === 0),\n ),\n ];\n };\n\n return {\n update(next) {\n const merged = { ...options, ...next };\n const changed = (Object.keys(next) as (keyof typeof next)[]).some(\n (key) => next[key] !== options[key],\n );\n if (!changed) return;\n if (next.columns && next.columns !== options.columns) {\n resolved = resolveColumns(next.columns);\n byId = new Map(resolved.map((c) => [c.id, c]));\n tanStackById = new Map(resolved.map((c) => [c.id, toTanStack(c)]));\n }\n if (\n merged.columns !== options.columns ||\n merged.columnSizing !== options.columnSizing ||\n merged.autoWidths !== options.autoWidths\n ) {\n sizing = undefined;\n }\n if (merged.columns !== options.columns || merged.columnOrder !== options.columnOrder) {\n order = undefined;\n }\n options = merged;\n views = undefined;\n table.setOptions((prev) => ({ ...prev, ...tableOptions() }));\n },\n\n get columns() {\n return (views ??= buildViews());\n },\n\n get rowCount() {\n return options.remote?.rowCount ?? rows().length;\n },\n\n get totalWidth() {\n return table.getTotalSize();\n },\n\n rowAt(index) {\n if (options.remote) {\n const entry = options.remote.entryAt(index);\n return entry && { id: entry.id, index, original: entry.row };\n }\n const row = rows()[index];\n if (!row) return undefined;\n let view = rowViews.get(row);\n if (view?.index !== index) {\n view = { id: row.id, index, original: row.original as TData };\n rowViews.set(row, view);\n }\n return view;\n },\n\n indexOf(rowId) {\n if (options.remote) return options.remote.indexOf(rowId);\n const current = rows();\n if (indexById?.rows !== current) {\n indexById = { rows: current, map: new Map(current.map((r, i) => [r.id, i])) };\n }\n return indexById.map.get(rowId);\n },\n\n valueAt(index, columnId) {\n if (options.remote) {\n const entry = options.remote.entryAt(index);\n return entry && byId.get(columnId)?.getValue(entry.row, index);\n }\n return rows()[index]?.getValue(columnId);\n },\n\n cellContext(index, columnId) {\n if (options.remote) {\n const entry = options.remote.entryAt(index);\n const column = byId.get(columnId);\n if (!entry || !column) return undefined;\n return {\n row: entry.row,\n rowId: entry.id,\n index,\n columnId,\n value: column.getValue(entry.row, index),\n };\n }\n const row = rows()[index];\n if (!row || !byId.has(columnId)) return undefined;\n return {\n row: row.original as TData,\n rowId: row.id,\n index,\n columnId,\n value: row.getValue(columnId),\n };\n },\n\n nextSorting(columnId) {\n if (byId.get(columnId)?.sortable !== true) return undefined;\n const current = options.sorting.find((s) => s.id === columnId);\n if (!current) return [{ id: columnId, desc: false }];\n return current.desc ? [] : [{ id: columnId, desc: true }];\n },\n\n nextSizing(columnId, width) {\n const column = byId.get(columnId);\n if (!column?.resizable || !Number.isFinite(width)) return undefined;\n const clamped = Math.round(Math.min(column.maxWidth, Math.max(column.minWidth, width)));\n return { ...options.columnSizing, [columnId]: clamped };\n },\n\n orderOf(base) {\n return base === undefined ? orderState() : normalizeOrder(base, resolved);\n },\n\n nextOrder(columnId, to, base) {\n return moveColumn(base ?? orderState(), resolved, columnId, to);\n },\n\n pinnedCount(side) {\n return pinned(side).length;\n },\n\n pinnedRowAt(side, index) {\n if (index < 0 || index >= pinned(side).length) return undefined;\n const row = pinned(side)[index] as TData;\n const id = options.getRowId ? options.getRowId(row, index) : `${side}-${String(index)}`;\n return { id, index, original: row };\n },\n\n pinnedValueAt(side, index, columnId) {\n if (index < 0 || index >= pinned(side).length) return undefined;\n return byId.get(columnId)?.getValue(pinned(side)[index] as TData, index);\n },\n\n pinnedCellContext(side, index, columnId) {\n const row = this.pinnedRowAt(side, index);\n const column = byId.get(columnId);\n if (!row || !column) return undefined;\n return {\n row: row.original,\n rowId: row.id,\n index,\n columnId,\n value: column.getValue(row.original, index),\n pinned: side,\n };\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import { column_getCanSort, column_getSortFn } from "@tanstack/table-core/static-functions";
2
+ // ../core/src/table/sorted-row-model.ts
3
+ /**
4
+ * The sorted row model, in place of TanStack's `createSortedRowModel()`, giving the
5
+ * same order: the same sort functions (`auto` included), `sortUndefined`,
6
+ * `invertSorting`, ties by row index.
7
+ *
8
+ * It reads each row's value once, through the column's accessor, and sorts the row
9
+ * positions by those values. TanStack's model calls `row.getValue()` four times per
10
+ * comparison, and its row methods take rest arguments (`function (...args)`): in
11
+ * Firefox that slows a large sort down by orders of magnitude (750k rows: 176 s
12
+ * against 0.9 s for the same comparator without rest arguments). Reading values
13
+ * once is also faster everywhere, most of all for text, which is lowercased once
14
+ * per row instead of twice per comparison.
15
+ *
16
+ * The registered sort functions come from `constructSortFn`: each carries a value
17
+ * comparator (`sort`, which compares values only) and a normalizer
18
+ * (`resolveDataValue`). The rows are flat (no sub-rows), so there is nothing to
19
+ * sort below them.
20
+ */
21
+ function sortedRowModel() {
22
+ return (table) => {
23
+ let last;
24
+ return () => {
25
+ const source = table.getPreSortedRowModel();
26
+ const sorting = table.options.state?.sorting;
27
+ const prev = last;
28
+ if (prev && prev.sorting === sorting && prev.source === source) return prev.model;
29
+ const model = sortRows(table, source, sorting ?? []);
30
+ last = {
31
+ sorting,
32
+ source,
33
+ model
34
+ };
35
+ return model;
36
+ };
37
+ };
38
+ }
39
+ function sortRows(table, source, sorting) {
40
+ const rows = source.rows;
41
+ const sortKeys = [];
42
+ for (const { id, desc } of sorting) {
43
+ const column = table.getColumn(id);
44
+ if (!column || !column_getCanSort(column)) continue;
45
+ const fn = column_getSortFn(column);
46
+ const def = column.columnDef;
47
+ const read = column.accessorFn;
48
+ const resolve = fn.resolveDataValue;
49
+ const sortUndefined = def.sortUndefined;
50
+ sortKeys.push({
51
+ keys: rows.map((row) => {
52
+ const value = read?.(row.original, row.index);
53
+ return resolve && !(value === void 0 && sortUndefined) ? resolve(value) : value;
54
+ }),
55
+ compare: fn.sort,
56
+ sign: (desc ? -1 : 1) * (def.invertSorting ? -1 : 1),
57
+ sortUndefined
58
+ });
59
+ }
60
+ if (!sortKeys.length || !rows.length) return source;
61
+ const compare = (i, j) => {
62
+ for (const { keys, compare, sign, sortUndefined } of sortKeys) {
63
+ const a = keys[i];
64
+ const b = keys[j];
65
+ let result = 0;
66
+ if (sortUndefined && (a === void 0 || b === void 0)) {
67
+ if (a === void 0 && b === void 0) continue;
68
+ if (sortUndefined === "first") return a === void 0 ? -1 : 1;
69
+ if (sortUndefined === "last") return a === void 0 ? 1 : -1;
70
+ result = a === void 0 ? sortUndefined : -sortUndefined;
71
+ }
72
+ if (result === 0) result = compare(a, b);
73
+ if (result !== 0) return result * sign;
74
+ }
75
+ return (rows[i]?.index ?? i) - (rows[j]?.index ?? j);
76
+ };
77
+ const sorted = rows.map((_, i) => i).sort(compare).map((i) => rows[i]).filter((row) => row !== void 0);
78
+ return {
79
+ rows: sorted,
80
+ flatRows: sorted,
81
+ rowsById: source.rowsById
82
+ };
83
+ }
84
+ export { sortedRowModel };
85
+
86
+ //# sourceMappingURL=sorted-row-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sorted-row-model.js","names":[],"sources":["../../../../../core/src/table/sorted-row-model.ts"],"sourcesContent":["import type {\n ColumnDef_RowSorting,\n RowData,\n RowModel,\n SortFnDef,\n Table,\n TableFeatures,\n} from '@tanstack/table-core';\nimport { column_getCanSort, column_getSortFn } from '@tanstack/table-core/static-functions';\n\ntype Sorting = readonly { id: string; desc: boolean }[];\n\ninterface SortKey {\n /** Each row's value by its position in the unsorted rows, resolved for `compare`. */\n keys: unknown[];\n compare: (a: unknown, b: unknown) => number;\n /** -1 flips `compare` (descending, inverted). */\n sign: number;\n sortUndefined: false | -1 | 1 | 'first' | 'last' | undefined;\n}\n\n/**\n * The sorted row model, in place of TanStack's `createSortedRowModel()`, giving the\n * same order: the same sort functions (`auto` included), `sortUndefined`,\n * `invertSorting`, ties by row index.\n *\n * It reads each row's value once, through the column's accessor, and sorts the row\n * positions by those values. TanStack's model calls `row.getValue()` four times per\n * comparison, and its row methods take rest arguments (`function (...args)`): in\n * Firefox that slows a large sort down by orders of magnitude (750k rows: 176 s\n * against 0.9 s for the same comparator without rest arguments). Reading values\n * once is also faster everywhere, most of all for text, which is lowercased once\n * per row instead of twice per comparison.\n *\n * The registered sort functions come from `constructSortFn`: each carries a value\n * comparator (`sort`, which compares values only) and a normalizer\n * (`resolveDataValue`). The rows are flat (no sub-rows), so there is nothing to\n * sort below them.\n */\nexport function sortedRowModel<TFeatures extends TableFeatures, TData extends RowData>() {\n return (table: Table<TFeatures, TData>) => {\n let last:\n | { sorting: unknown; source: RowModel<TFeatures, TData>; model: RowModel<TFeatures, TData> }\n | undefined;\n return (): RowModel<TFeatures, TData> => {\n const source = table.getPreSortedRowModel();\n const sorting = (table.options.state as { sorting?: Sorting } | undefined)?.sorting;\n const prev = last;\n if (prev && prev.sorting === sorting && prev.source === source) return prev.model;\n const model = sortRows(table, source, sorting ?? []);\n last = { sorting, source, model };\n return model;\n };\n };\n}\n\nfunction sortRows<TFeatures extends TableFeatures, TData extends RowData>(\n table: Table<TFeatures, TData>,\n source: RowModel<TFeatures, TData>,\n sorting: Sorting,\n): RowModel<TFeatures, TData> {\n const rows = source.rows;\n const sortKeys: SortKey[] = [];\n for (const { id, desc } of sorting) {\n const column = table.getColumn(id);\n if (!column || !column_getCanSort(column)) continue;\n const fn = column_getSortFn(column) as unknown as SortFnDef<TFeatures, TData>;\n const def = column.columnDef as ColumnDef_RowSorting<TFeatures, TData>;\n const read = column.accessorFn;\n const resolve = fn.resolveDataValue;\n const sortUndefined = def.sortUndefined;\n sortKeys.push({\n keys: rows.map((row) => {\n const value = read?.(row.original, row.index);\n // Undefined stays undefined where `sortUndefined` places it.\n return resolve && !(value === undefined && sortUndefined)\n ? (resolve(value) as unknown)\n : value;\n }),\n compare: fn.sort as (a: unknown, b: unknown) => number,\n sign: (desc ? -1 : 1) * (def.invertSorting ? -1 : 1),\n sortUndefined,\n });\n }\n if (!sortKeys.length || !rows.length) return source;\n\n const compare = (i: number, j: number): number => {\n for (const { keys, compare, sign, sortUndefined } of sortKeys) {\n const a = keys[i];\n const b = keys[j];\n let result = 0;\n if (sortUndefined && (a === undefined || b === undefined)) {\n if (a === undefined && b === undefined) continue;\n if (sortUndefined === 'first') return a === undefined ? -1 : 1;\n if (sortUndefined === 'last') return a === undefined ? 1 : -1;\n result = a === undefined ? sortUndefined : -sortUndefined;\n }\n if (result === 0) result = compare(a, b);\n if (result !== 0) return result * sign;\n }\n return (rows[i]?.index ?? i) - (rows[j]?.index ?? j);\n };\n\n const sorted = rows\n .map((_, i) => i)\n .sort(compare)\n .map((i) => rows[i])\n .filter((row) => row !== undefined);\n return { rows: sorted, flatRows: sorted, rowsById: source.rowsById };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,iBAAyE;CACvF,QAAQ,UAAmC;EACzC,IAAI;EAGJ,aAAyC;GACvC,MAAM,SAAS,MAAM,qBAAqB;GAC1C,MAAM,UAAW,MAAM,QAAQ,OAA6C;GAC5E,MAAM,OAAO;GACb,IAAI,QAAQ,KAAK,YAAY,WAAW,KAAK,WAAW,QAAQ,OAAO,KAAK;GAC5E,MAAM,QAAQ,SAAS,OAAO,QAAQ,WAAW,CAAC,CAAC;GACnD,OAAO;IAAE;IAAS;IAAQ;GAAM;GAChC,OAAO;EACT;CACF;AACF;AAEA,SAAS,SACP,OACA,QACA,SAC4B;CAC5B,MAAM,OAAO,OAAO;CACpB,MAAM,WAAsB,CAAC;CAC7B,KAAK,MAAM,EAAE,IAAI,UAAU,SAAS;EAClC,MAAM,SAAS,MAAM,UAAU,EAAE;EACjC,IAAI,CAAC,UAAU,CAAC,kBAAkB,MAAM,GAAG;EAC3C,MAAM,KAAK,iBAAiB,MAAM;EAClC,MAAM,MAAM,OAAO;EACnB,MAAM,OAAO,OAAO;EACpB,MAAM,UAAU,GAAG;EACnB,MAAM,gBAAgB,IAAI;EAC1B,SAAS,KAAK;GACZ,MAAM,KAAK,KAAK,QAAQ;IACtB,MAAM,QAAQ,OAAO,IAAI,UAAU,IAAI,KAAK;IAE5C,OAAO,WAAW,EAAE,UAAU,KAAA,KAAa,iBACtC,QAAQ,KAAK,IACd;GACN,CAAC;GACD,SAAS,GAAG;GACZ,OAAO,OAAO,KAAK,MAAM,IAAI,gBAAgB,KAAK;GAClD;EACF,CAAC;CACH;CACA,IAAI,CAAC,SAAS,UAAU,CAAC,KAAK,QAAQ,OAAO;CAE7C,MAAM,WAAW,GAAW,MAAsB;EAChD,KAAK,MAAM,EAAE,MAAM,SAAS,MAAM,mBAAmB,UAAU;GAC7D,MAAM,IAAI,KAAK;GACf,MAAM,IAAI,KAAK;GACf,IAAI,SAAS;GACb,IAAI,kBAAkB,MAAM,KAAA,KAAa,MAAM,KAAA,IAAY;IACzD,IAAI,MAAM,KAAA,KAAa,MAAM,KAAA,GAAW;IACxC,IAAI,kBAAkB,SAAS,OAAO,MAAM,KAAA,IAAY,KAAK;IAC7D,IAAI,kBAAkB,QAAQ,OAAO,MAAM,KAAA,IAAY,IAAI;IAC3D,SAAS,MAAM,KAAA,IAAY,gBAAgB,CAAC;GAC9C;GACA,IAAI,WAAW,GAAG,SAAS,QAAQ,GAAG,CAAC;GACvC,IAAI,WAAW,GAAG,OAAO,SAAS;EACpC;EACA,QAAQ,KAAK,EAAE,EAAE,SAAS,MAAM,KAAK,EAAE,EAAE,SAAS;CACpD;CAEA,MAAM,SAAS,KACZ,KAAK,GAAG,MAAM,CAAC,CAAC,CAChB,KAAK,OAAO,CAAC,CACb,KAAK,MAAM,KAAK,EAAE,CAAC,CACnB,QAAQ,QAAQ,QAAQ,KAAA,CAAS;CACpC,OAAO;EAAE,MAAM;EAAQ,UAAU;EAAQ,UAAU,OAAO;CAAS;AACrE"}
@@ -0,0 +1,74 @@
1
+ // ../core/src/virtual/scroll-scale.ts
2
+ /**
3
+ * Maps the scroll position of a body that is too tall to render onto the rows.
4
+ *
5
+ * Engines limit an element's height (about 33.5M px in Chromium and WebKit, less
6
+ * when zoomed; about 17.9M px in Firefox, which drops a taller element to 0). A
7
+ * body taller than `cap` renders `cap` px tall, and the scroll position is scaled
8
+ * linearly: the top of the physical scroll range is the first row, its bottom the
9
+ * last. Offsets the virtualizer works with are logical (unscaled) px; the
10
+ * scroller's `scrollTop` is physical. Under the cap the mapping is the identity.
11
+ *
12
+ * The scroll range is the scroller's: the body plus what scrolls with it (header,
13
+ * pinned rows) minus the viewport, so pass the physical range,
14
+ * `scrollHeight - clientHeight`. The logical range is longer by `excess`.
15
+ */
16
+ var ScrollScale = class {
17
+ #logical = 0;
18
+ #cap = Infinity;
19
+ /** The logical offset last written and the physical offset it landed on. */
20
+ #landed;
21
+ /**
22
+ * `logical`: the height of all body rows in px. `cap`: the tallest body the
23
+ * engine renders. Returns whether anything changed.
24
+ */
25
+ configure(logical, cap) {
26
+ if (logical === this.#logical && cap === this.#cap) return false;
27
+ this.#logical = logical;
28
+ this.#cap = cap;
29
+ this.#landed = void 0;
30
+ return true;
31
+ }
32
+ /** Whether the body is taller than the cap, so offsets are scaled. */
33
+ get scaled() {
34
+ return this.#logical > this.#cap;
35
+ }
36
+ /** The body's rendered height in px. */
37
+ get size() {
38
+ return Math.min(this.#logical, this.#cap);
39
+ }
40
+ /** How much longer the logical scroll range is than the physical one. */
41
+ get excess() {
42
+ return this.#logical - this.size;
43
+ }
44
+ /** The logical offset for a physical one, given the physical scroll range. */
45
+ toLogical(physical, range) {
46
+ if (!this.scaled || range <= 0) return physical;
47
+ const landed = this.#landed;
48
+ if (landed?.physical === physical) return landed.logical;
49
+ return clamp(physical, range) * (range + this.excess) / range;
50
+ }
51
+ /** The physical offset for a logical one, given the physical scroll range. */
52
+ toPhysical(logical, range) {
53
+ if (!this.scaled || range <= 0) return logical;
54
+ const total = range + this.excess;
55
+ return clamp(logical, total) * range / total;
56
+ }
57
+ /**
58
+ * Records that a write of the logical offset `logical` left the scroller at
59
+ * `physical` (read back, after the engine's rounding), so reading that physical
60
+ * offset gives the exact logical one again. Without it a scaled offset could be
61
+ * off by up to the scale ratio, and a row scrolled to the top would sit under
62
+ * the header by a few px.
63
+ */
64
+ land(logical, physical) {
65
+ this.#landed = this.scaled ? {
66
+ physical,
67
+ logical
68
+ } : void 0;
69
+ }
70
+ };
71
+ var clamp = (value, max) => Math.min(Math.max(value, 0), max);
72
+ export { ScrollScale };
73
+
74
+ //# sourceMappingURL=scroll-scale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scroll-scale.js","names":[],"sources":["../../../../../core/src/virtual/scroll-scale.ts"],"sourcesContent":["/**\n * Maps the scroll position of a body that is too tall to render onto the rows.\n *\n * Engines limit an element's height (about 33.5M px in Chromium and WebKit, less\n * when zoomed; about 17.9M px in Firefox, which drops a taller element to 0). A\n * body taller than `cap` renders `cap` px tall, and the scroll position is scaled\n * linearly: the top of the physical scroll range is the first row, its bottom the\n * last. Offsets the virtualizer works with are logical (unscaled) px; the\n * scroller's `scrollTop` is physical. Under the cap the mapping is the identity.\n *\n * The scroll range is the scroller's: the body plus what scrolls with it (header,\n * pinned rows) minus the viewport, so pass the physical range,\n * `scrollHeight - clientHeight`. The logical range is longer by `excess`.\n */\nexport class ScrollScale {\n #logical = 0;\n #cap = Infinity;\n /** The logical offset last written and the physical offset it landed on. */\n #landed: { physical: number; logical: number } | undefined;\n\n /**\n * `logical`: the height of all body rows in px. `cap`: the tallest body the\n * engine renders. Returns whether anything changed.\n */\n configure(logical: number, cap: number): boolean {\n if (logical === this.#logical && cap === this.#cap) return false;\n this.#logical = logical;\n this.#cap = cap;\n this.#landed = undefined;\n return true;\n }\n\n /** Whether the body is taller than the cap, so offsets are scaled. */\n get scaled(): boolean {\n return this.#logical > this.#cap;\n }\n\n /** The body's rendered height in px. */\n get size(): number {\n return Math.min(this.#logical, this.#cap);\n }\n\n /** How much longer the logical scroll range is than the physical one. */\n get excess(): number {\n return this.#logical - this.size;\n }\n\n /** The logical offset for a physical one, given the physical scroll range. */\n toLogical(physical: number, range: number): number {\n if (!this.scaled || range <= 0) return physical;\n const landed = this.#landed;\n if (landed?.physical === physical) return landed.logical;\n return (clamp(physical, range) * (range + this.excess)) / range;\n }\n\n /** The physical offset for a logical one, given the physical scroll range. */\n toPhysical(logical: number, range: number): number {\n if (!this.scaled || range <= 0) return logical;\n const total = range + this.excess;\n return (clamp(logical, total) * range) / total;\n }\n\n /**\n * Records that a write of the logical offset `logical` left the scroller at\n * `physical` (read back, after the engine's rounding), so reading that physical\n * offset gives the exact logical one again. Without it a scaled offset could be\n * off by up to the scale ratio, and a row scrolled to the top would sit under\n * the header by a few px.\n */\n land(logical: number, physical: number): void {\n this.#landed = this.scaled ? { physical, logical } : undefined;\n }\n}\n\nconst clamp = (value: number, max: number) => Math.min(Math.max(value, 0), max);\n"],"mappings":";;;;;;;;;;;;;;;AAcA,IAAa,cAAb,MAAyB;CACvB,WAAW;CACX,OAAO;;CAEP;;;;;CAMA,UAAU,SAAiB,KAAsB;EAC/C,IAAI,YAAY,KAAK,YAAY,QAAQ,KAAK,MAAM,OAAO;EAC3D,KAAK,WAAW;EAChB,KAAK,OAAO;EACZ,KAAK,UAAU,KAAA;EACf,OAAO;CACT;;CAGA,IAAI,SAAkB;EACpB,OAAO,KAAK,WAAW,KAAK;CAC9B;;CAGA,IAAI,OAAe;EACjB,OAAO,KAAK,IAAI,KAAK,UAAU,KAAK,IAAI;CAC1C;;CAGA,IAAI,SAAiB;EACnB,OAAO,KAAK,WAAW,KAAK;CAC9B;;CAGA,UAAU,UAAkB,OAAuB;EACjD,IAAI,CAAC,KAAK,UAAU,SAAS,GAAG,OAAO;EACvC,MAAM,SAAS,KAAK;EACpB,IAAI,QAAQ,aAAa,UAAU,OAAO,OAAO;EACjD,OAAQ,MAAM,UAAU,KAAK,KAAK,QAAQ,KAAK,UAAW;CAC5D;;CAGA,WAAW,SAAiB,OAAuB;EACjD,IAAI,CAAC,KAAK,UAAU,SAAS,GAAG,OAAO;EACvC,MAAM,QAAQ,QAAQ,KAAK;EAC3B,OAAQ,MAAM,SAAS,KAAK,IAAI,QAAS;CAC3C;;;;;;;;CASA,KAAK,SAAiB,UAAwB;EAC5C,KAAK,UAAU,KAAK,SAAS;GAAE;GAAU;EAAQ,IAAI,KAAA;CACvD;AACF;AAEA,IAAM,SAAS,OAAe,QAAgB,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG,GAAG"}