argus-table 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/argus-column-group.js +66 -0
- package/dist/argus-column-group.js.map +1 -0
- package/dist/argus-column.js +4 -25
- package/dist/argus-column.js.map +1 -1
- package/dist/argus-table.js +553 -200
- package/dist/argus-table.js.map +1 -1
- package/dist/attributes.js +21 -0
- package/dist/attributes.js.map +1 -0
- package/dist/cdn/argus-table.js +92 -57
- package/dist/cdn/argus-table.js.map +1 -1
- package/dist/column-registry.js +23 -0
- package/dist/column-registry.js.map +1 -0
- package/dist/controllers/auto-width.js +6 -5
- package/dist/controllers/auto-width.js.map +1 -1
- package/dist/controllers/column-children.js +32 -9
- package/dist/controllers/column-children.js.map +1 -1
- package/dist/controllers/column-window.js +0 -4
- package/dist/controllers/column-window.js.map +1 -1
- package/dist/controllers/plugins.js +25 -6
- package/dist/controllers/plugins.js.map +1 -1
- package/dist/controllers/reorder-drag.js +12 -9
- package/dist/controllers/reorder-drag.js.map +1 -1
- package/dist/controllers/row-measure.js +2 -1
- package/dist/controllers/row-measure.js.map +1 -1
- package/dist/controllers/virtual-controller.js +25 -32
- package/dist/controllers/virtual-controller.js.map +1 -1
- package/dist/core/src/columns/column-groups.js +129 -0
- package/dist/core/src/columns/column-groups.js.map +1 -0
- package/dist/core/src/columns/column-order.js +125 -31
- package/dist/core/src/columns/column-order.js.map +1 -1
- package/dist/core/src/columns/resolve-columns.js +0 -1
- package/dist/core/src/columns/resolve-columns.js.map +1 -1
- package/dist/core/src/columns/spread-width.js +43 -0
- package/dist/core/src/columns/spread-width.js.map +1 -0
- package/dist/core/src/filters/filter.js +6 -8
- package/dist/core/src/filters/filter.js.map +1 -1
- package/dist/core/src/grid/row-layout.js +19 -11
- package/dist/core/src/grid/row-layout.js.map +1 -1
- package/dist/core/src/state/selection.js +2 -1
- package/dist/core/src/state/selection.js.map +1 -1
- package/dist/core/src/table/create-table.js +57 -22
- package/dist/core/src/table/create-table.js.map +1 -1
- package/dist/core/src/table/sorted-row-model.js.map +1 -1
- package/dist/core/src/virtual/row-sizes.js +0 -4
- package/dist/core/src/virtual/row-sizes.js.map +1 -1
- package/dist/custom-elements.json +489 -245
- package/dist/events.js +16 -16
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +273 -127
- package/dist/index.js +3 -2
- package/dist/render/body.js +2 -2
- package/dist/render/body.js.map +1 -1
- package/dist/render/cells.js +11 -3
- package/dist/render/cells.js.map +1 -1
- package/dist/render/content-directive.js +1 -1
- package/dist/render/content-directive.js.map +1 -1
- package/dist/render/content.js +15 -3
- package/dist/render/content.js.map +1 -1
- package/dist/render/header.js +82 -19
- package/dist/render/header.js.map +1 -1
- package/dist/styles/base.js +1 -1
- package/dist/styles/base.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// ../core/src/columns/spread-width.ts
|
|
2
|
+
/**
|
|
3
|
+
* Spreads `total` px over columns in proportion to their widths, each within its
|
|
4
|
+
* bounds: a column that hits a bound keeps it, and the rest is spread over the
|
|
5
|
+
* others. Widths are whole px that add up to `total` (rounded) as far as the
|
|
6
|
+
* bounds allow. Columns of no width share equally.
|
|
7
|
+
*/
|
|
8
|
+
function spreadWidth(columns, total) {
|
|
9
|
+
const exact = /* @__PURE__ */ new Map();
|
|
10
|
+
let open = [...columns];
|
|
11
|
+
let remaining = total;
|
|
12
|
+
while (open.length > 0) {
|
|
13
|
+
const sum = open.reduce((s, c) => s + Math.max(0, c.width), 0);
|
|
14
|
+
const share = (c) => sum > 0 ? Math.max(0, c.width) / sum * remaining : remaining / open.length;
|
|
15
|
+
const clamped = open.filter((c) => {
|
|
16
|
+
const w = share(c);
|
|
17
|
+
return w < c.minWidth || w > c.maxWidth;
|
|
18
|
+
});
|
|
19
|
+
if (clamped.length === 0) {
|
|
20
|
+
for (const c of open) exact.set(c.id, share(c));
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
for (const c of clamped) {
|
|
24
|
+
const w = Math.min(c.maxWidth, Math.max(c.minWidth, share(c)));
|
|
25
|
+
exact.set(c.id, w);
|
|
26
|
+
remaining -= w;
|
|
27
|
+
}
|
|
28
|
+
open = open.filter((c) => !clamped.includes(c));
|
|
29
|
+
}
|
|
30
|
+
const widths = /* @__PURE__ */ new Map();
|
|
31
|
+
let acc = 0;
|
|
32
|
+
let rounded = 0;
|
|
33
|
+
for (const c of columns) {
|
|
34
|
+
acc += exact.get(c.id) ?? c.width;
|
|
35
|
+
const next = Math.round(acc);
|
|
36
|
+
widths.set(c.id, next - rounded);
|
|
37
|
+
rounded = next;
|
|
38
|
+
}
|
|
39
|
+
return widths;
|
|
40
|
+
}
|
|
41
|
+
export { spreadWidth };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=spread-width.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spread-width.js","names":[],"sources":["../../../../../core/src/columns/spread-width.ts"],"sourcesContent":["/** A column taking part in a group resize: its width now, and its bounds. */\nexport interface SpreadColumn {\n id: string;\n width: number;\n minWidth: number;\n maxWidth: number;\n}\n\n/**\n * Spreads `total` px over columns in proportion to their widths, each within its\n * bounds: a column that hits a bound keeps it, and the rest is spread over the\n * others. Widths are whole px that add up to `total` (rounded) as far as the\n * bounds allow. Columns of no width share equally.\n */\nexport function spreadWidth(columns: readonly SpreadColumn[], total: number): Map<string, number> {\n const exact = new Map<string, number>();\n let open = [...columns];\n let remaining = total;\n while (open.length > 0) {\n const sum = open.reduce((s, c) => s + Math.max(0, c.width), 0);\n const share = (c: SpreadColumn) =>\n sum > 0 ? (Math.max(0, c.width) / sum) * remaining : remaining / open.length;\n const clamped = open.filter((c) => {\n const w = share(c);\n return w < c.minWidth || w > c.maxWidth;\n });\n if (clamped.length === 0) {\n for (const c of open) exact.set(c.id, share(c));\n break;\n }\n for (const c of clamped) {\n const w = Math.min(c.maxWidth, Math.max(c.minWidth, share(c)));\n exact.set(c.id, w);\n remaining -= w;\n }\n open = open.filter((c) => !clamped.includes(c));\n }\n // Whole px: round the running sum, so the parts add up to the rounded total.\n const widths = new Map<string, number>();\n let acc = 0;\n let rounded = 0;\n for (const c of columns) {\n acc += exact.get(c.id) ?? c.width;\n const next = Math.round(acc);\n widths.set(c.id, next - rounded);\n rounded = next;\n }\n return widths;\n}\n"],"mappings":";;;;;;;AAcA,SAAgB,YAAY,SAAkC,OAAoC;CAChG,MAAM,wBAAQ,IAAI,IAAoB;CACtC,IAAI,OAAO,CAAC,GAAG,OAAO;CACtB,IAAI,YAAY;CAChB,OAAO,KAAK,SAAS,GAAG;EACtB,MAAM,MAAM,KAAK,QAAQ,GAAG,MAAM,IAAI,KAAK,IAAI,GAAG,EAAE,KAAK,GAAG,CAAC;EAC7D,MAAM,SAAS,MACb,MAAM,IAAK,KAAK,IAAI,GAAG,EAAE,KAAK,IAAI,MAAO,YAAY,YAAY,KAAK;EACxE,MAAM,UAAU,KAAK,QAAQ,MAAM;GACjC,MAAM,IAAI,MAAM,CAAC;GACjB,OAAO,IAAI,EAAE,YAAY,IAAI,EAAE;EACjC,CAAC;EACD,IAAI,QAAQ,WAAW,GAAG;GACxB,KAAK,MAAM,KAAK,MAAM,MAAM,IAAI,EAAE,IAAI,MAAM,CAAC,CAAC;GAC9C;EACF;EACA,KAAK,MAAM,KAAK,SAAS;GACvB,MAAM,IAAI,KAAK,IAAI,EAAE,UAAU,KAAK,IAAI,EAAE,UAAU,MAAM,CAAC,CAAC,CAAC;GAC7D,MAAM,IAAI,EAAE,IAAI,CAAC;GACjB,aAAa;EACf;EACA,OAAO,KAAK,QAAQ,MAAM,CAAC,QAAQ,SAAS,CAAC,CAAC;CAChD;CAEA,MAAM,yBAAS,IAAI,IAAoB;CACvC,IAAI,MAAM;CACV,IAAI,UAAU;CACd,KAAK,MAAM,KAAK,SAAS;EACvB,OAAO,MAAM,IAAI,EAAE,EAAE,KAAK,EAAE;EAC5B,MAAM,OAAO,KAAK,MAAM,GAAG;EAC3B,OAAO,IAAI,EAAE,IAAI,OAAO,OAAO;EAC/B,UAAU;CACZ;CACA,OAAO;AACT"}
|
|
@@ -87,11 +87,9 @@ function compileFilter(filter) {
|
|
|
87
87
|
if (filter.op === "notEmpty") return (v) => textOf(v) !== "";
|
|
88
88
|
const needle = (filter.value ?? "").toLowerCase();
|
|
89
89
|
if (!needle) return void 0;
|
|
90
|
-
|
|
91
|
-
return (v) =>
|
|
92
|
-
|
|
93
|
-
return op === "equals" ? text === needle : op === "startsWith" ? text.startsWith(needle) : text.includes(needle);
|
|
94
|
-
};
|
|
90
|
+
if (filter.op === "equals") return (v) => textOf(v).toLowerCase() === needle;
|
|
91
|
+
if (filter.op === "startsWith") return (v) => textOf(v).toLowerCase().startsWith(needle);
|
|
92
|
+
return (v) => textOf(v).toLowerCase().includes(needle);
|
|
95
93
|
}
|
|
96
94
|
case "number": {
|
|
97
95
|
const test = compare(filter.op, numberOf(filter.value), numberOf(filter.to));
|
|
@@ -118,13 +116,13 @@ function quickTerms(quickFilter) {
|
|
|
118
116
|
*
|
|
119
117
|
* Each value is read once per test through the column's `getValue`. A column with a
|
|
120
118
|
* `filterFn` uses it in place of the built-in matching. Filters for unknown columns,
|
|
121
|
-
* and `except` (for the values a column's own filter offers), are skipped.
|
|
119
|
+
* and on the `except` columns (for the values a column's own filter offers), are skipped.
|
|
122
120
|
*/
|
|
123
121
|
function rowPredicate(columns, filters, quickFilter, quickColumns, except) {
|
|
124
122
|
const tests = [];
|
|
125
123
|
for (const [id, filter] of Object.entries(filters ?? {})) {
|
|
126
124
|
const column = columns.get(id);
|
|
127
|
-
if (!column || id
|
|
125
|
+
if (!column || except?.has(id)) continue;
|
|
128
126
|
const { getValue, filterFn } = column;
|
|
129
127
|
if (filterFn) {
|
|
130
128
|
tests.push((row, index) => filterFn(getValue(row, index), filter, row));
|
|
@@ -180,6 +178,6 @@ function distinctValues(rows, column, keep, limit) {
|
|
|
180
178
|
truncated
|
|
181
179
|
};
|
|
182
180
|
}
|
|
183
|
-
export { compareSetValues, compileFilter, dayOf, distinctValues, isEmptyValue,
|
|
181
|
+
export { compareSetValues, compileFilter, dayOf, distinctValues, isEmptyValue, rowPredicate, setValueOf, textOf };
|
|
184
182
|
|
|
185
183
|
//# sourceMappingURL=filter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter.js","names":[],"sources":["../../../../../core/src/filters/filter.ts"],"sourcesContent":["import type { ColumnFilter, ColumnFilterState, FilterValue, RangeFilterOp } from '../types.js';\n\n// Filtering rules, free of TanStack and the DOM: each filter compiles once into a\n// test of one value, and the tests of a filter state combine into one test of a row.\n\n/** A column as filtering sees it. */\nexport interface FilterColumn<TData> {\n id: string;\n getValue: (row: TData, index: number) => unknown;\n filterFn?: ((value: unknown, filter: ColumnFilter, row: TData) => boolean) | undefined;\n}\n\n/** Keeps a row (by its object and its index in `data`) or drops it. */\nexport type RowPredicate<TData> = (row: TData, index: number) => boolean;\n\n/** `true` for `undefined`, `null`, `''`, `NaN` and an invalid `Date`. */\nexport function isEmptyValue(value: unknown): boolean {\n if (value === undefined || value === null || value === '') return true;\n if (typeof value === 'number') return Number.isNaN(value);\n return value instanceof Date && Number.isNaN(value.getTime());\n}\n\nconst pad = (n: number, width = 2) => String(n).padStart(width, '0');\n\n/** A `Date` as its local day, `'2026-11-10'`. */\nfunction isoDay(date: Date): string {\n return `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;\n}\n\n/**\n * A value as the text filters and the quick filter read it: strings as they are,\n * numbers and booleans as `String()` gives them, a `Date` as its local day\n * (`'2026-11-10'`), and anything else (empty values, objects) as `''`.\n */\nexport function textOf(value: unknown): string {\n if (typeof value === 'string') return value;\n if (typeof value === 'number') return Number.isNaN(value) ? '' : String(value);\n if (typeof value === 'boolean') return String(value);\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? '' : isoDay(value);\n return '';\n}\n\nconst DAY = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\n/**\n * A value's local day as a sortable number (`20261110`), or `undefined`. Takes a `Date`,\n * a timestamp, an ISO date (`'2026-11-10'`, read as that day, not as UTC midnight) or\n * any other string `Date` can parse.\n */\nexport function dayOf(value: unknown): number | undefined {\n let date: Date;\n if (value instanceof Date) date = value;\n else if (typeof value === 'number') date = new Date(value);\n else if (typeof value === 'string') {\n const day = DAY.exec(value.trim());\n if (day) return Number(day[1]) * 10000 + Number(day[2]) * 100 + Number(day[3]);\n if (!value.trim()) return undefined;\n date = new Date(value);\n } else return undefined;\n const time = date.getTime();\n if (Number.isNaN(time)) return undefined;\n return date.getFullYear() * 10000 + (date.getMonth() + 1) * 100 + date.getDate();\n}\n\n/** A value as a number for the number filters: numbers, and strings that are numbers. */\nfunction numberOf(value: unknown): number | undefined {\n if (typeof value === 'number') return Number.isNaN(value) ? undefined : value;\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n return Number.isNaN(n) ? undefined : n;\n }\n return undefined;\n}\n\n/**\n * The value a `'set'` filter lists and compares: strings, numbers and booleans stay,\n * a `Date` becomes its local day, and empty values (and objects) become `null`.\n */\nexport function setValueOf(value: unknown): FilterValue {\n if (isEmptyValue(value)) return null;\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return value;\n }\n return value instanceof Date ? isoDay(value) : null;\n}\n\nfunction compare(\n op: RangeFilterOp,\n from: number | undefined,\n to: number | undefined,\n): ((n: number | undefined) => boolean) | undefined {\n if (op === 'empty') return (n) => n === undefined;\n if (op === 'notEmpty') return (n) => n !== undefined;\n if (op === 'between') {\n if (from === undefined && to === undefined) return undefined;\n return (n) =>\n n !== undefined && (from === undefined || n >= from) && (to === undefined || n <= to);\n }\n if (from === undefined) return undefined;\n switch (op) {\n case 'eq':\n return (n) => n === from;\n case 'ne':\n // Like a spreadsheet: an empty cell isn't equal to the number, so it stays.\n return (n) => n !== from;\n case 'lt':\n return (n) => n !== undefined && n < from;\n case 'le':\n return (n) => n !== undefined && n <= from;\n case 'gt':\n return (n) => n !== undefined && n > from;\n case 'ge':\n return (n) => n !== undefined && n >= from;\n }\n}\n\n/**\n * A test of one value for a filter, or `undefined` when the filter keeps every row\n * (no text to match, no bound to compare with). Text matching ignores case.\n */\nexport function compileFilter(filter: ColumnFilter): ((value: unknown) => boolean) | undefined {\n switch (filter.kind) {\n case 'text': {\n if (filter.op === 'empty') return (v) => textOf(v) === '';\n if (filter.op === 'notEmpty') return (v) => textOf(v) !== '';\n const needle = (filter.value ?? '').toLowerCase();\n if (!needle) return undefined;\n const op = filter.op;\n return (v) => {\n const text = textOf(v).toLowerCase();\n return op === 'equals'\n ? text === needle\n : op === 'startsWith'\n ? text.startsWith(needle)\n : text.includes(needle);\n };\n }\n case 'number': {\n const test = compare(filter.op, numberOf(filter.value), numberOf(filter.to));\n return test && ((v) => test(numberOf(v)));\n }\n case 'date': {\n const test = compare(filter.op, dayOf(filter.value), dayOf(filter.to));\n return test && ((v) => test(dayOf(v)));\n }\n case 'set': {\n const values = new Set(filter.values);\n return (v) => values.has(setValueOf(v));\n }\n }\n}\n\n/** The words of a quick filter, lowercased; empty when it keeps every row. */\nexport function quickTerms(quickFilter: string | undefined): string[] {\n return (quickFilter ?? '').toLowerCase().split(/\\s+/).filter(Boolean);\n}\n\n/**\n * One test of a row for the column filters and the quick filter, or `undefined` when\n * they keep every row. A row passes every column filter, and each word of the quick\n * filter is found in the text of one of `quickColumns` (ignoring case).\n *\n * Each value is read once per test through the column's `getValue`. A column with a\n * `filterFn` uses it in place of the built-in matching. Filters for unknown columns,\n * and `except` (for the values a column's own filter offers), are skipped.\n */\nexport function rowPredicate<TData>(\n columns: ReadonlyMap<string, FilterColumn<TData>>,\n filters: ColumnFilterState | undefined,\n quickFilter: string | undefined,\n quickColumns: readonly FilterColumn<TData>[],\n except?: string,\n): RowPredicate<TData> | undefined {\n const tests: RowPredicate<TData>[] = [];\n for (const [id, filter] of Object.entries(filters ?? {})) {\n const column = columns.get(id);\n if (!column || id === except) continue;\n const { getValue, filterFn } = column;\n if (filterFn) {\n tests.push((row, index) => filterFn(getValue(row, index), filter, row));\n continue;\n }\n const test = compileFilter(filter);\n if (test) tests.push((row, index) => test(getValue(row, index)));\n }\n const terms = quickTerms(quickFilter);\n if (terms.length && quickColumns.length) {\n const textAt = (c: FilterColumn<TData>, row: TData, index: number) =>\n textOf(c.getValue(row, index)).toLowerCase();\n const [term] = terms;\n tests.push(\n terms.length === 1 && term !== undefined\n ? (row, index) => quickColumns.some((c) => textAt(c, row, index).includes(term))\n : (row, index) => {\n const texts = quickColumns.map((c) => textAt(c, row, index));\n return terms.every((t) => texts.some((text) => text.includes(t)));\n },\n );\n }\n if (!tests.length) return undefined;\n if (tests.length === 1) return tests[0];\n return (row, index) => tests.every((test) => test(row, index));\n}\n\nconst kindOrder = (v: FilterValue) =>\n v === null ? 3 : typeof v === 'boolean' ? 2 : typeof v === 'number' ? 0 : 1;\n\n/** Orders set values: numbers, then text (by locale, numbers inside text by value), then booleans, then empty. */\nexport function compareSetValues(a: FilterValue, b: FilterValue): number {\n const kind = kindOrder(a) - kindOrder(b);\n if (kind) return kind;\n if (typeof a === 'number' && typeof b === 'number') return a - b;\n if (typeof a === 'string' && typeof b === 'string') {\n return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' });\n }\n return Number(a) - Number(b);\n}\n\n/**\n * The distinct values of `column` over the rows `keep` passes, as a `'set'` filter\n * lists them (see `setValueOf`), sorted. Stops after `limit` values (`truncated`).\n */\nexport function distinctValues<TData>(\n rows: Iterable<{ original: TData; index: number }>,\n column: FilterColumn<TData>,\n keep: RowPredicate<TData> | undefined,\n limit: number,\n): { values: FilterValue[]; truncated: boolean } {\n const seen = new Set<FilterValue>();\n let truncated = false;\n for (const { original, index } of rows) {\n if (keep && !keep(original, index)) continue;\n const value = setValueOf(column.getValue(original, index));\n if (seen.has(value)) continue;\n if (seen.size === limit) {\n truncated = true;\n break;\n }\n seen.add(value);\n }\n return { values: [...seen].sort(compareSetValues), truncated };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"filter.js","names":[],"sources":["../../../../../core/src/filters/filter.ts"],"sourcesContent":["import type { ColumnFilter, ColumnFilterState, FilterValue, RangeFilterOp } from '../types.js';\n\n// Filtering rules, free of TanStack and the DOM: each filter compiles once into a\n// test of one value, and the tests of a filter state combine into one test of a row.\n\n/** A column as filtering sees it. */\nexport interface FilterColumn<TData> {\n id: string;\n getValue: (row: TData, index: number) => unknown;\n filterFn?: ((value: unknown, filter: ColumnFilter, row: TData) => boolean) | undefined;\n}\n\n/** Keeps a row (by its object and its index in `data`) or drops it. */\nexport type RowPredicate<TData> = (row: TData, index: number) => boolean;\n\n/** `true` for `undefined`, `null`, `''`, `NaN` and an invalid `Date`. */\nexport function isEmptyValue(value: unknown): boolean {\n if (value === undefined || value === null || value === '') return true;\n if (typeof value === 'number') return Number.isNaN(value);\n return value instanceof Date && Number.isNaN(value.getTime());\n}\n\nconst pad = (n: number, width = 2) => String(n).padStart(width, '0');\n\n/** A `Date` as its local day, `'2026-11-10'`. */\nfunction isoDay(date: Date): string {\n return `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;\n}\n\n/**\n * A value as the text filters and the quick filter read it: strings as they are,\n * numbers and booleans as `String()` gives them, a `Date` as its local day\n * (`'2026-11-10'`), and anything else (empty values, objects) as `''`.\n */\nexport function textOf(value: unknown): string {\n if (typeof value === 'string') return value;\n if (typeof value === 'number') return Number.isNaN(value) ? '' : String(value);\n if (typeof value === 'boolean') return String(value);\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? '' : isoDay(value);\n return '';\n}\n\nconst DAY = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\n/**\n * A value's local day as a sortable number (`20261110`), or `undefined`. Takes a `Date`,\n * a timestamp, an ISO date (`'2026-11-10'`, read as that day, not as UTC midnight) or\n * any other string `Date` can parse.\n */\nexport function dayOf(value: unknown): number | undefined {\n let date: Date;\n if (value instanceof Date) date = value;\n else if (typeof value === 'number') date = new Date(value);\n else if (typeof value === 'string') {\n const day = DAY.exec(value.trim());\n if (day) return Number(day[1]) * 10000 + Number(day[2]) * 100 + Number(day[3]);\n if (!value.trim()) return undefined;\n date = new Date(value);\n } else return undefined;\n const time = date.getTime();\n if (Number.isNaN(time)) return undefined;\n return date.getFullYear() * 10000 + (date.getMonth() + 1) * 100 + date.getDate();\n}\n\n/** A value as a number for the number filters: numbers, and strings that are numbers. */\nfunction numberOf(value: unknown): number | undefined {\n if (typeof value === 'number') return Number.isNaN(value) ? undefined : value;\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n return Number.isNaN(n) ? undefined : n;\n }\n return undefined;\n}\n\n/**\n * The value a `'set'` filter lists and compares: strings, numbers and booleans stay,\n * a `Date` becomes its local day, and empty values (and objects) become `null`.\n */\nexport function setValueOf(value: unknown): FilterValue {\n if (isEmptyValue(value)) return null;\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return value;\n }\n return value instanceof Date ? isoDay(value) : null;\n}\n\nfunction compare(\n op: RangeFilterOp,\n from: number | undefined,\n to: number | undefined,\n): ((n: number | undefined) => boolean) | undefined {\n if (op === 'empty') return (n) => n === undefined;\n if (op === 'notEmpty') return (n) => n !== undefined;\n if (op === 'between') {\n if (from === undefined && to === undefined) return undefined;\n return (n) =>\n n !== undefined && (from === undefined || n >= from) && (to === undefined || n <= to);\n }\n if (from === undefined) return undefined;\n switch (op) {\n case 'eq':\n return (n) => n === from;\n case 'ne':\n // Like a spreadsheet: an empty cell isn't equal to the number, so it stays.\n return (n) => n !== from;\n case 'lt':\n return (n) => n !== undefined && n < from;\n case 'le':\n return (n) => n !== undefined && n <= from;\n case 'gt':\n return (n) => n !== undefined && n > from;\n case 'ge':\n return (n) => n !== undefined && n >= from;\n }\n}\n\n/**\n * A test of one value for a filter, or `undefined` when the filter keeps every row\n * (no text to match, no bound to compare with). Text matching ignores case.\n */\nexport function compileFilter(filter: ColumnFilter): ((value: unknown) => boolean) | undefined {\n switch (filter.kind) {\n case 'text': {\n if (filter.op === 'empty') return (v) => textOf(v) === '';\n if (filter.op === 'notEmpty') return (v) => textOf(v) !== '';\n const needle = (filter.value ?? '').toLowerCase();\n if (!needle) return undefined;\n if (filter.op === 'equals') return (v) => textOf(v).toLowerCase() === needle;\n if (filter.op === 'startsWith') return (v) => textOf(v).toLowerCase().startsWith(needle);\n return (v) => textOf(v).toLowerCase().includes(needle);\n }\n case 'number': {\n const test = compare(filter.op, numberOf(filter.value), numberOf(filter.to));\n return test && ((v) => test(numberOf(v)));\n }\n case 'date': {\n const test = compare(filter.op, dayOf(filter.value), dayOf(filter.to));\n return test && ((v) => test(dayOf(v)));\n }\n case 'set': {\n const values = new Set(filter.values);\n return (v) => values.has(setValueOf(v));\n }\n }\n}\n\n/** The words of a quick filter, lowercased; empty when it keeps every row. */\nfunction quickTerms(quickFilter: string | undefined): string[] {\n return (quickFilter ?? '').toLowerCase().split(/\\s+/).filter(Boolean);\n}\n\n/**\n * One test of a row for the column filters and the quick filter, or `undefined` when\n * they keep every row. A row passes every column filter, and each word of the quick\n * filter is found in the text of one of `quickColumns` (ignoring case).\n *\n * Each value is read once per test through the column's `getValue`. A column with a\n * `filterFn` uses it in place of the built-in matching. Filters for unknown columns,\n * and on the `except` columns (for the values a column's own filter offers), are skipped.\n */\nexport function rowPredicate<TData>(\n columns: ReadonlyMap<string, FilterColumn<TData>>,\n filters: ColumnFilterState | undefined,\n quickFilter: string | undefined,\n quickColumns: readonly FilterColumn<TData>[],\n except?: ReadonlySet<string>,\n): RowPredicate<TData> | undefined {\n const tests: RowPredicate<TData>[] = [];\n for (const [id, filter] of Object.entries(filters ?? {})) {\n const column = columns.get(id);\n if (!column || except?.has(id)) continue;\n const { getValue, filterFn } = column;\n if (filterFn) {\n tests.push((row, index) => filterFn(getValue(row, index), filter, row));\n continue;\n }\n const test = compileFilter(filter);\n if (test) tests.push((row, index) => test(getValue(row, index)));\n }\n const terms = quickTerms(quickFilter);\n if (terms.length && quickColumns.length) {\n const textAt = (c: FilterColumn<TData>, row: TData, index: number) =>\n textOf(c.getValue(row, index)).toLowerCase();\n const [term] = terms;\n tests.push(\n terms.length === 1 && term !== undefined\n ? (row, index) => quickColumns.some((c) => textAt(c, row, index).includes(term))\n : (row, index) => {\n const texts = quickColumns.map((c) => textAt(c, row, index));\n return terms.every((t) => texts.some((text) => text.includes(t)));\n },\n );\n }\n if (!tests.length) return undefined;\n if (tests.length === 1) return tests[0];\n return (row, index) => tests.every((test) => test(row, index));\n}\n\nconst kindOrder = (v: FilterValue) =>\n v === null ? 3 : typeof v === 'boolean' ? 2 : typeof v === 'number' ? 0 : 1;\n\n/** Orders set values: numbers, then text (by locale, numbers inside text by value), then booleans, then empty. */\nexport function compareSetValues(a: FilterValue, b: FilterValue): number {\n const kind = kindOrder(a) - kindOrder(b);\n if (kind) return kind;\n if (typeof a === 'number' && typeof b === 'number') return a - b;\n if (typeof a === 'string' && typeof b === 'string') {\n return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' });\n }\n return Number(a) - Number(b);\n}\n\n/**\n * The distinct values of `column` over the rows `keep` passes, as a `'set'` filter\n * lists them (see `setValueOf`), sorted. Stops after `limit` values (`truncated`).\n */\nexport function distinctValues<TData>(\n rows: Iterable<{ original: TData; index: number }>,\n column: FilterColumn<TData>,\n keep: RowPredicate<TData> | undefined,\n limit: number,\n): { values: FilterValue[]; truncated: boolean } {\n const seen = new Set<FilterValue>();\n let truncated = false;\n for (const { original, index } of rows) {\n if (keep && !keep(original, index)) continue;\n const value = setValueOf(column.getValue(original, index));\n if (seen.has(value)) continue;\n if (seen.size === limit) {\n truncated = true;\n break;\n }\n seen.add(value);\n }\n return { values: [...seen].sort(compareSetValues), truncated };\n}\n"],"mappings":"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"}
|
|
@@ -1,37 +1,45 @@
|
|
|
1
1
|
// ../core/src/grid/row-layout.ts
|
|
2
2
|
/**
|
|
3
|
-
* Where each row sits in the grid as a whole: the header
|
|
3
|
+
* Where each row sits in the grid as a whole: the header rows, then the rows
|
|
4
4
|
* pinned to the top, the body rows, and the rows pinned to the bottom. The
|
|
5
5
|
* position is 0-based and `aria-rowindex` is one more. Keyboard navigation
|
|
6
6
|
* steps through positions, so it crosses sections without special cases.
|
|
7
7
|
*/
|
|
8
8
|
var RowLayout = class {
|
|
9
|
-
constructor(top, body, bottom) {
|
|
9
|
+
constructor(top, body, bottom, header = 1) {
|
|
10
10
|
this.top = top;
|
|
11
11
|
this.body = body;
|
|
12
12
|
this.bottom = bottom;
|
|
13
|
+
this.header = Math.max(1, header);
|
|
13
14
|
}
|
|
14
|
-
/** Number of rows, the header included (`aria-rowcount`). */
|
|
15
|
+
/** Number of rows, the header rows included (`aria-rowcount`). */
|
|
15
16
|
get total() {
|
|
16
|
-
return
|
|
17
|
+
return this.header + this.top + this.body + this.bottom;
|
|
18
|
+
}
|
|
19
|
+
/** The row of column headers: the last header row. */
|
|
20
|
+
get leafHeader() {
|
|
21
|
+
return {
|
|
22
|
+
section: "header",
|
|
23
|
+
index: this.header - 1
|
|
24
|
+
};
|
|
17
25
|
}
|
|
18
26
|
/** 0-based position of a row in the grid. */
|
|
19
27
|
position({ section, index }) {
|
|
20
28
|
switch (section) {
|
|
21
|
-
case "header": return
|
|
22
|
-
case "top": return
|
|
23
|
-
case "body": return
|
|
24
|
-
case "bottom": return
|
|
29
|
+
case "header": return index;
|
|
30
|
+
case "top": return this.header + index;
|
|
31
|
+
case "body": return this.header + this.top + index;
|
|
32
|
+
case "bottom": return this.header + this.top + this.body + index;
|
|
25
33
|
}
|
|
26
34
|
}
|
|
27
35
|
/** The row at a 0-based position, or `undefined` outside the grid. */
|
|
28
36
|
at(position) {
|
|
29
37
|
if (!Number.isInteger(position) || position < 0 || position >= this.total) return void 0;
|
|
30
|
-
if (position
|
|
38
|
+
if (position < this.header) return {
|
|
31
39
|
section: "header",
|
|
32
|
-
index:
|
|
40
|
+
index: position
|
|
33
41
|
};
|
|
34
|
-
let index = position -
|
|
42
|
+
let index = position - this.header;
|
|
35
43
|
if (index < this.top) return {
|
|
36
44
|
section: "top",
|
|
37
45
|
index
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"row-layout.js","names":[],"sources":["../../../../../core/src/grid/row-layout.ts"],"sourcesContent":["/** The row groups of the grid, top to bottom. */\nexport type GridSection = 'header' | 'top' | 'body' | 'bottom';\n\n
|
|
1
|
+
{"version":3,"file":"row-layout.js","names":[],"sources":["../../../../../core/src/grid/row-layout.ts"],"sourcesContent":["/** The row groups of the grid, top to bottom. */\nexport type GridSection = 'header' | 'top' | 'body' | 'bottom';\n\n/**\n * A row of the grid: its section and its index within it. Header rows count from\n * the top: with column groups the column headers are the last header row.\n */\nexport interface GridRow {\n section: GridSection;\n index: number;\n}\n\n/**\n * Where each row sits in the grid as a whole: the header rows, then the rows\n * pinned to the top, the body rows, and the rows pinned to the bottom. The\n * position is 0-based and `aria-rowindex` is one more. Keyboard navigation\n * steps through positions, so it crosses sections without special cases.\n */\nexport class RowLayout {\n readonly top: number;\n readonly body: number;\n readonly bottom: number;\n /** Number of header rows: one, and one more per level of column groups. */\n readonly header: number;\n\n constructor(top: number, body: number, bottom: number, header = 1) {\n this.top = top;\n this.body = body;\n this.bottom = bottom;\n this.header = Math.max(1, header);\n }\n\n /** Number of rows, the header rows included (`aria-rowcount`). */\n get total(): number {\n return this.header + this.top + this.body + this.bottom;\n }\n\n /** The row of column headers: the last header row. */\n get leafHeader(): GridRow {\n return { section: 'header', index: this.header - 1 };\n }\n\n /** 0-based position of a row in the grid. */\n position({ section, index }: GridRow): number {\n switch (section) {\n case 'header':\n return index;\n case 'top':\n return this.header + index;\n case 'body':\n return this.header + this.top + index;\n case 'bottom':\n return this.header + this.top + this.body + index;\n }\n }\n\n /** The row at a 0-based position, or `undefined` outside the grid. */\n at(position: number): GridRow | undefined {\n if (!Number.isInteger(position) || position < 0 || position >= this.total) return undefined;\n if (position < this.header) return { section: 'header', index: position };\n let index = position - this.header;\n if (index < this.top) return { section: 'top', index };\n index -= this.top;\n if (index < this.body) return { section: 'body', index };\n return { section: 'bottom', index: index - this.body };\n }\n\n ariaRowIndex(row: GridRow): number {\n return this.position(row) + 1;\n }\n\n fromAriaRowIndex(ariaRowIndex: number): GridRow | undefined {\n return this.at(ariaRowIndex - 1);\n }\n}\n"],"mappings":";;;;;;;AAkBA,IAAa,YAAb,MAAuB;CAOrB,YAAY,KAAa,MAAc,QAAgB,SAAS,GAAG;EACjE,KAAK,MAAM;EACX,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,SAAS,KAAK,IAAI,GAAG,MAAM;CAClC;;CAGA,IAAI,QAAgB;EAClB,OAAO,KAAK,SAAS,KAAK,MAAM,KAAK,OAAO,KAAK;CACnD;;CAGA,IAAI,aAAsB;EACxB,OAAO;GAAE,SAAS;GAAU,OAAO,KAAK,SAAS;EAAE;CACrD;;CAGA,SAAS,EAAE,SAAS,SAA0B;EAC5C,QAAQ,SAAR;GACE,KAAK,UACH,OAAO;GACT,KAAK,OACH,OAAO,KAAK,SAAS;GACvB,KAAK,QACH,OAAO,KAAK,SAAS,KAAK,MAAM;GAClC,KAAK,UACH,OAAO,KAAK,SAAS,KAAK,MAAM,KAAK,OAAO;EAChD;CACF;;CAGA,GAAG,UAAuC;EACxC,IAAI,CAAC,OAAO,UAAU,QAAQ,KAAK,WAAW,KAAK,YAAY,KAAK,OAAO,OAAO,KAAA;EAClF,IAAI,WAAW,KAAK,QAAQ,OAAO;GAAE,SAAS;GAAU,OAAO;EAAS;EACxE,IAAI,QAAQ,WAAW,KAAK;EAC5B,IAAI,QAAQ,KAAK,KAAK,OAAO;GAAE,SAAS;GAAO;EAAM;EACrD,SAAS,KAAK;EACd,IAAI,QAAQ,KAAK,MAAM,OAAO;GAAE,SAAS;GAAQ;EAAM;EACvD,OAAO;GAAE,SAAS;GAAU,OAAO,QAAQ,KAAK;EAAK;CACvD;CAEA,aAAa,KAAsB;EACjC,OAAO,KAAK,SAAS,GAAG,IAAI;CAC9B;CAEA,iBAAiB,cAA2C;EAC1D,OAAO,KAAK,GAAG,eAAe,CAAC;CACjC;AACF"}
|
|
@@ -28,7 +28,8 @@ function toggleRow(current, mode, id) {
|
|
|
28
28
|
function selectRange(current, from, to, idAt, add) {
|
|
29
29
|
const ids = add ? [...current] : [];
|
|
30
30
|
const have = new Set(ids);
|
|
31
|
-
const
|
|
31
|
+
const first = Math.min(from, to);
|
|
32
|
+
const last = Math.max(from, to);
|
|
32
33
|
for (let index = first; index <= last; index++) {
|
|
33
34
|
const id = idAt(index);
|
|
34
35
|
if (id === void 0 || have.has(id)) continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selection.js","names":[],"sources":["../../../../../core/src/state/selection.ts"],"sourcesContent":["import type { SelectionMode, SelectionState } from '../types.js';\n\n/**\n * Row selection rules, DOM-free. The element owns the state (through\n * `controllable`) and the anchor; these functions only compute the next ones.\n *\n * The anchor is the row a range starts from: the last row clicked or toggled\n * without Shift. Ranges are by index in the current row order, so they follow\n * sorting; `idAt` returns `undefined` for a row that can't be selected (a\n * `dataSource` row not loaded), and such rows are left out.\n */\n\n/** The selection after a step, and the row later ranges start from. */\nexport interface SelectionStep {\n selection: SelectionState;\n anchor: string | undefined;\n}\n\n/** The row a click or key acts on: its id and its index in the current row order. */\nexport interface SelectionTarget {\n id: string;\n index: number;\n}\n\n/** Modifier keys of a click: Ctrl or Cmd toggles, Shift selects a range. */\nexport interface SelectionModifiers {\n toggle: boolean;\n range: boolean;\n}\n\n/** Whether two selections hold the same ids, in any order. */\nexport function sameSelection(a: readonly string[], b: readonly string[]): boolean {\n if (a.length !== b.length) return false;\n const ids = new Set(a);\n return b.every((id) => ids.has(id));\n}\n\n/**\n * Selects or deselects one row and makes it the anchor. In `'single'` mode,\n * selecting a row replaces the selection.\n */\nexport function toggleRow(\n current: readonly string[],\n mode: SelectionMode,\n id: string,\n): SelectionStep | undefined {\n if (mode === 'none') return undefined;\n if (current.includes(id)) return { selection: current.filter((s) => s !== id), anchor: id };\n return { selection: mode === 'single' ? [id] : [...current, id], anchor: id };\n}\n\n/**\n * The rows from index `from` to `to` (either order, both inclusive), in row\n * order. With `add`, they join the current selection instead of replacing it.\n * The anchor stays where it was.\n */\nexport function selectRange(\n current: readonly string[],\n from: number,\n to: number,\n idAt: (index: number) => string | undefined,\n add: boolean,\n): SelectionState {\n const ids: string[] = add ? [...current] : [];\n const have = new Set(ids);\n const
|
|
1
|
+
{"version":3,"file":"selection.js","names":[],"sources":["../../../../../core/src/state/selection.ts"],"sourcesContent":["import type { SelectionMode, SelectionState } from '../types.js';\n\n/**\n * Row selection rules, DOM-free. The element owns the state (through\n * `controllable`) and the anchor; these functions only compute the next ones.\n *\n * The anchor is the row a range starts from: the last row clicked or toggled\n * without Shift. Ranges are by index in the current row order, so they follow\n * sorting; `idAt` returns `undefined` for a row that can't be selected (a\n * `dataSource` row not loaded), and such rows are left out.\n */\n\n/** The selection after a step, and the row later ranges start from. */\nexport interface SelectionStep {\n selection: SelectionState;\n anchor: string | undefined;\n}\n\n/** The row a click or key acts on: its id and its index in the current row order. */\nexport interface SelectionTarget {\n id: string;\n index: number;\n}\n\n/** Modifier keys of a click: Ctrl or Cmd toggles, Shift selects a range. */\nexport interface SelectionModifiers {\n toggle: boolean;\n range: boolean;\n}\n\n/** Whether two selections hold the same ids, in any order. */\nexport function sameSelection(a: readonly string[], b: readonly string[]): boolean {\n if (a.length !== b.length) return false;\n const ids = new Set(a);\n return b.every((id) => ids.has(id));\n}\n\n/**\n * Selects or deselects one row and makes it the anchor. In `'single'` mode,\n * selecting a row replaces the selection.\n */\nexport function toggleRow(\n current: readonly string[],\n mode: SelectionMode,\n id: string,\n): SelectionStep | undefined {\n if (mode === 'none') return undefined;\n if (current.includes(id)) return { selection: current.filter((s) => s !== id), anchor: id };\n return { selection: mode === 'single' ? [id] : [...current, id], anchor: id };\n}\n\n/**\n * The rows from index `from` to `to` (either order, both inclusive), in row\n * order. With `add`, they join the current selection instead of replacing it.\n * The anchor stays where it was.\n */\nexport function selectRange(\n current: readonly string[],\n from: number,\n to: number,\n idAt: (index: number) => string | undefined,\n add: boolean,\n): SelectionState {\n const ids: string[] = add ? [...current] : [];\n const have = new Set(ids);\n const first = Math.min(from, to);\n const last = Math.max(from, to);\n for (let index = first; index <= last; index++) {\n const id = idAt(index);\n if (id === undefined || have.has(id)) continue;\n have.add(id);\n ids.push(id);\n }\n return ids;\n}\n\n/**\n * Every row of `ids` selected, keeping the rows already selected (with a\n * `dataSource`, `ids` are only the loaded rows: selected rows elsewhere stay).\n */\nexport function selectAll(current: readonly string[], ids: readonly string[]): SelectionState {\n const have = new Set(current);\n return [...current, ...ids.filter((id) => !have.has(id))];\n}\n\n/**\n * A click on a row, desktop style. A plain click selects only that row; Ctrl or\n * Cmd toggles it. In `'multi'` mode, Shift selects the range from the anchor\n * (at `anchorIndex`) to the row, and Ctrl+Shift adds that range. Without an\n * anchor in the current rows, Shift acts like a plain click. `undefined` in\n * `'none'` mode. The selection may come back unchanged (a click on the one\n * selected row): check with `sameSelection` before reporting a change.\n */\nexport function clickSelection(\n current: readonly string[],\n mode: SelectionMode,\n target: SelectionTarget,\n anchor: SelectionTarget | undefined,\n modifiers: SelectionModifiers,\n idAt: (index: number) => string | undefined,\n): SelectionStep | undefined {\n if (mode === 'none') return undefined;\n if (mode === 'multi' && modifiers.range && anchor) {\n return {\n selection: selectRange(current, anchor.index, target.index, idAt, modifiers.toggle),\n anchor: anchor.id,\n };\n }\n if (modifiers.toggle) return toggleRow(current, mode, target.id);\n return { selection: [target.id], anchor: target.id };\n}\n"],"mappings":";;AA+BA,SAAgB,cAAc,GAAsB,GAA+B;CACjF,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;CAClC,MAAM,MAAM,IAAI,IAAI,CAAC;CACrB,OAAO,EAAE,OAAO,OAAO,IAAI,IAAI,EAAE,CAAC;AACpC;;;;;AAMA,SAAgB,UACd,SACA,MACA,IAC2B;CAC3B,IAAI,SAAS,QAAQ,OAAO,KAAA;CAC5B,IAAI,QAAQ,SAAS,EAAE,GAAG,OAAO;EAAE,WAAW,QAAQ,QAAQ,MAAM,MAAM,EAAE;EAAG,QAAQ;CAAG;CAC1F,OAAO;EAAE,WAAW,SAAS,WAAW,CAAC,EAAE,IAAI,CAAC,GAAG,SAAS,EAAE;EAAG,QAAQ;CAAG;AAC9E;;;;;;AAOA,SAAgB,YACd,SACA,MACA,IACA,MACA,KACgB;CAChB,MAAM,MAAgB,MAAM,CAAC,GAAG,OAAO,IAAI,CAAC;CAC5C,MAAM,OAAO,IAAI,IAAI,GAAG;CACxB,MAAM,QAAQ,KAAK,IAAI,MAAM,EAAE;CAC/B,MAAM,OAAO,KAAK,IAAI,MAAM,EAAE;CAC9B,KAAK,IAAI,QAAQ,OAAO,SAAS,MAAM,SAAS;EAC9C,MAAM,KAAK,KAAK,KAAK;EACrB,IAAI,OAAO,KAAA,KAAa,KAAK,IAAI,EAAE,GAAG;EACtC,KAAK,IAAI,EAAE;EACX,IAAI,KAAK,EAAE;CACb;CACA,OAAO;AACT;;;;;AAMA,SAAgB,UAAU,SAA4B,KAAwC;CAC5F,MAAM,OAAO,IAAI,IAAI,OAAO;CAC5B,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,QAAQ,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;AAC1D;;;;;;;;;AAUA,SAAgB,eACd,SACA,MACA,QACA,QACA,WACA,MAC2B;CAC3B,IAAI,SAAS,QAAQ,OAAO,KAAA;CAC5B,IAAI,SAAS,WAAW,UAAU,SAAS,QACzC,OAAO;EACL,WAAW,YAAY,SAAS,OAAO,OAAO,OAAO,OAAO,MAAM,UAAU,MAAM;EAClF,QAAQ,OAAO;CACjB;CAEF,IAAI,UAAU,QAAQ,OAAO,UAAU,SAAS,MAAM,OAAO,EAAE;CAC/D,OAAO;EAAE,WAAW,CAAC,OAAO,EAAE;EAAG,QAAQ,OAAO;CAAG;AACrD"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { cellOver, headerGroupRows, resolveColumnGroups } from "../columns/column-groups.js";
|
|
2
|
+
import { moveUnit, normalizeOrder, siblingUnits } from "../columns/column-order.js";
|
|
2
3
|
import { resolveColumns } from "../columns/resolve-columns.js";
|
|
3
4
|
import { distinctValues, rowPredicate } from "../filters/filter.js";
|
|
4
5
|
import { filteredRowModel, setRowPredicate } from "./filtered-row-model.js";
|
|
@@ -89,6 +90,9 @@ function createTableModel(initial) {
|
|
|
89
90
|
let byId = new Map(resolved.map((c) => [c.id, c]));
|
|
90
91
|
let tanStackById = new Map(resolved.map((c) => [c.id, toTanStack(c)]));
|
|
91
92
|
let views;
|
|
93
|
+
const treeOf = () => resolveColumnGroups(options.columnGroups, new Set(resolved.map((c) => c.id)));
|
|
94
|
+
let tree = treeOf();
|
|
95
|
+
let groupRows;
|
|
92
96
|
const pinOf = (column, pinning = options.columnPinning) => {
|
|
93
97
|
const state = pinning?.[column.id];
|
|
94
98
|
if (state === "start" || state === "end") return state;
|
|
@@ -129,7 +133,7 @@ function createTableModel(initial) {
|
|
|
129
133
|
reorderable: c.reorderable
|
|
130
134
|
}));
|
|
131
135
|
let order;
|
|
132
|
-
const orderState = () => order ??= normalizeOrder(options.columnOrder ?? [], orderable());
|
|
136
|
+
const orderState = () => order ??= normalizeOrder(options.columnOrder ?? [], orderable(), tree);
|
|
133
137
|
const rowViews = /* @__PURE__ */ new WeakMap();
|
|
134
138
|
let indexById;
|
|
135
139
|
let ordered;
|
|
@@ -154,12 +158,12 @@ function createTableModel(initial) {
|
|
|
154
158
|
onColumnSizingChange: () => void 0
|
|
155
159
|
});
|
|
156
160
|
const table = constructTable(tableOptions());
|
|
157
|
-
const predicateFor = (except) => options.remote ? void 0 : rowPredicate(byId, options.columnFilters, options.quickFilter, resolved.filter((c) => c.hasAccessor && !hiddenOf(c)), except);
|
|
161
|
+
const predicateFor = (except, quick = false) => options.remote ? void 0 : rowPredicate(byId, options.columnFilters, options.quickFilter, resolved.filter((c) => c.hasAccessor && !hiddenOf(c) && !(quick && except?.has(c.id))), except);
|
|
158
162
|
setRowPredicate(table, predicateFor());
|
|
159
163
|
const rows = () => table.getRowModel().rows;
|
|
160
164
|
const pinned = (side) => options.pinnedRows?.[side] ?? [];
|
|
161
165
|
const widthOf = (id) => table.getColumn(id)?.getSize() ?? 0;
|
|
162
|
-
const view = (def, colIndex, offset, pinEdge
|
|
166
|
+
const view = (def, colIndex, offset, pinEdge) => {
|
|
163
167
|
const sort = options.sorting.find((s) => s.id === def.id);
|
|
164
168
|
const pin = pinOf(def);
|
|
165
169
|
const resized = options.columnSizing[def.id] !== void 0;
|
|
@@ -171,7 +175,7 @@ function createTableModel(initial) {
|
|
|
171
175
|
offset,
|
|
172
176
|
pinEdge,
|
|
173
177
|
hidden: colIndex === 0,
|
|
174
|
-
width,
|
|
178
|
+
width: widthOf(def.id),
|
|
175
179
|
flex: pin === void 0 && !resized ? def.flex : void 0,
|
|
176
180
|
auto: def.auto && !resized,
|
|
177
181
|
minWidth: def.minWidth,
|
|
@@ -206,23 +210,22 @@ function createTableModel(initial) {
|
|
|
206
210
|
if (lead && byId.has("--argus-selection")) throw new Error(`argus-table: the column id '${SELECTION_COLUMN_ID}' is reserved.`);
|
|
207
211
|
let x = lead * 40;
|
|
208
212
|
const list = start.map((def, i) => {
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
x += width;
|
|
213
|
+
const v = view(def, lead + i + 1, x, i === start.length - 1);
|
|
214
|
+
x += v.width;
|
|
212
215
|
return v;
|
|
213
216
|
});
|
|
214
217
|
x = 0;
|
|
215
218
|
for (const [i, def] of center.entries()) {
|
|
216
|
-
const
|
|
217
|
-
list.push(
|
|
218
|
-
x += width;
|
|
219
|
+
const v = view(def, lead + start.length + i + 1, x, false);
|
|
220
|
+
list.push(v);
|
|
221
|
+
x += v.width;
|
|
219
222
|
}
|
|
220
223
|
const ends = [];
|
|
221
224
|
x = 0;
|
|
222
225
|
for (const [i, def] of [...end.entries()].reverse()) {
|
|
223
|
-
const
|
|
224
|
-
ends.unshift(
|
|
225
|
-
x += width;
|
|
226
|
+
const v = view(def, lead + start.length + center.length + i + 1, x, i === 0);
|
|
227
|
+
ends.unshift(v);
|
|
228
|
+
x += v.width;
|
|
226
229
|
}
|
|
227
230
|
list.push(...ends);
|
|
228
231
|
if (lead) list.unshift(selectionView(start.length === 0));
|
|
@@ -235,7 +238,7 @@ function createTableModel(initial) {
|
|
|
235
238
|
const shownView = byShownId.get(id);
|
|
236
239
|
if (shownView) return [shownView];
|
|
237
240
|
const def = byId.get(id);
|
|
238
|
-
return def ? [view(def, 0, 0, false
|
|
241
|
+
return def ? [view(def, 0, 0, false)] : [];
|
|
239
242
|
});
|
|
240
243
|
};
|
|
241
244
|
return {
|
|
@@ -252,11 +255,14 @@ function createTableModel(initial) {
|
|
|
252
255
|
byId = new Map(resolved.map((c) => [c.id, c]));
|
|
253
256
|
tanStackById = new Map(resolved.map((c) => [c.id, toTanStack(c)]));
|
|
254
257
|
}
|
|
258
|
+
const columnsChanged = merged.columns !== options.columns;
|
|
255
259
|
const pinningChanged = merged.columnPinning !== options.columnPinning;
|
|
256
|
-
|
|
257
|
-
if (merged.
|
|
258
|
-
|
|
260
|
+
const groupsChanged = merged.columnGroups !== options.columnGroups;
|
|
261
|
+
if (columnsChanged || merged.columnSizing !== options.columnSizing || merged.autoWidths !== options.autoWidths || pinningChanged) sizing = void 0;
|
|
262
|
+
if (columnsChanged || groupsChanged || merged.columnOrder !== options.columnOrder || pinningChanged) order = void 0;
|
|
263
|
+
const refilter = columnsChanged || merged.columnFilters !== options.columnFilters || merged.quickFilter !== options.quickFilter || merged.columnVisibility !== options.columnVisibility || merged.remote !== options.remote;
|
|
259
264
|
options = merged;
|
|
265
|
+
if (columnsChanged || groupsChanged) tree = treeOf();
|
|
260
266
|
views = void 0;
|
|
261
267
|
allViews = void 0;
|
|
262
268
|
if (refilter) setRowPredicate(table, predicateFor());
|
|
@@ -284,6 +290,25 @@ function createTableModel(initial) {
|
|
|
284
290
|
get totalWidth() {
|
|
285
291
|
return this.columns.reduce((sum, column) => sum + column.width, 0);
|
|
286
292
|
},
|
|
293
|
+
get headerGroups() {
|
|
294
|
+
const current = this.columns;
|
|
295
|
+
if (groupRows?.views === current && groupRows.tree === tree) return groupRows.rows;
|
|
296
|
+
const key = tree.groups.size ? current.map((c) => `${c.id}\u0000${String(c.pin)}\u0000${String(c.offset)}\u0000${String(c.pinEdge)}\u0000${String(c.reorderable)}\u0000${String(c.resizable)}`).join("") : "";
|
|
297
|
+
if (groupRows?.tree !== tree || groupRows.key !== key) groupRows = {
|
|
298
|
+
views: current,
|
|
299
|
+
tree,
|
|
300
|
+
key,
|
|
301
|
+
rows: headerGroupRows(tree, current)
|
|
302
|
+
};
|
|
303
|
+
else groupRows.views = current;
|
|
304
|
+
return groupRows.rows;
|
|
305
|
+
},
|
|
306
|
+
get headerRows() {
|
|
307
|
+
return this.headerGroups.length + 1;
|
|
308
|
+
},
|
|
309
|
+
headerCell(level, columnId) {
|
|
310
|
+
return cellOver(this.headerGroups, level, columnId);
|
|
311
|
+
},
|
|
287
312
|
rowAt(index) {
|
|
288
313
|
if (options.remote) {
|
|
289
314
|
const entry = options.remote.entryAt(index);
|
|
@@ -371,10 +396,13 @@ function createTableModel(initial) {
|
|
|
371
396
|
};
|
|
372
397
|
},
|
|
373
398
|
orderOf(base) {
|
|
374
|
-
return base === void 0 ? orderState() : normalizeOrder(base, orderable());
|
|
399
|
+
return base === void 0 ? orderState() : normalizeOrder(base, orderable(), tree);
|
|
375
400
|
},
|
|
376
|
-
|
|
377
|
-
return
|
|
401
|
+
siblingUnits(unit, base) {
|
|
402
|
+
return siblingUnits(base ?? orderState(), orderable(), tree, unit, shown);
|
|
403
|
+
},
|
|
404
|
+
nextMove(unit, to, base) {
|
|
405
|
+
return moveUnit(base ?? orderState(), orderable(), tree, unit, to, shown);
|
|
378
406
|
},
|
|
379
407
|
nextPinning(columnId, pin, base = options.columnPinning) {
|
|
380
408
|
const column = byId.get(columnId);
|
|
@@ -420,7 +448,14 @@ function createTableModel(initial) {
|
|
|
420
448
|
};
|
|
421
449
|
if (options.remote) return void 0;
|
|
422
450
|
const rows = table.getCoreRowModel().rows;
|
|
423
|
-
return distinctValues(rows, column, predicateFor(columnId), limit);
|
|
451
|
+
return distinctValues(rows, column, predicateFor(/* @__PURE__ */ new Set([columnId])), limit);
|
|
452
|
+
},
|
|
453
|
+
indexesPassing(except) {
|
|
454
|
+
if (options.remote) return void 0;
|
|
455
|
+
const keep = predicateFor(new Set(except), true);
|
|
456
|
+
const kept = [];
|
|
457
|
+
for (const row of table.getCoreRowModel().rows) if (!keep || keep(row.original, row.index)) kept.push(row.index);
|
|
458
|
+
return kept;
|
|
424
459
|
},
|
|
425
460
|
pinnedCount(side) {
|
|
426
461
|
return pinned(side).length;
|