argus-table 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +1 -1
  2. package/dist/argus-column-group.js +66 -0
  3. package/dist/argus-column-group.js.map +1 -0
  4. package/dist/argus-column.js +28 -26
  5. package/dist/argus-column.js.map +1 -1
  6. package/dist/argus-table.js +1497 -285
  7. package/dist/argus-table.js.map +1 -1
  8. package/dist/attributes.js +21 -0
  9. package/dist/attributes.js.map +1 -0
  10. package/dist/cdn/argus-table.js +120 -61
  11. package/dist/cdn/argus-table.js.map +1 -1
  12. package/dist/column-registry.js +23 -0
  13. package/dist/column-registry.js.map +1 -0
  14. package/dist/controllers/auto-width.js +16 -4
  15. package/dist/controllers/auto-width.js.map +1 -1
  16. package/dist/controllers/column-children.js +32 -9
  17. package/dist/controllers/column-children.js.map +1 -1
  18. package/dist/controllers/column-window.js +135 -0
  19. package/dist/controllers/column-window.js.map +1 -0
  20. package/dist/controllers/data-controller.js +9 -4
  21. package/dist/controllers/data-controller.js.map +1 -1
  22. package/dist/controllers/plugins.js +121 -0
  23. package/dist/controllers/plugins.js.map +1 -0
  24. package/dist/controllers/reorder-drag.js +19 -17
  25. package/dist/controllers/reorder-drag.js.map +1 -1
  26. package/dist/controllers/row-measure.js +122 -0
  27. package/dist/controllers/row-measure.js.map +1 -0
  28. package/dist/controllers/virtual-controller.js +315 -21
  29. package/dist/controllers/virtual-controller.js.map +1 -1
  30. package/dist/core/src/columns/column-groups.js +129 -0
  31. package/dist/core/src/columns/column-groups.js.map +1 -0
  32. package/dist/core/src/columns/column-order.js +125 -27
  33. package/dist/core/src/columns/column-order.js.map +1 -1
  34. package/dist/core/src/columns/resolve-columns.js +18 -5
  35. package/dist/core/src/columns/resolve-columns.js.map +1 -1
  36. package/dist/core/src/columns/spread-width.js +43 -0
  37. package/dist/core/src/columns/spread-width.js.map +1 -0
  38. package/dist/core/src/data/page-cache.js +4 -0
  39. package/dist/core/src/data/page-cache.js.map +1 -1
  40. package/dist/core/src/filters/filter.js +183 -0
  41. package/dist/core/src/filters/filter.js.map +1 -0
  42. package/dist/core/src/grid/row-layout.js +19 -11
  43. package/dist/core/src/grid/row-layout.js.map +1 -1
  44. package/dist/core/src/state/selection.js +71 -0
  45. package/dist/core/src/state/selection.js.map +1 -0
  46. package/dist/core/src/table/create-table.js +276 -66
  47. package/dist/core/src/table/create-table.js.map +1 -1
  48. package/dist/core/src/table/filtered-row-model.js +48 -0
  49. package/dist/core/src/table/filtered-row-model.js.map +1 -0
  50. package/dist/core/src/table/sorted-row-model.js +13 -2
  51. package/dist/core/src/table/sorted-row-model.js.map +1 -1
  52. package/dist/core/src/virtual/column-window.js +72 -0
  53. package/dist/core/src/virtual/column-window.js.map +1 -0
  54. package/dist/core/src/virtual/row-pool.js +2 -1
  55. package/dist/core/src/virtual/row-pool.js.map +1 -1
  56. package/dist/core/src/virtual/row-sizes.js +152 -0
  57. package/dist/core/src/virtual/row-sizes.js.map +1 -0
  58. package/dist/core/src/virtual/scroll-scale.js +41 -3
  59. package/dist/core/src/virtual/scroll-scale.js.map +1 -1
  60. package/dist/custom-elements.json +1683 -211
  61. package/dist/events.js +16 -8
  62. package/dist/events.js.map +1 -1
  63. package/dist/index.d.ts +808 -127
  64. package/dist/index.js +4 -2
  65. package/dist/interactive.js +22 -0
  66. package/dist/interactive.js.map +1 -0
  67. package/dist/max-height.js +22 -8
  68. package/dist/max-height.js.map +1 -1
  69. package/dist/render/body.js +48 -8
  70. package/dist/render/body.js.map +1 -1
  71. package/dist/render/cells.js +22 -11
  72. package/dist/render/cells.js.map +1 -1
  73. package/dist/render/content-directive.js +115 -0
  74. package/dist/render/content-directive.js.map +1 -0
  75. package/dist/render/content.js +33 -10
  76. package/dist/render/content.js.map +1 -1
  77. package/dist/render/header.js +104 -22
  78. package/dist/render/header.js.map +1 -1
  79. package/dist/render/pinned-rows.js +1 -1
  80. package/dist/render/pinned-rows.js.map +1 -1
  81. package/dist/styles/base.js +1 -1
  82. package/dist/styles/base.js.map +1 -1
  83. package/package.json +1 -1
