apex-grid 3.4.0 → 3.6.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/CHANGELOG.md +439 -0
- package/LICENSE.md +125 -0
- package/README.md +107 -14
- package/components/cell.d.ts +30 -4
- package/components/cell.js +199 -53
- package/components/cell.js.map +1 -1
- package/components/filter-row.d.ts +2 -0
- package/components/filter-row.js +82 -28
- package/components/filter-row.js.map +1 -1
- package/components/grid.d.ts +257 -20
- package/components/grid.js +463 -99
- package/components/grid.js.map +1 -1
- package/components/group-header-row.js +28 -25
- package/components/group-header-row.js.map +1 -1
- package/components/header-row.d.ts +7 -5
- package/components/header-row.js +19 -9
- package/components/header-row.js.map +1 -1
- package/components/header.js +36 -22
- package/components/header.js.map +1 -1
- package/components/row.d.ts +19 -3
- package/components/row.js +96 -22
- package/components/row.js.map +1 -1
- package/components/toolbar.js +2 -1
- package/components/toolbar.js.map +1 -1
- package/components/virtualizer.d.ts +45 -1
- package/components/virtualizer.js +184 -3
- package/components/virtualizer.js.map +1 -1
- package/controllers/dom.d.ts +4 -4
- package/controllers/dom.js +40 -22
- package/controllers/dom.js.map +1 -1
- package/controllers/editing.d.ts +104 -37
- package/controllers/editing.js +230 -78
- package/controllers/editing.js.map +1 -1
- package/controllers/expansion.d.ts +14 -5
- package/controllers/expansion.js +24 -1
- package/controllers/expansion.js.map +1 -1
- package/controllers/filter.d.ts +10 -2
- package/controllers/filter.js +62 -17
- package/controllers/filter.js.map +1 -1
- package/controllers/history.d.ts +45 -8
- package/controllers/history.js +58 -20
- package/controllers/history.js.map +1 -1
- package/controllers/navigation.d.ts +68 -13
- package/controllers/navigation.js +196 -51
- package/controllers/navigation.js.map +1 -1
- package/controllers/pagination.d.ts +6 -0
- package/controllers/pagination.js +10 -2
- package/controllers/pagination.js.map +1 -1
- package/controllers/reorder.js +42 -24
- package/controllers/reorder.js.map +1 -1
- package/controllers/resize.d.ts +17 -2
- package/controllers/resize.js +16 -6
- package/controllers/resize.js.map +1 -1
- package/controllers/row-pin.d.ts +20 -2
- package/controllers/row-pin.js +16 -9
- package/controllers/row-pin.js.map +1 -1
- package/controllers/row-reorder.d.ts +31 -11
- package/controllers/row-reorder.js +117 -37
- package/controllers/row-reorder.js.map +1 -1
- package/controllers/selection.d.ts +11 -4
- package/controllers/selection.js +26 -11
- package/controllers/selection.js.map +1 -1
- package/controllers/sort.d.ts +22 -2
- package/controllers/sort.js +29 -23
- package/controllers/sort.js.map +1 -1
- package/controllers/state.d.ts +65 -10
- package/controllers/state.js +86 -6
- package/controllers/state.js.map +1 -1
- package/controllers/tree.d.ts +15 -10
- package/controllers/tree.js +0 -0
- package/controllers/tree.js.map +1 -1
- package/custom-elements.json +3589 -452
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/highlight.css +15 -8
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/ApexGrid.html +660 -0
- package/docs/classes/ApexGridPaginator.html +36 -0
- package/docs/classes/ApexGridToolbar.html +67 -0
- package/docs/functions/localize.html +2 -2
- package/docs/functions/setup.html +1 -2
- package/docs/hierarchy.html +1 -1
- package/docs/index.html +105 -15
- package/docs/interfaces/ApexCellValidationFailedEvent.html +3 -2
- package/docs/interfaces/ApexCellValueChangedEvent.html +4 -2
- package/docs/interfaces/ApexCellValueChangingEvent.html +3 -2
- package/docs/interfaces/ApexColumnMovedEvent.html +2 -2
- package/docs/interfaces/ApexColumnMovingEvent.html +1 -1
- package/docs/interfaces/ApexDetailContext.html +2 -2
- package/docs/interfaces/ApexGridEventMap.html +9 -6
- package/docs/interfaces/ApexRowEditEndedEvent.html +2 -1
- package/docs/interfaces/ApexRowEditStartedEvent.html +2 -1
- package/docs/interfaces/ApexRowMovingEvent.html +9 -3
- package/docs/interfaces/ApexRowPinningEvent.html +1 -1
- package/docs/interfaces/ApexTreeRowExpandedEvent.html +4 -1
- package/docs/interfaces/ApexTreeRowExpandingEvent.html +4 -1
- package/docs/interfaces/BaseApexCellContext.html +1 -1
- package/docs/interfaces/BaseApexEditorContext.html +1 -1
- package/docs/interfaces/BaseColumnConfiguration.html +3 -3
- package/docs/interfaces/BaseFilterExpression.html +2 -2
- package/docs/interfaces/CSVExportOptions.html +13 -5
- package/docs/interfaces/DataPipelineConfiguration.html +2 -2
- package/docs/interfaces/EditingHistoryConfiguration.html +1 -1
- package/docs/interfaces/ExportFormat.html +2 -2
- package/docs/interfaces/ExportOptions.html +5 -3
- package/docs/interfaces/GridExpansionConfiguration.html +6 -2
- package/docs/interfaces/GridRowPinningConfiguration.html +1 -1
- package/docs/interfaces/GridRowReorderingConfiguration.html +2 -2
- package/docs/interfaces/PaginationConfiguration.html +1 -1
- package/docs/interfaces/PaginationState.html +1 -1
- package/docs/interfaces/SetStateResult.html +3 -2
- package/docs/interfaces/ValidatorContext.html +7 -2
- package/docs/media/_token-values.scss +118 -0
- package/docs/media/package.json +5 -5
- package/docs/media/state-persistence.html +0 -10
- package/docs/modules.html +1 -1
- package/docs/types/DataPipelineParams.html +2 -2
- package/docs/types/EditMode.html +2 -2
- package/docs/types/ExportSource.html +6 -4
- package/docs/types/GridLocaleText.html +1 -1
- package/docs/types/OperandKeys.html +9 -0
- package/docs/types/PaginationMode.html +1 -1
- package/docs/variables/EN_LOCALE.html +3 -3
- package/docs/variables/esLocale.html +1 -1
- package/i18n/en.d.ts +19 -0
- package/i18n/en.js +19 -0
- package/i18n/en.js.map +1 -1
- package/i18n/es.js +19 -0
- package/i18n/es.js.map +1 -1
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/index.js.map +1 -1
- package/internal/column-types.d.ts +27 -1
- package/internal/column-types.js +75 -49
- package/internal/column-types.js.map +1 -1
- package/internal/constants.d.ts +1 -2
- package/internal/constants.js +0 -4
- package/internal/constants.js.map +1 -1
- package/internal/document-styles.d.ts +30 -0
- package/internal/document-styles.js +48 -0
- package/internal/document-styles.js.map +1 -0
- package/internal/export.d.ts +39 -7
- package/internal/export.js +35 -34
- package/internal/export.js.map +1 -1
- package/internal/feature-module.d.ts +82 -3
- package/internal/feature-module.js +11 -0
- package/internal/feature-module.js.map +1 -1
- package/internal/index.d.ts +6 -4
- package/internal/index.js +5 -3
- package/internal/index.js.map +1 -1
- package/internal/normalize-case.d.ts +8 -1
- package/internal/normalize-case.js +3 -2
- package/internal/normalize-case.js.map +1 -1
- package/internal/state-snapshot.d.ts +60 -8
- package/internal/state-snapshot.js +87 -19
- package/internal/state-snapshot.js.map +1 -1
- package/internal/types.d.ts +17 -2
- package/internal/types.js.map +1 -1
- package/internal/utils.d.ts +48 -6
- package/internal/utils.js +35 -21
- package/internal/utils.js.map +1 -1
- package/internal/validators.js +1 -0
- package/internal/validators.js.map +1 -1
- package/operations/filter/operands/number.js +9 -6
- package/operations/filter/operands/number.js.map +1 -1
- package/operations/filter/operands/string.js +2 -3
- package/operations/filter/operands/string.js.map +1 -1
- package/operations/filter/types.d.ts +13 -1
- package/operations/filter/types.js.map +1 -1
- package/operations/filter.d.ts +6 -0
- package/operations/filter.js +23 -7
- package/operations/filter.js.map +1 -1
- package/operations/quick-filter.js +2 -5
- package/operations/quick-filter.js.map +1 -1
- package/operations/sort.d.ts +21 -2
- package/operations/sort.js +41 -14
- package/operations/sort.js.map +1 -1
- package/package.json +8 -4
- package/setup.d.ts +23 -11
- package/setup.js +19 -21
- package/setup.js.map +1 -1
- package/styles/_common.css.js +1 -1
- package/styles/_common.css.js.map +1 -1
- package/styles/_token-values.css.d.ts +1 -0
- package/styles/_token-values.css.js +3 -0
- package/styles/_token-values.css.js.map +1 -0
- package/styles/_tokens.css.js +1 -1
- package/styles/_tokens.css.js.map +1 -1
- package/styles/body-cell/body-cell.css.js +1 -1
- package/styles/body-cell/body-cell.css.js.map +1 -1
- package/styles/body-cell/cell-renderers.css.js +1 -1
- package/styles/body-cell/cell-renderers.css.js.map +1 -1
- package/styles/body-row/body-row.css.js +1 -1
- package/styles/body-row/body-row.css.js.map +1 -1
- package/styles/document-tokens.css.d.ts +1 -0
- package/styles/document-tokens.css.js +3 -0
- package/styles/document-tokens.css.js.map +1 -0
- package/styles/filter-row/filter-row.css.js +1 -1
- package/styles/filter-row/filter-row.css.js.map +1 -1
- package/styles/grid/_grid.base.css.js +1 -1
- package/styles/grid/_grid.base.css.js.map +1 -1
- package/styles/grid/grid.css.js +1 -1
- package/styles/grid/grid.css.js.map +1 -1
- package/styles/group-header-row/group-header-row.css.js +1 -1
- package/styles/group-header-row/group-header-row.css.js.map +1 -1
- package/styles/header-cell/header-cell.css.js +1 -1
- package/styles/header-cell/header-cell.css.js.map +1 -1
- package/styles/header-row/header-row.base.css.js +1 -1
- package/styles/header-row/header-row.base.css.js.map +1 -1
- package/styles/paginator/paginator.css.js +1 -1
- package/styles/paginator/paginator.css.js.map +1 -1
- package/styles/toolbar/toolbar.css.js +1 -1
- package/styles/toolbar/toolbar.css.js.map +1 -1
- package/tokens.css +78 -0
- package/LICENSE +0 -80
- package/docs/media/LICENSE +0 -80
- package/docs/media/_tokens.scss +0 -193
|
@@ -4,23 +4,30 @@ import type { GridHost } from '../internal/types.js';
|
|
|
4
4
|
* The default expansion configuration used when the grid has none set.
|
|
5
5
|
*
|
|
6
6
|
* @remarks
|
|
7
|
-
* `detailTemplate` defaults to `null` here
|
|
7
|
+
* `detailTemplate` defaults to `null` here; the controller will refuse to
|
|
8
8
|
* toggle rows when the configuration is missing or no template is provided.
|
|
9
9
|
*/
|
|
10
10
|
export declare const DEFAULT_EXPANSION_CONFIG: Readonly<{
|
|
11
11
|
enabled: false;
|
|
12
12
|
showToggleColumn: true;
|
|
13
13
|
}>;
|
|
14
|
+
/**
|
|
15
|
+
* Returns `rows` without the entries that are no longer in `data`, for an
|
|
16
|
+
* expansion set that follows a data refresh. The survivors are counted first,
|
|
17
|
+
* so the usual refresh (nothing left) allocates nothing and returns `rows`
|
|
18
|
+
* itself. A row listed twice in `data` counts twice, which can keep one stale
|
|
19
|
+
* entry until a later refresh.
|
|
20
|
+
*/
|
|
21
|
+
export declare function retainPresent<T>(rows: Set<T>, data: ReadonlyArray<T>): Set<T>;
|
|
14
22
|
/**
|
|
15
23
|
* Reactive controller backing row expansion (master-detail).
|
|
16
24
|
*
|
|
17
25
|
* @remarks
|
|
18
|
-
* Expansion state is reference-based
|
|
26
|
+
* Expansion state is reference-based: the controller holds a `Set` of row
|
|
19
27
|
* data references so the expansion set survives sort / filter / pagination as
|
|
20
28
|
* long as those operations preserve row identity (the default in-place
|
|
21
|
-
* pipeline does). Replacing {@link ApexGrid.data}
|
|
22
|
-
*
|
|
23
|
-
* {@link collapseAll} or re-apply expansion afterwards.
|
|
29
|
+
* pipeline does). Replacing {@link ApexGrid.data} keeps the rows that are
|
|
30
|
+
* still present and drops the rest, without events.
|
|
24
31
|
*
|
|
25
32
|
* Mutations go through the cancellable `rowExpanding` event and emit a
|
|
26
33
|
* follow-up `rowExpanded` event, mirroring the pattern used by selection
|
|
@@ -33,6 +40,8 @@ export declare class ExpansionController<T extends object> implements ReactiveCo
|
|
|
33
40
|
expanded: Set<T>;
|
|
34
41
|
constructor(host: GridHost<T>);
|
|
35
42
|
hostConnected(): void;
|
|
43
|
+
/** Drops expanded rows that left {@link ApexGrid.data} once it is replaced. */
|
|
44
|
+
hostUpdate(): void;
|
|
36
45
|
/** Whether expansion is enabled at the grid level. */
|
|
37
46
|
get enabled(): boolean;
|
|
38
47
|
/** Whether the built-in chevron toggle column should be rendered. */
|
package/controllers/expansion.js
CHANGED
|
@@ -2,13 +2,36 @@ export const DEFAULT_EXPANSION_CONFIG = Object.freeze({
|
|
|
2
2
|
enabled: false,
|
|
3
3
|
showToggleColumn: true,
|
|
4
4
|
});
|
|
5
|
+
export function retainPresent(rows, data) {
|
|
6
|
+
if (rows.size === 0)
|
|
7
|
+
return rows;
|
|
8
|
+
let present = 0;
|
|
9
|
+
for (const row of data)
|
|
10
|
+
if (rows.has(row))
|
|
11
|
+
present++;
|
|
12
|
+
if (present === rows.size)
|
|
13
|
+
return rows;
|
|
14
|
+
const next = new Set();
|
|
15
|
+
for (const row of data)
|
|
16
|
+
if (rows.has(row))
|
|
17
|
+
next.add(row);
|
|
18
|
+
return next;
|
|
19
|
+
}
|
|
5
20
|
export class ExpansionController {
|
|
21
|
+
#data;
|
|
6
22
|
constructor(host) {
|
|
7
23
|
this.host = host;
|
|
8
24
|
this.expanded = new Set();
|
|
9
25
|
this.host.addController(this);
|
|
10
26
|
}
|
|
11
27
|
hostConnected() { }
|
|
28
|
+
hostUpdate() {
|
|
29
|
+
const data = this.host.data;
|
|
30
|
+
if (!data || data === this.#data)
|
|
31
|
+
return;
|
|
32
|
+
this.#data = data;
|
|
33
|
+
this.expanded = retainPresent(this.expanded, data);
|
|
34
|
+
}
|
|
12
35
|
get enabled() {
|
|
13
36
|
return Boolean(this.host.expansion?.enabled);
|
|
14
37
|
}
|
|
@@ -111,7 +134,7 @@ export class ExpansionController {
|
|
|
111
134
|
else if (added.length === 0 && removed.length === 1) {
|
|
112
135
|
this.host.announce(this.host.localize('announce.rowCollapsed'));
|
|
113
136
|
}
|
|
114
|
-
else if (added.length > 0 &&
|
|
137
|
+
else if (added.length > 0 && removed.length === 0) {
|
|
115
138
|
this.host.announce(this.host.localize('announce.rowsExpanded', { count: added.length }));
|
|
116
139
|
}
|
|
117
140
|
else if (removed.length > 0 && next.size === 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expansion.js","sourceRoot":"","sources":["../../src/controllers/expansion.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport type { GridHost } from '../internal/types.js';\n\n/**\n * The default expansion configuration used when the grid has none set.\n *\n * @remarks\n * `detailTemplate` defaults to `null` here — the controller will refuse to\n * toggle rows when the configuration is missing or no template is provided.\n */\nexport const DEFAULT_EXPANSION_CONFIG = Object.freeze({\n enabled: false,\n showToggleColumn: true,\n});\n\n/**\n * Reactive controller backing row expansion (master-detail).\n *\n * @remarks\n * Expansion state is reference-based — the controller holds a `Set` of row\n * data references so the expansion set survives sort / filter / pagination as\n * long as those operations preserve row identity (the default in-place\n * pipeline does). Replacing {@link ApexGrid.data} wholesale invalidates any\n * expanded rows that are no longer present; consumers should call\n * {@link collapseAll} or re-apply expansion afterwards.\n *\n * Mutations go through the cancellable `rowExpanding` event and emit a\n * follow-up `rowExpanded` event, mirroring the pattern used by selection\n * and editing.\n */\nexport class ExpansionController<T extends object> implements ReactiveController {\n /** Set of currently expanded row data references. */\n public expanded: Set<T> = new Set();\n\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n public hostConnected() {}\n\n /** Whether expansion is enabled at the grid level. */\n public get enabled(): boolean {\n return Boolean(this.host.expansion?.enabled);\n }\n\n /** Whether the built-in chevron toggle column should be rendered. */\n public get showToggleColumn(): boolean {\n return (\n this.enabled &&\n (this.host.expansion?.showToggleColumn ?? DEFAULT_EXPANSION_CONFIG.showToggleColumn)\n );\n }\n\n /** Whether `row` is currently expanded. */\n public isExpanded(row: T): boolean {\n return this.expanded.has(row);\n }\n\n /**\n * Whether `row` is permitted to expand. Combines the grid-wide `enabled`\n * flag, the presence of a `detailTemplate`, and the optional per-row\n * `isExpandable` predicate.\n */\n public canExpand(row: T): boolean {\n if (!this.enabled) return false;\n if (typeof this.host.expansion?.detailTemplate !== 'function') return false;\n const predicate = this.host.expansion?.isExpandable;\n return predicate ? Boolean(predicate(row)) : true;\n }\n\n /** Snapshot of currently expanded rows in insertion order. */\n public expandedRows(): T[] {\n return Array.from(this.expanded);\n }\n\n /** Toggles the expansion of `row`. */\n public async toggleRow(row: T): Promise<boolean> {\n if (!this.canExpand(row) && !this.expanded.has(row)) return false;\n const next = new Set(this.expanded);\n if (next.has(row)) next.delete(row);\n else next.add(row);\n return this.#commit(next);\n }\n\n /** Expands `row`. No-op when the row is already expanded or not expandable. */\n public async expandRow(row: T): Promise<boolean> {\n if (this.expanded.has(row)) return true;\n if (!this.canExpand(row)) return false;\n const next = new Set(this.expanded);\n next.add(row);\n return this.#commit(next);\n }\n\n /** Collapses `row`. No-op when the row is not expanded. */\n public async collapseRow(row: T): Promise<boolean> {\n if (!this.expanded.has(row)) return true;\n const next = new Set(this.expanded);\n next.delete(row);\n return this.#commit(next);\n }\n\n /**\n * Expands every expandable row in {@link ApexGrid.dataView}. Skips rows\n * the optional `isExpandable` predicate rejects.\n */\n public async expandAll(): Promise<boolean> {\n if (!this.enabled || typeof this.host.expansion?.detailTemplate !== 'function') return false;\n const predicate = this.host.expansion?.isExpandable;\n const next = new Set(this.expanded);\n for (const row of this.host.dataView as ReadonlyArray<T>) {\n if (!predicate || predicate(row)) next.add(row);\n }\n return this.#commit(next);\n }\n\n /** Collapses every currently expanded row. */\n public async collapseAll(): Promise<boolean> {\n if (this.expanded.size === 0) return true;\n return this.#commit(new Set());\n }\n\n /**\n * Replaces the expansion set with `rows`. Used by the public\n * {@link ApexGrid.expandedRows} setter for programmatic control.\n */\n public async replaceExpansion(rows: ReadonlyArray<T>): Promise<boolean> {\n if (!this.enabled) return false;\n const predicate = this.host.expansion?.isExpandable;\n const next = new Set(predicate ? rows.filter((row) => predicate(row)) : rows);\n return this.#commit(next);\n }\n\n async #commit(next: Set<T>): Promise<boolean> {\n const added: T[] = [];\n const removed: T[] = [];\n for (const row of next) {\n if (!this.expanded.has(row)) added.push(row);\n }\n for (const row of this.expanded) {\n if (!next.has(row)) removed.push(row);\n }\n if (added.length === 0 && removed.length === 0) return true;\n\n const proceed = this.host.emitEvent('rowExpanding', {\n detail: {\n added,\n removed,\n current: Array.from(this.expanded),\n next: Array.from(next),\n },\n cancelable: true,\n });\n if (!proceed) return false;\n\n this.expanded = next;\n this.host.requestUpdate();\n\n if (added.length === 1 && removed.length === 0) {\n this.host.announce(this.host.localize('announce.rowExpanded'));\n } else if (added.length === 0 && removed.length === 1) {\n this.host.announce(this.host.localize('announce.rowCollapsed'));\n } else if (added.length > 0 && next.size === added.length + this.expanded.size - added.length) {\n this.host.announce(this.host.localize('announce.rowsExpanded', { count: added.length }));\n } else if (removed.length > 0 && next.size === 0) {\n this.host.announce(this.host.localize('announce.allRowsCollapsed'));\n }\n\n this.host.emitEvent('rowExpanded', {\n detail: { added, removed, expanded: Array.from(this.expanded) },\n });\n return true;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"expansion.js","sourceRoot":"","sources":["../../src/controllers/expansion.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport type { GridHost } from '../internal/types.js';\n\n/**\n * The default expansion configuration used when the grid has none set.\n *\n * @remarks\n * `detailTemplate` defaults to `null` here; the controller will refuse to\n * toggle rows when the configuration is missing or no template is provided.\n */\nexport const DEFAULT_EXPANSION_CONFIG = Object.freeze({\n enabled: false,\n showToggleColumn: true,\n});\n\n/**\n * Returns `rows` without the entries that are no longer in `data`, for an\n * expansion set that follows a data refresh. The survivors are counted first,\n * so the usual refresh (nothing left) allocates nothing and returns `rows`\n * itself. A row listed twice in `data` counts twice, which can keep one stale\n * entry until a later refresh.\n */\nexport function retainPresent<T>(rows: Set<T>, data: ReadonlyArray<T>): Set<T> {\n if (rows.size === 0) return rows;\n let present = 0;\n for (const row of data) if (rows.has(row)) present++;\n if (present === rows.size) return rows;\n const next = new Set<T>();\n for (const row of data) if (rows.has(row)) next.add(row);\n return next;\n}\n\n/**\n * Reactive controller backing row expansion (master-detail).\n *\n * @remarks\n * Expansion state is reference-based: the controller holds a `Set` of row\n * data references so the expansion set survives sort / filter / pagination as\n * long as those operations preserve row identity (the default in-place\n * pipeline does). Replacing {@link ApexGrid.data} keeps the rows that are\n * still present and drops the rest, without events.\n *\n * Mutations go through the cancellable `rowExpanding` event and emit a\n * follow-up `rowExpanded` event, mirroring the pattern used by selection\n * and editing.\n */\nexport class ExpansionController<T extends object> implements ReactiveController {\n /** Set of currently expanded row data references. */\n public expanded: Set<T> = new Set();\n /** The data array the expansion set was last reconciled with. */\n #data?: ReadonlyArray<T>;\n\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n public hostConnected() {}\n\n /** Drops expanded rows that left {@link ApexGrid.data} once it is replaced. */\n public hostUpdate() {\n const data = this.host.data;\n if (!data || data === this.#data) return;\n this.#data = data;\n this.expanded = retainPresent(this.expanded, data);\n }\n\n /** Whether expansion is enabled at the grid level. */\n public get enabled(): boolean {\n return Boolean(this.host.expansion?.enabled);\n }\n\n /** Whether the built-in chevron toggle column should be rendered. */\n public get showToggleColumn(): boolean {\n return (\n this.enabled &&\n (this.host.expansion?.showToggleColumn ?? DEFAULT_EXPANSION_CONFIG.showToggleColumn)\n );\n }\n\n /** Whether `row` is currently expanded. */\n public isExpanded(row: T): boolean {\n return this.expanded.has(row);\n }\n\n /**\n * Whether `row` is permitted to expand. Combines the grid-wide `enabled`\n * flag, the presence of a `detailTemplate`, and the optional per-row\n * `isExpandable` predicate.\n */\n public canExpand(row: T): boolean {\n if (!this.enabled) return false;\n if (typeof this.host.expansion?.detailTemplate !== 'function') return false;\n const predicate = this.host.expansion?.isExpandable;\n return predicate ? Boolean(predicate(row)) : true;\n }\n\n /** Snapshot of currently expanded rows in insertion order. */\n public expandedRows(): T[] {\n return Array.from(this.expanded);\n }\n\n /** Toggles the expansion of `row`. */\n public async toggleRow(row: T): Promise<boolean> {\n if (!this.canExpand(row) && !this.expanded.has(row)) return false;\n const next = new Set(this.expanded);\n if (next.has(row)) next.delete(row);\n else next.add(row);\n return this.#commit(next);\n }\n\n /** Expands `row`. No-op when the row is already expanded or not expandable. */\n public async expandRow(row: T): Promise<boolean> {\n if (this.expanded.has(row)) return true;\n if (!this.canExpand(row)) return false;\n const next = new Set(this.expanded);\n next.add(row);\n return this.#commit(next);\n }\n\n /** Collapses `row`. No-op when the row is not expanded. */\n public async collapseRow(row: T): Promise<boolean> {\n if (!this.expanded.has(row)) return true;\n const next = new Set(this.expanded);\n next.delete(row);\n return this.#commit(next);\n }\n\n /**\n * Expands every expandable row in {@link ApexGrid.dataView}. Skips rows\n * the optional `isExpandable` predicate rejects.\n */\n public async expandAll(): Promise<boolean> {\n if (!this.enabled || typeof this.host.expansion?.detailTemplate !== 'function') return false;\n const predicate = this.host.expansion?.isExpandable;\n const next = new Set(this.expanded);\n for (const row of this.host.dataView as ReadonlyArray<T>) {\n if (!predicate || predicate(row)) next.add(row);\n }\n return this.#commit(next);\n }\n\n /** Collapses every currently expanded row. */\n public async collapseAll(): Promise<boolean> {\n if (this.expanded.size === 0) return true;\n return this.#commit(new Set());\n }\n\n /**\n * Replaces the expansion set with `rows`. Used by the public\n * {@link ApexGrid.expandedRows} setter for programmatic control.\n */\n public async replaceExpansion(rows: ReadonlyArray<T>): Promise<boolean> {\n if (!this.enabled) return false;\n const predicate = this.host.expansion?.isExpandable;\n const next = new Set(predicate ? rows.filter((row) => predicate(row)) : rows);\n return this.#commit(next);\n }\n\n async #commit(next: Set<T>): Promise<boolean> {\n const added: T[] = [];\n const removed: T[] = [];\n for (const row of next) {\n if (!this.expanded.has(row)) added.push(row);\n }\n for (const row of this.expanded) {\n if (!next.has(row)) removed.push(row);\n }\n if (added.length === 0 && removed.length === 0) return true;\n\n const proceed = this.host.emitEvent('rowExpanding', {\n detail: {\n added,\n removed,\n current: Array.from(this.expanded),\n next: Array.from(next),\n },\n cancelable: true,\n });\n if (!proceed) return false;\n\n this.expanded = next;\n this.host.requestUpdate();\n\n if (added.length === 1 && removed.length === 0) {\n this.host.announce(this.host.localize('announce.rowExpanded'));\n } else if (added.length === 0 && removed.length === 1) {\n this.host.announce(this.host.localize('announce.rowCollapsed'));\n } else if (added.length > 0 && removed.length === 0) {\n // Only a pure expansion has a count to report; a swap that also\n // collapses rows would be misread as \"N rows expanded\".\n this.host.announce(this.host.localize('announce.rowsExpanded', { count: added.length }));\n } else if (removed.length > 0 && next.size === 0) {\n this.host.announce(this.host.localize('announce.allRowsCollapsed'));\n }\n\n this.host.emitEvent('rowExpanded', {\n detail: { added, removed, expanded: Array.from(this.expanded) },\n });\n return true;\n }\n}\n"]}
|
package/controllers/filter.d.ts
CHANGED
|
@@ -15,7 +15,15 @@ export declare class FilterController<T extends object> implements ReactiveContr
|
|
|
15
15
|
setActiveColumn(column?: ColumnConfiguration<T>, triggerRect?: DOMRect): void;
|
|
16
16
|
getDefaultExpression(column: ColumnConfiguration<T>): FilterExpression<T>;
|
|
17
17
|
removeAllExpressions(key: Keys<T>): Promise<void>;
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
/** Resolves to false when nothing was removed (not applied, or cancelled). */
|
|
19
|
+
removeExpression(expression: FilterExpression<T>): Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Emits the cancelable `filtering` event for `expression` and applies it
|
|
22
|
+
* when the event passes. With a `target` (the panel's draft or an applied
|
|
23
|
+
* expression), `expression` is the proposed copy: it is written into
|
|
24
|
+
* `target` only after the event passes, so a cancelled change leaves the
|
|
25
|
+
* applied filter untouched. Resolves to false when cancelled.
|
|
26
|
+
*/
|
|
27
|
+
filterWithEvent(expression: FilterExpression<T>, type: 'add' | 'modify' | 'remove', target?: FilterExpression<T>): Promise<boolean>;
|
|
20
28
|
filter(expression: FilterExpression<T> | FilterExpression<T>[]): void;
|
|
21
29
|
}
|
package/controllers/filter.js
CHANGED
|
@@ -5,14 +5,13 @@ export class FilterController {
|
|
|
5
5
|
constructor(host) {
|
|
6
6
|
this.host = host;
|
|
7
7
|
this.state = new FilterState();
|
|
8
|
+
this.#applied = [];
|
|
9
|
+
this.#touched = false;
|
|
8
10
|
this.host.addController(this);
|
|
9
11
|
}
|
|
10
12
|
get filterRow() {
|
|
11
13
|
return this.host.filterRow;
|
|
12
14
|
}
|
|
13
|
-
get #virtualizer() {
|
|
14
|
-
return this.host.scrollContainer;
|
|
15
|
-
}
|
|
16
15
|
#emitFilteringEvent(expression, type) {
|
|
17
16
|
return this.host.emitEvent('filtering', {
|
|
18
17
|
detail: {
|
|
@@ -30,11 +29,20 @@ export class FilterController {
|
|
|
30
29
|
for (const expr of asArray(expression)) {
|
|
31
30
|
this.state.set(expr);
|
|
32
31
|
}
|
|
33
|
-
this.#
|
|
32
|
+
this.#touched = true;
|
|
34
33
|
this.host.requestUpdate(PIPELINE);
|
|
35
34
|
}
|
|
35
|
+
#applied;
|
|
36
|
+
#touched;
|
|
36
37
|
hostConnected() { }
|
|
37
38
|
hostUpdate() {
|
|
39
|
+
if (this.#touched) {
|
|
40
|
+
this.#touched = false;
|
|
41
|
+
const next = snapshot(this.state);
|
|
42
|
+
if (!sameFilter(this.#applied, next))
|
|
43
|
+
this.host.scrollTop = 0;
|
|
44
|
+
this.#applied = next;
|
|
45
|
+
}
|
|
38
46
|
this.filterRow?.requestUpdate();
|
|
39
47
|
}
|
|
40
48
|
get(key) {
|
|
@@ -42,6 +50,7 @@ export class FilterController {
|
|
|
42
50
|
}
|
|
43
51
|
reset(key) {
|
|
44
52
|
key ? this.state.delete(key) : this.state.clear();
|
|
53
|
+
this.#touched = true;
|
|
45
54
|
}
|
|
46
55
|
setActiveColumn(column, triggerRect) {
|
|
47
56
|
const filterRow = this.filterRow;
|
|
@@ -72,6 +81,8 @@ export class FilterController {
|
|
|
72
81
|
};
|
|
73
82
|
}
|
|
74
83
|
async removeAllExpressions(key) {
|
|
84
|
+
if (!this.state.has(key))
|
|
85
|
+
return;
|
|
75
86
|
const state = this.get(key)?.all ?? [];
|
|
76
87
|
if (!this.host.emitEvent('filtering', {
|
|
77
88
|
detail: {
|
|
@@ -85,33 +96,37 @@ export class FilterController {
|
|
|
85
96
|
}
|
|
86
97
|
this.reset(key);
|
|
87
98
|
this.#filter([]);
|
|
88
|
-
await this.host.
|
|
99
|
+
await this.host.pipelineSettled();
|
|
89
100
|
this.#announce(key, 'cleared');
|
|
90
101
|
this.#emitFilteredEvent({ key, state: this.get(key)?.all ?? [] });
|
|
91
102
|
}
|
|
92
103
|
async removeExpression(expression) {
|
|
93
104
|
const state = this.get(expression.key);
|
|
105
|
+
if (!state?.has(expression))
|
|
106
|
+
return false;
|
|
94
107
|
if (!this.#emitFilteringEvent(expression, 'remove')) {
|
|
95
|
-
return;
|
|
108
|
+
return false;
|
|
96
109
|
}
|
|
97
|
-
state
|
|
98
|
-
if (state
|
|
110
|
+
state.remove(expression);
|
|
111
|
+
if (state.empty) {
|
|
99
112
|
this.reset(state.key);
|
|
100
113
|
}
|
|
101
114
|
this.#filter([]);
|
|
102
|
-
await this.host.
|
|
115
|
+
await this.host.pipelineSettled();
|
|
103
116
|
this.#announce(expression.key, 'cleared');
|
|
104
|
-
this.#emitFilteredEvent({ key: expression.key, state: state
|
|
117
|
+
this.#emitFilteredEvent({ key: expression.key, state: state.all });
|
|
118
|
+
return true;
|
|
105
119
|
}
|
|
106
|
-
async filterWithEvent(expression, type) {
|
|
120
|
+
async filterWithEvent(expression, type, target = expression) {
|
|
107
121
|
if (!this.#emitFilteringEvent(expression, type)) {
|
|
108
|
-
return;
|
|
122
|
+
return false;
|
|
109
123
|
}
|
|
110
|
-
this.#filter(expression);
|
|
111
|
-
await this.host.
|
|
124
|
+
this.#filter(Object.assign(target, expression));
|
|
125
|
+
await this.host.pipelineSettled();
|
|
112
126
|
if (type === 'add')
|
|
113
|
-
this.#announce(
|
|
114
|
-
this.#emitFilteredEvent({ key:
|
|
127
|
+
this.#announce(target.key, 'applied');
|
|
128
|
+
this.#emitFilteredEvent({ key: target.key, state: this.get(target.key)?.all ?? [] });
|
|
129
|
+
return true;
|
|
115
130
|
}
|
|
116
131
|
#announce(key, kind) {
|
|
117
132
|
const column = this.host.getColumn(key);
|
|
@@ -121,7 +136,37 @@ export class FilterController {
|
|
|
121
136
|
}));
|
|
122
137
|
}
|
|
123
138
|
filter(expression) {
|
|
124
|
-
this.#filter(asArray(expression).map((expr) =>
|
|
139
|
+
this.#filter(asArray(expression).map((expr) => {
|
|
140
|
+
const defaults = this.getDefaultExpression(this.host.getColumn(expr.key));
|
|
141
|
+
return Object.assign(defaults, expr, {
|
|
142
|
+
condition: expr.condition ?? defaults.condition,
|
|
143
|
+
caseSensitive: expr.caseSensitive ?? defaults.caseSensitive,
|
|
144
|
+
});
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function snapshot(state) {
|
|
149
|
+
const out = [];
|
|
150
|
+
for (const tree of state.values) {
|
|
151
|
+
for (const e of tree)
|
|
152
|
+
out.push(e.key, e.condition, e.searchTerm, e.criteria, e.caseSensitive);
|
|
125
153
|
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
function sameFilter(a, b) {
|
|
157
|
+
if (a.length !== b.length)
|
|
158
|
+
return false;
|
|
159
|
+
for (let i = 0; i < a.length; i++) {
|
|
160
|
+
const x = a[i];
|
|
161
|
+
const y = b[i];
|
|
162
|
+
if (Object.is(x, y))
|
|
163
|
+
continue;
|
|
164
|
+
if (x instanceof Date && y instanceof Date && x.getTime() === y.getTime())
|
|
165
|
+
continue;
|
|
166
|
+
if (Array.isArray(x) && Array.isArray(y) && sameFilter(x, y))
|
|
167
|
+
continue;
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
126
171
|
}
|
|
127
172
|
//# sourceMappingURL=filter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter.js","sourceRoot":"","sources":["../../src/controllers/filter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport type { ApexFilteredEvent } from '../components/grid.js';\nimport { PIPELINE } from '../internal/constants.js';\nimport type { ColumnConfiguration, GridHost, Keys } from '../internal/types.js';\nimport { asArray, getFilterOperandsFor } from '../internal/utils.js';\nimport { FilterState } from '../operations/filter/state.js';\nimport type { FilterExpression } from '../operations/filter/types.js';\n\nexport class FilterController<T extends object> implements ReactiveController {\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n public state: FilterState<T> = new FilterState();\n\n public get filterRow() {\n // @ts-expect-error - protected access\n return this.host.filterRow;\n }\n\n get #virtualizer() {\n // @ts-expect-error - protected access\n return this.host.scrollContainer;\n }\n\n #emitFilteringEvent(expression: FilterExpression<T>, type: 'add' | 'modify' | 'remove') {\n return this.host.emitEvent('filtering', {\n detail: {\n key: expression.key,\n expressions: [expression],\n type,\n },\n cancelable: true,\n });\n }\n\n #emitFilteredEvent(detail?: ApexFilteredEvent<T>) {\n return this.host.emitEvent('filtered', { detail });\n }\n\n #filter(expression: FilterExpression<T> | FilterExpression<T>[]) {\n for (const expr of asArray(expression)) {\n this.state.set(expr);\n }\n\n // HACK: In the case where the scrollTop is a large and amount and a big chunk of data is filtered out\n // HACK: the virtualizer can't recalculate its scroll position correctly. Thus, we reset the scrollTop state.\n this.#virtualizer?.scrollTo({ top: 0 });\n this.host.requestUpdate(PIPELINE);\n }\n\n public hostConnected() {}\n\n public hostUpdate(): void {\n this.filterRow?.requestUpdate();\n }\n\n public get(key: Keys<T>) {\n return this.state.get(key);\n }\n\n public reset(key?: Keys<T>) {\n key ? this.state.delete(key) : this.state.clear();\n }\n\n public setActiveColumn(column?: ColumnConfiguration<T>, triggerRect?: DOMRect) {\n const filterRow = this.filterRow;\n if (!filterRow) return;\n\n if (!column?.filter) {\n filterRow.active = false;\n return;\n }\n\n // Toggle: clicking the same column's filter while the panel is open closes it.\n if (filterRow.active && filterRow.column?.key === column.key) {\n filterRow.active = false;\n return;\n }\n\n filterRow.triggerRect = triggerRect ?? null;\n filterRow.column = column;\n filterRow.expression = this.getDefaultExpression(column);\n filterRow.active = true;\n this.host.requestUpdate();\n }\n\n public getDefaultExpression(column: ColumnConfiguration<T>) {\n const caseSensitive =\n typeof column.filter === 'boolean' ? false : Boolean(column.filter?.caseSensitive);\n const operands = getFilterOperandsFor(column);\n const keys = Object.keys(operands) as Keys<typeof operands>[];\n\n // XXX: Types\n return {\n key: column.key,\n condition: operands[keys[0]],\n caseSensitive,\n } as unknown as FilterExpression<T>;\n }\n\n public async removeAllExpressions(key: Keys<T>) {\n const state = this.get(key)?.all ?? [];\n\n if (\n !this.host.emitEvent('filtering', {\n detail: {\n key,\n expressions: state,\n type: 'remove',\n },\n cancelable: true,\n })\n ) {\n return;\n }\n\n this.reset(key);\n this.#filter([]);\n\n await this.host.updateComplete;\n this.#announce(key, 'cleared');\n this.#emitFilteredEvent({ key, state: this.get(key)?.all ?? [] });\n }\n\n public async removeExpression(expression: FilterExpression<T>) {\n const state = this.get(expression.key);\n\n if (!this.#emitFilteringEvent(expression, 'remove')) {\n return;\n }\n\n state?.remove(expression);\n\n if (state?.empty) {\n this.reset(state.key);\n }\n\n this.#filter([]);\n\n await this.host.updateComplete;\n this.#announce(expression.key, 'cleared');\n this.#emitFilteredEvent({ key: expression.key, state: state?.all ?? [] });\n }\n\n public async filterWithEvent(expression: FilterExpression<T>, type: 'add' | 'modify' | 'remove') {\n if (!this.#emitFilteringEvent(expression, type)) {\n return;\n }\n\n this.#filter(expression);\n\n await this.host.updateComplete;\n // Announce only on `add`: `modify` fires per keystroke while typing a\n // filter value and would flood the live region.\n if (type === 'add') this.#announce(expression.key, 'applied');\n this.#emitFilteredEvent({ key: expression.key, state: this.get(expression.key)?.all ?? [] });\n }\n\n #announce(key: Keys<T>, kind: 'applied' | 'cleared') {\n const column = this.host.getColumn(key);\n const label = column?.headerText ?? String(key);\n this.host.announce(\n this.host.localize(kind === 'applied' ? 'announce.filtered' : 'announce.filterCleared', {\n label,\n })\n );\n }\n\n public filter(expression: FilterExpression<T> | FilterExpression<T>[]) {\n this.#filter(\n asArray(expression).map((expr) =>\n Object.assign(this.getDefaultExpression(this.host.getColumn(expr.key)!), expr)\n )\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"filter.js","sourceRoot":"","sources":["../../src/controllers/filter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport type { ApexFilteredEvent } from '../components/grid.js';\nimport { PIPELINE } from '../internal/constants.js';\nimport type { ColumnConfiguration, GridHost, Keys } from '../internal/types.js';\nimport { asArray, getFilterOperandsFor } from '../internal/utils.js';\nimport { FilterState } from '../operations/filter/state.js';\nimport type { FilterExpression } from '../operations/filter/types.js';\n\nexport class FilterController<T extends object> implements ReactiveController {\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n public state: FilterState<T> = new FilterState();\n\n public get filterRow() {\n // @ts-expect-error - protected access\n return this.host.filterRow;\n }\n\n #emitFilteringEvent(expression: FilterExpression<T>, type: 'add' | 'modify' | 'remove') {\n return this.host.emitEvent('filtering', {\n detail: {\n key: expression.key,\n expressions: [expression],\n type,\n },\n cancelable: true,\n });\n }\n\n #emitFilteredEvent(detail?: ApexFilteredEvent<T>) {\n return this.host.emitEvent('filtered', { detail });\n }\n\n #filter(expression: FilterExpression<T> | FilterExpression<T>[]) {\n for (const expr of asArray(expression)) {\n this.state.set(expr);\n }\n\n this.#touched = true;\n this.host.requestUpdate(PIPELINE);\n }\n\n /** The filter as of the last host update, to tell a change from a reapply. */\n #applied: unknown[] = [];\n /** Set by every write to the filter state; checked at the next update. */\n #touched = false;\n\n public hostConnected() {}\n\n public hostUpdate(): void {\n if (this.#touched) {\n this.#touched = false;\n const next = snapshot(this.state);\n // A changed match set starts at its first row. The grid host owns\n // vertical scrolling (the virtualizer is not a scroller), so reset it\n // there; left alone, a deep offset is clamped to the end of a shorter\n // result. Reapplying the same filter (setState(getState()), or a\n // wrapper that assigns filterExpressions on every render) keeps the\n // position.\n if (!sameFilter(this.#applied, next)) this.host.scrollTop = 0;\n this.#applied = next;\n }\n this.filterRow?.requestUpdate();\n }\n\n public get(key: Keys<T>) {\n return this.state.get(key);\n }\n\n public reset(key?: Keys<T>) {\n key ? this.state.delete(key) : this.state.clear();\n this.#touched = true;\n }\n\n public setActiveColumn(column?: ColumnConfiguration<T>, triggerRect?: DOMRect) {\n const filterRow = this.filterRow;\n if (!filterRow) return;\n\n if (!column?.filter) {\n filterRow.active = false;\n return;\n }\n\n // Toggle: clicking the same column's filter while the panel is open closes it.\n if (filterRow.active && filterRow.column?.key === column.key) {\n filterRow.active = false;\n return;\n }\n\n filterRow.triggerRect = triggerRect ?? null;\n filterRow.column = column;\n filterRow.expression = this.getDefaultExpression(column);\n filterRow.active = true;\n this.host.requestUpdate();\n }\n\n public getDefaultExpression(column: ColumnConfiguration<T>) {\n const caseSensitive =\n typeof column.filter === 'boolean' ? false : Boolean(column.filter?.caseSensitive);\n const operands = getFilterOperandsFor(column);\n const keys = Object.keys(operands) as Keys<typeof operands>[];\n\n // XXX: Types\n return {\n key: column.key,\n condition: operands[keys[0]],\n caseSensitive,\n } as unknown as FilterExpression<T>;\n }\n\n public async removeAllExpressions(key: Keys<T>) {\n // Nothing to clear: no events and no announcement.\n if (!this.state.has(key)) return;\n const state = this.get(key)?.all ?? [];\n\n if (\n !this.host.emitEvent('filtering', {\n detail: {\n key,\n expressions: state,\n type: 'remove',\n },\n cancelable: true,\n })\n ) {\n return;\n }\n\n this.reset(key);\n this.#filter([]);\n\n // @ts-expect-error - protected member access\n await this.host.pipelineSettled();\n this.#announce(key, 'cleared');\n this.#emitFilteredEvent({ key, state: this.get(key)?.all ?? [] });\n }\n\n /** Resolves to false when nothing was removed (not applied, or cancelled). */\n public async removeExpression(expression: FilterExpression<T>) {\n const state = this.get(expression.key);\n // An expression that was never applied (the panel's draft) is a no-op.\n if (!state?.has(expression)) return false;\n\n if (!this.#emitFilteringEvent(expression, 'remove')) {\n return false;\n }\n\n state.remove(expression);\n\n if (state.empty) {\n this.reset(state.key);\n }\n\n this.#filter([]);\n\n // @ts-expect-error - protected member access\n await this.host.pipelineSettled();\n this.#announce(expression.key, 'cleared');\n this.#emitFilteredEvent({ key: expression.key, state: state.all });\n return true;\n }\n\n /**\n * Emits the cancelable `filtering` event for `expression` and applies it\n * when the event passes. With a `target` (the panel's draft or an applied\n * expression), `expression` is the proposed copy: it is written into\n * `target` only after the event passes, so a cancelled change leaves the\n * applied filter untouched. Resolves to false when cancelled.\n */\n public async filterWithEvent(\n expression: FilterExpression<T>,\n type: 'add' | 'modify' | 'remove',\n target = expression\n ) {\n if (!this.#emitFilteringEvent(expression, type)) {\n return false;\n }\n\n this.#filter(Object.assign(target, expression));\n\n // @ts-expect-error - protected member access\n await this.host.pipelineSettled();\n // Announce only on `add`: `modify` fires per keystroke while typing a\n // filter value and would flood the live region.\n if (type === 'add') this.#announce(target.key, 'applied');\n this.#emitFilteredEvent({ key: target.key, state: this.get(target.key)?.all ?? [] });\n return true;\n }\n\n #announce(key: Keys<T>, kind: 'applied' | 'cleared') {\n const column = this.host.getColumn(key);\n const label = column?.headerText ?? String(key);\n this.host.announce(\n this.host.localize(kind === 'applied' ? 'announce.filtered' : 'announce.filterCleared', {\n label,\n })\n );\n }\n\n public filter(expression: FilterExpression<T> | FilterExpression<T>[]) {\n this.#filter(\n asArray(expression).map((expr) => {\n const defaults = this.getDefaultExpression(this.host.getColumn(expr.key)!);\n // An omitted or undefined field keeps the column default, so a\n // caseSensitive: undefined from setState or an AI patch inherits the\n // column's filter.caseSensitive instead of overriding it.\n return Object.assign(defaults, expr, {\n condition: expr.condition ?? defaults.condition,\n caseSensitive: expr.caseSensitive ?? defaults.caseSensitive,\n });\n })\n );\n }\n}\n\n/**\n * The parts of every applied expression that decide the match set, flattened\n * in state order. Values are captured, so a later in-place edit of an\n * expression (the filter panel's draft) still reads as a change.\n */\nfunction snapshot<T>(state: FilterState<T>): unknown[] {\n const out: unknown[] = [];\n for (const tree of state.values) {\n for (const e of tree) out.push(e.key, e.condition, e.searchTerm, e.criteria, e.caseSensitive);\n }\n return out;\n}\n\nfunction sameFilter(a: unknown[], b: unknown[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n const x = a[i];\n const y = b[i];\n if (Object.is(x, y)) continue;\n if (x instanceof Date && y instanceof Date && x.getTime() === y.getTime()) continue;\n // Array terms (a set filter's values) are copied by getState and setState.\n if (Array.isArray(x) && Array.isArray(y) && sameFilter(x, y)) continue;\n return false;\n }\n return true;\n}\n"]}
|
package/controllers/history.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { ReactiveController } from 'lit';
|
|
|
2
2
|
import type { GridHost, Keys } from '../internal/types.js';
|
|
3
3
|
/**
|
|
4
4
|
* A single recorded cell mutation: the record reference, its column, and the
|
|
5
|
-
* before / after values. `rowIndex` is the view-relative index at record time
|
|
6
|
-
*
|
|
5
|
+
* before / after values. `rowIndex` is the view-relative index at record time;
|
|
6
|
+
* replay reports the record's index at replay time instead (the live record
|
|
7
7
|
* reference is authoritative).
|
|
8
8
|
*/
|
|
9
9
|
export interface CellChange<T extends object> {
|
|
@@ -24,20 +24,38 @@ export interface CellChange<T extends object> {
|
|
|
24
24
|
export interface EditCommand {
|
|
25
25
|
apply(): void;
|
|
26
26
|
revert(): void;
|
|
27
|
+
/** The record the command acts on: history is cleared once a reload drops it. */
|
|
28
|
+
readonly record?: object;
|
|
29
|
+
/**
|
|
30
|
+
* For a command staged with a row edit: the value to stage for its cell,
|
|
31
|
+
* re-read from the row's staged values just before the row commits, so
|
|
32
|
+
* validation judges what the command will leave there.
|
|
33
|
+
*/
|
|
34
|
+
stagedValue?(staged: ReadonlyMap<PropertyKey, unknown>): unknown;
|
|
27
35
|
}
|
|
28
36
|
/**
|
|
29
37
|
* One or more cell mutations that undo / redo as a single step. A single-cell
|
|
30
38
|
* edit holds one change; a row-mode commit or a bulk paste / fill holds many so
|
|
31
|
-
* one Ctrl+Z reverts the whole batch.
|
|
39
|
+
* one Ctrl+Z reverts the whole batch. `commands` are other commands pushed in
|
|
40
|
+
* the same batch (a feature's state riding with the edit, such as a formula):
|
|
41
|
+
* they replay after the cell values in both directions, so a value write can
|
|
42
|
+
* never undo the state they restore.
|
|
32
43
|
*/
|
|
33
44
|
export declare class CellEditCommand<T extends object> implements EditCommand {
|
|
34
45
|
#private;
|
|
35
46
|
private host;
|
|
36
47
|
readonly changes: ReadonlyArray<CellChange<T>>;
|
|
37
|
-
|
|
48
|
+
readonly commands: ReadonlyArray<EditCommand>;
|
|
49
|
+
constructor(host: GridHost<T>, changes: ReadonlyArray<CellChange<T>>, commands?: ReadonlyArray<EditCommand>);
|
|
38
50
|
apply(): void;
|
|
39
51
|
revert(): void;
|
|
40
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Returns a lookup of a record's index in `items` (by reference, `-1` when
|
|
55
|
+
* absent). For more than one lookup it indexes `items` once, so a large batch
|
|
56
|
+
* costs one pass instead of one scan per record.
|
|
57
|
+
*/
|
|
58
|
+
export declare function viewIndexer<T>(items: ReadonlyArray<T>, lookups: number): (record: T) => number;
|
|
41
59
|
/**
|
|
42
60
|
* Reactive controller owning the undo / redo stacks for cell-data edits.
|
|
43
61
|
*
|
|
@@ -67,20 +85,39 @@ export declare class HistoryController<T extends object> implements ReactiveCont
|
|
|
67
85
|
* disabled.
|
|
68
86
|
*/
|
|
69
87
|
record(change: CellChange<T>): void;
|
|
88
|
+
/**
|
|
89
|
+
* Records a command that is not a plain cell write (its first `apply` has
|
|
90
|
+
* already run). Joins the open batch, replaying after the batch's cell
|
|
91
|
+
* changes, otherwise pushed as its own step. No-op when history is disabled.
|
|
92
|
+
*/
|
|
93
|
+
push(command: EditCommand): void;
|
|
70
94
|
/**
|
|
71
95
|
* Opens a batch so subsequent {@link record} calls coalesce into one command.
|
|
72
|
-
*
|
|
96
|
+
* Nested calls are flattened: a batch opened while another is open (a
|
|
97
|
+
* `cellValueChanged` listener that calls `applyEdits`, say) joins it, and
|
|
98
|
+
* only the outermost {@link endBatch} commits. Counted even while history
|
|
99
|
+
* is disabled, so every begin pairs with its end.
|
|
73
100
|
*/
|
|
74
101
|
beginBatch(): void;
|
|
75
102
|
/**
|
|
76
|
-
* Closes the open batch
|
|
77
|
-
*
|
|
103
|
+
* Closes the innermost open batch. Closing the outermost pushes everything
|
|
104
|
+
* it captured as a single command.
|
|
78
105
|
*/
|
|
79
106
|
endBatch(): void;
|
|
80
107
|
/** Reverts the most recent command. No-op when the undo stack is empty. */
|
|
81
108
|
undo(): void;
|
|
82
109
|
/** Re-applies the most recently undone command. No-op when nothing to redo. */
|
|
83
110
|
redo(): void;
|
|
84
|
-
/** Clears both stacks
|
|
111
|
+
/** Clears both stacks, and the changes an open batch has captured so far. */
|
|
85
112
|
clear(): void;
|
|
113
|
+
/**
|
|
114
|
+
* Clears both stacks when the grid's data was replaced and a recorded change
|
|
115
|
+
* (or a command's {@link EditCommand.record}) names a record that `records`
|
|
116
|
+
* no longer yields (a reload with new objects).
|
|
117
|
+
* Replaying onto such a detached record would change nothing on screen while
|
|
118
|
+
* still reporting the edit. New data that keeps the edited records (an
|
|
119
|
+
* append, a re-sorted copy) keeps its history. `records` is read only while
|
|
120
|
+
* a stack has entries, so the caller may pass an expensive source.
|
|
121
|
+
*/
|
|
122
|
+
reconcile(records: () => Iterable<T>): void;
|
|
86
123
|
}
|
package/controllers/history.js
CHANGED
|
@@ -1,37 +1,53 @@
|
|
|
1
1
|
import { PIPELINE } from '../internal/constants.js';
|
|
2
2
|
const DEFAULT_STACK_SIZE = 100;
|
|
3
3
|
export class CellEditCommand {
|
|
4
|
-
constructor(host, changes) {
|
|
4
|
+
constructor(host, changes, commands = []) {
|
|
5
5
|
this.host = host;
|
|
6
6
|
this.changes = changes;
|
|
7
|
+
this.commands = commands;
|
|
7
8
|
}
|
|
8
9
|
apply() {
|
|
10
|
+
const indexOf = viewIndexer(this.host.pageItems, this.changes.length);
|
|
9
11
|
for (const change of this.changes) {
|
|
10
|
-
this.#write(change, change.newValue);
|
|
12
|
+
this.#write(change, change.newValue, indexOf);
|
|
11
13
|
}
|
|
14
|
+
for (const command of this.commands)
|
|
15
|
+
command.apply();
|
|
12
16
|
}
|
|
13
17
|
revert() {
|
|
18
|
+
const indexOf = viewIndexer(this.host.pageItems, this.changes.length);
|
|
14
19
|
for (let i = this.changes.length - 1; i >= 0; i -= 1) {
|
|
15
|
-
this.#write(this.changes[i], this.changes[i].oldValue);
|
|
20
|
+
this.#write(this.changes[i], this.changes[i].oldValue, indexOf);
|
|
16
21
|
}
|
|
22
|
+
for (let i = this.commands.length - 1; i >= 0; i -= 1)
|
|
23
|
+
this.commands[i].revert();
|
|
17
24
|
}
|
|
18
|
-
#write(change, value) {
|
|
25
|
+
#write(change, value, indexOf) {
|
|
19
26
|
change.record[change.key] = value;
|
|
20
|
-
const liveIndex = this.host.pageItems.indexOf(change.record);
|
|
21
27
|
this.host.emitEvent('cellValueChanged', {
|
|
22
|
-
detail: {
|
|
23
|
-
key: change.key,
|
|
24
|
-
rowIndex: liveIndex >= 0 ? liveIndex : change.rowIndex,
|
|
25
|
-
data: change.record,
|
|
26
|
-
value,
|
|
27
|
-
},
|
|
28
|
+
detail: { key: change.key, rowIndex: indexOf(change.record), data: change.record, value },
|
|
28
29
|
});
|
|
29
30
|
}
|
|
30
31
|
}
|
|
32
|
+
export function viewIndexer(items, lookups) {
|
|
33
|
+
if (lookups < 2)
|
|
34
|
+
return (record) => items.indexOf(record);
|
|
35
|
+
let index;
|
|
36
|
+
return (record) => {
|
|
37
|
+
if (!index) {
|
|
38
|
+
index = new Map();
|
|
39
|
+
for (let i = items.length - 1; i >= 0; i -= 1)
|
|
40
|
+
index.set(items[i], i);
|
|
41
|
+
}
|
|
42
|
+
return index.get(record) ?? -1;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
31
45
|
export class HistoryController {
|
|
32
46
|
#undo = [];
|
|
33
47
|
#redo = [];
|
|
34
|
-
#batch =
|
|
48
|
+
#batch = [];
|
|
49
|
+
#commands = [];
|
|
50
|
+
#depth = 0;
|
|
35
51
|
constructor(host) {
|
|
36
52
|
this.host = host;
|
|
37
53
|
this.host.addController(this);
|
|
@@ -53,22 +69,32 @@ export class HistoryController {
|
|
|
53
69
|
record(change) {
|
|
54
70
|
if (!this.enabled)
|
|
55
71
|
return;
|
|
56
|
-
if (this.#
|
|
72
|
+
if (this.#depth > 0) {
|
|
57
73
|
this.#batch.push(change);
|
|
58
74
|
return;
|
|
59
75
|
}
|
|
60
76
|
this.#push(new CellEditCommand(this.host, [change]));
|
|
61
77
|
}
|
|
62
|
-
|
|
78
|
+
push(command) {
|
|
63
79
|
if (!this.enabled)
|
|
64
80
|
return;
|
|
65
|
-
this.#
|
|
81
|
+
if (this.#depth > 0)
|
|
82
|
+
this.#commands.push(command);
|
|
83
|
+
else
|
|
84
|
+
this.#push(command);
|
|
85
|
+
}
|
|
86
|
+
beginBatch() {
|
|
87
|
+
this.#depth += 1;
|
|
66
88
|
}
|
|
67
89
|
endBatch() {
|
|
90
|
+
if (this.#depth === 0 || --this.#depth > 0)
|
|
91
|
+
return;
|
|
68
92
|
const batch = this.#batch;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
93
|
+
const commands = this.#commands;
|
|
94
|
+
this.#batch = [];
|
|
95
|
+
this.#commands = [];
|
|
96
|
+
if (batch.length > 0 || commands.length > 0) {
|
|
97
|
+
this.#push(new CellEditCommand(this.host, batch, commands));
|
|
72
98
|
}
|
|
73
99
|
}
|
|
74
100
|
undo() {
|
|
@@ -88,13 +114,25 @@ export class HistoryController {
|
|
|
88
114
|
this.#afterReplay(command, 'redo');
|
|
89
115
|
}
|
|
90
116
|
clear() {
|
|
117
|
+
this.#batch = [];
|
|
118
|
+
this.#commands = [];
|
|
91
119
|
if (this.#undo.length === 0 && this.#redo.length === 0)
|
|
92
120
|
return;
|
|
93
121
|
this.#undo = [];
|
|
94
122
|
this.#redo = [];
|
|
95
|
-
this.#batch = null;
|
|
96
123
|
this.#emitChanged();
|
|
97
124
|
}
|
|
125
|
+
reconcile(records) {
|
|
126
|
+
if (this.#undo.length === 0 && this.#redo.length === 0)
|
|
127
|
+
return;
|
|
128
|
+
const live = new Set(records());
|
|
129
|
+
const stale = (command) => (command.record !== undefined && !live.has(command.record)) ||
|
|
130
|
+
(command instanceof CellEditCommand &&
|
|
131
|
+
(command.changes.some((change) => !live.has(change.record)) ||
|
|
132
|
+
command.commands.some(stale)));
|
|
133
|
+
if (this.#undo.some(stale) || this.#redo.some(stale))
|
|
134
|
+
this.clear();
|
|
135
|
+
}
|
|
98
136
|
#push(command) {
|
|
99
137
|
this.#undo.push(command);
|
|
100
138
|
this.#redo = [];
|
|
@@ -105,7 +143,7 @@ export class HistoryController {
|
|
|
105
143
|
}
|
|
106
144
|
#afterReplay(command, kind) {
|
|
107
145
|
this.host.requestUpdate(PIPELINE);
|
|
108
|
-
const count = command instanceof CellEditCommand ? command.changes.length : 1;
|
|
146
|
+
const count = command instanceof CellEditCommand ? command.changes.length || 1 : 1;
|
|
109
147
|
const key = kind === 'undo'
|
|
110
148
|
? count === 1
|
|
111
149
|
? 'announce.undoOne'
|