@svgrid/grid 3.0.5 → 3.0.6
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/README.md +28 -7
- package/dist/GridMenus.svelte +34 -4
- package/dist/SvGrid.controller.svelte.d.ts +21 -3
- package/dist/SvGrid.controller.svelte.js +205 -50
- package/dist/SvGrid.css +127 -2
- package/dist/SvGrid.svelte +223 -38
- package/dist/SvGrid.types.d.ts +77 -4
- package/dist/SvGridDropdown.svelte +20 -0
- package/dist/SvGridDropdown.svelte.d.ts +12 -0
- package/dist/SvModal.svelte +22 -0
- package/dist/SvModal.svelte.d.ts +7 -0
- package/dist/a11y/dismissable.d.ts +10 -0
- package/dist/a11y/dismissable.js +24 -2
- package/dist/build-api.js +81 -31
- package/dist/cdn/{GridMenus-CID62rlI.js → GridMenus-C-NtjlrJ.js} +150 -141
- package/dist/cdn/GridMenus-TssHej3q.js +644 -0
- package/dist/cdn/{SvChartMenu-FBSMINA6.js → SvChartMenu-BFSTpvB-.js} +1 -1
- package/dist/cdn/{SvChartMenu-Bl6PBbkT.js → SvChartMenu-BZ1Iwv0y.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-DQwt4UAs.js → SvDateTimePicker-D9u_cOf8.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-vNU6bZ-q.js → SvDateTimePicker-s_6IUpI7.js} +1 -1
- package/dist/cdn/{SvGridCellEditor-B1p-vCK5.js → SvGridCellEditor-BlyCSp0q.js} +1 -1
- package/dist/cdn/{SvGridCellEditor-D_0q4xAu.js → SvGridCellEditor-ClRRA9lW.js} +1 -1
- package/dist/cdn/{SvGridChart-DzLkSwCH.js → SvGridChart-B4EV8U6t.js} +4 -4
- package/dist/cdn/{SvGridChart-C3EWAZaM.js → SvGridChart-DsmwTyZF.js} +4 -4
- package/dist/cdn/{SvGridChartBuilder-CfII62sZ.js → SvGridChartBuilder-cHloHobN.js} +4 -4
- package/dist/cdn/{SvGridChartBuilder-BE2T1ykB.js → SvGridChartBuilder-vOl7C0Za.js} +4 -4
- package/dist/cdn/{SvGridChartPanel-xf4sWVTo.js → SvGridChartPanel-AcqF-Ll5.js} +5 -5
- package/dist/cdn/{SvGridChartPanel-CCX5_Wgd.js → SvGridChartPanel-biMtSBPA.js} +5 -5
- package/dist/cdn/{SvGridChartView-DYwabQWj.js → SvGridChartView-Dx_GmZuL.js} +2 -2
- package/dist/cdn/{SvGridChartView-CfuXmY5I.js → SvGridChartView-I3rDHXAq.js} +2 -2
- package/dist/cdn/{SvGridDropdown-D0VdjeR8.js → SvGridDropdown-BWeM3lMe.js} +46 -46
- package/dist/cdn/{SvGridDropdown-D13MtJ2j.js → SvGridDropdown-CJ6QqTxu.js} +67 -67
- package/dist/cdn/{SvModal-CIZWCcad.js → SvModal-ClxGmpAc.js} +187 -186
- package/dist/cdn/{SvModal-BAE-pjZX.js → SvModal-DhB_VMFR.js} +148 -147
- package/dist/cdn/{cell-formatting-C2Hf5gqd.js → cell-formatting-DVVKOSI9.js} +1 -1
- package/dist/cdn/{chart-Dz7SqMXH.js → chart-DEZxwC0i.js} +1 -1
- package/dist/cdn/{chart-panel-messages-CUbf2R4i.js → chart-panel-messages-D12XcTZJ.js} +1 -1
- package/dist/cdn/{chart-panel-messages-CmNrMdsr.js → chart-panel-messages-DguS7gC5.js} +1 -1
- package/dist/cdn/{chart-summary-BJW_tg_X.js → chart-summary-CMAXBGY-.js} +1 -1
- package/dist/cdn/{chart-trend-CaN9mDEV.js → chart-trend-BkEp61Gj.js} +1 -1
- package/dist/cdn/{dismissable-DAHetSNk.js → dismissable-Brc4EZU1.js} +5 -1
- package/dist/cdn/{export-format-CUDLy2yn.js → export-format-YzdwBww2.js} +1 -1
- package/dist/cdn/{row-resize-niQCp040.js → row-resize-DtOS1KG6.js} +41 -32
- package/dist/cdn/{server-block-cache-CGoWz-87.js → server-block-cache-DIqb3VZn.js} +4 -0
- package/dist/cdn/{src-jcFMkBnN.js → src-CgoeSM8k.js} +4697 -4131
- package/dist/cdn/{src-Bklofsad.js → src-DfwxB1TH.js} +4727 -4161
- package/dist/cdn/svgrid.js +17 -17
- package/dist/cdn/svgrid.svelte-external.js +17 -17
- package/dist/cdn/{validate-C7Rn_Bns.js → validate-AZoD8BoR.js} +6 -2
- package/dist/cell-formatting.js +18 -1
- package/dist/cell-render.js +25 -6
- package/dist/clipboard.d.ts +2 -2
- package/dist/clipboard.js +72 -23
- package/dist/columns.js +7 -2
- package/dist/conditional-formatting.d.ts +2 -0
- package/dist/conditional-formatting.js +2 -0
- package/dist/core.d.ts +22 -6
- package/dist/core.js +365 -25
- package/dist/editing.js +24 -0
- package/dist/grid-messages.d.ts +4 -0
- package/dist/grid-messages.js +4 -0
- package/dist/headless.d.ts +5 -0
- package/dist/headless.js +12 -0
- package/dist/history.d.ts +21 -0
- package/dist/history.js +34 -4
- package/dist/index.d.ts +1 -1
- package/dist/keyboard-handlers.js +24 -1
- package/dist/keyboard.js +4 -0
- package/dist/row-drag-touch.d.ts +1 -1
- package/dist/row-drag.js +35 -1
- package/dist/row-model.d.ts +6 -0
- package/dist/row-resize.js +22 -1
- package/dist/selection.js +1 -1
- package/dist/server-block-cache.d.ts +5 -0
- package/dist/server-block-cache.js +8 -0
- package/dist/server-data-source.d.ts +35 -3
- package/dist/server-data-source.js +11 -2
- package/dist/server.d.ts +1 -1
- package/dist/sparkline.js +7 -5
- package/dist/svgrid-wrapper.types.d.ts +33 -0
- package/dist/validate.js +9 -1
- package/package.json +6 -1
- package/src/GridMenus.svelte +34 -4
- package/src/SvGrid.controller.svelte.ts +206 -53
- package/src/SvGrid.css +127 -2
- package/src/SvGrid.svelte +223 -38
- package/src/SvGrid.types.ts +77 -4
- package/src/SvGridDropdown.svelte +20 -0
- package/src/SvModal.svelte +22 -0
- package/src/SvModal.test.ts +30 -1
- package/src/a11y/dismissable.test.ts +10 -1
- package/src/a11y/dismissable.ts +22 -2
- package/src/build-api.ts +85 -27
- package/src/cell-formatting.test.ts +17 -0
- package/src/cell-formatting.ts +18 -1
- package/src/cell-render.test.ts +24 -2
- package/src/cell-render.ts +24 -8
- package/src/clipboard.test.ts +107 -0
- package/src/clipboard.ts +62 -23
- package/src/columns.test.ts +4 -4
- package/src/columns.ts +7 -2
- package/src/conditional-formatting.test.ts +8 -0
- package/src/conditional-formatting.ts +3 -0
- package/src/core.tick-repair.test.ts +287 -0
- package/src/core.ts +374 -26
- package/src/editing.test.ts +52 -0
- package/src/editing.ts +25 -0
- package/src/grid-messages.test.ts +7 -0
- package/src/grid-messages.ts +10 -0
- package/src/headless.ts +24 -0
- package/src/history.test.ts +49 -1
- package/src/history.ts +48 -2
- package/src/index.ts +2 -0
- package/src/keyboard-handlers.coverage.test.ts +65 -0
- package/src/keyboard-handlers.ts +26 -1
- package/src/keyboard.ts +3 -0
- package/src/row-drag-touch.ts +2 -2
- package/src/row-drag.test.ts +51 -0
- package/src/row-drag.ts +35 -2
- package/src/row-model.ts +6 -0
- package/src/row-resize.ts +24 -1
- package/src/selection.ts +1 -1
- package/src/server-block-cache.test.ts +27 -0
- package/src/server-block-cache.ts +13 -0
- package/src/server-data-source.infinite.test.ts +62 -0
- package/src/server-data-source.test.ts +19 -0
- package/src/server-data-source.ts +48 -4
- package/src/server.ts +2 -0
- package/src/sparkline.test.ts +7 -0
- package/src/sparkline.ts +6 -4
- package/src/svgrid-wrapper.types.ts +32 -0
- package/src/svgrid.api.test.ts +21 -0
- package/src/svgrid.charting.test.ts +12 -4
- package/src/svgrid.context-menu.test.ts +12 -0
- package/src/svgrid.detail-rows.svelte.test.ts +187 -0
- package/src/svgrid.filter-menu-scroll.test.ts +9 -0
- package/src/svgrid.gantt-seam.test.ts +1 -1
- package/src/svgrid.live-update-paths.svelte.test.ts +184 -0
- package/src/svgrid.menu-scroll-close.test.ts +10 -0
- package/src/svgrid.row-model-seam.svelte.test.ts +16 -0
- package/src/svgrid.row-pinning.test.ts +15 -1
- package/src/svgrid.selection-bar-seam.test.ts +1 -1
- package/src/svgrid.sticky-groups.svelte.test.ts +145 -0
- package/src/svgrid.upsell-license.test.ts +1 -1
- package/src/test-setup.ts +12 -0
- package/src/transaction.test.ts +58 -0
- package/src/validate.ts +7 -1
- package/dist/cdn/GridMenus-DLL-qZeu.js +0 -635
|
@@ -45,6 +45,18 @@
|
|
|
45
45
|
* loading line instead of "No options", which would read as "nothing to
|
|
46
46
|
* pick" when the truth is "not here yet". */
|
|
47
47
|
loading?: boolean
|
|
48
|
+
/**
|
|
49
|
+
* The trigger's element id, so a form's `<label for>` reaches it and
|
|
50
|
+
* clicking the label focuses it. Without one a select in a form was a
|
|
51
|
+
* button with no name.
|
|
52
|
+
*/
|
|
53
|
+
id?: string
|
|
54
|
+
/** Accessible name for the trigger when no label points at `id`. */
|
|
55
|
+
ariaLabel?: string
|
|
56
|
+
/** Marks the trigger `aria-invalid`, the way an input in the same form is. */
|
|
57
|
+
invalid?: boolean
|
|
58
|
+
/** `aria-describedby` for the trigger: the error or help text of the field. */
|
|
59
|
+
describedBy?: string
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
let {
|
|
@@ -59,6 +71,10 @@
|
|
|
59
71
|
onCommit,
|
|
60
72
|
onCancel,
|
|
61
73
|
loading = false,
|
|
74
|
+
id,
|
|
75
|
+
ariaLabel,
|
|
76
|
+
invalid = false,
|
|
77
|
+
describedBy,
|
|
62
78
|
}: Props = $props()
|
|
63
79
|
|
|
64
80
|
/** Stable per-instance id used to build option ids for
|
|
@@ -348,10 +364,14 @@
|
|
|
348
364
|
<!-- svelte-ignore a11y_role_supports_aria_props_implicit -->
|
|
349
365
|
<button
|
|
350
366
|
type="button"
|
|
367
|
+
{id}
|
|
351
368
|
class="sv-grid-dropdown-trigger"
|
|
352
369
|
class:sv-grid-dropdown-trigger-chips={renderChipsInTrigger && selectedArr.length > 0}
|
|
353
370
|
aria-haspopup="listbox"
|
|
354
371
|
aria-expanded={open}
|
|
372
|
+
aria-label={ariaLabel}
|
|
373
|
+
aria-invalid={invalid || undefined}
|
|
374
|
+
aria-describedby={describedBy}
|
|
355
375
|
aria-activedescendant={open ? optionId(highlighted) : undefined}
|
|
356
376
|
bind:this={triggerEl}
|
|
357
377
|
use:focusTrigger
|
|
@@ -42,6 +42,18 @@ type Props = {
|
|
|
42
42
|
* loading line instead of "No options", which would read as "nothing to
|
|
43
43
|
* pick" when the truth is "not here yet". */
|
|
44
44
|
loading?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* The trigger's element id, so a form's `<label for>` reaches it and
|
|
47
|
+
* clicking the label focuses it. Without one a select in a form was a
|
|
48
|
+
* button with no name.
|
|
49
|
+
*/
|
|
50
|
+
id?: string;
|
|
51
|
+
/** Accessible name for the trigger when no label points at `id`. */
|
|
52
|
+
ariaLabel?: string;
|
|
53
|
+
/** Marks the trigger `aria-invalid`, the way an input in the same form is. */
|
|
54
|
+
invalid?: boolean;
|
|
55
|
+
/** `aria-describedby` for the trigger: the error or help text of the field. */
|
|
56
|
+
describedBy?: string;
|
|
45
57
|
};
|
|
46
58
|
declare const SvGridDropdown: import("svelte").Component<Props, {}, "">;
|
|
47
59
|
type SvGridDropdown = ReturnType<typeof SvGridDropdown>;
|
package/dist/SvModal.svelte
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { portalToBody } from './popover'
|
|
17
17
|
import { nextEditorId } from './editor-contract'
|
|
18
18
|
import { createOverlay } from './createOverlay.svelte'
|
|
19
|
+
import { getFocusable } from './a11y/focus-trap'
|
|
19
20
|
|
|
20
21
|
type Props = {
|
|
21
22
|
open?: boolean
|
|
@@ -33,6 +34,13 @@
|
|
|
33
34
|
closeOnEsc?: boolean
|
|
34
35
|
/** Hide the header close (x) button. */
|
|
35
36
|
hideClose?: boolean
|
|
37
|
+
/**
|
|
38
|
+
* The element to focus when the dialog opens. Defaults to the first
|
|
39
|
+
* focusable control in the body or footer - never the header Close (x),
|
|
40
|
+
* so opening a dialog lands on its content and Enter does not dismiss it.
|
|
41
|
+
* Pass a getter to target a specific field (e.g. a search input).
|
|
42
|
+
*/
|
|
43
|
+
initialFocus?: () => HTMLElement | null
|
|
36
44
|
children?: Snippet
|
|
37
45
|
footer?: Snippet
|
|
38
46
|
}
|
|
@@ -48,6 +56,7 @@
|
|
|
48
56
|
closeOnBackdrop = true,
|
|
49
57
|
closeOnEsc = true,
|
|
50
58
|
hideClose = false,
|
|
59
|
+
initialFocus,
|
|
51
60
|
children,
|
|
52
61
|
footer,
|
|
53
62
|
}: Props = $props()
|
|
@@ -69,6 +78,19 @@
|
|
|
69
78
|
closeOnEsc: () => closeOnEsc,
|
|
70
79
|
closeOnBackdrop: () => closeOnBackdrop,
|
|
71
80
|
onOpen: () => { drag = { x: 0, y: 0 }; box = null },
|
|
81
|
+
// Land focus on the dialog's content, not the header Close (x). The x is
|
|
82
|
+
// the first focusable in DOM order, so the trap's "first focusable"
|
|
83
|
+
// default sent focus there - where Enter immediately dismissed the
|
|
84
|
+
// dialog and the loud focus ring pointed at "close" over the primary
|
|
85
|
+
// action. Prefer the first focusable outside the header; fall back to the
|
|
86
|
+
// panel itself when the body has no control of its own.
|
|
87
|
+
initialFocus: () => {
|
|
88
|
+
const explicit = initialFocus?.()
|
|
89
|
+
if (explicit) return explicit
|
|
90
|
+
if (!dialogEl) return null
|
|
91
|
+
const first = getFocusable(dialogEl).find((el) => !el.closest('.sv-modal__header'))
|
|
92
|
+
return first ?? dialogEl
|
|
93
|
+
},
|
|
72
94
|
})
|
|
73
95
|
|
|
74
96
|
// --- Drag (header) ---------------------------------------------------------
|
package/dist/SvModal.svelte.d.ts
CHANGED
|
@@ -28,6 +28,13 @@ type Props = {
|
|
|
28
28
|
closeOnEsc?: boolean;
|
|
29
29
|
/** Hide the header close (x) button. */
|
|
30
30
|
hideClose?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The element to focus when the dialog opens. Defaults to the first
|
|
33
|
+
* focusable control in the body or footer - never the header Close (x),
|
|
34
|
+
* so opening a dialog lands on its content and Enter does not dismiss it.
|
|
35
|
+
* Pass a getter to target a specific field (e.g. a search input).
|
|
36
|
+
*/
|
|
37
|
+
initialFocus?: () => HTMLElement | null;
|
|
31
38
|
children?: Snippet;
|
|
32
39
|
footer?: Snippet;
|
|
33
40
|
};
|
|
@@ -37,6 +37,16 @@ export declare function dismissableDepth(): number;
|
|
|
37
37
|
* window listener gets wrong: inner scroll containers don't bubble, so the
|
|
38
38
|
* capture listener sees them and dismisses the panel the user is scrolling.
|
|
39
39
|
*
|
|
40
|
+
* The listener arms one animation frame after it is asked for, not at once.
|
|
41
|
+
* Scroll events are queued and dispatched in the next rendering step, so a
|
|
42
|
+
* scroll that FINISHED before the click that opened the panel - the browser
|
|
43
|
+
* pulling a half-hidden funnel button into view as it focuses it, a test
|
|
44
|
+
* driver scrolling the header into place - still lands after the panel has
|
|
45
|
+
* mounted and wired this listener, and used to close it on the spot. The
|
|
46
|
+
* panel measured its anchor after that scroll, so the event carries nothing
|
|
47
|
+
* it needs to react to. Rendering runs the scroll steps before the animation
|
|
48
|
+
* frame callbacks, which is why one frame is exactly the right delay.
|
|
49
|
+
*
|
|
40
50
|
* Returns the unsubscribe function.
|
|
41
51
|
*/
|
|
42
52
|
export declare function onScrollOutside(element: () => DismissTarget, onOutside: () => void): () => void;
|
package/dist/a11y/dismissable.js
CHANGED
|
@@ -60,6 +60,16 @@ export function dismissableDepth() {
|
|
|
60
60
|
* window listener gets wrong: inner scroll containers don't bubble, so the
|
|
61
61
|
* capture listener sees them and dismisses the panel the user is scrolling.
|
|
62
62
|
*
|
|
63
|
+
* The listener arms one animation frame after it is asked for, not at once.
|
|
64
|
+
* Scroll events are queued and dispatched in the next rendering step, so a
|
|
65
|
+
* scroll that FINISHED before the click that opened the panel - the browser
|
|
66
|
+
* pulling a half-hidden funnel button into view as it focuses it, a test
|
|
67
|
+
* driver scrolling the header into place - still lands after the panel has
|
|
68
|
+
* mounted and wired this listener, and used to close it on the spot. The
|
|
69
|
+
* panel measured its anchor after that scroll, so the event carries nothing
|
|
70
|
+
* it needs to react to. Rendering runs the scroll steps before the animation
|
|
71
|
+
* frame callbacks, which is why one frame is exactly the right delay.
|
|
72
|
+
*
|
|
63
73
|
* Returns the unsubscribe function.
|
|
64
74
|
*/
|
|
65
75
|
export function onScrollOutside(element, onOutside) {
|
|
@@ -70,6 +80,18 @@ export function onScrollOutside(element, onOutside) {
|
|
|
70
80
|
return;
|
|
71
81
|
onOutside();
|
|
72
82
|
};
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
let armed = false;
|
|
84
|
+
let frame = requestAnimationFrame(() => {
|
|
85
|
+
frame = null;
|
|
86
|
+
armed = true;
|
|
87
|
+
window.addEventListener('scroll', handler, true);
|
|
88
|
+
});
|
|
89
|
+
return () => {
|
|
90
|
+
if (frame !== null)
|
|
91
|
+
cancelAnimationFrame(frame);
|
|
92
|
+
frame = null;
|
|
93
|
+
if (armed)
|
|
94
|
+
window.removeEventListener('scroll', handler, true);
|
|
95
|
+
armed = false;
|
|
96
|
+
};
|
|
75
97
|
}
|
package/dist/build-api.js
CHANGED
|
@@ -2,8 +2,30 @@ import "./sv-grid-scrollbar.js";
|
|
|
2
2
|
import { untrack } from "svelte";
|
|
3
3
|
import { isGroupRow, getColumnAlign, columnDefMatchesId, } from "./cell-values.js";
|
|
4
4
|
import { hasAdvancedFilterEngine } from "./advanced-filter.svelte.js";
|
|
5
|
-
import { undoHistory, redoHistory } from "./history.js";
|
|
5
|
+
import { undoHistory, redoHistory, peekUndoHistory, peekRedoHistory } from "./history.js";
|
|
6
6
|
import { buildCommandContext } from "./command-context.js";
|
|
7
|
+
/**
|
|
8
|
+
* Row id -> index, per data array, for `applyTransaction`.
|
|
9
|
+
*
|
|
10
|
+
* An update batch used to walk every row calling `getRowId` to find the ones
|
|
11
|
+
* it replaced - 100k calls per tick on a 100k-row feed, whatever the batch
|
|
12
|
+
* size. The map is built once per array and, for an update-only transaction,
|
|
13
|
+
* carried to the array it produces: same ids at the same indices. An add or
|
|
14
|
+
* remove changes indices, so those transactions leave the next array to
|
|
15
|
+
* build its own map on first use. Keyed weakly so a replaced array takes its
|
|
16
|
+
* map with it.
|
|
17
|
+
*/
|
|
18
|
+
const rowIndexByData = new WeakMap();
|
|
19
|
+
function rowIndexOf(data, getId) {
|
|
20
|
+
let byId = rowIndexByData.get(data);
|
|
21
|
+
if (!byId) {
|
|
22
|
+
byId = new Map();
|
|
23
|
+
for (let i = 0; i < data.length; i++)
|
|
24
|
+
byId.set(getId(data[i], i), i);
|
|
25
|
+
rowIndexByData.set(data, byId);
|
|
26
|
+
}
|
|
27
|
+
return byId;
|
|
28
|
+
}
|
|
7
29
|
/**
|
|
8
30
|
* The selection as the user made it: committed ranges first, the active one
|
|
9
31
|
* last, each keeping its anchor as `from` and its focus as `to`. The active
|
|
@@ -69,6 +91,10 @@ export function createGridApi(ctx) {
|
|
|
69
91
|
return projectGridRows(exportScopeRows(scope), exportColumns(), { columns, rawValues });
|
|
70
92
|
};
|
|
71
93
|
return {
|
|
94
|
+
getElement() {
|
|
95
|
+
const el = ctx.scrollContainer;
|
|
96
|
+
return el?.closest(".sv-grid-root") ?? null;
|
|
97
|
+
},
|
|
72
98
|
getCellValue(rowIndex, columnId) {
|
|
73
99
|
const row = ctx.internalData[rowIndex];
|
|
74
100
|
const column = findColumn(columnId);
|
|
@@ -208,39 +234,41 @@ export function createGridApi(ctx) {
|
|
|
208
234
|
},
|
|
209
235
|
applyTransaction(tx) {
|
|
210
236
|
const getId = ctx.props.getRowId;
|
|
211
|
-
|
|
237
|
+
const current = ctx.internalData;
|
|
238
|
+
let next = current.slice();
|
|
212
239
|
let added = 0;
|
|
213
240
|
let updated = 0;
|
|
214
241
|
let removed = 0;
|
|
242
|
+
// Everything resolves against the indices of `current`: the id ->
|
|
243
|
+
// index map answers updates and id removals with one lookup each, a
|
|
244
|
+
// reference removal with one `indexOf`, and no pass over the rows
|
|
245
|
+
// that did not change. A row both updated and removed in the same
|
|
246
|
+
// transaction ends up removed, as it did when removes ran first.
|
|
247
|
+
const removeIdx = new Set();
|
|
215
248
|
if (tx.remove?.length) {
|
|
216
|
-
const
|
|
217
|
-
const removeRefs = new Set();
|
|
249
|
+
const byId = getId ? rowIndexOf(current, getId) : null;
|
|
218
250
|
for (const r of tx.remove) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
removeRefs.add(r);
|
|
251
|
+
const idx = typeof r === "string" ? byId?.get(r) : current.indexOf(r);
|
|
252
|
+
if (idx !== undefined && idx >= 0)
|
|
253
|
+
removeIdx.add(idx);
|
|
223
254
|
}
|
|
224
|
-
next = next.filter((row, i) => {
|
|
225
|
-
const hit = removeRefs.has(row) ||
|
|
226
|
-
(getId ? removeIds.has(getId(row, i)) : false);
|
|
227
|
-
if (hit)
|
|
228
|
-
removed += 1;
|
|
229
|
-
return !hit;
|
|
230
|
-
});
|
|
231
255
|
}
|
|
232
256
|
if (tx.update?.length && getId) {
|
|
233
|
-
const byId =
|
|
234
|
-
for (const u of tx.update)
|
|
235
|
-
byId.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
257
|
+
const byId = rowIndexOf(current, getId);
|
|
258
|
+
for (const u of tx.update) {
|
|
259
|
+
const idx = byId.get(getId(u, 0));
|
|
260
|
+
if (idx === undefined)
|
|
261
|
+
continue;
|
|
262
|
+
next[idx] = u;
|
|
263
|
+
updated += 1;
|
|
264
|
+
}
|
|
265
|
+
// Same ids at the same indices: the next array can reuse the map.
|
|
266
|
+
if (!removeIdx.size && !tx.add?.length)
|
|
267
|
+
rowIndexByData.set(next, byId);
|
|
268
|
+
}
|
|
269
|
+
if (removeIdx.size) {
|
|
270
|
+
next = next.filter((_, i) => !removeIdx.has(i));
|
|
271
|
+
removed = removeIdx.size;
|
|
244
272
|
}
|
|
245
273
|
if (tx.add?.length) {
|
|
246
274
|
next.push(...tx.add);
|
|
@@ -472,6 +500,26 @@ export function createGridApi(ctx) {
|
|
|
472
500
|
getData() {
|
|
473
501
|
return ctx.internalData;
|
|
474
502
|
},
|
|
503
|
+
getRowId(row) {
|
|
504
|
+
// The stable id the grid keys a row by: what `getRowId` computed,
|
|
505
|
+
// or the row's position when no `getRowId` prop is set. A number is
|
|
506
|
+
// a DISPLAY index into the rendered rows, the space getActiveCell,
|
|
507
|
+
// scrollToRow and the cell methods work in; a row object is looked
|
|
508
|
+
// up wherever it sits. Null when the index or the object is unknown,
|
|
509
|
+
// so a caller can tell "no such row" from a real id.
|
|
510
|
+
if (typeof row === "number") {
|
|
511
|
+
const found = ctx.allRows[row];
|
|
512
|
+
return found && !isGroupRow(found) ? found.id : null;
|
|
513
|
+
}
|
|
514
|
+
const inRows = ctx.allRows.find((r) => r.original === row);
|
|
515
|
+
if (inRows)
|
|
516
|
+
return inRows.id;
|
|
517
|
+
const getId = ctx.props.getRowId;
|
|
518
|
+
const index = ctx.internalData.indexOf(row);
|
|
519
|
+
if (index < 0)
|
|
520
|
+
return null;
|
|
521
|
+
return getId ? getId(row, index) : String(index);
|
|
522
|
+
},
|
|
475
523
|
getColumns() {
|
|
476
524
|
// Snapshot every column with its human-readable label and
|
|
477
525
|
// visibility flag. Used by external code (exporters, column
|
|
@@ -580,11 +628,10 @@ export function createGridApi(ctx) {
|
|
|
580
628
|
ctx.autosizeColumn(columnId);
|
|
581
629
|
},
|
|
582
630
|
getRowHeight(rowIndex) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
return typeof declared === "function" ? declared(rowIndex) : (declared ?? 30);
|
|
631
|
+
// The height the grid draws the row at, whatever decided it: a drag,
|
|
632
|
+
// a measurement under auto height, a detail row's own height, the
|
|
633
|
+
// declared rowHeight or the default.
|
|
634
|
+
return ctx.rowSizePxOf(rowIndex);
|
|
588
635
|
},
|
|
589
636
|
setRowHeight(rowIndex, height) {
|
|
590
637
|
ctx.setRowResizeHeight(rowIndex, height);
|
|
@@ -657,6 +704,9 @@ export function createGridApi(ctx) {
|
|
|
657
704
|
canUndo() { void ctx.historyVersion; return ctx.historyPtr >= 0; },
|
|
658
705
|
canRedo() { void ctx.historyVersion; return ctx.historyPtr < ctx.history.length - 1; },
|
|
659
706
|
clearHistory() { ctx.history = []; ctx.historyPtr = -1; ctx.historyVersion += 1; },
|
|
707
|
+
setHistoryTag(tag) { ctx.historyTag = tag; },
|
|
708
|
+
peekUndo() { void ctx.historyVersion; return peekUndoHistory(ctx); },
|
|
709
|
+
peekRedo() { void ctx.historyVersion; return peekRedoHistory(ctx); },
|
|
660
710
|
// ---- Find
|
|
661
711
|
openFind() { ctx.findOpen = true; },
|
|
662
712
|
closeFind() { ctx.findOpen = false; ctx.findQuery = ''; },
|