@@ -0,0 +1,48 @@
1
+ import { valueVersion } from "./sorted-row-model.js";
2
+ // ../core/src/table/filtered-row-model.ts
3
+ var predicates = /* @__PURE__ */ new WeakMap();
4
+ /** Sets the test the filtered row model of `table` keeps rows by. */
5
+ function setRowPredicate(table, predicate) {
6
+ predicates.set(table, predicate);
7
+ }
8
+ /**
9
+ * The filtered row model, in place of TanStack's column filtering feature, which
10
+ * would cost bundle size and read values through `row.getValue()` (slow in Firefox,
11
+ * see sorted-row-model.ts). The model compiles the filters into one row test
12
+ * (`rowPredicate` in filters/filter.ts) and this keeps the rows it passes, reading
13
+ * values through the columns' accessors.
14
+ *
15
+ * It filters again when the test, the rows or the values (`valuesChanged`) change;
16
+ * the sorted row model then sorts again, since its source is new. The rows are the
17
+ * same `Row` objects. `rowsById` stays the unfiltered map: only TanStack's
18
+ * `getRow(id)` reads it, and the model finds rows by their index instead.
19
+ */
20
+ function filteredRowModel() {
21
+ return (table) => {
22
+ let last;
23
+ return () => {
24
+ const source = table.getPreFilteredRowModel();
25
+ const predicate = predicates.get(table);
26
+ if (!predicate) return source;
27
+ const version = valueVersion(table);
28
+ const prev = last;
29
+ if (prev?.predicate === predicate && prev.source === source && prev.version === version) return prev.model;
30
+ const rows = source.rows.filter((row) => predicate(row.original, row.index));
31
+ const model = {
32
+ rows,
33
+ flatRows: rows,
34
+ rowsById: source.rowsById
35
+ };
36
+ last = {
37
+ predicate,
38
+ source,
39
+ version,
40
+ model
41
+ };
42
+ return model;
43
+ };
44
+ };
45
+ }
46
+ export { filteredRowModel, setRowPredicate };
47
+
48
+ //# sourceMappingURL=filtered-row-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filtered-row-model.js","names":[],"sources":["../../../../../core/src/table/filtered-row-model.ts"],"sourcesContent":["import type { RowData, RowModel, Table, TableFeatures } from '@tanstack/table-core';\nimport type { RowPredicate } from '../filters/filter.js';\nimport { valueVersion } from './sorted-row-model.js';\n\n// The row test of each table, set by the model whenever filters, the quick filter or\n// the columns change: a new function filters again, `undefined` keeps every row.\nconst predicates = new WeakMap<object, RowPredicate<never> | undefined>();\n\n/** Sets the test the filtered row model of `table` keeps rows by. */\nexport function setRowPredicate<TData>(\n table: object,\n predicate: RowPredicate<TData> | undefined,\n): void {\n predicates.set(table, predicate);\n}\n\n/**\n * The filtered row model, in place of TanStack's column filtering feature, which\n * would cost bundle size and read values through `row.getValue()` (slow in Firefox,\n * see sorted-row-model.ts). The model compiles the filters into one row test\n * (`rowPredicate` in filters/filter.ts) and this keeps the rows it passes, reading\n * values through the columns' accessors.\n *\n * It filters again when the test, the rows or the values (`valuesChanged`) change;\n * the sorted row model then sorts again, since its source is new. The rows are the\n * same `Row` objects. `rowsById` stays the unfiltered map: only TanStack's\n * `getRow(id)` reads it, and the model finds rows by their index instead.\n */\nexport function filteredRowModel<TFeatures extends TableFeatures, TData extends RowData>() {\n return (table: Table<TFeatures, TData>) => {\n let last:\n | {\n predicate: unknown;\n source: RowModel<TFeatures, TData>;\n version: number | undefined;\n model: RowModel<TFeatures, TData>;\n }\n | undefined;\n return (): RowModel<TFeatures, TData> => {\n const source = table.getPreFilteredRowModel();\n const predicate = predicates.get(table) as RowPredicate<TData> | undefined;\n if (!predicate) return source;\n const version = valueVersion(table);\n const prev = last;\n if (prev?.predicate === predicate && prev.source === source && prev.version === version) {\n return prev.model;\n }\n const rows = source.rows.filter((row) => predicate(row.original, row.index));\n const model = { rows, flatRows: rows, rowsById: source.rowsById };\n last = { predicate, source, version, model };\n return model;\n };\n };\n}\n"],"mappings":";;AAMA,IAAM,6BAAa,IAAI,QAAiD;;AAGxE,SAAgB,gBACd,OACA,WACM;CACN,WAAW,IAAI,OAAO,SAAS;AACjC;;;;;;;;;;;;;AAcA,SAAgB,mBAA2E;CACzF,QAAQ,UAAmC;EACzC,IAAI;EAQJ,aAAyC;GACvC,MAAM,SAAS,MAAM,uBAAuB;GAC5C,MAAM,YAAY,WAAW,IAAI,KAAK;GACtC,IAAI,CAAC,WAAW,OAAO;GACvB,MAAM,UAAU,aAAa,KAAK;GAClC,MAAM,OAAO;GACb,IAAI,MAAM,cAAc,aAAa,KAAK,WAAW,UAAU,KAAK,YAAY,SAC9E,OAAO,KAAK;GAEd,MAAM,OAAO,OAAO,KAAK,QAAQ,QAAQ,UAAU,IAAI,UAAU,IAAI,KAAK,CAAC;GAC3E,MAAM,QAAQ;IAAE;IAAM,UAAU;IAAM,UAAU,OAAO;GAAS;GAChE,OAAO;IAAE;IAAW;IAAQ;IAAS;GAAM;GAC3C,OAAO;EACT;CACF;AACF"}
@@ -1,5 +1,14 @@
1
1
  import { column_getCanSort, column_getSortFn } from "@tanstack/table-core/static-functions";
2
2
  // ../core/src/table/sorted-row-model.ts
