apex-grid 3.6.0 → 4.0.0-beta.2
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 +301 -0
- package/LICENSE.md +2 -1
- package/README.md +117 -18
- package/components/cell.d.ts +13 -1
- package/components/cell.js +28 -13
- package/components/cell.js.map +1 -1
- package/components/grid.d.ts +175 -28
- package/components/grid.js +183 -63
- package/components/grid.js.map +1 -1
- package/components/header-row.d.ts +0 -6
- package/components/header-row.js +1 -19
- package/components/header-row.js.map +1 -1
- package/components/header.js +39 -2
- package/components/header.js.map +1 -1
- package/components/paginator.d.ts +20 -5
- package/components/paginator.js +54 -17
- package/components/paginator.js.map +1 -1
- package/components/row.js +149 -40
- package/components/row.js.map +1 -1
- package/components/toolbar.d.ts +5 -1
- package/components/toolbar.js +4 -1
- package/components/toolbar.js.map +1 -1
- package/components/virtualize-rows.d.ts +38 -0
- package/components/virtualize-rows.js +286 -0
- package/components/virtualize-rows.js.map +1 -0
- package/components/virtualizer.d.ts +71 -1
- package/components/virtualizer.js +105 -29
- package/components/virtualizer.js.map +1 -1
- package/controllers/dom.d.ts +10 -3
- package/controllers/dom.js +5 -5
- package/controllers/dom.js.map +1 -1
- package/controllers/navigation.d.ts +63 -0
- package/controllers/navigation.js +295 -21
- package/controllers/navigation.js.map +1 -1
- package/controllers/pagination.d.ts +7 -6
- package/controllers/pagination.js +27 -28
- package/controllers/pagination.js.map +1 -1
- package/controllers/row-pin.d.ts +6 -0
- package/controllers/row-pin.js +8 -2
- package/controllers/row-pin.js.map +1 -1
- package/controllers/row-reorder.d.ts +12 -4
- package/controllers/row-reorder.js +20 -6
- package/controllers/row-reorder.js.map +1 -1
- package/controllers/selection.d.ts +32 -10
- package/controllers/selection.js +33 -10
- package/controllers/selection.js.map +1 -1
- package/controllers/sort.js +2 -3
- package/controllers/sort.js.map +1 -1
- package/controllers/state.d.ts +18 -5
- package/controllers/state.js +53 -46
- package/controllers/state.js.map +1 -1
- package/custom-elements.json +1762 -339
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/ApexGrid.html +166 -66
- package/docs/classes/ApexGridPaginator.html +39 -8
- package/docs/classes/ApexGridToolbar.html +9 -5
- package/docs/functions/localize.html +2 -2
- package/docs/hierarchy.html +1 -1
- package/docs/index.html +123 -20
- package/docs/interfaces/ApexPageChangedEvent.html +3 -1
- package/docs/interfaces/BaseApexCellContext.html +18 -5
- package/docs/interfaces/BaseApexEditorContext.html +20 -5
- package/docs/interfaces/BaseFilterExpression.html +1 -1
- package/docs/interfaces/CSVExportOptions.html +1 -1
- package/docs/interfaces/DataPipelineConfiguration.html +4 -9
- package/docs/interfaces/ExportOptions.html +1 -1
- package/docs/interfaces/GridSelectionConfiguration.html +19 -4
- package/docs/interfaces/PaginationConfiguration.html +11 -4
- package/docs/interfaces/PaginationState.html +4 -2
- package/docs/media/_token-values.scss +42 -16
- package/docs/media/package.json +12 -4
- package/docs/types/DataPipelineParams.html +2 -2
- package/docs/types/PaginationMode.html +5 -4
- package/docs/variables/EN_LOCALE.html +2 -2
- package/i18n/en.d.ts +2 -0
- package/i18n/en.js +2 -0
- package/i18n/en.js.map +1 -1
- package/i18n/es.js +2 -0
- package/i18n/es.js.map +1 -1
- package/ignite-ui.d.ts +1 -0
- package/ignite-ui.js +4 -0
- package/ignite-ui.js.map +1 -0
- package/internal/column-types.d.ts +11 -1
- package/internal/column-types.js +8 -6
- package/internal/column-types.js.map +1 -1
- package/internal/design-system.d.ts +32 -0
- package/internal/design-system.js +66 -0
- package/internal/design-system.js.map +1 -0
- package/internal/export.d.ts +6 -0
- package/internal/export.js +4 -0
- package/internal/export.js.map +1 -1
- package/internal/feature-module.d.ts +7 -0
- package/internal/feature-module.js.map +1 -1
- package/internal/flip.d.ts +28 -16
- package/internal/flip.js +42 -10
- package/internal/flip.js.map +1 -1
- package/internal/icons.d.ts +2 -16
- package/internal/icons.js +0 -8
- package/internal/icons.js.map +1 -1
- package/internal/ignite-ui.d.ts +10 -0
- package/internal/ignite-ui.js +118 -0
- package/internal/ignite-ui.js.map +1 -0
- package/internal/index.d.ts +4 -2
- package/internal/index.js +3 -2
- package/internal/index.js.map +1 -1
- package/internal/types.d.ts +75 -19
- package/internal/types.js.map +1 -1
- package/operations/filter/types.d.ts +1 -1
- package/operations/filter/types.js.map +1 -1
- package/package.json +7 -2
- package/styles/_token-blocks.css.d.ts +1 -0
- package/styles/_token-blocks.css.js +3 -0
- package/styles/_token-blocks.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.js +1 -1
- package/styles/document-tokens.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/ignite-ui/ignite-ui-dark.css.d.ts +1 -0
- package/styles/ignite-ui/ignite-ui-dark.css.js +3 -0
- package/styles/ignite-ui/ignite-ui-dark.css.js.map +1 -0
- package/styles/ignite-ui/ignite-ui-document.css.d.ts +1 -0
- package/styles/ignite-ui/ignite-ui-document.css.js +3 -0
- package/styles/ignite-ui/ignite-ui-document.css.js.map +1 -0
- package/styles/ignite-ui/ignite-ui.css.d.ts +1 -0
- package/styles/ignite-ui/ignite-ui.css.js +3 -0
- package/styles/ignite-ui/ignite-ui.css.js.map +1 -0
- 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 +14 -15
- package/operations/pagination.d.ts +0 -19
- package/operations/pagination.js +0 -12
- package/operations/pagination.js.map +0 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { adoptOnce } from './document-styles.js';
|
|
2
|
+
const REGISTRY_KEY = '__apex_grid_design_systems_v1__';
|
|
3
|
+
function registry() {
|
|
4
|
+
const slot = globalThis;
|
|
5
|
+
slot[REGISTRY_KEY] ??= {
|
|
6
|
+
styles: null,
|
|
7
|
+
grids: new Set(),
|
|
8
|
+
documents: new WeakMap(),
|
|
9
|
+
listeners: new Set(),
|
|
10
|
+
};
|
|
11
|
+
return slot[REGISTRY_KEY];
|
|
12
|
+
}
|
|
13
|
+
function attachDocument(styles, doc) {
|
|
14
|
+
if (!('adoptedStyleSheets' in doc) ||
|
|
15
|
+
typeof CSSStyleSheet === 'undefined' ||
|
|
16
|
+
!('replaceSync' in CSSStyleSheet.prototype)) {
|
|
17
|
+
adoptOnce(doc, `design-system:${styles.name}`, styles.documentCss);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const { documents } = registry();
|
|
21
|
+
let sheet = documents.get(doc);
|
|
22
|
+
if (!sheet) {
|
|
23
|
+
const Sheet = doc.defaultView?.CSSStyleSheet ?? CSSStyleSheet;
|
|
24
|
+
sheet = new Sheet();
|
|
25
|
+
sheet.replaceSync(styles.documentCss);
|
|
26
|
+
documents.set(doc, sheet);
|
|
27
|
+
}
|
|
28
|
+
const adopted = doc.adoptedStyleSheets;
|
|
29
|
+
if (adopted[adopted.length - 1] !== sheet) {
|
|
30
|
+
doc.adoptedStyleSheets = [...adopted.filter((each) => each !== sheet), sheet];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function attach(styles, grid) {
|
|
34
|
+
attachDocument(styles, grid.ownerDocument);
|
|
35
|
+
const root = grid.shadowRoot;
|
|
36
|
+
if (root)
|
|
37
|
+
styles.attach(grid, root);
|
|
38
|
+
}
|
|
39
|
+
export function registerDesignSystem(styles) {
|
|
40
|
+
const reg = registry();
|
|
41
|
+
if (reg.styles)
|
|
42
|
+
return;
|
|
43
|
+
reg.styles = styles;
|
|
44
|
+
for (const grid of reg.grids)
|
|
45
|
+
attach(styles, grid);
|
|
46
|
+
for (const listener of reg.listeners)
|
|
47
|
+
listener();
|
|
48
|
+
}
|
|
49
|
+
export function designSystemName() {
|
|
50
|
+
return registry().styles?.name ?? null;
|
|
51
|
+
}
|
|
52
|
+
export function onDesignSystemRegistered(listener) {
|
|
53
|
+
const { listeners } = registry();
|
|
54
|
+
listeners.add(listener);
|
|
55
|
+
return () => listeners.delete(listener);
|
|
56
|
+
}
|
|
57
|
+
export function connectDesignSystem(grid) {
|
|
58
|
+
const reg = registry();
|
|
59
|
+
reg.grids.add(grid);
|
|
60
|
+
if (reg.styles)
|
|
61
|
+
attach(reg.styles, grid);
|
|
62
|
+
}
|
|
63
|
+
export function disconnectDesignSystem(grid) {
|
|
64
|
+
registry().grids.delete(grid);
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=design-system.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"design-system.js","sourceRoot":"","sources":["../../src/internal/design-system.ts"],"names":[],"mappings":"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","sourcesContent":["import { adoptOnce } from './document-styles.js';\n\n/**\n * A design-system add-on (`apex-grid/ignite-ui`): the rules it adds to the\n * grid's document and to each grid's shadow root.\n *\n * The grid ships no design-system code of its own. An add-on registers once\n * on import; from then on every grid adopts its rules as it connects, and the\n * grids already on the page get them right away.\n */\nexport interface DesignSystemStyles {\n /** The design system's id, such as `'igniteui'`. */\n readonly name: string;\n /** Rules for the grid's document, adopted after the `--ag-*` root defaults. */\n readonly documentCss: string;\n /** Adds the add-on's rules to one grid's shadow root, after the grid's own. */\n attach(grid: HTMLElement, root: ShadowRoot): void;\n}\n\n/**\n * The registry, on a versioned `globalThis` slot rather than in this module:\n * a page can hold two copies of it (the grid and the add-on bundled\n * separately, a CDN's per-file builds, two apps on one page), and an add-on\n * registered into a copy no grid reads would do nothing, with no error. The\n * family add-ons share their registries the same way.\n */\ninterface Registry {\n /** The registered add-on; the first registration wins. */\n styles: DesignSystemStyles | null;\n /** Every connected grid, of every copy, so a late registration reaches them too. */\n readonly grids: Set<HTMLElement>;\n /** The add-on's document sheet, per document, kept after the root defaults. */\n readonly documents: WeakMap<Document, CSSStyleSheet>;\n /** Called when an add-on registers (the enterprise companions re-read the grid's theme). */\n readonly listeners: Set<() => void>;\n}\n\nconst REGISTRY_KEY = '__apex_grid_design_systems_v1__';\n\nfunction registry(): Registry {\n const slot = globalThis as unknown as Record<string, Registry | undefined>;\n slot[REGISTRY_KEY] ??= {\n styles: null,\n grids: new Set(),\n documents: new WeakMap(),\n listeners: new Set(),\n };\n return slot[REGISTRY_KEY];\n}\n\n/**\n * Adopts the add-on's document rules once per document, and keeps them last:\n * a second copy of the grid adopts its own root defaults when its first grid\n * connects, and the add-on's rules (which lift some of those defaults) must\n * still come after them.\n */\nfunction attachDocument(styles: DesignSystemStyles, doc: Document): void {\n if (\n !('adoptedStyleSheets' in doc) ||\n typeof CSSStyleSheet === 'undefined' ||\n !('replaceSync' in CSSStyleSheet.prototype)\n ) {\n adoptOnce(doc, `design-system:${styles.name}`, styles.documentCss);\n return;\n }\n const { documents } = registry();\n let sheet = documents.get(doc);\n if (!sheet) {\n const Sheet =\n (doc.defaultView as (Window & typeof globalThis) | null)?.CSSStyleSheet ?? CSSStyleSheet;\n sheet = new Sheet();\n sheet.replaceSync(styles.documentCss);\n documents.set(doc, sheet);\n }\n const adopted = doc.adoptedStyleSheets;\n if (adopted[adopted.length - 1] !== sheet) {\n doc.adoptedStyleSheets = [...adopted.filter((each) => each !== sheet), sheet];\n }\n}\n\nfunction attach(styles: DesignSystemStyles, grid: HTMLElement): void {\n attachDocument(styles, grid.ownerDocument);\n const root = grid.shadowRoot;\n if (root) styles.attach(grid, root);\n}\n\n/**\n * Registers a design-system add-on. The first registration wins: a second\n * import of the same add-on (or of another one) changes nothing.\n */\nexport function registerDesignSystem(styles: DesignSystemStyles): void {\n const reg = registry();\n if (reg.styles) return;\n reg.styles = styles;\n for (const grid of reg.grids) attach(styles, grid);\n for (const listener of reg.listeners) listener();\n}\n\n/** The id of the registered design-system add-on, or null when none is loaded. */\nexport function designSystemName(): string | null {\n return registry().styles?.name ?? null;\n}\n\n/**\n * Calls `listener` when a design-system add-on registers (an import after the\n * grids connected). Returns the function that stops listening.\n */\nexport function onDesignSystemRegistered(listener: () => void): () => void {\n const { listeners } = registry();\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\n/** Called by every grid as it connects, after its own document styles. */\nexport function connectDesignSystem(grid: HTMLElement): void {\n const reg = registry();\n reg.grids.add(grid);\n if (reg.styles) attach(reg.styles, grid);\n}\n\n/** Called by every grid as it disconnects. */\nexport function disconnectDesignSystem(grid: HTMLElement): void {\n registry().grids.delete(grid);\n}\n"]}
|
package/internal/export.d.ts
CHANGED
|
@@ -111,6 +111,12 @@ export declare function defaultExportFormat<T extends object>(column: ColumnConf
|
|
|
111
111
|
export declare function resolveExportValue<T extends object>(column: ColumnConfiguration<T>, row: T, opts: ExportOptions<T>): ExportCellValue;
|
|
112
112
|
/** Formats a `Date` into the configured date style for CSV output. */
|
|
113
113
|
export declare function formatDateForCSV<T extends object>(column: ColumnConfiguration<T>, value: Date): string;
|
|
114
|
+
/**
|
|
115
|
+
* A cell value as CSV export writes it, before escaping: a date in the
|
|
116
|
+
* column's date style, a select option's label, a number as a number, and
|
|
117
|
+
* blank for no value. Chart category labels read the same text.
|
|
118
|
+
*/
|
|
119
|
+
export declare function formatDisplayText<T extends object>(column: ColumnConfiguration<T>, value: unknown): string;
|
|
114
120
|
/**
|
|
115
121
|
* Prefixes an apostrophe to text a spreadsheet would read as a formula (it
|
|
116
122
|
* starts with `=`, `+`, `-`, `@`, a tab or a carriage return and is not a
|
package/internal/export.js
CHANGED
|
@@ -71,6 +71,10 @@ export function resolveExportValue(column, row, opts) {
|
|
|
71
71
|
export function formatDateForCSV(column, value) {
|
|
72
72
|
return Number.isNaN(value.getTime()) ? '' : dateFormatter(column.format).format(value);
|
|
73
73
|
}
|
|
74
|
+
export function formatDisplayText(column, value) {
|
|
75
|
+
const text = defaultExportFormat(column, value);
|
|
76
|
+
return text instanceof Date ? formatDateForCSV(column, text) : String(text ?? '');
|
|
77
|
+
}
|
|
74
78
|
const FORMULA_LEADING = /^(?![+-]\d*\.?\d+$)[=+\-@\t\r]/;
|
|
75
79
|
export function escapeFormula(value) {
|
|
76
80
|
return typeof value === 'string' && FORMULA_LEADING.test(value) ? `'${value}` : value;
|
package/internal/export.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/internal/export.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ApexGrid } from '../components/grid.js';\nimport type { StateController } from '../controllers/state.js';\nimport { dateFormatter, parseDate } from './column-types.js';\nimport type { ColumnConfiguration, Keys } from './types.js';\nimport { getDisplayColumns, isNumericColumn } from './utils.js';\n\n/**\n * Primitive cell values supported by the export pipeline.\n *\n * @remarks\n * Returned by the default formatter and accepted from user-supplied formatters.\n * `Date` is preserved (not stringified) so that XLSX export can write it as a\n * native date cell instead of inline text.\n */\nexport type ExportCellValue = string | number | boolean | Date | null | undefined;\n\n/**\n * Which row set should drive the export.\n *\n * - `'view'` (default): post-filter, post-sort rows across all pages.\n * - `'all'`: the raw `data` array as supplied by the consumer.\n * - `'page'`: only the rows in the current page slice.\n * - `'selected'`: the selected rows, in view order. Selected rows the\n * current view hides (filtered out or under a collapsed parent) follow,\n * in the order they were selected.\n */\nexport type ExportSource = 'view' | 'all' | 'page' | 'selected';\n\n/**\n * Options shared by all export targets.\n */\nexport interface ExportOptions<T extends object> {\n /** Output filename, without extension. Defaults to `'data'`. */\n filename?: string;\n /** Row set to export. Defaults to `'view'`. */\n source?: ExportSource;\n /**\n * Narrows the export to these column keys. The list filters the visible\n * columns with `exportable !== false`, so a hidden or non-exportable key is\n * skipped, and the output keeps display order rather than the list's order.\n * When omitted, every visible exportable column is exported.\n */\n columns?: ReadonlyArray<Keys<T>>;\n /** Whether to emit a header row. Defaults to `true`. */\n includeHeader?: boolean;\n /**\n * Per-cell value formatter. Receives the column configuration, the raw value\n * from the record, and the record itself. Return one of {@link ExportCellValue}\n * (return a `Date` to keep date typing in XLSX).\n */\n formatter?: (column: ColumnConfiguration<T>, value: unknown, row: T) => ExportCellValue;\n}\n\n/**\n * Options for {@link ApexGrid.exportToCSV}.\n */\nexport interface CSVExportOptions<T extends object> extends ExportOptions<T> {\n /** Field delimiter. Defaults to `','`. */\n delimiter?: string;\n /**\n * Prepend a UTF-8 byte-order mark so Excel auto-detects the encoding when\n * opening the file. Defaults to `true`.\n */\n bom?: boolean;\n /** Line separator. Defaults to `'\\r\\n'` (RFC 4180). */\n newline?: string;\n /**\n * Prefix an apostrophe to a text cell or header that starts with `=`, `+`,\n * `-`, `@`, a tab or a carriage return, so a spreadsheet opening the file\n * shows it as text instead of evaluating it as a formula. Numbers and text\n * that is a plain signed decimal (`-5`, `+1.5`) are written as they are.\n * Pass `false` to write such text unchanged. Defaults to `true`.\n */\n escapeFormulas?: boolean;\n}\n\n/**\n * Describes an export format offered by the grid's toolbar menu.\n *\n * @remarks\n * The toolbar renders one menu item per entry returned by\n * {@link ApexGrid.exportFormats} and dispatches the chosen `id` to\n * {@link ApexGrid.exportAs}. The community grid offers `'csv'`; derived grids\n * (e.g. `apex-grid-enterprise`) extend the list (e.g. with `'xlsx'`).\n */\nexport interface ExportFormat {\n /** Stable format identifier, e.g. `'csv'`. */\n id: string;\n /** Menu item label, e.g. `'Export CSV'`. */\n label: string;\n}\n\n/**\n * Resolves the columns that should appear in the export, in display order.\n */\nexport function resolveExportColumns<T extends object>(\n grid: ApexGrid<T>,\n opts: ExportOptions<T>\n): Array<ColumnConfiguration<T>> {\n const all = getDisplayColumns(grid.columns).filter(\n (column) => !column.hidden && column.exportable !== false\n );\n if (!opts.columns?.length) return all;\n const keys = new Set(opts.columns as Keys<T>[]);\n return all.filter((column) => keys.has(column.key));\n}\n\n/**\n * Resolves the rows that should appear in the export. The `view` and `page`\n * sources hold only data records: rows a feature module synthesized into the\n * view (group headers, a grand-total row) are dropped, see\n * {@link StateController.isDataRow}. `view` also writes the leaves of\n * collapsed groups ({@link StateController.viewRecords}); `page` writes what\n * the page shows.\n */\nexport function resolveExportRows<T extends object>(\n grid: ApexGrid<T>,\n source: ExportSource = 'view'\n): ReadonlyArray<T> {\n switch (source) {\n case 'all':\n return grid.data;\n case 'page':\n return stateOf(grid)?.dataRows(grid.pageItems) ?? grid.pageItems;\n case 'selected': {\n // Selection is a Set in the order rows were picked; write the rows the\n // way the screen lists them, then any the view hides (filtered out or\n // under a collapsed parent).\n const selected = new Set(grid.selectedRows);\n const inView = viewRows(grid).filter((row) => selected.delete(row));\n return [...inView, ...selected];\n }\n default:\n return viewRows(grid);\n }\n}\n\nfunction viewRows<T extends object>(grid: ApexGrid<T>): ReadonlyArray<T> {\n return stateOf(grid)?.viewRecords(grid.dataView) ?? grid.dataView;\n}\n\nfunction stateOf<T extends object>(grid: ApexGrid<T>): StateController<T> | undefined {\n return (grid as unknown as { stateController?: StateController<T> }).stateController;\n}\n\n/**\n * Returns the column's display label, used as the header cell value.\n */\nexport function getColumnLabel<T extends object>(column: ColumnConfiguration<T>): string {\n return column.headerText ?? String(column.key);\n}\n\n/**\n * Default cell-value formatter. Mirrors the on-screen rendering for each\n * column type while keeping primitive types primitive (so number/boolean/date\n * survive into XLSX as native cells instead of strings).\n */\nexport function defaultExportFormat<T extends object>(\n column: ColumnConfiguration<T>,\n value: unknown\n): ExportCellValue {\n if (value === null || value === undefined) return '';\n if (isNumericColumn(column)) {\n const n = Number(value);\n return Number.isFinite(n) ? n : '';\n }\n switch (column.type) {\n case 'select': {\n const found = column.options?.find((option) => {\n if (option && typeof option === 'object' && 'value' in option) {\n return (option as { value: unknown }).value === value;\n }\n return option === value;\n });\n if (found && typeof found === 'object' && 'label' in found) {\n const label = (found as { label?: unknown }).label;\n if (typeof label === 'string' && label.length > 0) return label;\n }\n return typeof value === 'number' || typeof value === 'boolean' ? value : String(value);\n }\n // The cell's own parse: a 'YYYY-MM-DD' string is the local date the cell\n // shows (not UTC midnight), and an invalid Date is empty, as on screen.\n // Text that is no date stays text.\n case 'date':\n return parseDate(value) ?? (typeof value === 'object' ? '' : String(value));\n // Checked only for `true`, like the cell's checkbox and the filter.\n case 'boolean':\n return value === true;\n default:\n return typeof value === 'number' || typeof value === 'boolean' ? value : String(value);\n }\n}\n\n/**\n * Resolves the export value for a single cell, applying the user-supplied\n * formatter when present and falling back to {@link defaultExportFormat}.\n */\nexport function resolveExportValue<T extends object>(\n column: ColumnConfiguration<T>,\n row: T,\n opts: ExportOptions<T>\n): ExportCellValue {\n const raw = (row as Record<string, unknown>)[column.key as string];\n const value = opts.formatter\n ? opts.formatter(column, raw, row)\n : defaultExportFormat(column, raw);\n // An invalid Date from a user formatter has no text or serial to write.\n return value instanceof Date && Number.isNaN(value.getTime()) ? '' : value;\n}\n\n/** Formats a `Date` into the configured date style for CSV output. */\nexport function formatDateForCSV<T extends object>(\n column: ColumnConfiguration<T>,\n value: Date\n): string {\n return Number.isNaN(value.getTime()) ? '' : dateFormatter(column.format).format(value);\n}\n\n/** Text a spreadsheet would evaluate: a formula trigger first, not a signed decimal. */\nconst FORMULA_LEADING = /^(?![+-]\\d*\\.?\\d+$)[=+\\-@\\t\\r]/;\n\n/**\n * Prefixes an apostrophe to text a spreadsheet would read as a formula (it\n * starts with `=`, `+`, `-`, `@`, a tab or a carriage return and is not a\n * plain signed decimal), the usual way to keep it text. Any other value is\n * returned as is, a boxed `String` included: an export formatter returns one\n * to mark text it means to keep raw, such as a formula source. CSV export\n * and clipboard copy share this rule.\n */\nexport function escapeFormula<V>(value: V): V | string {\n return typeof value === 'string' && FORMULA_LEADING.test(value) ? `'${value}` : value;\n}\n\n/**\n * Quotes one delimited-text field when it holds the delimiter, a double quote\n * or a line break (doubling inner quotes), the convention CSV files and\n * spreadsheet clipboards share. Blank values become an empty field.\n */\nexport function escapeCSVCell(value: ExportCellValue, delimiter: string): string {\n if (value === null || value === undefined || value === '') return '';\n const text = value instanceof Date ? value.toISOString() : String(value);\n const needsQuoting =\n text.includes(delimiter) || text.includes('\"') || text.includes('\\n') || text.includes('\\r');\n return needsQuoting ? `\"${text.replace(/\"/g, '\"\"')}\"` : text;\n}\n\n/**\n * Builds the CSV string for the grid without triggering a download.\n *\n * @remarks\n * Useful when consumers want to ship the bytes through their own transport\n * (clipboard, HTTP upload). The {@link ApexGrid.exportToCSV} method calls\n * this and then triggers a browser download.\n */\nexport function buildCSV<T extends object>(\n grid: ApexGrid<T>,\n opts: CSVExportOptions<T> = {}\n): string {\n const delimiter = opts.delimiter ?? ',';\n const newline = opts.newline ?? '\\r\\n';\n const includeHeader = opts.includeHeader ?? true;\n const cell = (value: ExportCellValue) =>\n escapeCSVCell(opts.escapeFormulas === false ? value : escapeFormula(value), delimiter);\n const columns = resolveExportColumns(grid, opts);\n const rows = resolveExportRows(grid, opts.source);\n\n const lines: string[] = [];\n if (includeHeader) {\n lines.push(columns.map((column) => cell(getColumnLabel(column))).join(delimiter));\n }\n for (const row of rows) {\n const cells = columns.map((column) => {\n let value = resolveExportValue(column, row, opts);\n if (value instanceof Date) {\n value = formatDateForCSV(column, value);\n }\n return cell(value);\n });\n lines.push(cells.join(delimiter));\n }\n\n const body = lines.join(newline);\n return opts.bom === false ? body : `\\uFEFF${body}`;\n}\n\n/**\n * Triggers a browser download for the given payload.\n *\n * @remarks\n * Falls back to a no-op when called outside the browser (during SSR or unit\n * tests on JSDOM without a body), so consumers can still call the public\n * export API safely.\n */\nexport function downloadBlob(\n filename: string,\n content: string | Uint8Array,\n mimeType: string\n): void {\n if (typeof document === 'undefined' || typeof URL?.createObjectURL !== 'function') return;\n // BlobPart accepts both string and Uint8Array; the cast quiets TS' strict\n // overload resolution which prefers ArrayBufferView only.\n const blob = new Blob([content as BlobPart], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = filename;\n anchor.style.display = 'none';\n document.body.appendChild(anchor);\n anchor.click();\n document.body.removeChild(anchor);\n setTimeout(() => URL.revokeObjectURL(url), 0);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/internal/export.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ApexGrid } from '../components/grid.js';\nimport type { StateController } from '../controllers/state.js';\nimport { dateFormatter, parseDate } from './column-types.js';\nimport type { ColumnConfiguration, Keys } from './types.js';\nimport { getDisplayColumns, isNumericColumn } from './utils.js';\n\n/**\n * Primitive cell values supported by the export pipeline.\n *\n * @remarks\n * Returned by the default formatter and accepted from user-supplied formatters.\n * `Date` is preserved (not stringified) so that XLSX export can write it as a\n * native date cell instead of inline text.\n */\nexport type ExportCellValue = string | number | boolean | Date | null | undefined;\n\n/**\n * Which row set should drive the export.\n *\n * - `'view'` (default): post-filter, post-sort rows across all pages.\n * - `'all'`: the raw `data` array as supplied by the consumer.\n * - `'page'`: only the rows in the current page slice.\n * - `'selected'`: the selected rows, in view order. Selected rows the\n * current view hides (filtered out or under a collapsed parent) follow,\n * in the order they were selected.\n */\nexport type ExportSource = 'view' | 'all' | 'page' | 'selected';\n\n/**\n * Options shared by all export targets.\n */\nexport interface ExportOptions<T extends object> {\n /** Output filename, without extension. Defaults to `'data'`. */\n filename?: string;\n /** Row set to export. Defaults to `'view'`. */\n source?: ExportSource;\n /**\n * Narrows the export to these column keys. The list filters the visible\n * columns with `exportable !== false`, so a hidden or non-exportable key is\n * skipped, and the output keeps display order rather than the list's order.\n * When omitted, every visible exportable column is exported.\n */\n columns?: ReadonlyArray<Keys<T>>;\n /** Whether to emit a header row. Defaults to `true`. */\n includeHeader?: boolean;\n /**\n * Per-cell value formatter. Receives the column configuration, the raw value\n * from the record, and the record itself. Return one of {@link ExportCellValue}\n * (return a `Date` to keep date typing in XLSX).\n */\n formatter?: (column: ColumnConfiguration<T>, value: unknown, row: T) => ExportCellValue;\n}\n\n/**\n * Options for {@link ApexGrid.exportToCSV}.\n */\nexport interface CSVExportOptions<T extends object> extends ExportOptions<T> {\n /** Field delimiter. Defaults to `','`. */\n delimiter?: string;\n /**\n * Prepend a UTF-8 byte-order mark so Excel auto-detects the encoding when\n * opening the file. Defaults to `true`.\n */\n bom?: boolean;\n /** Line separator. Defaults to `'\\r\\n'` (RFC 4180). */\n newline?: string;\n /**\n * Prefix an apostrophe to a text cell or header that starts with `=`, `+`,\n * `-`, `@`, a tab or a carriage return, so a spreadsheet opening the file\n * shows it as text instead of evaluating it as a formula. Numbers and text\n * that is a plain signed decimal (`-5`, `+1.5`) are written as they are.\n * Pass `false` to write such text unchanged. Defaults to `true`.\n */\n escapeFormulas?: boolean;\n}\n\n/**\n * Describes an export format offered by the grid's toolbar menu.\n *\n * @remarks\n * The toolbar renders one menu item per entry returned by\n * {@link ApexGrid.exportFormats} and dispatches the chosen `id` to\n * {@link ApexGrid.exportAs}. The community grid offers `'csv'`; derived grids\n * (e.g. `apex-grid-enterprise`) extend the list (e.g. with `'xlsx'`).\n */\nexport interface ExportFormat {\n /** Stable format identifier, e.g. `'csv'`. */\n id: string;\n /** Menu item label, e.g. `'Export CSV'`. */\n label: string;\n}\n\n/**\n * Resolves the columns that should appear in the export, in display order.\n */\nexport function resolveExportColumns<T extends object>(\n grid: ApexGrid<T>,\n opts: ExportOptions<T>\n): Array<ColumnConfiguration<T>> {\n const all = getDisplayColumns(grid.columns).filter(\n (column) => !column.hidden && column.exportable !== false\n );\n if (!opts.columns?.length) return all;\n const keys = new Set(opts.columns as Keys<T>[]);\n return all.filter((column) => keys.has(column.key));\n}\n\n/**\n * Resolves the rows that should appear in the export. The `view` and `page`\n * sources hold only data records: rows a feature module synthesized into the\n * view (group headers, a grand-total row) are dropped, see\n * {@link StateController.isDataRow}. `view` also writes the leaves of\n * collapsed groups ({@link StateController.viewRecords}); `page` writes what\n * the page shows.\n */\nexport function resolveExportRows<T extends object>(\n grid: ApexGrid<T>,\n source: ExportSource = 'view'\n): ReadonlyArray<T> {\n switch (source) {\n case 'all':\n return grid.data;\n case 'page':\n return stateOf(grid)?.dataRows(grid.pageItems) ?? grid.pageItems;\n case 'selected': {\n // Selection is a Set in the order rows were picked; write the rows the\n // way the screen lists them, then any the view hides (filtered out or\n // under a collapsed parent).\n const selected = new Set(grid.selectedRows);\n const inView = viewRows(grid).filter((row) => selected.delete(row));\n return [...inView, ...selected];\n }\n default:\n return viewRows(grid);\n }\n}\n\nfunction viewRows<T extends object>(grid: ApexGrid<T>): ReadonlyArray<T> {\n return stateOf(grid)?.viewRecords(grid.dataView) ?? grid.dataView;\n}\n\nfunction stateOf<T extends object>(grid: ApexGrid<T>): StateController<T> | undefined {\n return (grid as unknown as { stateController?: StateController<T> }).stateController;\n}\n\n/**\n * Returns the column's display label, used as the header cell value.\n */\nexport function getColumnLabel<T extends object>(column: ColumnConfiguration<T>): string {\n return column.headerText ?? String(column.key);\n}\n\n/**\n * Default cell-value formatter. Mirrors the on-screen rendering for each\n * column type while keeping primitive types primitive (so number/boolean/date\n * survive into XLSX as native cells instead of strings).\n */\nexport function defaultExportFormat<T extends object>(\n column: ColumnConfiguration<T>,\n value: unknown\n): ExportCellValue {\n if (value === null || value === undefined) return '';\n if (isNumericColumn(column)) {\n const n = Number(value);\n return Number.isFinite(n) ? n : '';\n }\n switch (column.type) {\n case 'select': {\n const found = column.options?.find((option) => {\n if (option && typeof option === 'object' && 'value' in option) {\n return (option as { value: unknown }).value === value;\n }\n return option === value;\n });\n if (found && typeof found === 'object' && 'label' in found) {\n const label = (found as { label?: unknown }).label;\n if (typeof label === 'string' && label.length > 0) return label;\n }\n return typeof value === 'number' || typeof value === 'boolean' ? value : String(value);\n }\n // The cell's own parse: a 'YYYY-MM-DD' string is the local date the cell\n // shows (not UTC midnight), and an invalid Date is empty, as on screen.\n // Text that is no date stays text.\n case 'date':\n return parseDate(value) ?? (typeof value === 'object' ? '' : String(value));\n // Checked only for `true`, like the cell's checkbox and the filter.\n case 'boolean':\n return value === true;\n default:\n return typeof value === 'number' || typeof value === 'boolean' ? value : String(value);\n }\n}\n\n/**\n * Resolves the export value for a single cell, applying the user-supplied\n * formatter when present and falling back to {@link defaultExportFormat}.\n */\nexport function resolveExportValue<T extends object>(\n column: ColumnConfiguration<T>,\n row: T,\n opts: ExportOptions<T>\n): ExportCellValue {\n const raw = (row as Record<string, unknown>)[column.key as string];\n const value = opts.formatter\n ? opts.formatter(column, raw, row)\n : defaultExportFormat(column, raw);\n // An invalid Date from a user formatter has no text or serial to write.\n return value instanceof Date && Number.isNaN(value.getTime()) ? '' : value;\n}\n\n/** Formats a `Date` into the configured date style for CSV output. */\nexport function formatDateForCSV<T extends object>(\n column: ColumnConfiguration<T>,\n value: Date\n): string {\n return Number.isNaN(value.getTime()) ? '' : dateFormatter(column.format).format(value);\n}\n\n/**\n * A cell value as CSV export writes it, before escaping: a date in the\n * column's date style, a select option's label, a number as a number, and\n * blank for no value. Chart category labels read the same text.\n */\nexport function formatDisplayText<T extends object>(\n column: ColumnConfiguration<T>,\n value: unknown\n): string {\n const text = defaultExportFormat(column, value);\n return text instanceof Date ? formatDateForCSV(column, text) : String(text ?? '');\n}\n\n/** Text a spreadsheet would evaluate: a formula trigger first, not a signed decimal. */\nconst FORMULA_LEADING = /^(?![+-]\\d*\\.?\\d+$)[=+\\-@\\t\\r]/;\n\n/**\n * Prefixes an apostrophe to text a spreadsheet would read as a formula (it\n * starts with `=`, `+`, `-`, `@`, a tab or a carriage return and is not a\n * plain signed decimal), the usual way to keep it text. Any other value is\n * returned as is, a boxed `String` included: an export formatter returns one\n * to mark text it means to keep raw, such as a formula source. CSV export\n * and clipboard copy share this rule.\n */\nexport function escapeFormula<V>(value: V): V | string {\n return typeof value === 'string' && FORMULA_LEADING.test(value) ? `'${value}` : value;\n}\n\n/**\n * Quotes one delimited-text field when it holds the delimiter, a double quote\n * or a line break (doubling inner quotes), the convention CSV files and\n * spreadsheet clipboards share. Blank values become an empty field.\n */\nexport function escapeCSVCell(value: ExportCellValue, delimiter: string): string {\n if (value === null || value === undefined || value === '') return '';\n const text = value instanceof Date ? value.toISOString() : String(value);\n const needsQuoting =\n text.includes(delimiter) || text.includes('\"') || text.includes('\\n') || text.includes('\\r');\n return needsQuoting ? `\"${text.replace(/\"/g, '\"\"')}\"` : text;\n}\n\n/**\n * Builds the CSV string for the grid without triggering a download.\n *\n * @remarks\n * Useful when consumers want to ship the bytes through their own transport\n * (clipboard, HTTP upload). The {@link ApexGrid.exportToCSV} method calls\n * this and then triggers a browser download.\n */\nexport function buildCSV<T extends object>(\n grid: ApexGrid<T>,\n opts: CSVExportOptions<T> = {}\n): string {\n const delimiter = opts.delimiter ?? ',';\n const newline = opts.newline ?? '\\r\\n';\n const includeHeader = opts.includeHeader ?? true;\n const cell = (value: ExportCellValue) =>\n escapeCSVCell(opts.escapeFormulas === false ? value : escapeFormula(value), delimiter);\n const columns = resolveExportColumns(grid, opts);\n const rows = resolveExportRows(grid, opts.source);\n\n const lines: string[] = [];\n if (includeHeader) {\n lines.push(columns.map((column) => cell(getColumnLabel(column))).join(delimiter));\n }\n for (const row of rows) {\n const cells = columns.map((column) => {\n let value = resolveExportValue(column, row, opts);\n if (value instanceof Date) {\n value = formatDateForCSV(column, value);\n }\n return cell(value);\n });\n lines.push(cells.join(delimiter));\n }\n\n const body = lines.join(newline);\n return opts.bom === false ? body : `\\uFEFF${body}`;\n}\n\n/**\n * Triggers a browser download for the given payload.\n *\n * @remarks\n * Falls back to a no-op when called outside the browser (during SSR or unit\n * tests on JSDOM without a body), so consumers can still call the public\n * export API safely.\n */\nexport function downloadBlob(\n filename: string,\n content: string | Uint8Array,\n mimeType: string\n): void {\n if (typeof document === 'undefined' || typeof URL?.createObjectURL !== 'function') return;\n // BlobPart accepts both string and Uint8Array; the cast quiets TS' strict\n // overload resolution which prefers ArrayBufferView only.\n const blob = new Blob([content as BlobPart], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = filename;\n anchor.style.display = 'none';\n document.body.appendChild(anchor);\n anchor.click();\n document.body.removeChild(anchor);\n setTimeout(() => URL.revokeObjectURL(url), 0);\n}\n"]}
|
|
@@ -112,6 +112,11 @@ export interface PresentedRow {
|
|
|
112
112
|
*/
|
|
113
113
|
export interface RowPresenter<T extends object = any> {
|
|
114
114
|
presentRow(row: T, ctx: RowPresenterContext<T>): PresentedRow | null;
|
|
115
|
+
/**
|
|
116
|
+
* True while the rows this module presents carry `aria-level` or
|
|
117
|
+
* `aria-expanded`, so the grid takes the `treegrid` role that supports them.
|
|
118
|
+
*/
|
|
119
|
+
readonly hierarchical?: boolean;
|
|
115
120
|
}
|
|
116
121
|
/** Runtime type-guard for {@link RowPresenter}. */
|
|
117
122
|
export declare function isRowPresenter<T extends object>(controller: unknown): controller is RowPresenter<T>;
|
|
@@ -134,6 +139,8 @@ export interface RowAria {
|
|
|
134
139
|
*/
|
|
135
140
|
export interface RowAriaProvider<T extends object = any> {
|
|
136
141
|
describeRow(row: T, ctx: RowPresenterContext<T>): RowAria | null;
|
|
142
|
+
/** As {@link RowPresenter.hierarchical}: true while {@link describeRow} gives rows tree ARIA. */
|
|
143
|
+
readonly hierarchical?: boolean;
|
|
137
144
|
}
|
|
138
145
|
/** Runtime type-guard for {@link RowAriaProvider}. */
|
|
139
146
|
export declare function isRowAriaProvider<T extends object>(controller: unknown): controller is RowAriaProvider<T>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-module.js","sourceRoot":"","sources":["../../src/internal/feature-module.ts"],"names":[],"mappings":"AAsFA,MAAM,UAAU,gBAAgB,CAC9B,UAAmB;IAEnB,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAAgC,CAAC,WAAW,KAAK,UAAU,CACpE,CAAC;AACJ,CAAC;AA2CD,MAAM,UAAU,cAAc,CAC5B,UAAmB;IAEnB,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAA8B,CAAC,UAAU,KAAK,UAAU,CACjE,CAAC;AACJ,CAAC;AAyBD,MAAM,UAAU,iBAAiB,CAC/B,UAAmB;IAEnB,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAAiC,CAAC,WAAW,KAAK,UAAU,CACrE,CAAC;AACJ,CAAC;AAqBD,MAAM,UAAU,mBAAmB,CACjC,UAAmB;IAEnB,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAAmC,CAAC,aAAa,KAAK,UAAU;QACxE,OAAQ,UAAmC,CAAC,gBAAgB,KAAK,UAAU,CAC5E,CAAC;AACJ,CAAC;AAmDD,MAAM,UAAU,eAAe,CAC7B,UAAmB;IAEnB,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAA+B,CAAC,YAAY,KAAK,UAAU,CACpE,CAAC;AACJ,CAAC;AAmBD,MAAM,UAAU,oBAAoB,CAAC,UAAmB;IACtD,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAAiC,CAAC,kBAAkB,KAAK,UAAU,CAC5E,CAAC;AACJ,CAAC;AAoCD,MAAM,UAAU,wBAAwB,CACtC,UAAmB;IAEnB,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAAwC,CAAC,qBAAqB,KAAK,UAAU,CACtF,CAAC;AACJ,CAAC;AAgCD,MAAM,UAAU,oBAAoB,CAAC,UAAmB;IACtD,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAAiC,CAAC,cAAc,KAAK,UAAU;QACvE,OAAQ,UAAiC,CAAC,YAAY,KAAK,UAAU,CACtE,CAAC;AACJ,CAAC","sourcesContent":["import type { ReactiveController, TemplateResult } from 'lit';\n// Type-only import — erased at runtime, so this does not create a runtime\n// import cycle with `controllers/state.ts` (which imports the guards below).\nimport type { StateController } from '../controllers/state.js';\nimport type { ColumnConfiguration, GridHost } from './types.js';\n\n/**\n * Extension point for layering optional features onto the grid without baking\n * them into the community build.\n *\n * A feature module contributes a {@link ReactiveController} that is constructed\n * alongside the grid's built-in controllers (see {@link StateController}). It is\n * the seam used by the first-party `@apexcharts/grid-enterprise` package; it is\n * intentionally minimal and additive so the community `<apex-grid>` element is\n * unaffected (it registers zero modules).\n *\n * @remarks This API lives under the unstable `apex-grid/internal` entry point.\n */\nexport interface GridFeatureModule<T extends object = any> {\n /**\n * Stable identifier, e.g. `'grouping'`. Used to de-duplicate modules and to\n * look the controller up later via {@link StateController.module}.\n */\n readonly id: string;\n\n /**\n * Construct the feature's controller. The controller is expected to register\n * itself with the host (typically `host.addController(this)` in its\n * constructor), exactly like the built-in controllers.\n *\n * @param host The grid host the controller drives.\n * @param state The owning {@link StateController}, passed so a module can\n * coordinate with cross-cutting grid state (e.g. request a cell-decoration\n * refresh via {@link StateController.bumpDecoration} or look up a peer\n * module). Modules that don't need it may ignore the argument.\n */\n create(host: GridHost<T>, state: StateController<T>): ReactiveController;\n}\n\n/**\n * Optional capability a feature module's controller may implement to transform\n * the row set *after* the built-in pipeline (filter → sort → tree) and *before*\n * pagination is sliced at render time. Lets a module inject or reorder rows —\n * e.g. synthesized group-header rows — in the rendered `dataView`.\n *\n * Controllers that don't implement this are skipped, so the community grid\n * (which registers no modules) is unaffected.\n *\n * Transforms run in a fixed order set by {@link transformOrder}, not in the\n * order modules were registered, because they do not commute (a grand-total\n * row appended before grouping would itself be grouped).\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface RowTransformer<T extends object = any> {\n /** Return the transformed rows. Must not mutate the input array in place. */\n processRows(rows: ReadonlyArray<T>): T[];\n\n /**\n * Position of this transform in the module chain: lower runs first, ties keep\n * registration order, unset means `0`. First-party values: grouping `100`,\n * pivot `200`, grand-total row `1000`. Transforms that restructure the view\n * come first; rows that summarise the finished view come last. Leaving it\n * unset (`0`) runs the transform before grouping.\n */\n readonly transformOrder?: number;\n\n /**\n * Whether `row` is one this transform synthesized (a group header, a total\n * row) rather than a data record. Synthesized rows are kept out of row\n * selection and export (see {@link StateController.isDataRow}). Leave it\n * unset when the transform's output rows are the view's records (pivot).\n */\n isSyntheticRow?(row: T): boolean;\n\n /**\n * The data records a synthesized `row` stands for that the view does not\n * also hold, in view order: a collapsed group header's leaves. Return an\n * empty array when they are in the view too (an expanded group) or the row\n * stands for none (a total row). Lets Select all and a `'view'` export\n * cover collapsed groups (see {@link StateController.viewRecords}).\n */\n hiddenRecords?(row: T): ReadonlyArray<T>;\n}\n\n/** Runtime type-guard for {@link RowTransformer}. */\nexport function isRowTransformer<T extends object>(\n controller: unknown\n): controller is RowTransformer<T> {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as RowTransformer<T>).processRows === 'function'\n );\n}\n\n/** Per-row context handed to a {@link RowPresenter}. */\nexport interface RowPresenterContext<T extends object = any> {\n /** The grid's visible columns (post-reorder/visibility). */\n readonly columns: ReadonlyArray<ColumnConfiguration<T>>;\n /** Index of the row within the current page/view. */\n readonly rowIndex: number;\n}\n\n/**\n * A module-rendered, full-width row. Rendered inside a container that spans all\n * columns (`grid-column: 1 / -1`), mirroring the master-detail panel pattern.\n */\nexport interface PresentedRow {\n /** Template rendered inside the full-width container. */\n readonly content: TemplateResult;\n /** Optional 1-based `aria-level` applied to the row element. */\n readonly level?: number;\n /** Optional `aria-expanded` for the row element; omit for non-expandable rows. */\n readonly expanded?: boolean;\n /** Shadow part for the container element; defaults to `group-row`. */\n readonly part?: string;\n /**\n * Parts used inside {@link content} that the row should re-export, so\n * `apex-grid::part(name)` can style them. The container {@link part} is\n * exported without being listed here.\n */\n readonly exportParts?: ReadonlyArray<string>;\n}\n\n/**\n * Optional capability a feature module's controller may implement to render\n * specific rows as full-width custom content (e.g. group headers) instead of\n * the normal cell grid. Returns `null` for rows the module does not own.\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface RowPresenter<T extends object = any> {\n presentRow(row: T, ctx: RowPresenterContext<T>): PresentedRow | null;\n}\n\n/** Runtime type-guard for {@link RowPresenter}. */\nexport function isRowPresenter<T extends object>(\n controller: unknown\n): controller is RowPresenter<T> {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as RowPresenter<T>).presentRow === 'function'\n );\n}\n\n/** ARIA tree/expansion semantics a module can attach to a normal (cell-rendered) row. */\nexport interface RowAria {\n /** 1-based `aria-level` for the row element. */\n readonly level?: number;\n /** `aria-expanded` for the row element; omit for non-expandable rows. */\n readonly expanded?: boolean;\n}\n\n/**\n * Optional capability a module's controller may implement to attach ARIA\n * tree/expansion semantics (`aria-level` / `aria-expanded`) to rows that still\n * render as a normal cell grid — unlike {@link RowPresenter}, which replaces the\n * cells with full-width content. Used by pivoting's expandable row groups, whose\n * parent rows must keep showing per-column aggregate values. Returns `null` for\n * rows the module does not own.\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface RowAriaProvider<T extends object = any> {\n describeRow(row: T, ctx: RowPresenterContext<T>): RowAria | null;\n}\n\n/** Runtime type-guard for {@link RowAriaProvider}. */\nexport function isRowAriaProvider<T extends object>(\n controller: unknown\n): controller is RowAriaProvider<T> {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as RowAriaProvider<T>).describeRow === 'function'\n );\n}\n\n/**\n * Optional capability a module's controller may implement to label the\n * spreadsheet coordinate hints (`coordinateHints`): the row-number gutter and\n * the column letters. A module whose references address the data rather than\n * the view (the enterprise formula engine) answers with the coordinates a\n * reference reads, so the hints stay true through sort, filter, paging, hidden\n * columns and column moves. Return `null` for no label, or `undefined` to leave\n * the default (view position) in place.\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface CoordinateLabeler<T extends object = any> {\n /** The gutter label for a row. */\n rowCoordinate(row: T): string | null | undefined;\n /** The letter label for a column key. */\n columnCoordinate(key: string): string | null | undefined;\n}\n\n/** Runtime type-guard for {@link CoordinateLabeler}. */\nexport function isCoordinateLabeler<T extends object>(\n controller: unknown\n): controller is CoordinateLabeler<T> {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as CoordinateLabeler<T>).rowCoordinate === 'function' &&\n typeof (controller as CoordinateLabeler<T>).columnCoordinate === 'function'\n );\n}\n\n/** Per-cell context handed to a {@link CellDecorator}. */\nexport interface CellDecoratorContext<T extends object = any> {\n /** The row's backing data (or a synthesized row object). */\n readonly row: T;\n /**\n * Index of the row within the current page/view (matches `host.pageItems`),\n * or `-1` for a row rendered in a pinned band, which is outside that view.\n * Key pinned-row state by {@link row} instead.\n */\n readonly rowIndex: number;\n /** The cell's column configuration. */\n readonly column: ColumnConfiguration<T>;\n /** The cell's resolved value (`row[column.key]`). */\n readonly value: unknown;\n}\n\n/**\n * A set of host attributes a module asks the cell to reflect on itself. This is\n * the only thing a {@link CellDecorator} returns — styling is expressed by\n * keying CSS off these attributes (the cell ships an inert, variable-driven\n * `:host([data-range])` style; enterprise themes it via inheriting custom\n * properties).\n */\nexport interface CellDecoration {\n /**\n * Attributes to set on the decorated cell host. Use a `data-*` namespace\n * (e.g. `data-range`) so decoration never collides with the cell's built-in\n * reflected attributes (`active`, `editing`, `data-pinned`, …). A `null`/\n * `undefined` value removes the attribute. Attributes set on a previous\n * update that are absent here are removed automatically.\n */\n readonly attributes: Readonly<Record<string, string | null | undefined>>;\n}\n\n/**\n * Optional capability a feature module's controller may implement to decorate\n * individual cells with extra host attributes during the cell's own render\n * lifecycle — e.g. flagging the cells inside a selected range. Consulted per\n * cell via {@link StateController.decorateCell}; controllers that don't\n * implement it are skipped, so the community grid is unaffected.\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface CellDecorator<T extends object = any> {\n /** Return decoration for this cell, or `null` to leave it undecorated. */\n decorateCell(ctx: CellDecoratorContext<T>): CellDecoration | null;\n}\n\n/** Runtime type-guard for {@link CellDecorator}. */\nexport function isCellDecorator<T extends object>(\n controller: unknown\n): controller is CellDecorator<T> {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as CellDecorator<T>).decorateCell === 'function'\n );\n}\n\n/**\n * Optional capability a feature module's controller may implement to signal that\n * it provides a **column header menu** (opened from the header's kebab button).\n * The core header shows the kebab whenever a column is sortable or resizable, or\n * when a module reports `true` here, so a feature (e.g. the enterprise context\n * menu) can surface pin / hide / group / chart actions on any column. The module\n * owns the menu content itself (it opens on the `apex-grid-column-menu` event the\n * header dispatches). Inert for the community grid, which registers no modules.\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface ColumnMenuProvider {\n /** Whether the column-menu (kebab) button should be offered for columns. */\n providesColumnMenu(): boolean;\n}\n\n/** Runtime type-guard for {@link ColumnMenuProvider}. */\nexport function isColumnMenuProvider(controller: unknown): controller is ColumnMenuProvider {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as ColumnMenuProvider).providesColumnMenu === 'function'\n );\n}\n\n/** The kind of pointer interaction forwarded from a body cell. */\nexport type CellInteractionKind = 'down' | 'over' | 'up';\n\n/** A pointer interaction on a body cell, forwarded to feature modules. */\nexport interface CellInteraction<T extends object = any> {\n /** `down` on press, `over` while the pointer moves across cells, `up` on release. */\n readonly kind: CellInteractionKind;\n /** The cell's row data. */\n readonly row: T;\n /** Index of the row within the current page/view (matches `host.pageItems`). */\n readonly rowIndex: number;\n /** The cell's column configuration. */\n readonly column: ColumnConfiguration<T>;\n readonly shiftKey: boolean;\n readonly ctrlKey: boolean;\n readonly metaKey: boolean;\n /** The originating pointer event (e.g. to call `preventDefault`). */\n readonly originalEvent: PointerEvent;\n}\n\n/**\n * Optional capability a feature module's controller may implement to receive\n * pointer interactions on body cells (press / drag-over / release) — the seam a\n * range-selection feature uses to track a drag. The grid forwards interactions\n * only when at least one feature module is registered, so the community grid\n * pays nothing.\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface CellInteractionHandler<T extends object = any> {\n handleCellInteraction(interaction: CellInteraction<T>): void;\n}\n\n/** Runtime type-guard for {@link CellInteractionHandler}. */\nexport function isCellInteractionHandler<T extends object>(\n controller: unknown\n): controller is CellInteractionHandler<T> {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as CellInteractionHandler<T>).handleCellInteraction === 'function'\n );\n}\n\n/**\n * Optional capability a feature module's controller may implement so its state\n * participates in grid-state snapshots ({@link StateController.serializeModuleState}\n * / {@link StateController.restoreModuleState}, driven by `ApexGrid.getState` /\n * `ApexGrid.setState`). The grid collects each implementer's\n * {@link serializeState} output under the module's id and dispatches the\n * matching value back via {@link restoreState} on restore. Ids with no\n * implementer are reported back by {@link StateController.restoreModuleState}.\n *\n * Controllers that don't implement this are skipped, so the community grid\n * (which registers no modules) contributes nothing to a snapshot.\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface SerializableModule {\n /**\n * Return a JSON-serializable snapshot of this module's restorable state\n * (e.g. the options the feature was configured with). Exclude transient/derived\n * state and anything not JSON-safe (functions, DOM nodes).\n */\n serializeState(): unknown;\n /**\n * Restore module state from a value previously produced by\n * {@link serializeState}. Implementations should tolerate partial or\n * unexpected shapes (validate before applying) since the blob is opaque.\n */\n restoreState(data: unknown): void;\n}\n\n/** Runtime type-guard for {@link SerializableModule}. */\nexport function isSerializableModule(controller: unknown): controller is SerializableModule {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as SerializableModule).serializeState === 'function' &&\n typeof (controller as SerializableModule).restoreState === 'function'\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"feature-module.js","sourceRoot":"","sources":["../../src/internal/feature-module.ts"],"names":[],"mappings":"AAsFA,MAAM,UAAU,gBAAgB,CAC9B,UAAmB;IAEnB,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAAgC,CAAC,WAAW,KAAK,UAAU,CACpE,CAAC;AACJ,CAAC;AAgDD,MAAM,UAAU,cAAc,CAC5B,UAAmB;IAEnB,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAA8B,CAAC,UAAU,KAAK,UAAU,CACjE,CAAC;AACJ,CAAC;AA2BD,MAAM,UAAU,iBAAiB,CAC/B,UAAmB;IAEnB,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAAiC,CAAC,WAAW,KAAK,UAAU,CACrE,CAAC;AACJ,CAAC;AAqBD,MAAM,UAAU,mBAAmB,CACjC,UAAmB;IAEnB,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAAmC,CAAC,aAAa,KAAK,UAAU;QACxE,OAAQ,UAAmC,CAAC,gBAAgB,KAAK,UAAU,CAC5E,CAAC;AACJ,CAAC;AAmDD,MAAM,UAAU,eAAe,CAC7B,UAAmB;IAEnB,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAA+B,CAAC,YAAY,KAAK,UAAU,CACpE,CAAC;AACJ,CAAC;AAmBD,MAAM,UAAU,oBAAoB,CAAC,UAAmB;IACtD,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAAiC,CAAC,kBAAkB,KAAK,UAAU,CAC5E,CAAC;AACJ,CAAC;AAoCD,MAAM,UAAU,wBAAwB,CACtC,UAAmB;IAEnB,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAAwC,CAAC,qBAAqB,KAAK,UAAU,CACtF,CAAC;AACJ,CAAC;AAgCD,MAAM,UAAU,oBAAoB,CAAC,UAAmB;IACtD,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,OAAQ,UAAiC,CAAC,cAAc,KAAK,UAAU;QACvE,OAAQ,UAAiC,CAAC,YAAY,KAAK,UAAU,CACtE,CAAC;AACJ,CAAC","sourcesContent":["import type { ReactiveController, TemplateResult } from 'lit';\n// Type-only import — erased at runtime, so this does not create a runtime\n// import cycle with `controllers/state.ts` (which imports the guards below).\nimport type { StateController } from '../controllers/state.js';\nimport type { ColumnConfiguration, GridHost } from './types.js';\n\n/**\n * Extension point for layering optional features onto the grid without baking\n * them into the community build.\n *\n * A feature module contributes a {@link ReactiveController} that is constructed\n * alongside the grid's built-in controllers (see {@link StateController}). It is\n * the seam used by the first-party `@apexcharts/grid-enterprise` package; it is\n * intentionally minimal and additive so the community `<apex-grid>` element is\n * unaffected (it registers zero modules).\n *\n * @remarks This API lives under the unstable `apex-grid/internal` entry point.\n */\nexport interface GridFeatureModule<T extends object = any> {\n /**\n * Stable identifier, e.g. `'grouping'`. Used to de-duplicate modules and to\n * look the controller up later via {@link StateController.module}.\n */\n readonly id: string;\n\n /**\n * Construct the feature's controller. The controller is expected to register\n * itself with the host (typically `host.addController(this)` in its\n * constructor), exactly like the built-in controllers.\n *\n * @param host The grid host the controller drives.\n * @param state The owning {@link StateController}, passed so a module can\n * coordinate with cross-cutting grid state (e.g. request a cell-decoration\n * refresh via {@link StateController.bumpDecoration} or look up a peer\n * module). Modules that don't need it may ignore the argument.\n */\n create(host: GridHost<T>, state: StateController<T>): ReactiveController;\n}\n\n/**\n * Optional capability a feature module's controller may implement to transform\n * the row set *after* the built-in pipeline (filter → sort → tree) and *before*\n * pagination is sliced at render time. Lets a module inject or reorder rows —\n * e.g. synthesized group-header rows — in the rendered `dataView`.\n *\n * Controllers that don't implement this are skipped, so the community grid\n * (which registers no modules) is unaffected.\n *\n * Transforms run in a fixed order set by {@link transformOrder}, not in the\n * order modules were registered, because they do not commute (a grand-total\n * row appended before grouping would itself be grouped).\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface RowTransformer<T extends object = any> {\n /** Return the transformed rows. Must not mutate the input array in place. */\n processRows(rows: ReadonlyArray<T>): T[];\n\n /**\n * Position of this transform in the module chain: lower runs first, ties keep\n * registration order, unset means `0`. First-party values: grouping `100`,\n * pivot `200`, grand-total row `1000`. Transforms that restructure the view\n * come first; rows that summarise the finished view come last. Leaving it\n * unset (`0`) runs the transform before grouping.\n */\n readonly transformOrder?: number;\n\n /**\n * Whether `row` is one this transform synthesized (a group header, a total\n * row) rather than a data record. Synthesized rows are kept out of row\n * selection and export (see {@link StateController.isDataRow}). Leave it\n * unset when the transform's output rows are the view's records (pivot).\n */\n isSyntheticRow?(row: T): boolean;\n\n /**\n * The data records a synthesized `row` stands for that the view does not\n * also hold, in view order: a collapsed group header's leaves. Return an\n * empty array when they are in the view too (an expanded group) or the row\n * stands for none (a total row). Lets Select all and a `'view'` export\n * cover collapsed groups (see {@link StateController.viewRecords}).\n */\n hiddenRecords?(row: T): ReadonlyArray<T>;\n}\n\n/** Runtime type-guard for {@link RowTransformer}. */\nexport function isRowTransformer<T extends object>(\n controller: unknown\n): controller is RowTransformer<T> {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as RowTransformer<T>).processRows === 'function'\n );\n}\n\n/** Per-row context handed to a {@link RowPresenter}. */\nexport interface RowPresenterContext<T extends object = any> {\n /** The grid's visible columns (post-reorder/visibility). */\n readonly columns: ReadonlyArray<ColumnConfiguration<T>>;\n /** Index of the row within the current page/view. */\n readonly rowIndex: number;\n}\n\n/**\n * A module-rendered, full-width row. Rendered inside a container that spans all\n * columns (`grid-column: 1 / -1`), mirroring the master-detail panel pattern.\n */\nexport interface PresentedRow {\n /** Template rendered inside the full-width container. */\n readonly content: TemplateResult;\n /** Optional 1-based `aria-level` applied to the row element. */\n readonly level?: number;\n /** Optional `aria-expanded` for the row element; omit for non-expandable rows. */\n readonly expanded?: boolean;\n /** Shadow part for the container element; defaults to `group-row`. */\n readonly part?: string;\n /**\n * Parts used inside {@link content} that the row should re-export, so\n * `apex-grid::part(name)` can style them. The container {@link part} is\n * exported without being listed here.\n */\n readonly exportParts?: ReadonlyArray<string>;\n}\n\n/**\n * Optional capability a feature module's controller may implement to render\n * specific rows as full-width custom content (e.g. group headers) instead of\n * the normal cell grid. Returns `null` for rows the module does not own.\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface RowPresenter<T extends object = any> {\n presentRow(row: T, ctx: RowPresenterContext<T>): PresentedRow | null;\n /**\n * True while the rows this module presents carry `aria-level` or\n * `aria-expanded`, so the grid takes the `treegrid` role that supports them.\n */\n readonly hierarchical?: boolean;\n}\n\n/** Runtime type-guard for {@link RowPresenter}. */\nexport function isRowPresenter<T extends object>(\n controller: unknown\n): controller is RowPresenter<T> {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as RowPresenter<T>).presentRow === 'function'\n );\n}\n\n/** ARIA tree/expansion semantics a module can attach to a normal (cell-rendered) row. */\nexport interface RowAria {\n /** 1-based `aria-level` for the row element. */\n readonly level?: number;\n /** `aria-expanded` for the row element; omit for non-expandable rows. */\n readonly expanded?: boolean;\n}\n\n/**\n * Optional capability a module's controller may implement to attach ARIA\n * tree/expansion semantics (`aria-level` / `aria-expanded`) to rows that still\n * render as a normal cell grid — unlike {@link RowPresenter}, which replaces the\n * cells with full-width content. Used by pivoting's expandable row groups, whose\n * parent rows must keep showing per-column aggregate values. Returns `null` for\n * rows the module does not own.\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface RowAriaProvider<T extends object = any> {\n describeRow(row: T, ctx: RowPresenterContext<T>): RowAria | null;\n /** As {@link RowPresenter.hierarchical}: true while {@link describeRow} gives rows tree ARIA. */\n readonly hierarchical?: boolean;\n}\n\n/** Runtime type-guard for {@link RowAriaProvider}. */\nexport function isRowAriaProvider<T extends object>(\n controller: unknown\n): controller is RowAriaProvider<T> {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as RowAriaProvider<T>).describeRow === 'function'\n );\n}\n\n/**\n * Optional capability a module's controller may implement to label the\n * spreadsheet coordinate hints (`coordinateHints`): the row-number gutter and\n * the column letters. A module whose references address the data rather than\n * the view (the enterprise formula engine) answers with the coordinates a\n * reference reads, so the hints stay true through sort, filter, paging, hidden\n * columns and column moves. Return `null` for no label, or `undefined` to leave\n * the default (view position) in place.\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface CoordinateLabeler<T extends object = any> {\n /** The gutter label for a row. */\n rowCoordinate(row: T): string | null | undefined;\n /** The letter label for a column key. */\n columnCoordinate(key: string): string | null | undefined;\n}\n\n/** Runtime type-guard for {@link CoordinateLabeler}. */\nexport function isCoordinateLabeler<T extends object>(\n controller: unknown\n): controller is CoordinateLabeler<T> {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as CoordinateLabeler<T>).rowCoordinate === 'function' &&\n typeof (controller as CoordinateLabeler<T>).columnCoordinate === 'function'\n );\n}\n\n/** Per-cell context handed to a {@link CellDecorator}. */\nexport interface CellDecoratorContext<T extends object = any> {\n /** The row's backing data (or a synthesized row object). */\n readonly row: T;\n /**\n * Index of the row within the current page/view (matches `host.pageItems`),\n * or `-1` for a row rendered in a pinned band, which is outside that view.\n * Key pinned-row state by {@link row} instead.\n */\n readonly rowIndex: number;\n /** The cell's column configuration. */\n readonly column: ColumnConfiguration<T>;\n /** The cell's resolved value (`row[column.key]`). */\n readonly value: unknown;\n}\n\n/**\n * A set of host attributes a module asks the cell to reflect on itself. This is\n * the only thing a {@link CellDecorator} returns — styling is expressed by\n * keying CSS off these attributes (the cell ships an inert, variable-driven\n * `:host([data-range])` style; enterprise themes it via inheriting custom\n * properties).\n */\nexport interface CellDecoration {\n /**\n * Attributes to set on the decorated cell host. Use a `data-*` namespace\n * (e.g. `data-range`) so decoration never collides with the cell's built-in\n * reflected attributes (`active`, `editing`, `data-pinned`, …). A `null`/\n * `undefined` value removes the attribute. Attributes set on a previous\n * update that are absent here are removed automatically.\n */\n readonly attributes: Readonly<Record<string, string | null | undefined>>;\n}\n\n/**\n * Optional capability a feature module's controller may implement to decorate\n * individual cells with extra host attributes during the cell's own render\n * lifecycle — e.g. flagging the cells inside a selected range. Consulted per\n * cell via {@link StateController.decorateCell}; controllers that don't\n * implement it are skipped, so the community grid is unaffected.\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface CellDecorator<T extends object = any> {\n /** Return decoration for this cell, or `null` to leave it undecorated. */\n decorateCell(ctx: CellDecoratorContext<T>): CellDecoration | null;\n}\n\n/** Runtime type-guard for {@link CellDecorator}. */\nexport function isCellDecorator<T extends object>(\n controller: unknown\n): controller is CellDecorator<T> {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as CellDecorator<T>).decorateCell === 'function'\n );\n}\n\n/**\n * Optional capability a feature module's controller may implement to signal that\n * it provides a **column header menu** (opened from the header's kebab button).\n * The core header shows the kebab whenever a column is sortable or resizable, or\n * when a module reports `true` here, so a feature (e.g. the enterprise context\n * menu) can surface pin / hide / group / chart actions on any column. The module\n * owns the menu content itself (it opens on the `apex-grid-column-menu` event the\n * header dispatches). Inert for the community grid, which registers no modules.\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface ColumnMenuProvider {\n /** Whether the column-menu (kebab) button should be offered for columns. */\n providesColumnMenu(): boolean;\n}\n\n/** Runtime type-guard for {@link ColumnMenuProvider}. */\nexport function isColumnMenuProvider(controller: unknown): controller is ColumnMenuProvider {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as ColumnMenuProvider).providesColumnMenu === 'function'\n );\n}\n\n/** The kind of pointer interaction forwarded from a body cell. */\nexport type CellInteractionKind = 'down' | 'over' | 'up';\n\n/** A pointer interaction on a body cell, forwarded to feature modules. */\nexport interface CellInteraction<T extends object = any> {\n /** `down` on press, `over` while the pointer moves across cells, `up` on release. */\n readonly kind: CellInteractionKind;\n /** The cell's row data. */\n readonly row: T;\n /** Index of the row within the current page/view (matches `host.pageItems`). */\n readonly rowIndex: number;\n /** The cell's column configuration. */\n readonly column: ColumnConfiguration<T>;\n readonly shiftKey: boolean;\n readonly ctrlKey: boolean;\n readonly metaKey: boolean;\n /** The originating pointer event (e.g. to call `preventDefault`). */\n readonly originalEvent: PointerEvent;\n}\n\n/**\n * Optional capability a feature module's controller may implement to receive\n * pointer interactions on body cells (press / drag-over / release) — the seam a\n * range-selection feature uses to track a drag. The grid forwards interactions\n * only when at least one feature module is registered, so the community grid\n * pays nothing.\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface CellInteractionHandler<T extends object = any> {\n handleCellInteraction(interaction: CellInteraction<T>): void;\n}\n\n/** Runtime type-guard for {@link CellInteractionHandler}. */\nexport function isCellInteractionHandler<T extends object>(\n controller: unknown\n): controller is CellInteractionHandler<T> {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as CellInteractionHandler<T>).handleCellInteraction === 'function'\n );\n}\n\n/**\n * Optional capability a feature module's controller may implement so its state\n * participates in grid-state snapshots ({@link StateController.serializeModuleState}\n * / {@link StateController.restoreModuleState}, driven by `ApexGrid.getState` /\n * `ApexGrid.setState`). The grid collects each implementer's\n * {@link serializeState} output under the module's id and dispatches the\n * matching value back via {@link restoreState} on restore. Ids with no\n * implementer are reported back by {@link StateController.restoreModuleState}.\n *\n * Controllers that don't implement this are skipped, so the community grid\n * (which registers no modules) contributes nothing to a snapshot.\n *\n * @remarks Unstable; part of `apex-grid/internal`.\n */\nexport interface SerializableModule {\n /**\n * Return a JSON-serializable snapshot of this module's restorable state\n * (e.g. the options the feature was configured with). Exclude transient/derived\n * state and anything not JSON-safe (functions, DOM nodes).\n */\n serializeState(): unknown;\n /**\n * Restore module state from a value previously produced by\n * {@link serializeState}. Implementations should tolerate partial or\n * unexpected shapes (validate before applying) since the blob is opaque.\n */\n restoreState(data: unknown): void;\n}\n\n/** Runtime type-guard for {@link SerializableModule}. */\nexport function isSerializableModule(controller: unknown): controller is SerializableModule {\n return (\n typeof controller === 'object' &&\n controller !== null &&\n typeof (controller as SerializableModule).serializeState === 'function' &&\n typeof (controller as SerializableModule).restoreState === 'function'\n );\n}\n"]}
|
package/internal/flip.d.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* user has opted out of motion.
|
|
14
|
-
*/
|
|
1
|
+
import type { GridHost } from './types.js';
|
|
2
|
+
/** Animation id of a vertical (row) FLIP. */
|
|
3
|
+
export declare const FLIP_ID_Y = "apex-flip-y";
|
|
4
|
+
/** Animation id of a horizontal (column) FLIP. */
|
|
5
|
+
export declare const FLIP_ID_X = "apex-flip-x";
|
|
6
|
+
/** Animation id of a FLIP that may move on both axes. */
|
|
7
|
+
export declare const FLIP_ID_BOTH = "apex-flip";
|
|
8
|
+
type FlipAxis = 'x' | 'y' | 'both';
|
|
9
|
+
/** Whether any FLIP animation is running right now. */
|
|
10
|
+
export declare function hasFlipsInFlight(): boolean;
|
|
11
|
+
/** Cancels the running FLIP animations with `id` on `element` (not its subtree). */
|
|
12
|
+
export declare function cancelFlips(element: Element, id: string): void;
|
|
15
13
|
/**
|
|
16
14
|
* One element to FLIP, captured at its "first" (pre-mutation) position.
|
|
17
15
|
*/
|
|
@@ -29,7 +27,7 @@ export interface FlipEntry {
|
|
|
29
27
|
* vertical motion (row sort), `'both'` for diagonal motion if either axis
|
|
30
28
|
* could move.
|
|
31
29
|
*/
|
|
32
|
-
export declare function playFlip(entries: ReadonlyArray<FlipEntry>, axis?:
|
|
30
|
+
export declare function playFlip(entries: ReadonlyArray<FlipEntry>, axis?: FlipAxis): void;
|
|
33
31
|
/**
|
|
34
32
|
* Convenience: capture a rect for an element, returning a {@link FlipEntry}.
|
|
35
33
|
*/
|
|
@@ -52,6 +50,14 @@ interface UpdatableElement {
|
|
|
52
50
|
* silently no-ops.
|
|
53
51
|
*/
|
|
54
52
|
export declare function awaitChildUpdates(elements: ReadonlyArray<UpdatableElement | null | undefined>): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Waits until the rows can be measured for a row FLIP's "Last" rects, after
|
|
55
|
+
* a sort or a row move: the pipeline run it started and the grid's render of
|
|
56
|
+
* the result, then the body's rows render (new items reach the rows only once
|
|
57
|
+
* the layout reports the range) and update. Rows outside the body (pinned
|
|
58
|
+
* rows) are awaited too. Sort and row reorder both use this one wait.
|
|
59
|
+
*/
|
|
60
|
+
export declare function settleRowsForFlip<T extends object>(host: GridHost<T>): Promise<void>;
|
|
55
61
|
/**
|
|
56
62
|
* Keyed FLIP entry — pairs an arbitrary identity key with a captured rect.
|
|
57
63
|
*
|
|
@@ -75,6 +81,12 @@ export interface KeyedFlipEntry<K> {
|
|
|
75
81
|
* @param resolveElement - Function that returns the current DOM element
|
|
76
82
|
* representing `key`, or `null` if the keyed row is no longer in view.
|
|
77
83
|
* @param axis - Same semantics as {@link playFlip}.
|
|
84
|
+
*
|
|
85
|
+
* @remarks
|
|
86
|
+
* A FLIP of the same axis still running on an element is cancelled before
|
|
87
|
+
* its new rect is measured, so a quick second move replaces the first
|
|
88
|
+
* rather than stacking on it. Motion stays continuous: the captured "First"
|
|
89
|
+
* rect already includes the cancelled animation's offset.
|
|
78
90
|
*/
|
|
79
|
-
export declare function playKeyedFlip<K>(before: ReadonlyArray<KeyedFlipEntry<K>>, resolveElement: (key: K) => HTMLElement | null, axis?:
|
|
91
|
+
export declare function playKeyedFlip<K>(before: ReadonlyArray<KeyedFlipEntry<K>>, resolveElement: (key: K) => HTMLElement | null, axis?: FlipAxis): void;
|
|
80
92
|
export {};
|
package/internal/flip.js
CHANGED
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
const FLIP_DURATION_MS = 220;
|
|
2
2
|
const FLIP_EASING = 'cubic-bezier(0.2, 0, 0.2, 1)';
|
|
3
3
|
const MIN_DELTA_PX = 0.5;
|
|
4
|
+
export const FLIP_ID_Y = 'apex-flip-y';
|
|
5
|
+
export const FLIP_ID_X = 'apex-flip-x';
|
|
6
|
+
export const FLIP_ID_BOTH = 'apex-flip';
|
|
7
|
+
const FLIP_IDS = { x: FLIP_ID_X, y: FLIP_ID_Y, both: FLIP_ID_BOTH };
|
|
8
|
+
let flipsInFlight = 0;
|
|
9
|
+
export function hasFlipsInFlight() {
|
|
10
|
+
return flipsInFlight > 0;
|
|
11
|
+
}
|
|
12
|
+
export function cancelFlips(element, id) {
|
|
13
|
+
for (const animation of element.getAnimations()) {
|
|
14
|
+
if (animation.id === id)
|
|
15
|
+
animation.cancel();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
4
18
|
function prefersReducedMotion() {
|
|
5
19
|
if (typeof window === 'undefined' || !window.matchMedia)
|
|
6
20
|
return false;
|
|
7
21
|
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
8
22
|
}
|
|
23
|
+
function animateFlip(element, dx, dy, axis) {
|
|
24
|
+
const animation = element.animate([{ transform: `translate(${dx}px, ${dy}px)` }, { transform: 'translate(0, 0)' }], {
|
|
25
|
+
id: FLIP_IDS[axis],
|
|
26
|
+
duration: FLIP_DURATION_MS,
|
|
27
|
+
easing: FLIP_EASING,
|
|
28
|
+
composite: 'add',
|
|
29
|
+
});
|
|
30
|
+
flipsInFlight++;
|
|
31
|
+
let counted = true;
|
|
32
|
+
const done = () => {
|
|
33
|
+
if (!counted)
|
|
34
|
+
return;
|
|
35
|
+
counted = false;
|
|
36
|
+
flipsInFlight--;
|
|
37
|
+
};
|
|
38
|
+
animation.addEventListener('finish', done);
|
|
39
|
+
animation.addEventListener('cancel', done);
|
|
40
|
+
}
|
|
9
41
|
export function playFlip(entries, axis = 'both') {
|
|
10
42
|
if (prefersReducedMotion() || entries.length === 0)
|
|
11
43
|
return;
|
|
@@ -17,11 +49,7 @@ export function playFlip(entries, axis = 'both') {
|
|
|
17
49
|
const dy = axis === 'x' ? 0 : oldRect.top - newRect.top;
|
|
18
50
|
if (Math.abs(dx) < MIN_DELTA_PX && Math.abs(dy) < MIN_DELTA_PX)
|
|
19
51
|
continue;
|
|
20
|
-
element
|
|
21
|
-
duration: FLIP_DURATION_MS,
|
|
22
|
-
easing: FLIP_EASING,
|
|
23
|
-
composite: 'add',
|
|
24
|
-
});
|
|
52
|
+
animateFlip(element, dx, dy, axis);
|
|
25
53
|
}
|
|
26
54
|
}
|
|
27
55
|
export function captureRect(element) {
|
|
@@ -30,6 +58,12 @@ export function captureRect(element) {
|
|
|
30
58
|
export async function awaitChildUpdates(elements) {
|
|
31
59
|
await Promise.all(elements.filter((el) => el != null).map((el) => el.updateComplete));
|
|
32
60
|
}
|
|
61
|
+
export async function settleRowsForFlip(host) {
|
|
62
|
+
const grid = host;
|
|
63
|
+
await grid.pipelineSettled();
|
|
64
|
+
await grid.scrollContainer?.rendered();
|
|
65
|
+
await awaitChildUpdates(grid.rows);
|
|
66
|
+
}
|
|
33
67
|
export function playKeyedFlip(before, resolveElement, axis = 'both') {
|
|
34
68
|
if (prefersReducedMotion() || before.length === 0)
|
|
35
69
|
return;
|
|
@@ -37,16 +71,14 @@ export function playKeyedFlip(before, resolveElement, axis = 'both') {
|
|
|
37
71
|
const element = resolveElement(key);
|
|
38
72
|
if (!element?.isConnected)
|
|
39
73
|
continue;
|
|
74
|
+
if (flipsInFlight > 0)
|
|
75
|
+
cancelFlips(element, FLIP_IDS[axis]);
|
|
40
76
|
const newRect = element.getBoundingClientRect();
|
|
41
77
|
const dx = axis === 'y' ? 0 : oldRect.left - newRect.left;
|
|
42
78
|
const dy = axis === 'x' ? 0 : oldRect.top - newRect.top;
|
|
43
79
|
if (Math.abs(dx) < MIN_DELTA_PX && Math.abs(dy) < MIN_DELTA_PX)
|
|
44
80
|
continue;
|
|
45
|
-
element
|
|
46
|
-
duration: FLIP_DURATION_MS,
|
|
47
|
-
easing: FLIP_EASING,
|
|
48
|
-
composite: 'add',
|
|
49
|
-
});
|
|
81
|
+
animateFlip(element, dx, dy, axis);
|
|
50
82
|
}
|
|
51
83
|
}
|
|
52
84
|
//# sourceMappingURL=flip.js.map
|
package/internal/flip.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flip.js","sourceRoot":"","sources":["../../src/internal/flip.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Lightweight FLIP (First / Last / Invert / Play) helpers for animating\n * element re-positioning during column reorder + row sort.\n *\n * @remarks\n * Uses the Web Animations API with `composite: 'add'` so the FLIP delta\n * stacks ON TOP of whatever inline `transform` the virtualizer already set\n * on row elements — without `'add'` our keyframe would clobber the\n * virtualizer's `translate(...)` and rows would jump to the origin before\n * settling.\n *\n * Respects `prefers-reduced-motion: reduce` and bails out entirely when the\n * user has opted out of motion.\n */\n\nconst FLIP_DURATION_MS = 220;\nconst FLIP_EASING = 'cubic-bezier(0.2, 0, 0.2, 1)';\nconst MIN_DELTA_PX = 0.5;\n\nfunction prefersReducedMotion(): boolean {\n if (typeof window === 'undefined' || !window.matchMedia) return false;\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/**\n * One element to FLIP, captured at its \"first\" (pre-mutation) position.\n */\nexport interface FlipEntry {\n element: HTMLElement;\n rect: DOMRect;\n}\n\n/**\n * Plays the FLIP animation for every captured element by computing the\n * delta against its current (post-mutation) bounding rect and animating it\n * back to identity.\n *\n * @param entries - Elements captured by {@link captureRect}.\n * @param axis - `'x'` for horizontal motion (column reorder), `'y'` for\n * vertical motion (row sort), `'both'` for diagonal motion if either axis\n * could move.\n */\nexport function playFlip(entries: ReadonlyArray<FlipEntry>, axis: 'x' | 'y' | 'both' = 'both') {\n if (prefersReducedMotion() || entries.length === 0) return;\n\n for (const { element, rect: oldRect } of entries) {\n if (!element.isConnected) continue;\n const newRect = element.getBoundingClientRect();\n const dx = axis === 'y' ? 0 : oldRect.left - newRect.left;\n const dy = axis === 'x' ? 0 : oldRect.top - newRect.top;\n if (Math.abs(dx) < MIN_DELTA_PX && Math.abs(dy) < MIN_DELTA_PX) continue;\n\n // `composite: 'add'` stacks our delta on top of any inline transform\n // (e.g. the virtualizer's `translate(0, Ypx)` on row elements).\n element.animate(\n [{ transform: `translate(${dx}px, ${dy}px)` }, { transform: 'translate(0, 0)' }],\n {\n duration: FLIP_DURATION_MS,\n easing: FLIP_EASING,\n composite: 'add',\n }\n );\n }\n}\n\n/**\n * Convenience: capture a rect for an element, returning a {@link FlipEntry}.\n */\nexport function captureRect(element: HTMLElement): FlipEntry {\n return { element, rect: element.getBoundingClientRect() };\n}\n\n/**\n * A subset of `LitElement` needed for {@link awaitChildUpdates}.\n */\ninterface UpdatableElement {\n updateComplete: Promise<unknown>;\n}\n\n/**\n * Awaits a set of child elements' `updateComplete` promises.\n *\n * @remarks\n * The grid's `updateComplete` only resolves the grid's own update — child\n * components (header row, body rows, cells) schedule their updates one\n * microtask later. FLIP needs every relevant child to have its new DOM\n * committed before computing the \"Last\" rect; without this wait the\n * captured-before and measured-after rects match and the animation\n * silently no-ops.\n */\nexport async function awaitChildUpdates(\n elements: ReadonlyArray<UpdatableElement | null | undefined>\n) {\n await Promise.all(\n elements.filter((el): el is UpdatableElement => el != null).map((el) => el.updateComplete)\n );\n}\n\n/**\n * Keyed FLIP entry — pairs an arbitrary identity key with a captured rect.\n *\n * @remarks\n * Used for row sort animation: the virtualizer recycles `<apex-grid-row>`\n * DOM elements so the same element holds different row data before and\n * after a sort. We capture rects keyed by row data identity, then resolve\n * the new DOM element after the sort via the `resolveElement` callback.\n */\nexport interface KeyedFlipEntry<K> {\n key: K;\n rect: DOMRect;\n}\n\n/**\n * Plays a FLIP animation against elements located by key after a DOM\n * mutation has settled. Used for row sort, where DOM elements get recycled\n * across the mutation and can only be found by data identity.\n *\n * @param before - Rects captured before the mutation, keyed by data\n * identity (or any other stable key).\n * @param resolveElement - Function that returns the current DOM element\n * representing `key`, or `null` if the keyed row is no longer in view.\n * @param axis - Same semantics as {@link playFlip}.\n */\nexport function playKeyedFlip<K>(\n before: ReadonlyArray<KeyedFlipEntry<K>>,\n resolveElement: (key: K) => HTMLElement | null,\n axis: 'x' | 'y' | 'both' = 'both'\n) {\n if (prefersReducedMotion() || before.length === 0) return;\n\n for (const { key, rect: oldRect } of before) {\n const element = resolveElement(key);\n if (!element?.isConnected) continue;\n const newRect = element.getBoundingClientRect();\n const dx = axis === 'y' ? 0 : oldRect.left - newRect.left;\n const dy = axis === 'x' ? 0 : oldRect.top - newRect.top;\n if (Math.abs(dx) < MIN_DELTA_PX && Math.abs(dy) < MIN_DELTA_PX) continue;\n\n element.animate(\n [{ transform: `translate(${dx}px, ${dy}px)` }, { transform: 'translate(0, 0)' }],\n {\n duration: FLIP_DURATION_MS,\n easing: FLIP_EASING,\n composite: 'add',\n }\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"flip.js","sourceRoot":"","sources":["../../src/internal/flip.ts"],"names":[],"mappings":"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","sourcesContent":["import type { GridHost } from './types.js';\n\n/**\n * Lightweight FLIP (First / Last / Invert / Play) helpers for animating\n * element re-positioning during column reorder + row sort.\n *\n * @remarks\n * Uses the Web Animations API with `composite: 'add'` so the FLIP delta\n * stacks ON TOP of whatever inline `transform` the virtualizer already set\n * on row elements — without `'add'` our keyframe would clobber the\n * virtualizer's `translate(...)` and rows would jump to the origin before\n * settling.\n *\n * Respects `prefers-reduced-motion: reduce` and bails out entirely when the\n * user has opted out of motion.\n */\n\nconst FLIP_DURATION_MS = 220;\nconst FLIP_EASING = 'cubic-bezier(0.2, 0, 0.2, 1)';\nconst MIN_DELTA_PX = 0.5;\n\n/** Animation id of a vertical (row) FLIP. */\nexport const FLIP_ID_Y = 'apex-flip-y';\n/** Animation id of a horizontal (column) FLIP. */\nexport const FLIP_ID_X = 'apex-flip-x';\n/** Animation id of a FLIP that may move on both axes. */\nexport const FLIP_ID_BOTH = 'apex-flip';\n\ntype FlipAxis = 'x' | 'y' | 'both';\n\nconst FLIP_IDS: Record<FlipAxis, string> = { x: FLIP_ID_X, y: FLIP_ID_Y, both: FLIP_ID_BOTH };\n\n/**\n * FLIP animations started and not yet finished or cancelled, across every\n * grid on the page. A row re-bound to another record cancels its running\n * FLIP only while this is non-zero, so ordinary scroll re-binds pay nothing.\n */\nlet flipsInFlight = 0;\n\n/** Whether any FLIP animation is running right now. */\nexport function hasFlipsInFlight(): boolean {\n return flipsInFlight > 0;\n}\n\n/** Cancels the running FLIP animations with `id` on `element` (not its subtree). */\nexport function cancelFlips(element: Element, id: string): void {\n for (const animation of element.getAnimations()) {\n if (animation.id === id) animation.cancel();\n }\n}\n\nfunction prefersReducedMotion(): boolean {\n if (typeof window === 'undefined' || !window.matchMedia) return false;\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/**\n * Starts one FLIP on `element`, tagged with the axis id and counted in\n * {@link hasFlipsInFlight} until it finishes or is cancelled.\n */\nfunction animateFlip(element: HTMLElement, dx: number, dy: number, axis: FlipAxis): void {\n // `composite: 'add'` stacks our delta on top of any inline transform\n // (e.g. the virtualizer's `translate(0, Ypx)` on row elements).\n const animation = element.animate(\n [{ transform: `translate(${dx}px, ${dy}px)` }, { transform: 'translate(0, 0)' }],\n {\n id: FLIP_IDS[axis],\n duration: FLIP_DURATION_MS,\n easing: FLIP_EASING,\n composite: 'add',\n }\n );\n flipsInFlight++;\n let counted = true;\n const done = () => {\n if (!counted) return;\n counted = false;\n flipsInFlight--;\n };\n animation.addEventListener('finish', done);\n animation.addEventListener('cancel', done);\n}\n\n/**\n * One element to FLIP, captured at its \"first\" (pre-mutation) position.\n */\nexport interface FlipEntry {\n element: HTMLElement;\n rect: DOMRect;\n}\n\n/**\n * Plays the FLIP animation for every captured element by computing the\n * delta against its current (post-mutation) bounding rect and animating it\n * back to identity.\n *\n * @param entries - Elements captured by {@link captureRect}.\n * @param axis - `'x'` for horizontal motion (column reorder), `'y'` for\n * vertical motion (row sort), `'both'` for diagonal motion if either axis\n * could move.\n */\nexport function playFlip(entries: ReadonlyArray<FlipEntry>, axis: FlipAxis = 'both') {\n if (prefersReducedMotion() || entries.length === 0) return;\n\n for (const { element, rect: oldRect } of entries) {\n if (!element.isConnected) continue;\n const newRect = element.getBoundingClientRect();\n const dx = axis === 'y' ? 0 : oldRect.left - newRect.left;\n const dy = axis === 'x' ? 0 : oldRect.top - newRect.top;\n if (Math.abs(dx) < MIN_DELTA_PX && Math.abs(dy) < MIN_DELTA_PX) continue;\n animateFlip(element, dx, dy, axis);\n }\n}\n\n/**\n * Convenience: capture a rect for an element, returning a {@link FlipEntry}.\n */\nexport function captureRect(element: HTMLElement): FlipEntry {\n return { element, rect: element.getBoundingClientRect() };\n}\n\n/**\n * A subset of `LitElement` needed for {@link awaitChildUpdates}.\n */\ninterface UpdatableElement {\n updateComplete: Promise<unknown>;\n}\n\n/**\n * Awaits a set of child elements' `updateComplete` promises.\n *\n * @remarks\n * The grid's `updateComplete` only resolves the grid's own update — child\n * components (header row, body rows, cells) schedule their updates one\n * microtask later. FLIP needs every relevant child to have its new DOM\n * committed before computing the \"Last\" rect; without this wait the\n * captured-before and measured-after rects match and the animation\n * silently no-ops.\n */\nexport async function awaitChildUpdates(\n elements: ReadonlyArray<UpdatableElement | null | undefined>\n) {\n await Promise.all(\n elements.filter((el): el is UpdatableElement => el != null).map((el) => el.updateComplete)\n );\n}\n\n/** What {@link settleRowsForFlip} needs of the grid; the first two are protected there. */\ninterface FlipSettleHost {\n pipelineSettled(): Promise<void>;\n scrollContainer?: { rendered(): Promise<void> } | null;\n rows: ReadonlyArray<UpdatableElement>;\n}\n\n/**\n * Waits until the rows can be measured for a row FLIP's \"Last\" rects, after\n * a sort or a row move: the pipeline run it started and the grid's render of\n * the result, then the body's rows render (new items reach the rows only once\n * the layout reports the range) and update. Rows outside the body (pinned\n * rows) are awaited too. Sort and row reorder both use this one wait.\n */\nexport async function settleRowsForFlip<T extends object>(host: GridHost<T>): Promise<void> {\n const grid = host as unknown as FlipSettleHost;\n await grid.pipelineSettled();\n await grid.scrollContainer?.rendered();\n await awaitChildUpdates(grid.rows);\n}\n\n/**\n * Keyed FLIP entry — pairs an arbitrary identity key with a captured rect.\n *\n * @remarks\n * Used for row sort animation: the virtualizer recycles `<apex-grid-row>`\n * DOM elements so the same element holds different row data before and\n * after a sort. We capture rects keyed by row data identity, then resolve\n * the new DOM element after the sort via the `resolveElement` callback.\n */\nexport interface KeyedFlipEntry<K> {\n key: K;\n rect: DOMRect;\n}\n\n/**\n * Plays a FLIP animation against elements located by key after a DOM\n * mutation has settled. Used for row sort, where DOM elements get recycled\n * across the mutation and can only be found by data identity.\n *\n * @param before - Rects captured before the mutation, keyed by data\n * identity (or any other stable key).\n * @param resolveElement - Function that returns the current DOM element\n * representing `key`, or `null` if the keyed row is no longer in view.\n * @param axis - Same semantics as {@link playFlip}.\n *\n * @remarks\n * A FLIP of the same axis still running on an element is cancelled before\n * its new rect is measured, so a quick second move replaces the first\n * rather than stacking on it. Motion stays continuous: the captured \"First\"\n * rect already includes the cancelled animation's offset.\n */\nexport function playKeyedFlip<K>(\n before: ReadonlyArray<KeyedFlipEntry<K>>,\n resolveElement: (key: K) => HTMLElement | null,\n axis: FlipAxis = 'both'\n) {\n if (prefersReducedMotion() || before.length === 0) return;\n\n for (const { key, rect: oldRect } of before) {\n const element = resolveElement(key);\n if (!element?.isConnected) continue;\n if (flipsInFlight > 0) cancelFlips(element, FLIP_IDS[axis]);\n const newRect = element.getBoundingClientRect();\n const dx = axis === 'y' ? 0 : oldRect.left - newRect.left;\n const dy = axis === 'x' ? 0 : oldRect.top - newRect.top;\n if (Math.abs(dx) < MIN_DELTA_PX && Math.abs(dy) < MIN_DELTA_PX) continue;\n animateFlip(element, dx, dy, axis);\n }\n}\n"]}
|
package/internal/icons.d.ts
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
interface
|
|
3
|
-
viewBox: string;
|
|
4
|
-
paths: SVGTemplateResult;
|
|
5
|
-
/** When true, renders stroke="currentColor" fill="none" (Lucide style). */
|
|
6
|
-
stroke?: boolean;
|
|
7
|
-
}
|
|
8
|
-
declare const ICONS: Record<string, IconDef>;
|
|
9
|
-
export type GridIconName = keyof typeof ICONS;
|
|
10
|
-
export interface IconRenderOptions {
|
|
11
|
-
/** Lit `slot` attribute. Useful when icons sit inside slotted positions of other components. */
|
|
12
|
-
slot?: string;
|
|
1
|
+
import { type TemplateResult } from 'lit';
|
|
2
|
+
interface IconRenderOptions {
|
|
13
3
|
/** Lit `part` attribute. */
|
|
14
4
|
part?: string;
|
|
15
|
-
/** Inline `class` attribute. */
|
|
16
|
-
class?: string;
|
|
17
|
-
/** When provided, sets `data-sort-index` (used by the header sort indicator). */
|
|
18
|
-
sortIndex?: number | string;
|
|
19
5
|
}
|
|
20
6
|
/**
|
|
21
7
|
* Renders one of the grid's built-in icons as an inline `<svg>`. Lucide-based
|
package/internal/icons.js
CHANGED
|
@@ -97,11 +97,6 @@ const CHROME_ICONS = {
|
|
|
97
97
|
stroke: true,
|
|
98
98
|
paths: svg `<path d="M12 19V5"/><path d="m5 12 7-7 7 7"/>`,
|
|
99
99
|
},
|
|
100
|
-
'arrow-downward': {
|
|
101
|
-
viewBox: '0 0 24 24',
|
|
102
|
-
stroke: true,
|
|
103
|
-
paths: svg `<path d="M12 5v14"/><path d="m19 12-7 7-7-7"/>`,
|
|
104
|
-
},
|
|
105
100
|
download: {
|
|
106
101
|
viewBox: '0 0 24 24',
|
|
107
102
|
stroke: true,
|
|
@@ -160,10 +155,7 @@ export function renderIcon(name, opts = {}) {
|
|
|
160
155
|
aria-hidden="true"
|
|
161
156
|
focusable="false"
|
|
162
157
|
data-icon=${name}
|
|
163
|
-
slot=${ifDefined(opts.slot)}
|
|
164
158
|
part=${ifDefined(opts.part)}
|
|
165
|
-
class=${ifDefined(opts.class)}
|
|
166
|
-
data-sort-index=${ifDefined(opts.sortIndex)}
|
|
167
159
|
>${icon.paths}</svg>`;
|
|
168
160
|
}
|
|
169
161
|
export function renderSortArrows() {
|