3
+ var valueVersions = /* @__PURE__ */ new WeakMap();
4
+ /** Makes the filtered and sorted row models of `table` filter and sort again on their next read. */
5
+ function valuesChanged(table) {
6
+ valueVersions.set(table, (valueVersions.get(table) ?? 0) + 1);
7
+ }
8
+ /** The values version of `table`, bumped by `valuesChanged()`. */
9
+ function valueVersion(table) {
10
+ return valueVersions.get(table);
11
+ }
3
12
  /**
4
13
  * The sorted row model, in place of TanStack's `createSortedRowModel()`, giving the
5
14
  * same order: the same sort functions (`auto` included), `sortUndefined`,
@@ -24,12 +33,14 @@ function sortedRowModel() {
24
33
  return () => {
25
34
  const source = table.getPreSortedRowModel();
26
35
  const sorting = table.options.state?.sorting;
36
+ const version = valueVersion(table);
27
37
  const prev = last;
28
- if (prev && prev.sorting === sorting && prev.source === source) return prev.model;
38
+ if (prev && prev.sorting === sorting && prev.source === source && prev.version === version) return prev.model;
29
39
  const model = sortRows(table, source, sorting ?? []);
30
40
  last = {
31
41
  sorting,
32
42
  source,
43
+ version,
33
44
  model
34
45
  };
35
46
  return model;
@@ -81,6 +92,6 @@ function sortRows(table, source, sorting) {
81
92
  rowsById: source.rowsById
82
93
  };
83
94
  }
84
- export { sortedRowModel };
95
+ export { sortedRowModel, valueVersion, valuesChanged };
85
96
 
86
97
  //# sourceMappingURL=sorted-row-model.js.map
@@ -1 +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"}
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\n// Bumped per table when a column reads its values differently (same id, new\n// accessor): the rows, the filters and the sorting are the same, but which rows\n// pass and their order may not be.\nconst valueVersions = new WeakMap<object, number>();\n\n/** Makes the filtered and sorted row models of `table` filter and sort again on their next read. */\nexport function valuesChanged(table: object): void {\n valueVersions.set(table, (valueVersions.get(table) ?? 0) + 1);\n}\n\n/** The values version of `table`, bumped by `valuesChanged()`. */\nexport function valueVersion(table: object): number | undefined {\n return valueVersions.get(table);\n}\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 | {\n sorting: unknown;\n source: RowModel<TFeatures, TData>;\n version: number | undefined;\n model: RowModel<TFeatures, TData>;\n }\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 version = valueVersion(table);\n const prev = last;\n if (prev && prev.sorting === sorting && prev.source === source && prev.version === version) {\n return prev.model;\n }\n const model = sortRows(table, source, sorting ?? []);\n last = { sorting, source, version, 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":";;AAeA,IAAM,gCAAgB,IAAI,QAAwB;;AAGlD,SAAgB,cAAc,OAAqB;CACjD,cAAc,IAAI,QAAQ,cAAc,IAAI,KAAK,KAAK,KAAK,CAAC;AAC9D;;AAGA,SAAgB,aAAa,OAAmC;CAC9D,OAAO,cAAc,IAAI,KAAK;AAChC;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,iBAAyE;CACvF,QAAQ,UAAmC;EACzC,IAAI;EAQJ,aAAyC;GACvC,MAAM,SAAS,MAAM,qBAAqB;GAC1C,MAAM,UAAW,MAAM,QAAQ,OAA6C;GAC5E,MAAM,UAAU,aAAa,KAAK;GAClC,MAAM,OAAO;GACb,IAAI,QAAQ,KAAK,YAAY,WAAW,KAAK,WAAW,UAAU,KAAK,YAAY,SACjF,OAAO,KAAK;GAEd,MAAM,QAAQ,SAAS,OAAO,QAAQ,WAAW,CAAC,CAAC;GACnD,OAAO;IAAE;IAAS;IAAQ;IAAS;GAAM;GACzC,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,72 @@
1
+ import { steadyWindow } from "./steady-window.js";
2
+ // ../core/src/virtual/column-window.ts
3
+ /**
4
+ * The scrolling columns a view `viewWidth` px wide shows when scrolled
5
+ * `scrollOffset` px (always positive, RTL included), indexes inclusive.
6
+ * `undefined` when there are no columns or the view has no width. `columns`
7
+ * are in visual order, so their offsets ascend.
8
+ */
9
+ function visibleColumns(columns, scrollOffset, viewWidth) {
10
+ const count = columns.length;
11
+ if (count === 0 || viewWidth <= 0) return void 0;
12
+ let lo = 0;
13
+ let hi = count - 1;
14
+ while (lo < hi) {
15
+ const mid = lo + hi >> 1;
16
+ const c = columns[mid];
17
+ if (c && c.offset + c.width > scrollOffset) hi = mid;
18
+ else lo = mid + 1;
19
+ }
20
+ const startIndex = lo;
21
+ const end = scrollOffset + viewWidth;
22
+ lo = startIndex;
23
+ hi = count - 1;
24
+ while (lo < hi) {
25
+ const mid = lo + hi + 1 >> 1;
26
+ const c = columns[mid];
27
+ if (c && c.offset < end) lo = mid;
28
+ else hi = mid - 1;
29
+ }
30
+ return {
31
+ startIndex,
32
+ endIndex: lo
33
+ };
34
+ }
35
+ /**
36
+ * The most columns a view `viewWidth` px wide can show at once, partly ones
37
+ * included, wherever it is scrolled. The view shows the most columns when it
38
+ * starts just before some column's end, so that is the only place to try.
39
+ */
40
+ function maxVisibleColumns(columns, viewWidth) {
41
+ if (viewWidth <= 0) return 0;
42
+ let most = 0;
43
+ let j = 0;
44
+ for (let i = 0; i < columns.length; i++) {
45
+ const c = columns[i];
46
+ if (!c) continue;
47
+ const limit = c.offset + c.width + viewWidth;
48
+ j = Math.max(j, i);
49
+ for (let next = columns[j + 1]; next && next.offset < limit; next = columns[j + 1]) j++;
50
+ most = Math.max(most, j - i + 1);
51
+ }
52
+ return most;
53
+ }
54
+ /**
55
+ * The scrolling columns to render, as indexes: the visible ones plus `overscan`
56
+ * on each side, always the same number of them for one set of widths (like
57
+ * rows, see `steadyWindow`: in WebKit, a column pool that grows while scrolling
58
+ * cuts a wheel scroll short). Empty while the view has no width.
59
+ * `maxVisible` is `maxVisibleColumns()` for these columns and this view.
60
+ */
61
+ function columnWindow(columns, scrollOffset, viewWidth, overscan, maxVisible) {
62
+ const visible = visibleColumns(columns, scrollOffset, viewWidth);
63
+ if (!visible) return [];
64
+ return steadyWindow({
65
+ ...visible,
66
+ overscan,
67
+ count: columns.length
68
+ }, maxVisible);
69
+ }
70
+ export { columnWindow, maxVisibleColumns, visibleColumns };
71
+
72
+ //# sourceMappingURL=column-window.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"column-window.js","names":[],"sources":["../../../../../core/src/virtual/column-window.ts"],"sourcesContent":["import { steadyWindow } from './steady-window.js';\n\n/** Where a scrolling column sits: its start within the scrolling area and its width, in px. */\nexport interface ColumnExtent {\n offset: number;\n width: number;\n}\n\n/**\n * The scrolling columns a view `viewWidth` px wide shows when scrolled\n * `scrollOffset` px (always positive, RTL included), indexes inclusive.\n * `undefined` when there are no columns or the view has no width. `columns`\n * are in visual order, so their offsets ascend.\n */\nexport function visibleColumns(\n columns: readonly ColumnExtent[],\n scrollOffset: number,\n viewWidth: number,\n): { startIndex: number; endIndex: number } | undefined {\n const count = columns.length;\n if (count === 0 || viewWidth <= 0) return undefined;\n // The first column that ends past the scroll offset.\n let lo = 0;\n let hi = count - 1;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n const c = columns[mid];\n if (c && c.offset + c.width > scrollOffset) hi = mid;\n else lo = mid + 1;\n }\n const startIndex = lo;\n // The last column that starts before the view's end.\n const end = scrollOffset + viewWidth;\n lo = startIndex;\n hi = count - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n const c = columns[mid];\n if (c && c.offset < end) lo = mid;\n else hi = mid - 1;\n }\n return { startIndex, endIndex: lo };\n}\n\n/**\n * The most columns a view `viewWidth` px wide can show at once, partly ones\n * included, wherever it is scrolled. The view shows the most columns when it\n * starts just before some column's end, so that is the only place to try.\n */\nexport function maxVisibleColumns(columns: readonly ColumnExtent[], viewWidth: number): number {\n if (viewWidth <= 0) return 0;\n let most = 0;\n let j = 0;\n for (let i = 0; i < columns.length; i++) {\n const c = columns[i];\n if (!c) continue;\n const limit = c.offset + c.width + viewWidth;\n j = Math.max(j, i);\n for (let next = columns[j + 1]; next && next.offset < limit; next = columns[j + 1]) j++;\n most = Math.max(most, j - i + 1);\n }\n return most;\n}\n\n/**\n * The scrolling columns to render, as indexes: the visible ones plus `overscan`\n * on each side, always the same number of them for one set of widths (like\n * rows, see `steadyWindow`: in WebKit, a column pool that grows while scrolling\n * cuts a wheel scroll short). Empty while the view has no width.\n * `maxVisible` is `maxVisibleColumns()` for these columns and this view.\n */\nexport function columnWindow(\n columns: readonly ColumnExtent[],\n scrollOffset: number,\n viewWidth: number,\n overscan: number,\n maxVisible: number,\n): number[] {\n const visible = visibleColumns(columns, scrollOffset, viewWidth);\n if (!visible) return [];\n return steadyWindow({ ...visible, overscan, count: columns.length }, maxVisible);\n}\n"],"mappings":";;;;;;;;AAcA,SAAgB,eACd,SACA,cACA,WACsD;CACtD,MAAM,QAAQ,QAAQ;CACtB,IAAI,UAAU,KAAK,aAAa,GAAG,OAAO,KAAA;CAE1C,IAAI,KAAK;CACT,IAAI,KAAK,QAAQ;CACjB,OAAO,KAAK,IAAI;EACd,MAAM,MAAO,KAAK,MAAO;EACzB,MAAM,IAAI,QAAQ;EAClB,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,cAAc,KAAK;OAC5C,KAAK,MAAM;CAClB;CACA,MAAM,aAAa;CAEnB,MAAM,MAAM,eAAe;CAC3B,KAAK;CACL,KAAK,QAAQ;CACb,OAAO,KAAK,IAAI;EACd,MAAM,MAAO,KAAK,KAAK,KAAM;EAC7B,MAAM,IAAI,QAAQ;EAClB,IAAI,KAAK,EAAE,SAAS,KAAK,KAAK;OACzB,KAAK,MAAM;CAClB;CACA,OAAO;EAAE;EAAY,UAAU;CAAG;AACpC;;;;;;AAOA,SAAgB,kBAAkB,SAAkC,WAA2B;CAC7F,IAAI,aAAa,GAAG,OAAO;CAC3B,IAAI,OAAO;CACX,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,IAAI,QAAQ;EAClB,IAAI,CAAC,GAAG;EACR,MAAM,QAAQ,EAAE,SAAS,EAAE,QAAQ;EACnC,IAAI,KAAK,IAAI,GAAG,CAAC;EACjB,KAAK,IAAI,OAAO,QAAQ,IAAI,IAAI,QAAQ,KAAK,SAAS,OAAO,OAAO,QAAQ,IAAI,IAAI;EACpF,OAAO,KAAK,IAAI,MAAM,IAAI,IAAI,CAAC;CACjC;CACA,OAAO;AACT;;;;;;;;AASA,SAAgB,aACd,SACA,cACA,WACA,UACA,YACU;CACV,MAAM,UAAU,eAAe,SAAS,cAAc,SAAS;CAC/D,IAAI,CAAC,SAAS,OAAO,CAAC;CACtB,OAAO,aAAa;EAAE,GAAG;EAAS;EAAU,OAAO,QAAQ;CAAO,GAAG,UAAU;AACjF"}
@@ -1,6 +1,7 @@
1
1
  // ../core/src/virtual/row-pool.ts
2
2
  /**
3
- * Assigns pool slots to rendered rows (ADR 0001).
3
+ * Assigns pool slots to rendered rows (ADR 0001), and with `virtualizeColumns`
4
+ * to rendered columns too (the ids are then column ids).
4
5
  *
5
6
  * A row keeps its slot while it stays rendered. A row entering the window takes a
6
7
  * slot freed by a row that left, so its DOM is updated in place rather than created.
@@ -1 +1 @@
1
- {"version":3,"file":"row-pool.js","names":[],"sources":["../../../../../core/src/virtual/row-pool.ts"],"sourcesContent":["/** A rendered row and the pool slot whose DOM it uses. */\nexport interface PoolEntry {\n id: string;\n slot: number;\n}\n\n/**\n * Assigns pool slots to rendered rows (ADR 0001).\n *\n * A row keeps its slot while it stays rendered. A row entering the window takes a\n * slot freed by a row that left, so its DOM is updated in place rather than created.\n * Rows are returned in slot order (\"pool order\"); the ADR 0001 addendum ruled out\n * re-sorting them by row index, so assistive tech gets row positions from\n * `aria-rowindex` instead.\n */\nexport class RowPool {\n #slots = new Map<string, number>();\n #free: number[] = [];\n #size = 0;\n #pinned: string | undefined;\n\n /** Number of slots created so far: the most rows rendered at once. */\n get size(): number {\n return this.#size;\n }\n\n /** The row id whose slot is kept while it is out of range, if any. */\n get pinned(): string | undefined {\n return this.#pinned;\n }\n\n /**\n * Keeps one row's slot, and keeps it rendered, while it is outside the rendered\n * range. Used for the row holding focus. `undefined` releases it.\n */\n pin(id: string | undefined): void {\n this.#pinned = id;\n }\n\n /** The slot a row holds, or `undefined` when it isn't rendered. */\n slotOf(id: string): number | undefined {\n return this.#slots.get(id);\n }\n\n /**\n * Updates the pool for the rows that should be rendered and returns them, plus the\n * pinned row if it isn't among them, in slot order.\n */\n update(ids: Iterable<string>): PoolEntry[] {\n const keep = new Set(ids);\n if (this.#pinned !== undefined) keep.add(this.#pinned);\n\n for (const [id, slot] of this.#slots) {\n if (!keep.has(id)) {\n this.#slots.delete(id);\n this.#free.push(slot);\n }\n }\n for (const id of keep) {\n if (!this.#slots.has(id)) this.#slots.set(id, this.#free.pop() ?? this.#size++);\n }\n\n const entries: PoolEntry[] = [];\n for (const [id, slot] of this.#slots) entries.push({ id, slot });\n return entries.sort((a, b) => a.slot - b.slot);\n }\n\n /** Frees every slot, for example when the data is replaced. */\n clear(): void {\n this.#slots.clear();\n this.#free = [];\n this.#size = 0;\n this.#pinned = undefined;\n }\n}\n"],"mappings":";;;;;;;;;;AAeA,IAAa,UAAb,MAAqB;CACnB,yBAAS,IAAI,IAAoB;CACjC,QAAkB,CAAC;CACnB,QAAQ;CACR;;CAGA,IAAI,OAAe;EACjB,OAAO,KAAK;CACd;;CAGA,IAAI,SAA6B;EAC/B,OAAO,KAAK;CACd;;;;;CAMA,IAAI,IAA8B;EAChC,KAAK,UAAU;CACjB;;CAGA,OAAO,IAAgC;EACrC,OAAO,KAAK,OAAO,IAAI,EAAE;CAC3B;;;;;CAMA,OAAO,KAAoC;EACzC,MAAM,OAAO,IAAI,IAAI,GAAG;EACxB,IAAI,KAAK,YAAY,KAAA,GAAW,KAAK,IAAI,KAAK,OAAO;EAErD,KAAK,MAAM,CAAC,IAAI,SAAS,KAAK,QAC5B,IAAI,CAAC,KAAK,IAAI,EAAE,GAAG;GACjB,KAAK,OAAO,OAAO,EAAE;GACrB,KAAK,MAAM,KAAK,IAAI;EACtB;EAEF,KAAK,MAAM,MAAM,MACf,IAAI,CAAC,KAAK,OAAO,IAAI,EAAE,GAAG,KAAK,OAAO,IAAI,IAAI,KAAK,MAAM,IAAI,KAAK,KAAK,OAAO;EAGhF,MAAM,UAAuB,CAAC;EAC9B,KAAK,MAAM,CAAC,IAAI,SAAS,KAAK,QAAQ,QAAQ,KAAK;GAAE;GAAI;EAAK,CAAC;EAC/D,OAAO,QAAQ,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;CAC/C;;CAGA,QAAc;EACZ,KAAK,OAAO,MAAM;EAClB,KAAK,QAAQ,CAAC;EACd,KAAK,QAAQ;EACb,KAAK,UAAU,KAAA;CACjB;AACF"}
1
+ {"version":3,"file":"row-pool.js","names":[],"sources":["../../../../../core/src/virtual/row-pool.ts"],"sourcesContent":["/** A rendered row and the pool slot whose DOM it uses. */\nexport interface PoolEntry {\n id: string;\n slot: number;\n}\n\n/**\n * Assigns pool slots to rendered rows (ADR 0001), and with `virtualizeColumns`\n * to rendered columns too (the ids are then column ids).\n *\n * A row keeps its slot while it stays rendered. A row entering the window takes a\n * slot freed by a row that left, so its DOM is updated in place rather than created.\n * Rows are returned in slot order (\"pool order\"); the ADR 0001 addendum ruled out\n * re-sorting them by row index, so assistive tech gets row positions from\n * `aria-rowindex` instead.\n */\nexport class RowPool {\n #slots = new Map<string, number>();\n #free: number[] = [];\n #size = 0;\n #pinned: string | undefined;\n\n /** Number of slots created so far: the most rows rendered at once. */\n get size(): number {\n return this.#size;\n }\n\n /** The row id whose slot is kept while it is out of range, if any. */\n get pinned(): string | undefined {\n return this.#pinned;\n }\n\n /**\n * Keeps one row's slot, and keeps it rendered, while it is outside the rendered\n * range. Used for the row holding focus. `undefined` releases it.\n */\n pin(id: string | undefined): void {\n this.#pinned = id;\n }\n\n /** The slot a row holds, or `undefined` when it isn't rendered. */\n slotOf(id: string): number | undefined {\n return this.#slots.get(id);\n }\n\n /**\n * Updates the pool for the rows that should be rendered and returns them, plus the\n * pinned row if it isn't among them, in slot order.\n */\n update(ids: Iterable<string>): PoolEntry[] {\n const keep = new Set(ids);\n if (this.#pinned !== undefined) keep.add(this.#pinned);\n\n for (const [id, slot] of this.#slots) {\n if (!keep.has(id)) {\n this.#slots.delete(id);\n this.#free.push(slot);\n }\n }\n for (const id of keep) {\n if (!this.#slots.has(id)) this.#slots.set(id, this.#free.pop() ?? this.#size++);\n }\n\n const entries: PoolEntry[] = [];\n for (const [id, slot] of this.#slots) entries.push({ id, slot });\n return entries.sort((a, b) => a.slot - b.slot);\n }\n\n /** Frees every slot, for example when the data is replaced. */\n clear(): void {\n this.#slots.clear();\n this.#free = [];\n this.#size = 0;\n this.#pinned = undefined;\n }\n}\n"],"mappings":";;;;;;;;;;;AAgBA,IAAa,UAAb,MAAqB;CACnB,yBAAS,IAAI,IAAoB;CACjC,QAAkB,CAAC;CACnB,QAAQ;CACR;;CAGA,IAAI,OAAe;EACjB,OAAO,KAAK;CACd;;CAGA,IAAI,SAA6B;EAC/B,OAAO,KAAK;CACd;;;;;CAMA,IAAI,IAA8B;EAChC,KAAK,UAAU;CACjB;;CAGA,OAAO,IAAgC;EACrC,OAAO,KAAK,OAAO,IAAI,EAAE;CAC3B;;;;;CAMA,OAAO,KAAoC;EACzC,MAAM,OAAO,IAAI,IAAI,GAAG;EACxB,IAAI,KAAK,YAAY,KAAA,GAAW,KAAK,IAAI,KAAK,OAAO;EAErD,KAAK,MAAM,CAAC,IAAI,SAAS,KAAK,QAC5B,IAAI,CAAC,KAAK,IAAI,EAAE,GAAG;GACjB,KAAK,OAAO,OAAO,EAAE;GACrB,KAAK,MAAM,KAAK,IAAI;EACtB;EAEF,KAAK,MAAM,MAAM,MACf,IAAI,CAAC,KAAK,OAAO,IAAI,EAAE,GAAG,KAAK,OAAO,IAAI,IAAI,KAAK,MAAM,IAAI,KAAK,KAAK,OAAO;EAGhF,MAAM,UAAuB,CAAC;EAC9B,KAAK,MAAM,CAAC,IAAI,SAAS,KAAK,QAAQ,QAAQ,KAAK;GAAE;GAAI;EAAK,CAAC;EAC/D,OAAO,QAAQ,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;CAC/C;;CAGA,QAAc;EACZ,KAAK,OAAO,MAAM;EAClB,KAAK,QAAQ,CAAC;EACd,KAAK,QAAQ;EACb,KAAK,UAAU,KAAA;CACjB;AACF"}
@@ -0,0 +1,152 @@
1
+ // ../core/src/virtual/row-sizes.ts
2
+ /**
3
+ * Row heights for rows that aren't all the same height (`rowHeight: 'auto'`):
4
+ * every row is `estimate` px until it is measured. Offsets, the row at an
5
+ * offset and the total take O(log n), so measuring a row near the top of a
6
+ * million rows doesn't recompute every row below it.
7
+ *
8
+ * A Fenwick tree over each row's difference from the estimate. Nothing is
9
+ * allocated until a row differs from the estimate: one-line rows cost nothing.
10
+ */
11
+ var RowSizes = class {
12
+ #count = 0;
13
+ #estimate = 1;
14
+ /** Each row's difference from the estimate; `undefined` while every row is the estimate. */
15
+ #delta;
16
+ /** Fenwick tree over `#delta`, 1-based. */
17
+ #tree;
18
+ /** The highest power of two at most `#count`, for `indexAt`. */
19
+ #step = 0;
20
+ /** The sum of all differences. */
21
+ #sum = 0;
22
+ /** Number of rows. */
23
+ get count() {
24
+ return this.#count;
25
+ }
26
+ /** The height of all rows. */
27
+ get total() {
28
+ return this.#count * this.#estimate + this.#sum;
29
+ }
30
+ /**
31
+ * Starts over: `count` rows of `estimate` px, then the heights in `sizes`
32
+ * (`[index, px]`; indexes out of range are ignored). O(count).
33
+ */
34
+ reset(count, estimate, sizes = []) {
35
+ this.#count = Math.max(0, Math.floor(count));
36
+ this.#estimate = estimate > 0 ? estimate : 1;
37
+ this.#step = highestPowerOfTwo(this.#count);
38
+ this.#sum = 0;
39
+ let delta;
40
+ for (const [index, px] of sizes) {
41
+ if (index < 0 || index >= this.#count || !Number.isFinite(px)) continue;
42
+ const d = px - this.#estimate;
43
+ if (d === 0) continue;
44
+ delta ??= this.#buffer(this.#count);
45
+ this.#sum += d - (delta[index] ?? 0);
46
+ delta[index] = d;
47
+ }
48
+ if (delta) this.#build(delta);
49
+ else this.#clear();
50
+ }
51
+ /**
52
+ * Changes the number of rows (a data source counting more), keeping the
53
+ * heights of the rows that stay. O(count) when any row was measured.
54
+ */
55
+ resize(count) {
56
+ count = Math.max(0, Math.floor(count));
57
+ if (count === this.#count) return;
58
+ const old = this.#delta;
59
+ this.#count = count;
60
+ this.#step = highestPowerOfTwo(count);
61
+ if (!old) return;
62
+ const delta = new Float64Array(count);
63
+ delta.set(old.subarray(0, Math.min(count, old.length)));
64
+ this.#sum = 0;
65
+ for (const d of delta) this.#sum += d;
66
+ this.#build(delta);
67
+ }
68
+ /** The height of the row at `index`. */
69
+ sizeOf(index) {
70
+ return this.#estimate + (this.#delta?.[index] ?? 0);
71
+ }
72
+ /** Sets the height of the row at `index`. Returns how much it changed by. */
73
+ set(index, px) {
74
+ if (index < 0 || index >= this.#count || !Number.isFinite(px)) return 0;
75
+ const d = px - this.sizeOf(index);
76
+ if (Math.abs(d) < 1e-6) return 0;
77
+ if (!this.#delta || !this.#tree) this.#build(this.#buffer(this.#count));
78
+ const delta = this.#delta;
79
+ const tree = this.#tree;
80
+ if (!delta || !tree) return 0;
81
+ delta[index] = (delta[index] ?? 0) + d;
82
+ for (let i = index + 1; i <= this.#count; i += i & -i) tree[i] = (tree[i] ?? 0) + d;
83
+ this.#sum += d;
84
+ return d;
85
+ }
86
+ /** The offset of the row at `index` from the first row's top: the heights of the rows before it. */
87
+ startOf(index) {
88
+ const i = Math.min(Math.max(Math.floor(index), 0), this.#count);
89
+ let start = i * this.#estimate;
90
+ const tree = this.#tree;
91
+ if (tree) for (let j = i; j > 0; j -= j & -j) start += tree[j] ?? 0;
92
+ return start;
93
+ }
94
+ /** The row at `offset` px from the first row's top, clamped to the rows. 0 when there are none. */
95
+ indexAt(offset) {
96
+ const count = this.#count;
97
+ if (count === 0 || !(offset > 0)) return 0;
98
+ const estimate = this.#estimate;
99
+ const tree = this.#tree;
100
+ let index;
101
+ if (!tree) index = Math.floor(offset / estimate);
102
+ else {
103
+ index = 0;
104
+ let rest = offset;
105
+ for (let step = this.#step; step > 0; step >>= 1) {
106
+ const next = index + step;
107
+ if (next > count) continue;
108
+ const size = step * estimate + (tree[next] ?? 0);
109
+ if (size <= rest) {
110
+ index = next;
111
+ rest -= size;
112
+ }
113
+ }
114
+ }
115
+ return Math.min(index, count - 1);
116
+ }
117
+ /** A zeroed buffer for `count` differences, reusing the current one when it fits. */
118
+ #buffer(count) {
119
+ const delta = this.#delta;
120
+ if (delta?.length === count) {
121
+ delta.fill(0);
122
+ return delta;
123
+ }
124
+ return new Float64Array(count);
125
+ }
126
+ /** Builds the tree over `delta` in O(count). */
127
+ #build(delta) {
128
+ const count = this.#count;
129
+ let tree = this.#tree;
130
+ if (tree?.length !== count + 1) tree = new Float64Array(count + 1);
131
+ tree.set(delta, 1);
132
+ tree[0] = 0;
133
+ for (let i = 1; i <= count; i++) {
134
+ const parent = i + (i & -i);
135
+ if (parent <= count) tree[parent] = (tree[parent] ?? 0) + (tree[i] ?? 0);
136
+ }
137
+ this.#delta = delta;
138
+ this.#tree = tree;
139
+ }
140
+ #clear() {
141
+ this.#delta = void 0;
142
+ this.#tree = void 0;
143
+ }
144
+ };
145
+ function highestPowerOfTwo(n) {
146
+ let step = 1;
147
+ while (step * 2 <= n) step *= 2;
148
+ return n > 0 ? step : 0;
149
+ }
150
+ export { RowSizes };
151
+
152
+ //# sourceMappingURL=row-sizes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row-sizes.js","names":[],"sources":["../../../../../core/src/virtual/row-sizes.ts"],"sourcesContent":["/**\n * Row heights for rows that aren't all the same height (`rowHeight: 'auto'`):\n * every row is `estimate` px until it is measured. Offsets, the row at an\n * offset and the total take O(log n), so measuring a row near the top of a\n * million rows doesn't recompute every row below it.\n *\n * A Fenwick tree over each row's difference from the estimate. Nothing is\n * allocated until a row differs from the estimate: one-line rows cost nothing.\n */\nexport class RowSizes {\n #count = 0;\n #estimate = 1;\n /** Each row's difference from the estimate; `undefined` while every row is the estimate. */\n #delta: Float64Array | undefined;\n /** Fenwick tree over `#delta`, 1-based. */\n #tree: Float64Array | undefined;\n /** The highest power of two at most `#count`, for `indexAt`. */\n #step = 0;\n /** The sum of all differences. */\n #sum = 0;\n\n /** Number of rows. */\n get count(): number {\n return this.#count;\n }\n\n /** The height of all rows. */\n get total(): number {\n return this.#count * this.#estimate + this.#sum;\n }\n\n /**\n * Starts over: `count` rows of `estimate` px, then the heights in `sizes`\n * (`[index, px]`; indexes out of range are ignored). O(count).\n */\n reset(count: number, estimate: number, sizes: Iterable<readonly [number, number]> = []): void {\n this.#count = Math.max(0, Math.floor(count));\n this.#estimate = estimate > 0 ? estimate : 1;\n this.#step = highestPowerOfTwo(this.#count);\n this.#sum = 0;\n let delta: Float64Array | undefined;\n for (const [index, px] of sizes) {\n if (index < 0 || index >= this.#count || !Number.isFinite(px)) continue;\n const d = px - this.#estimate;\n if (d === 0) continue;\n delta ??= this.#buffer(this.#count);\n this.#sum += d - (delta[index] ?? 0);\n delta[index] = d;\n }\n if (delta) this.#build(delta);\n else this.#clear();\n }\n\n /**\n * Changes the number of rows (a data source counting more), keeping the\n * heights of the rows that stay. O(count) when any row was measured.\n */\n resize(count: number): void {\n count = Math.max(0, Math.floor(count));\n if (count === this.#count) return;\n const old = this.#delta;\n this.#count = count;\n this.#step = highestPowerOfTwo(count);\n if (!old) return;\n const delta = new Float64Array(count);\n delta.set(old.subarray(0, Math.min(count, old.length)));\n this.#sum = 0;\n for (const d of delta) this.#sum += d;\n this.#build(delta);\n }\n\n /** The height of the row at `index`. */\n sizeOf(index: number): number {\n return this.#estimate + (this.#delta?.[index] ?? 0);\n }\n\n /** Sets the height of the row at `index`. Returns how much it changed by. */\n set(index: number, px: number): number {\n if (index < 0 || index >= this.#count || !Number.isFinite(px)) return 0;\n const d = px - this.sizeOf(index);\n if (Math.abs(d) < 1e-6) return 0;\n if (!this.#delta || !this.#tree) this.#build(this.#buffer(this.#count));\n const delta = this.#delta;\n const tree = this.#tree;\n if (!delta || !tree) return 0;\n delta[index] = (delta[index] ?? 0) + d;\n for (let i = index + 1; i <= this.#count; i += i & -i) tree[i] = (tree[i] ?? 0) + d;\n this.#sum += d;\n return d;\n }\n\n /** The offset of the row at `index` from the first row's top: the heights of the rows before it. */\n startOf(index: number): number {\n const i = Math.min(Math.max(Math.floor(index), 0), this.#count);\n let start = i * this.#estimate;\n const tree = this.#tree;\n if (tree) for (let j = i; j > 0; j -= j & -j) start += tree[j] ?? 0;\n return start;\n }\n\n /** The row at `offset` px from the first row's top, clamped to the rows. 0 when there are none. */\n indexAt(offset: number): number {\n const count = this.#count;\n if (count === 0 || !(offset > 0)) return 0;\n const estimate = this.#estimate;\n const tree = this.#tree;\n let index: number;\n if (!tree) {\n index = Math.floor(offset / estimate);\n } else {\n // The most rows whose heights add up to at most `offset`: that many rows\n // end at or above it, so the row at it is the next one.\n index = 0;\n let rest = offset;\n for (let step = this.#step; step > 0; step >>= 1) {\n const next = index + step;\n if (next > count) continue;\n const size = step * estimate + (tree[next] ?? 0);\n if (size <= rest) {\n index = next;\n rest -= size;\n }\n }\n }\n return Math.min(index, count - 1);\n }\n\n /** A zeroed buffer for `count` differences, reusing the current one when it fits. */\n #buffer(count: number): Float64Array {\n const delta = this.#delta;\n if (delta?.length === count) {\n delta.fill(0);\n return delta;\n }\n return new Float64Array(count);\n }\n\n /** Builds the tree over `delta` in O(count). */\n #build(delta: Float64Array): void {\n const count = this.#count;\n let tree = this.#tree;\n if (tree?.length !== count + 1) tree = new Float64Array(count + 1);\n tree.set(delta, 1);\n tree[0] = 0;\n for (let i = 1; i <= count; i++) {\n const parent = i + (i & -i);\n if (parent <= count) tree[parent] = (tree[parent] ?? 0) + (tree[i] ?? 0);\n }\n this.#delta = delta;\n this.#tree = tree;\n }\n\n #clear(): void {\n this.#delta = undefined;\n this.#tree = undefined;\n }\n}\n\nfunction highestPowerOfTwo(n: number): number {\n let step = 1;\n while (step * 2 <= n) step *= 2;\n return n > 0 ? step : 0;\n}\n"],"mappings":"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"}
@@ -3,7 +3,8 @@
3
3
  * Maps the scroll position of a body that is too tall to render onto the rows.
4
4
  *
5
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
6
+ * when zoomed; about 17.9M px in Firefox, which drops a taller element to 0, and
7
+ * lets go of sticky elements past 8.95M px of scrolling, so 8M px there). A
7
8
  * body taller than `cap` renders `cap` px tall, and the scroll position is scaled
8
9
  * linearly: the top of the physical scroll range is the first row, its bottom the
9
10
  * last. Offsets the virtualizer works with are logical (unscaled) px; the
@@ -18,6 +19,8 @@ var ScrollScale = class {
18
19
  #cap = Infinity;
19
20
  /** The logical offset last written and the physical offset it landed on. */
20
21
  #landed;
22
+ /** A point the mapping passes through instead of the straight line (see `pivot`). */
23
+ #pivot;
21
24
  /**
22
25
  * `logical`: the height of all body rows in px. `cap`: the tallest body the
23
26
  * engine renders. Returns whether anything changed.
@@ -27,8 +30,28 @@ var ScrollScale = class {
27
30
  this.#logical = logical;
28
31
  this.#cap = cap;
29
32
  this.#landed = void 0;
33
+ this.#pivot = void 0;
30
34
  return true;
31
35
  }
36
+ /**
37
+ * Bends the mapping so that the physical offset `physical` maps to `logical`:
38
+ * linear from the top to that point and from there to the bottom. Measured row
39
+ * heights change the logical height of a scaled body while it scrolls; pivoting
40
+ * at the current offset keeps the rows in view where they are, without writing
41
+ * the scroll position (which would cut a scroll short). Call after
42
+ * `configure()`, which drops the pivot. Ignored at the ends of the range,
43
+ * where the straight line keeps the first and the last row reachable.
44
+ */
45
+ pivot(physical, logical, range) {
46
+ this.#landed = void 0;
47
+ const total = range + this.excess;
48
+ const inside = this.scaled && physical > 1 && physical < range - 1 && logical > 0 && logical < total;
49
+ this.#pivot = inside ? {
50
+ physical,
51
+ logical,
52
+ range
53
+ } : void 0;
54
+ }
32
55
  /** Whether the body is taller than the cap, so offsets are scaled. */
33
56
  get scaled() {
34
57
  return this.#logical > this.#cap;
@@ -46,13 +69,28 @@ var ScrollScale = class {
46
69
  if (!this.scaled || range <= 0) return physical;
47
70
  const landed = this.#landed;
48
71
  if (landed?.physical === physical) return landed.logical;
49
- return clamp(physical, range) * (range + this.excess) / range;
72
+ const total = range + this.excess;
73
+ const p = clamp(physical, range);
74
+ const pivot = this.#pivotFor(range);
75
+ if (!pivot) return p * total / range;
76
+ if (p <= pivot.physical) return p * pivot.logical / pivot.physical;
77
+ return pivot.logical + (p - pivot.physical) * (total - pivot.logical) / (range - pivot.physical);
50
78
  }
51
79
  /** The physical offset for a logical one, given the physical scroll range. */
52
80
  toPhysical(logical, range) {
53
81
  if (!this.scaled || range <= 0) return logical;
54
82
  const total = range + this.excess;
55
- return clamp(logical, total) * range / total;
83
+ const l = clamp(logical, total);
84
+ const pivot = this.#pivotFor(range);
85
+ if (!pivot) return l * range / total;
86
+ if (l <= pivot.logical) return l * pivot.physical / pivot.logical;
87
+ return pivot.physical + (l - pivot.logical) * (range - pivot.physical) / (total - pivot.logical);
88
+ }
89
+ /** The pivot, if it was set for this range and is still inside it. */
90
+ #pivotFor(range) {
91
+ const pivot = this.#pivot;
92
+ if (pivot?.range !== range || pivot.logical >= range + this.excess) return void 0;
93
+ return pivot;
56
94
  }
57
95
  /**
58
96
  * Records that a write of the logical offset `logical` left the scroller at
@@ -1 +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"}
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, and\n * lets go of sticky elements past 8.95M px of scrolling, so 8M px there). 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 /** A point the mapping passes through instead of the straight line (see `pivot`). */\n #pivot: { physical: number; logical: number; range: 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 this.#pivot = undefined;\n return true;\n }\n\n /**\n * Bends the mapping so that the physical offset `physical` maps to `logical`:\n * linear from the top to that point and from there to the bottom. Measured row\n * heights change the logical height of a scaled body while it scrolls; pivoting\n * at the current offset keeps the rows in view where they are, without writing\n * the scroll position (which would cut a scroll short). Call after\n * `configure()`, which drops the pivot. Ignored at the ends of the range,\n * where the straight line keeps the first and the last row reachable.\n */\n pivot(physical: number, logical: number, range: number): void {\n this.#landed = undefined;\n const total = range + this.excess;\n const inside =\n this.scaled && physical > 1 && physical < range - 1 && logical > 0 && logical < total;\n this.#pivot = inside ? { physical, logical, range } : undefined;\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 const total = range + this.excess;\n const p = clamp(physical, range);\n const pivot = this.#pivotFor(range);\n if (!pivot) return (p * total) / range;\n if (p <= pivot.physical) return (p * pivot.logical) / pivot.physical;\n return (\n pivot.logical + ((p - pivot.physical) * (total - pivot.logical)) / (range - pivot.physical)\n );\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 const l = clamp(logical, total);\n const pivot = this.#pivotFor(range);\n if (!pivot) return (l * range) / total;\n if (l <= pivot.logical) return (l * pivot.physical) / pivot.logical;\n return (\n pivot.physical + ((l - pivot.logical) * (range - pivot.physical)) / (total - pivot.logical)\n );\n }\n\n /** The pivot, if it was set for this range and is still inside it. */\n #pivotFor(range: number) {\n const pivot = this.#pivot;\n if (pivot?.range !== range || pivot.logical >= range + this.excess) return undefined;\n return pivot;\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":";;;;;;;;;;;;;;;;AAeA,IAAa,cAAb,MAAyB;CACvB,WAAW;CACX,OAAO;;CAEP;;CAEA;;;;;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,KAAK,SAAS,KAAA;EACd,OAAO;CACT;;;;;;;;;;CAWA,MAAM,UAAkB,SAAiB,OAAqB;EAC5D,KAAK,UAAU,KAAA;EACf,MAAM,QAAQ,QAAQ,KAAK;EAC3B,MAAM,SACJ,KAAK,UAAU,WAAW,KAAK,WAAW,QAAQ,KAAK,UAAU,KAAK,UAAU;EAClF,KAAK,SAAS,SAAS;GAAE;GAAU;GAAS;EAAM,IAAI,KAAA;CACxD;;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,MAAM,QAAQ,QAAQ,KAAK;EAC3B,MAAM,IAAI,MAAM,UAAU,KAAK;EAC/B,MAAM,QAAQ,KAAK,UAAU,KAAK;EAClC,IAAI,CAAC,OAAO,OAAQ,IAAI,QAAS;EACjC,IAAI,KAAK,MAAM,UAAU,OAAQ,IAAI,MAAM,UAAW,MAAM;EAC5D,OACE,MAAM,WAAY,IAAI,MAAM,aAAa,QAAQ,MAAM,YAAa,QAAQ,MAAM;CAEtF;;CAGA,WAAW,SAAiB,OAAuB;EACjD,IAAI,CAAC,KAAK,UAAU,SAAS,GAAG,OAAO;EACvC,MAAM,QAAQ,QAAQ,KAAK;EAC3B,MAAM,IAAI,MAAM,SAAS,KAAK;EAC9B,MAAM,QAAQ,KAAK,UAAU,KAAK;EAClC,IAAI,CAAC,OAAO,OAAQ,IAAI,QAAS;EACjC,IAAI,KAAK,MAAM,SAAS,OAAQ,IAAI,MAAM,WAAY,MAAM;EAC5D,OACE,MAAM,YAAa,IAAI,MAAM,YAAY,QAAQ,MAAM,aAAc,QAAQ,MAAM;CAEvF;;CAGA,UAAU,OAAe;EACvB,MAAM,QAAQ,KAAK;EACnB,IAAI,OAAO,UAAU,SAAS,MAAM,WAAW,QAAQ,KAAK,QAAQ,OAAO,KAAA;EAC3E,OAAO;CACT;;;;;;;;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"}