@svgrid/grid 1.1.0 → 1.1.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/dist/FlexRender.svelte +96 -96
- package/dist/GridMenus.svelte +205 -127
- package/dist/SvGrid.controller.svelte.d.ts +44 -1
- package/dist/SvGrid.controller.svelte.js +240 -44
- package/dist/SvGrid.css +325 -22
- package/dist/SvGrid.helpers.test.d.ts +1 -0
- package/dist/SvGrid.helpers.test.js +298 -0
- package/dist/SvGrid.svelte +729 -203
- package/dist/SvGrid.types.d.ts +91 -4
- package/dist/aligned-grids.d.ts +6 -0
- package/dist/aligned-grids.js +84 -0
- package/dist/aligned-grids.test.d.ts +1 -0
- package/dist/aligned-grids.test.js +75 -0
- package/dist/build-api.coverage.test.d.ts +20 -0
- package/dist/build-api.coverage.test.js +505 -0
- package/dist/build-api.js +61 -31
- package/dist/cell-render.test.d.ts +1 -0
- package/dist/cell-render.test.js +338 -0
- package/dist/cell-values.d.ts +1 -1
- package/dist/cell-values.js +7 -7
- package/dist/chart-export.test.d.ts +1 -0
- package/dist/chart-export.test.js +302 -0
- package/dist/chart.coverage.test.d.ts +1 -0
- package/dist/chart.coverage.test.js +748 -0
- package/dist/clipboard.js +88 -24
- package/dist/clipboard.test.d.ts +1 -0
- package/dist/clipboard.test.js +700 -0
- package/dist/collaboration.coverage.test.d.ts +1 -0
- package/dist/collaboration.coverage.test.js +200 -0
- package/dist/column-groups.d.ts +19 -0
- package/dist/column-groups.js +62 -0
- package/dist/column-groups.test.d.ts +1 -0
- package/dist/column-groups.test.js +56 -0
- package/dist/column-types.d.ts +10 -0
- package/dist/column-types.js +63 -0
- package/dist/column-types.test.d.ts +1 -0
- package/dist/column-types.test.js +62 -0
- package/dist/columns.test.d.ts +1 -0
- package/dist/columns.test.js +625 -0
- package/dist/core.d.ts +86 -1
- package/dist/core.js +2 -2
- package/dist/editing.d.ts +7 -0
- package/dist/editing.js +191 -5
- package/dist/editing.test.d.ts +1 -0
- package/dist/editing.test.js +732 -0
- package/dist/editors/cell-editors.coverage.test.d.ts +1 -0
- package/dist/editors/cell-editors.coverage.test.js +139 -0
- package/dist/facet-buckets.test.d.ts +1 -0
- package/dist/facet-buckets.test.js +296 -0
- package/dist/filter-operators.test.d.ts +1 -0
- package/dist/filter-operators.test.js +135 -0
- package/dist/hyperformula-adapter.test.d.ts +1 -0
- package/dist/hyperformula-adapter.test.js +205 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- package/dist/keyboard-handlers.coverage.test.d.ts +1 -0
- package/dist/keyboard-handlers.coverage.test.js +495 -0
- package/dist/keyboard-handlers.js +7 -2
- package/dist/menus.js +1 -0
- package/dist/menus.test.d.ts +1 -0
- package/dist/menus.test.js +560 -0
- package/dist/named-views.coverage.test.d.ts +1 -0
- package/dist/named-views.coverage.test.js +180 -0
- package/dist/row-drag.d.ts +49 -0
- package/dist/row-drag.js +221 -0
- package/dist/row-drag.test.d.ts +1 -0
- package/dist/row-drag.test.js +142 -0
- package/dist/row-resize.test.d.ts +1 -0
- package/dist/row-resize.test.js +329 -0
- package/dist/scroll-sync.js +3 -0
- package/dist/scroll-sync.test.d.ts +1 -0
- package/dist/scroll-sync.test.js +290 -0
- package/dist/selection.d.ts +7 -1
- package/dist/selection.js +76 -36
- package/dist/selection.multi-range.test.d.ts +1 -0
- package/dist/selection.multi-range.test.js +55 -0
- package/dist/selection.test.d.ts +1 -0
- package/dist/selection.test.js +647 -0
- package/dist/server-data-source.coverage.test.d.ts +1 -0
- package/dist/server-data-source.coverage.test.js +154 -0
- package/dist/spreadsheet.d.ts +30 -0
- package/dist/spreadsheet.js +48 -0
- package/dist/spreadsheet.test.d.ts +1 -0
- package/dist/spreadsheet.test.js +446 -0
- package/dist/summaries.js +4 -4
- package/dist/sv-grid-scrollbar.js +13 -1
- package/dist/svgrid-wrapper.types.d.ts +19 -0
- package/dist/svgrid.behavior.test.js +20 -0
- package/dist/svgrid.interaction.test.js +31 -0
- package/dist/svgrid.new-features.wrapper.test.js +34 -2
- package/dist/test-setup.js +9 -3
- package/dist/virtualization/scroll-scaling.d.ts +17 -0
- package/dist/virtualization/scroll-scaling.js +35 -0
- package/dist/virtualization/scroll-scaling.test.js +42 -1
- package/package.json +2 -1
- package/src/FlexRender.svelte +96 -96
- package/src/GridMenus.svelte +205 -127
- package/src/SvGrid.controller.svelte.ts +204 -36
- package/src/SvGrid.css +277 -12
- package/src/SvGrid.svelte +727 -205
- package/src/SvGrid.types.ts +85 -4
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +59 -59
- package/src/aligned-grids.test.ts +80 -0
- package/src/aligned-grids.ts +87 -0
- package/src/build-api.ts +43 -23
- package/src/cell-formatting.ts +169 -169
- package/src/cell-values.ts +4 -4
- package/src/clipboard.test.ts +49 -0
- package/src/clipboard.ts +51 -23
- package/src/column-groups.test.ts +59 -0
- package/src/column-groups.ts +80 -0
- package/src/column-types.test.ts +68 -0
- package/src/column-types.ts +82 -0
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1077 -999
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/editing.ts +161 -5
- package/src/flex-render.ts +3 -3
- package/src/index.ts +208 -196
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/menus.ts +1 -0
- package/src/merge-objects.ts +48 -48
- package/src/render-component.ts +28 -28
- package/src/row-drag.test.ts +168 -0
- package/src/row-drag.ts +255 -0
- package/src/scroll-sync.ts +2 -0
- package/src/selection.multi-range.test.ts +61 -0
- package/src/selection.ts +71 -37
- package/src/spreadsheet.test.ts +46 -2
- package/src/spreadsheet.ts +58 -0
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/summaries.ts +4 -4
- package/src/svgrid-wrapper.types.ts +412 -393
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.new-features.wrapper.test.ts +2 -2
- package/src/svgrid.wrapper.test.ts +40 -40
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +296 -296
package/dist/SvGrid.svelte
CHANGED
|
@@ -3,53 +3,48 @@
|
|
|
3
3
|
generics="TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData"
|
|
4
4
|
>
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
getGridCellA11yProps,
|
|
7
|
+
getGridCellDomId,
|
|
8
|
+
getGridHeaderA11yProps,
|
|
9
|
+
getGridRootA11yProps,
|
|
10
|
+
getGridRowA11yProps,
|
|
11
|
+
type EditorContext,
|
|
12
|
+
type CellEditorOption,
|
|
13
|
+
type Column,
|
|
14
|
+
type Row,
|
|
15
|
+
type RowData,
|
|
16
|
+
type TableFeatures,
|
|
17
|
+
} from "./index";
|
|
18
18
|
import "./sv-grid-scrollbar";
|
|
19
19
|
import "./SvGrid.css";
|
|
20
20
|
import type { Snippet } from "svelte";
|
|
21
21
|
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
import {
|
|
26
|
-
buildSparkline,
|
|
27
|
-
toSparklineValues,
|
|
28
|
-
} from "./sparkline";
|
|
22
|
+
RenderSnippetConfig,
|
|
23
|
+
RenderComponentConfig,
|
|
24
|
+
} from "./render-component";
|
|
25
|
+
import { buildSparkline, toSparklineValues } from "./sparkline";
|
|
29
26
|
import SvGridDropdown from "./SvGridDropdown.svelte";
|
|
30
27
|
import type {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
import {
|
|
40
|
-
cfTextStyle,
|
|
41
|
-
fmtStat,
|
|
42
|
-
getEditableInputValue,
|
|
43
|
-
getEditorInputType,
|
|
44
|
-
toValueArray,
|
|
45
|
-
getOptionLabel,
|
|
46
|
-
getOptionColor,
|
|
47
|
-
colorfulChipStyle,
|
|
48
|
-
getEditorClass,
|
|
49
|
-
} from "./SvGrid.helpers";
|
|
28
|
+
Props,
|
|
29
|
+
SelectionPoint,
|
|
30
|
+
SelectionRange,
|
|
31
|
+
CellEditState,
|
|
32
|
+
FilterOperator,
|
|
33
|
+
FilterOption,
|
|
34
|
+
MenuPosition,
|
|
35
|
+
} from "./SvGrid.types";
|
|
50
36
|
import {
|
|
51
|
-
|
|
52
|
-
|
|
37
|
+
cfTextStyle,
|
|
38
|
+
fmtStat,
|
|
39
|
+
getEditableInputValue,
|
|
40
|
+
getEditorInputType,
|
|
41
|
+
toValueArray,
|
|
42
|
+
getOptionLabel,
|
|
43
|
+
getOptionColor,
|
|
44
|
+
colorfulChipStyle,
|
|
45
|
+
getEditorClass,
|
|
46
|
+
} from "./SvGrid.helpers";
|
|
47
|
+
import { createSvGridController } from "./SvGrid.controller.svelte";
|
|
53
48
|
import GridMenus from "./GridMenus.svelte";
|
|
54
49
|
import GridFooter from "./GridFooter.svelte";
|
|
55
50
|
let props: Props<TFeatures, TData> = $props();
|
|
@@ -78,7 +73,9 @@
|
|
|
78
73
|
const hasVerticalOverflow = $derived(ctrl.hasVerticalOverflow);
|
|
79
74
|
const showGlobalFilterEffective = $derived(ctrl.showGlobalFilterEffective);
|
|
80
75
|
const showFilterRowEffective = $derived(ctrl.showFilterRowEffective);
|
|
81
|
-
const showInlineColumnFilterEffective = $derived(
|
|
76
|
+
const showInlineColumnFilterEffective = $derived(
|
|
77
|
+
ctrl.showInlineColumnFilterEffective,
|
|
78
|
+
);
|
|
82
79
|
const showRowSelectionEffective = $derived(ctrl.showRowSelectionEffective);
|
|
83
80
|
const grid = $derived(ctrl.grid);
|
|
84
81
|
const allColumns = $derived(ctrl.allColumns);
|
|
@@ -94,6 +91,36 @@
|
|
|
94
91
|
const onColumnHeaderDragLeave = $derived(ctrl.onColumnHeaderDragLeave);
|
|
95
92
|
const onColumnHeaderDrop = $derived(ctrl.onColumnHeaderDrop);
|
|
96
93
|
const onColumnHeaderDragEnd = $derived(ctrl.onColumnHeaderDragEnd);
|
|
94
|
+
// Managed row dragging
|
|
95
|
+
const rowDragManagedEffective = $derived(props.rowDragManaged === true);
|
|
96
|
+
const rowDropIndex = $derived(ctrl.rowDropIndex);
|
|
97
|
+
const rowDropSide = $derived(ctrl.rowDropSide);
|
|
98
|
+
const onRowDragStart = $derived(ctrl.onRowDragStart);
|
|
99
|
+
const onRowDragOver = $derived(ctrl.onRowDragOver);
|
|
100
|
+
const onRowDragLeave = $derived(ctrl.onRowDragLeave);
|
|
101
|
+
const onRowDrop = $derived(ctrl.onRowDrop);
|
|
102
|
+
const onRowsContainerDragOver = $derived(ctrl.onRowsContainerDragOver);
|
|
103
|
+
const onRowsContainerDrop = $derived(ctrl.onRowsContainerDrop);
|
|
104
|
+
const onRowDragEndHandler = $derived(ctrl.onRowDragEnd);
|
|
105
|
+
// Drag attributes spread onto each body <tr>; empty object when disabled so
|
|
106
|
+
// rows stay non-draggable and no handlers fire.
|
|
107
|
+
function rowDragAttrs(rowIndex: number) {
|
|
108
|
+
if (!rowDragManagedEffective) return {};
|
|
109
|
+
return {
|
|
110
|
+
draggable: true,
|
|
111
|
+
ondragstart: (e: DragEvent) => onRowDragStart(e, rowIndex),
|
|
112
|
+
ondragover: (e: DragEvent) => onRowDragOver(e, rowIndex),
|
|
113
|
+
ondragleave: () => onRowDragLeave(rowIndex),
|
|
114
|
+
ondrop: (e: DragEvent) => onRowDrop(e, rowIndex),
|
|
115
|
+
ondragend: () => onRowDragEndHandler(),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function rowDropClass(rowIndex: number) {
|
|
119
|
+
if (!rowDragManagedEffective || rowDropIndex !== rowIndex) return "";
|
|
120
|
+
return rowDropSide === "after"
|
|
121
|
+
? "sv-grid-row-drop-after"
|
|
122
|
+
: "sv-grid-row-drop-before";
|
|
123
|
+
}
|
|
97
124
|
const getColumnBaseValue = $derived(ctrl.getColumnBaseValue);
|
|
98
125
|
const hasConditionalFormats = $derived(ctrl.hasConditionalFormats);
|
|
99
126
|
const cellConditionalFormat = $derived(ctrl.cellConditionalFormat);
|
|
@@ -114,7 +141,9 @@
|
|
|
114
141
|
const toggleGroupInPanel = $derived(ctrl.toggleGroupInPanel);
|
|
115
142
|
const virtualizer = $derived(ctrl.virtualizer);
|
|
116
143
|
const rowVirtualizationEnabled = $derived(ctrl.rowVirtualizationEnabled);
|
|
117
|
-
const columnVirtualizationEnabled = $derived(
|
|
144
|
+
const columnVirtualizationEnabled = $derived(
|
|
145
|
+
ctrl.columnVirtualizationEnabled,
|
|
146
|
+
);
|
|
118
147
|
const virtualRows = $derived(ctrl.virtualRows);
|
|
119
148
|
// DOM-space spacer heights + capped total: identical to the logical
|
|
120
149
|
// virtualizer values for normal grids, scaled down past
|
|
@@ -125,6 +154,88 @@
|
|
|
125
154
|
const rowDomTotalSize = $derived(ctrl.rowDomTotalSize);
|
|
126
155
|
const renderedColumns = $derived(ctrl.renderedColumns);
|
|
127
156
|
const totalColumnWidth = $derived(ctrl.totalColumnWidth);
|
|
157
|
+
|
|
158
|
+
// ---- Built-in cell flash (ColumnDef `cellFlash`) ----------------------
|
|
159
|
+
// Flashes a cell when its value changes (edits, streaming feeds, server
|
|
160
|
+
// pushes). Keyed by rowId so virtualization recycling a <td> into a new row
|
|
161
|
+
// on scroll does NOT trigger a spurious flash - only a same-row value change
|
|
162
|
+
// does. Inert (active:false) for columns without `cellFlash`.
|
|
163
|
+
function cellFlashAction(
|
|
164
|
+
node: HTMLElement,
|
|
165
|
+
params: {
|
|
166
|
+
rowId: string;
|
|
167
|
+
value: unknown;
|
|
168
|
+
active: boolean;
|
|
169
|
+
className: string;
|
|
170
|
+
},
|
|
171
|
+
) {
|
|
172
|
+
let prevRow = params.rowId;
|
|
173
|
+
let prev = params.value;
|
|
174
|
+
return {
|
|
175
|
+
update(next: {
|
|
176
|
+
rowId: string;
|
|
177
|
+
value: unknown;
|
|
178
|
+
active: boolean;
|
|
179
|
+
className: string;
|
|
180
|
+
}) {
|
|
181
|
+
if (next.rowId !== prevRow) {
|
|
182
|
+
// A different row scrolled into this recycled slot - reset, no flash.
|
|
183
|
+
prevRow = next.rowId;
|
|
184
|
+
prev = next.value;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (next.active && next.className && !Object.is(next.value, prev)) {
|
|
188
|
+
node.classList.remove(next.className);
|
|
189
|
+
void node.offsetWidth; // reflow so the animation restarts
|
|
190
|
+
node.classList.add(next.className);
|
|
191
|
+
}
|
|
192
|
+
prev = next.value;
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function flashClassFor(
|
|
197
|
+
cfg: boolean | { className?: string } | undefined,
|
|
198
|
+
): string {
|
|
199
|
+
console.log("flash cell");
|
|
200
|
+
if (cfg && typeof cfg === "object" && cfg.className) return cfg.className;
|
|
201
|
+
return "sv-grid-cell-flash";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---- Full-row editing -------------------------------------------------
|
|
205
|
+
const fullRowEdit = $derived(ctrl.fullRowEdit);
|
|
206
|
+
// Commit the whole row when the user clicks away from its editors (Excel /
|
|
207
|
+
// AG-Grid feel). Clicking within any full-row editor keeps editing.
|
|
208
|
+
$effect(() => {
|
|
209
|
+
if (!fullRowEdit) return;
|
|
210
|
+
const onDown = (e: PointerEvent) => {
|
|
211
|
+
const t = e.target as HTMLElement | null;
|
|
212
|
+
if (t && t.closest(".sv-grid-fr-editor")) return;
|
|
213
|
+
ctrl.commitFullRowEdit();
|
|
214
|
+
};
|
|
215
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
216
|
+
return () => document.removeEventListener("pointerdown", onDown, true);
|
|
217
|
+
});
|
|
218
|
+
function fullRowKeydown(e: KeyboardEvent) {
|
|
219
|
+
if (e.key === "Enter") {
|
|
220
|
+
e.preventDefault();
|
|
221
|
+
ctrl.commitFullRowEdit();
|
|
222
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
223
|
+
} else if (e.key === "Escape") {
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
ctrl.cancelFullRowEdit();
|
|
226
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function optionValueOf(o: unknown): string {
|
|
230
|
+
return typeof o === "object" && o !== null && "value" in o
|
|
231
|
+
? String((o as { value: unknown }).value)
|
|
232
|
+
: String(o);
|
|
233
|
+
}
|
|
234
|
+
function optionLabelOf(o: unknown): string {
|
|
235
|
+
return typeof o === "object" && o !== null && "label" in o
|
|
236
|
+
? String((o as { label: unknown }).label)
|
|
237
|
+
: String(o);
|
|
238
|
+
}
|
|
128
239
|
const hasHorizontalOverflow = $derived(ctrl.hasHorizontalOverflow);
|
|
129
240
|
const columnWindowStart = $derived(ctrl.columnWindowStart);
|
|
130
241
|
const columnWindowRightSpacer = $derived(ctrl.columnWindowRightSpacer);
|
|
@@ -170,10 +281,16 @@
|
|
|
170
281
|
const focusOnMount = $derived(ctrl.focusOnMount);
|
|
171
282
|
const onHeaderSortClick = $derived(ctrl.onHeaderSortClick);
|
|
172
283
|
const onGridKeyDown = $derived(ctrl.onGridKeyDown);
|
|
284
|
+
const onGridPaste = $derived(ctrl.onGridPaste);
|
|
173
285
|
const changePage = $derived(ctrl.changePage);
|
|
174
286
|
const goToPage = $derived(ctrl.goToPage);
|
|
175
287
|
const setPageSize = $derived(ctrl.setPageSize);
|
|
176
288
|
const updateFilterRow = $derived(ctrl.updateFilterRow);
|
|
289
|
+
const updateFilterMenuValue = $derived(ctrl.updateFilterMenuValue);
|
|
290
|
+
const updateFilterMenuValueTo = $derived(ctrl.updateFilterMenuValueTo);
|
|
291
|
+
const updateFilterOperator = $derived(ctrl.updateFilterOperator);
|
|
292
|
+
const operatorsForColumn = $derived(ctrl.operatorsForColumn);
|
|
293
|
+
const clearColumnFilter = $derived(ctrl.clearColumnFilter);
|
|
177
294
|
const toggleCheckboxWithKeyboard = $derived(ctrl.toggleCheckboxWithKeyboard);
|
|
178
295
|
const operatorOption = $derived(ctrl.operatorOption);
|
|
179
296
|
const defaultOperatorFor = $derived(ctrl.defaultOperatorFor);
|
|
@@ -441,7 +558,10 @@
|
|
|
441
558
|
<!-- Custom editor slot. The columnDef provides a snippet that
|
|
442
559
|
receives the editor context (value + commit + cancel) so the
|
|
443
560
|
consumer fully owns the in-cell UI. -->
|
|
444
|
-
{@const customEditor = column.columnDef
|
|
561
|
+
{@const customEditor = column.columnDef
|
|
562
|
+
.cellEditor as unknown as import("svelte").Snippet<
|
|
563
|
+
[EditorContext<TData>]
|
|
564
|
+
>}
|
|
445
565
|
{@render customEditor({
|
|
446
566
|
cell: row.getAllCells().find((c) => c.column.id === column.id)!,
|
|
447
567
|
row,
|
|
@@ -452,19 +572,23 @@
|
|
|
452
572
|
update: (next: unknown) => {
|
|
453
573
|
// Stage the draft without closing. Live-preview controls
|
|
454
574
|
// (sliders, color pickers) call this on every input tick.
|
|
455
|
-
ctrl.editingCell = ctrl.editingCell
|
|
575
|
+
ctrl.editingCell = ctrl.editingCell
|
|
576
|
+
? { ...ctrl.editingCell, value: next }
|
|
577
|
+
: ctrl.editingCell;
|
|
456
578
|
},
|
|
457
579
|
commit: (next?: unknown) => {
|
|
458
580
|
// Write + close. If the caller passed a value, stage it
|
|
459
581
|
// first; otherwise save whatever update() last wrote.
|
|
460
582
|
if (next !== undefined) {
|
|
461
|
-
ctrl.editingCell = ctrl.editingCell
|
|
583
|
+
ctrl.editingCell = ctrl.editingCell
|
|
584
|
+
? { ...ctrl.editingCell, value: next }
|
|
585
|
+
: ctrl.editingCell;
|
|
462
586
|
}
|
|
463
|
-
saveEditingCell()
|
|
587
|
+
saveEditingCell();
|
|
464
588
|
},
|
|
465
589
|
cancel: () => {
|
|
466
|
-
ctrl.editingCell = null
|
|
467
|
-
ctrl.gridRootEl?.focus({ preventScroll: true })
|
|
590
|
+
ctrl.editingCell = null;
|
|
591
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
468
592
|
},
|
|
469
593
|
})}
|
|
470
594
|
{:else if ctrl.editingCell?.editorType === "checkbox"}
|
|
@@ -518,12 +642,11 @@
|
|
|
518
642
|
{@const selectedArr = toValueArray(ctrl.editingCell?.value)}
|
|
519
643
|
{@render chipsEditor(opts, multi, selectedArr)}
|
|
520
644
|
{:else if ctrl.editingCell?.editorType === "rating"}
|
|
521
|
-
{@const ratingVal = Math.max(
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
>
|
|
645
|
+
{@const ratingVal = Math.max(
|
|
646
|
+
0,
|
|
647
|
+
Math.min(5, Math.round(Number(ctrl.editingCell?.value) || 0)),
|
|
648
|
+
)}
|
|
649
|
+
<span class="sv-grid-rating-editor" role="radiogroup" aria-label="Rating">
|
|
527
650
|
{#each [1, 2, 3, 4, 5] as n (n)}
|
|
528
651
|
<button
|
|
529
652
|
type="button"
|
|
@@ -539,8 +662,8 @@
|
|
|
539
662
|
: ctrl.editingCell;
|
|
540
663
|
saveEditingCell();
|
|
541
664
|
}}
|
|
542
|
-
onkeydown={onEditorKeyDown}
|
|
543
|
-
|
|
665
|
+
onkeydown={onEditorKeyDown}>★</button
|
|
666
|
+
>
|
|
544
667
|
{/each}
|
|
545
668
|
<button
|
|
546
669
|
type="button"
|
|
@@ -553,8 +676,8 @@
|
|
|
553
676
|
? { ...ctrl.editingCell, value: 0 }
|
|
554
677
|
: ctrl.editingCell;
|
|
555
678
|
saveEditingCell();
|
|
556
|
-
}}
|
|
557
|
-
|
|
679
|
+
}}>×</button
|
|
680
|
+
>
|
|
558
681
|
</span>
|
|
559
682
|
{:else if ctrl.editingCell?.editorType === "select"}
|
|
560
683
|
<!-- Custom dropdown: opens a themed popover identical in feel to
|
|
@@ -612,18 +735,21 @@
|
|
|
612
735
|
(event.currentTarget as HTMLTextAreaElement).value,
|
|
613
736
|
)}
|
|
614
737
|
onkeydown={(event) => {
|
|
615
|
-
event.stopPropagation()
|
|
738
|
+
event.stopPropagation();
|
|
616
739
|
if (event.key === "Escape") {
|
|
617
|
-
event.preventDefault()
|
|
618
|
-
ctrl.editingCell = null
|
|
619
|
-
ctrl.gridRootEl?.focus({ preventScroll: true })
|
|
620
|
-
return
|
|
740
|
+
event.preventDefault();
|
|
741
|
+
ctrl.editingCell = null;
|
|
742
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
743
|
+
return;
|
|
621
744
|
}
|
|
622
745
|
// Tab and Ctrl/Cmd+Enter both commit. Plain Enter inserts a newline.
|
|
623
|
-
if (
|
|
624
|
-
event.
|
|
625
|
-
|
|
626
|
-
|
|
746
|
+
if (
|
|
747
|
+
event.key === "Tab" ||
|
|
748
|
+
(event.key === "Enter" && (event.ctrlKey || event.metaKey))
|
|
749
|
+
) {
|
|
750
|
+
event.preventDefault();
|
|
751
|
+
saveEditingCell();
|
|
752
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
627
753
|
}
|
|
628
754
|
}}
|
|
629
755
|
onblur={() => saveEditingCell()}
|
|
@@ -667,8 +793,8 @@
|
|
|
667
793
|
? { ...ctrl.editingCell, value: opt.value }
|
|
668
794
|
: ctrl.editingCell;
|
|
669
795
|
saveEditingCell();
|
|
670
|
-
}}
|
|
671
|
-
>
|
|
796
|
+
}}>{opt.label}</button
|
|
797
|
+
>
|
|
672
798
|
{/each}
|
|
673
799
|
</div>
|
|
674
800
|
{/if}
|
|
@@ -716,6 +842,63 @@
|
|
|
716
842
|
{/if}
|
|
717
843
|
{/snippet}
|
|
718
844
|
|
|
845
|
+
<!-- Full-row editor: a lightweight inline editor per editable cell, shown
|
|
846
|
+
for every editable column of the row in full-row edit. Kept separate
|
|
847
|
+
from `editorBody` (which owns the 14 rich single-cell editors) so
|
|
848
|
+
cell editing is untouched. Covers the common editor types. -->
|
|
849
|
+
{#snippet fullRowEditor(column: Column<TData>, row: Row<TData>)}
|
|
850
|
+
{@const et = column.columnDef.editorType ?? "text"}
|
|
851
|
+
{@const val = fullRowEdit?.draft[column.id]}
|
|
852
|
+
{#if et === "checkbox"}
|
|
853
|
+
<input
|
|
854
|
+
type="checkbox"
|
|
855
|
+
class="sv-grid-fr-editor sv-grid-fr-checkbox"
|
|
856
|
+
checked={Boolean(val)}
|
|
857
|
+
onchange={(e) =>
|
|
858
|
+
ctrl.setFullRowDraft(column.id, e.currentTarget.checked)}
|
|
859
|
+
onkeydown={fullRowKeydown}
|
|
860
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
861
|
+
onclick={(e) => e.stopPropagation()}
|
|
862
|
+
/>
|
|
863
|
+
{:else if et === "list" || et === "select" || et === "rich-select"}
|
|
864
|
+
{@const opts = getColumnEditorOptions(column, row)}
|
|
865
|
+
<select
|
|
866
|
+
class="sv-grid-cell-editor sv-grid-fr-editor"
|
|
867
|
+
value={String(val ?? "")}
|
|
868
|
+
onchange={(e) => ctrl.setFullRowDraft(column.id, e.currentTarget.value)}
|
|
869
|
+
onkeydown={fullRowKeydown}
|
|
870
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
871
|
+
onclick={(e) => e.stopPropagation()}
|
|
872
|
+
>
|
|
873
|
+
{#each opts as o (optionValueOf(o))}
|
|
874
|
+
<option value={optionValueOf(o)}>{optionLabelOf(o)}</option>
|
|
875
|
+
{/each}
|
|
876
|
+
</select>
|
|
877
|
+
{:else}
|
|
878
|
+
{@const inputType =
|
|
879
|
+
et === "number"
|
|
880
|
+
? "number"
|
|
881
|
+
: et === "date"
|
|
882
|
+
? "date"
|
|
883
|
+
: et === "datetime"
|
|
884
|
+
? "datetime-local"
|
|
885
|
+
: et === "time"
|
|
886
|
+
? "time"
|
|
887
|
+
: et === "password"
|
|
888
|
+
? "password"
|
|
889
|
+
: "text"}
|
|
890
|
+
<input
|
|
891
|
+
type={inputType}
|
|
892
|
+
class="sv-grid-cell-editor sv-grid-fr-editor"
|
|
893
|
+
value={String(val ?? "")}
|
|
894
|
+
oninput={(e) => ctrl.setFullRowDraft(column.id, e.currentTarget.value)}
|
|
895
|
+
onkeydown={fullRowKeydown}
|
|
896
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
897
|
+
onclick={(e) => e.stopPropagation()}
|
|
898
|
+
/>
|
|
899
|
+
{/if}
|
|
900
|
+
{/snippet}
|
|
901
|
+
|
|
719
902
|
{#snippet chipsEditor(
|
|
720
903
|
opts: CellEditorOption[],
|
|
721
904
|
multi: boolean,
|
|
@@ -899,7 +1082,11 @@
|
|
|
899
1082
|
inline editing, no row-selection checkbox, no fill handle.
|
|
900
1083
|
Position-sticky CSS keeps it anchored to the top of the body or
|
|
901
1084
|
the bottom of the viewport while the rest scrolls. -->
|
|
902
|
-
{#snippet pinnedRowBody(
|
|
1085
|
+
{#snippet pinnedRowBody(
|
|
1086
|
+
rowData: TData,
|
|
1087
|
+
where: "top" | "bottom",
|
|
1088
|
+
index: number,
|
|
1089
|
+
)}
|
|
903
1090
|
<tr
|
|
904
1091
|
class={`sv-grid-row sv-grid-pinned-row sv-grid-pinned-row-${where}`}
|
|
905
1092
|
data-pinned-row={where}
|
|
@@ -909,7 +1096,8 @@
|
|
|
909
1096
|
<td
|
|
910
1097
|
class="sv-grid-cell sv-grid-row-number-cell"
|
|
911
1098
|
style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
|
|
912
|
-
|
|
1099
|
+
>{where === "top" ? "↑" : "↓"}</td
|
|
1100
|
+
>
|
|
913
1101
|
{/if}
|
|
914
1102
|
{#if showRowSelectionEffective}
|
|
915
1103
|
<td
|
|
@@ -926,13 +1114,17 @@
|
|
|
926
1114
|
{/if}
|
|
927
1115
|
{#each renderedColumns as rendered (rendered.column.id)}
|
|
928
1116
|
{@const value = getPinnedCellValue(rowData, rendered.column)}
|
|
929
|
-
{@const userCellClass = computePinnedCellClass(
|
|
1117
|
+
{@const userCellClass = computePinnedCellClass(
|
|
1118
|
+
rowData,
|
|
1119
|
+
rendered.column,
|
|
1120
|
+
)}
|
|
930
1121
|
<td
|
|
931
1122
|
class={`sv-grid-cell ${userCellClass}`}
|
|
932
1123
|
data-col-id={rendered.column.id}
|
|
933
1124
|
data-pinned={isColumnPinned(rendered.column.id) ?? undefined}
|
|
934
1125
|
style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
|
|
935
|
-
|
|
1126
|
+
>{formatPinnedValue(rendered.column, value)}</td
|
|
1127
|
+
>
|
|
936
1128
|
{/each}
|
|
937
1129
|
{#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
|
|
938
1130
|
<td
|
|
@@ -955,13 +1147,45 @@
|
|
|
955
1147
|
</label>
|
|
956
1148
|
{/if}
|
|
957
1149
|
|
|
1150
|
+
{#if toolPanelEnabled}
|
|
1151
|
+
<div class="sv-grid-toolbar">
|
|
1152
|
+
<button
|
|
1153
|
+
type="button"
|
|
1154
|
+
class="sv-grid-toolbar-btn"
|
|
1155
|
+
class:is-active={ctrl.toolPanelOpen}
|
|
1156
|
+
aria-label={ctrl.toolPanelOpen
|
|
1157
|
+
? "Close tool panel"
|
|
1158
|
+
: "Open tool panel (columns & filters)"}
|
|
1159
|
+
aria-expanded={ctrl.toolPanelOpen}
|
|
1160
|
+
onclick={() => (ctrl.toolPanelOpen = !ctrl.toolPanelOpen)}
|
|
1161
|
+
>
|
|
1162
|
+
<svg
|
|
1163
|
+
viewBox="0 0 24 24"
|
|
1164
|
+
width="15"
|
|
1165
|
+
height="15"
|
|
1166
|
+
fill="none"
|
|
1167
|
+
stroke="currentColor"
|
|
1168
|
+
stroke-width="2"
|
|
1169
|
+
stroke-linecap="round"
|
|
1170
|
+
stroke-linejoin="round"
|
|
1171
|
+
aria-hidden="true"
|
|
1172
|
+
>
|
|
1173
|
+
<rect x="3" y="4" width="6" height="16" rx="1" />
|
|
1174
|
+
<rect x="11" y="4" width="4" height="16" rx="1" />
|
|
1175
|
+
<rect x="17" y="4" width="4" height="16" rx="1" />
|
|
1176
|
+
</svg>
|
|
1177
|
+
Columns & Filters
|
|
1178
|
+
</button>
|
|
1179
|
+
</div>
|
|
1180
|
+
{/if}
|
|
1181
|
+
|
|
958
1182
|
<div
|
|
959
1183
|
class="sv-grid-shell"
|
|
960
1184
|
style={`height: ${
|
|
961
1185
|
typeof props.containerHeight === "string"
|
|
962
1186
|
? props.containerHeight
|
|
963
1187
|
: `${props.containerHeight ?? 520}px`
|
|
964
|
-
}; --sg-thead-h: ${headerHeight}px; --sg-pinned-row-h: ${
|
|
1188
|
+
}; --sg-thead-h: ${headerHeight}px; --sg-pinned-row-h: ${typeof props.rowHeight === "number" ? props.rowHeight : 30}px;`}
|
|
965
1189
|
>
|
|
966
1190
|
<div
|
|
967
1191
|
class="sv-grid-container sv-grid-container-custom-scrollbars"
|
|
@@ -978,6 +1202,7 @@
|
|
|
978
1202
|
colCount: allColumns.length,
|
|
979
1203
|
})}
|
|
980
1204
|
onkeydown={onGridKeyDown}
|
|
1205
|
+
onpaste={onGridPaste}
|
|
981
1206
|
style={`min-width: ${totalColumnWidth}px;`}
|
|
982
1207
|
>
|
|
983
1208
|
<!-- svelte-ignore a11y_no_redundant_roles -->
|
|
@@ -1016,7 +1241,43 @@
|
|
|
1016
1241
|
style={`width: ${cell.widthPx}px; min-width: ${cell.widthPx}px; max-width: ${cell.widthPx}px;`}
|
|
1017
1242
|
>
|
|
1018
1243
|
{#if !cell.isPlaceholder}
|
|
1019
|
-
|
|
1244
|
+
{#if cell.collapsible}
|
|
1245
|
+
<button
|
|
1246
|
+
type="button"
|
|
1247
|
+
class="sv-grid-group-toggle"
|
|
1248
|
+
class:is-collapsed={cell.collapsed}
|
|
1249
|
+
aria-expanded={!cell.collapsed}
|
|
1250
|
+
aria-label={cell.collapsed
|
|
1251
|
+
? `Expand ${cell.label}`
|
|
1252
|
+
: `Collapse ${cell.label}`}
|
|
1253
|
+
title={cell.collapsed
|
|
1254
|
+
? "Expand group"
|
|
1255
|
+
: "Collapse group"}
|
|
1256
|
+
onclick={() => ctrl.toggleColumnGroup(cell.groupId!)}
|
|
1257
|
+
>
|
|
1258
|
+
<span class="sv-grid-group-header-label"
|
|
1259
|
+
>{cell.label}</span
|
|
1260
|
+
>
|
|
1261
|
+
<svg
|
|
1262
|
+
class="sv-grid-group-caret"
|
|
1263
|
+
viewBox="0 0 16 16"
|
|
1264
|
+
width="10"
|
|
1265
|
+
height="10"
|
|
1266
|
+
fill="none"
|
|
1267
|
+
stroke="currentColor"
|
|
1268
|
+
stroke-width="2.5"
|
|
1269
|
+
stroke-linecap="round"
|
|
1270
|
+
stroke-linejoin="round"
|
|
1271
|
+
aria-hidden="true"
|
|
1272
|
+
>
|
|
1273
|
+
<polyline points="4 6 8 10 12 6"></polyline>
|
|
1274
|
+
</svg>
|
|
1275
|
+
</button>
|
|
1276
|
+
{:else}
|
|
1277
|
+
<span class="sv-grid-group-header-label"
|
|
1278
|
+
>{cell.label}</span
|
|
1279
|
+
>
|
|
1280
|
+
{/if}
|
|
1020
1281
|
{/if}
|
|
1021
1282
|
</th>
|
|
1022
1283
|
{/each}
|
|
@@ -1077,14 +1338,18 @@
|
|
|
1077
1338
|
)}
|
|
1078
1339
|
<th
|
|
1079
1340
|
class="sv-grid-column"
|
|
1080
|
-
class:is-drag-target-before={colDropOnId ===
|
|
1081
|
-
|
|
1341
|
+
class:is-drag-target-before={colDropOnId ===
|
|
1342
|
+
header.column.id && colDropSide === "before"}
|
|
1343
|
+
class:is-drag-target-after={colDropOnId ===
|
|
1344
|
+
header.column.id && colDropSide === "after"}
|
|
1082
1345
|
class:is-dragging={colDragId === header.column.id}
|
|
1083
1346
|
data-svgrid-header-col={header.column.id}
|
|
1084
1347
|
data-align={getColumnAlign(rendered.column)}
|
|
1085
1348
|
data-pinned={isColumnPinned(rendered.column.id) ??
|
|
1086
1349
|
undefined}
|
|
1087
|
-
draggable={(props.enableColumnReorder ?? false)
|
|
1350
|
+
draggable={(props.enableColumnReorder ?? false)
|
|
1351
|
+
? true
|
|
1352
|
+
: undefined}
|
|
1088
1353
|
ondragstart={(e) =>
|
|
1089
1354
|
(props.enableColumnReorder ?? false) &&
|
|
1090
1355
|
onColumnHeaderDragStart(e, header.column.id)}
|
|
@@ -1114,7 +1379,9 @@
|
|
|
1114
1379
|
{#if !header.isPlaceholder}
|
|
1115
1380
|
<div class="sv-grid-header-cell">
|
|
1116
1381
|
{#if typeof header.column.columnDef.header === "function"}
|
|
1117
|
-
{@const rendered = header.column.columnDef.header(
|
|
1382
|
+
{@const rendered = header.column.columnDef.header(
|
|
1383
|
+
header.getContext(),
|
|
1384
|
+
)}
|
|
1118
1385
|
<!-- Custom header (snippet/component): rendered
|
|
1119
1386
|
OUTSIDE the sort button so the consumer's
|
|
1120
1387
|
own interactive elements (menu buttons,
|
|
@@ -1127,20 +1394,30 @@
|
|
|
1127
1394
|
role="button"
|
|
1128
1395
|
tabindex="-1"
|
|
1129
1396
|
onclick={(event) => {
|
|
1130
|
-
if (!header.column.getCanSort()) return
|
|
1397
|
+
if (!header.column.getCanSort()) return;
|
|
1131
1398
|
// If the click landed on an interactive
|
|
1132
1399
|
// element inside the custom header, let
|
|
1133
1400
|
// that element handle it.
|
|
1134
|
-
const t = event.target as HTMLElement | null
|
|
1135
|
-
if (
|
|
1136
|
-
|
|
1137
|
-
|
|
1401
|
+
const t = event.target as HTMLElement | null;
|
|
1402
|
+
if (
|
|
1403
|
+
t &&
|
|
1404
|
+
t.closest(
|
|
1405
|
+
'button, a, input, select, textarea, [role="button"], [role="menuitem"]',
|
|
1406
|
+
) &&
|
|
1407
|
+
!t.classList.contains("sv-grid-header-custom")
|
|
1408
|
+
)
|
|
1409
|
+
return;
|
|
1410
|
+
onHeaderSortClick(event, header.column.id);
|
|
1138
1411
|
}}
|
|
1139
1412
|
onkeydown={(event) => {
|
|
1140
|
-
if (event.key !==
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1413
|
+
if (event.key !== "Enter" && event.key !== " ")
|
|
1414
|
+
return;
|
|
1415
|
+
if (!header.column.getCanSort()) return;
|
|
1416
|
+
event.preventDefault();
|
|
1417
|
+
onHeaderSortClick(
|
|
1418
|
+
event as unknown as MouseEvent,
|
|
1419
|
+
header.column.id,
|
|
1420
|
+
);
|
|
1144
1421
|
}}
|
|
1145
1422
|
>
|
|
1146
1423
|
{#if rendered instanceof RenderSnippetConfig}
|
|
@@ -1155,10 +1432,12 @@
|
|
|
1155
1432
|
{#if header.column.getCanSort()}
|
|
1156
1433
|
{#if sortDirection === "asc"}
|
|
1157
1434
|
<span class="sv-grid-header-icon"
|
|
1158
|
-
>{@render icon("sort-asc")}</span
|
|
1435
|
+
>{@render icon("sort-asc")}</span
|
|
1436
|
+
>
|
|
1159
1437
|
{:else if sortDirection === "desc"}
|
|
1160
1438
|
<span class="sv-grid-header-icon"
|
|
1161
|
-
>{@render icon("sort-desc")}</span
|
|
1439
|
+
>{@render icon("sort-desc")}</span
|
|
1440
|
+
>
|
|
1162
1441
|
{/if}
|
|
1163
1442
|
{/if}
|
|
1164
1443
|
</div>
|
|
@@ -1170,7 +1449,8 @@
|
|
|
1170
1449
|
onHeaderSortClick(event, header.column.id)}
|
|
1171
1450
|
>
|
|
1172
1451
|
<span class="sv-grid-header-label">
|
|
1173
|
-
{typeof header.column.columnDef.header ===
|
|
1452
|
+
{typeof header.column.columnDef.header ===
|
|
1453
|
+
"string"
|
|
1174
1454
|
? header.column.columnDef.header
|
|
1175
1455
|
: header.id}
|
|
1176
1456
|
</span>
|
|
@@ -1325,9 +1605,15 @@
|
|
|
1325
1605
|
>
|
|
1326
1606
|
</button>
|
|
1327
1607
|
{#if activeOperator !== "isBlank"}
|
|
1608
|
+
{@const frType = getEditorInputType(
|
|
1609
|
+
rendered.column.columnDef.editorType ?? "text",
|
|
1610
|
+
)}
|
|
1328
1611
|
<input
|
|
1329
1612
|
class="sv-grid-filter-value"
|
|
1330
|
-
|
|
1613
|
+
type={frType}
|
|
1614
|
+
placeholder={activeOperator === "between"
|
|
1615
|
+
? "From"
|
|
1616
|
+
: "Filter…"}
|
|
1331
1617
|
data-svgrid-filter-col={rendered.column.id}
|
|
1332
1618
|
value={filterRowValues[rendered.column.id] ?? ""}
|
|
1333
1619
|
oninput={(event) =>
|
|
@@ -1336,6 +1622,21 @@
|
|
|
1336
1622
|
(event.currentTarget as HTMLInputElement).value,
|
|
1337
1623
|
)}
|
|
1338
1624
|
/>
|
|
1625
|
+
{#if activeOperator === "between"}
|
|
1626
|
+
<input
|
|
1627
|
+
class="sv-grid-filter-value sv-grid-filter-value-to"
|
|
1628
|
+
type={frType}
|
|
1629
|
+
placeholder="To"
|
|
1630
|
+
value={filterMenuValues[rendered.column.id]
|
|
1631
|
+
?.valueTo ?? ""}
|
|
1632
|
+
oninput={(event) =>
|
|
1633
|
+
updateFilterMenuValueTo(
|
|
1634
|
+
rendered.column.id,
|
|
1635
|
+
(event.currentTarget as HTMLInputElement)
|
|
1636
|
+
.value,
|
|
1637
|
+
)}
|
|
1638
|
+
/>
|
|
1639
|
+
{/if}
|
|
1339
1640
|
{/if}
|
|
1340
1641
|
</div>
|
|
1341
1642
|
</th>
|
|
@@ -1353,14 +1654,24 @@
|
|
|
1353
1654
|
</thead>
|
|
1354
1655
|
{#if props.pinnedTopRows && props.pinnedTopRows.length > 0}
|
|
1355
1656
|
<!-- svelte-ignore a11y_no_redundant_roles -->
|
|
1356
|
-
<tbody
|
|
1657
|
+
<tbody
|
|
1658
|
+
class="sv-grid-pinned sv-grid-pinned-top-body"
|
|
1659
|
+
role="rowgroup"
|
|
1660
|
+
>
|
|
1357
1661
|
{#each props.pinnedTopRows as r, i (i)}
|
|
1358
1662
|
{@render pinnedRowBody(r, "top", i)}
|
|
1359
1663
|
{/each}
|
|
1360
1664
|
</tbody>
|
|
1361
1665
|
{/if}
|
|
1362
1666
|
<!-- svelte-ignore a11y_no_redundant_roles -->
|
|
1363
|
-
<tbody
|
|
1667
|
+
<tbody
|
|
1668
|
+
class="sv-grid-body"
|
|
1669
|
+
role="rowgroup"
|
|
1670
|
+
ondragover={rowDragManagedEffective
|
|
1671
|
+
? onRowsContainerDragOver
|
|
1672
|
+
: undefined}
|
|
1673
|
+
ondrop={rowDragManagedEffective ? onRowsContainerDrop : undefined}
|
|
1674
|
+
>
|
|
1364
1675
|
{#if !allRows.length && !(props.loading && props.loadingOverlay)}
|
|
1365
1676
|
<tr class="sv-grid-row sv-grid-empty-row">
|
|
1366
1677
|
<td
|
|
@@ -1414,9 +1725,13 @@
|
|
|
1414
1725
|
{:else}
|
|
1415
1726
|
{@const userRowClass = computeRowClass(row, rowIndex)}
|
|
1416
1727
|
<tr
|
|
1417
|
-
class={`sv-grid-row ${userRowClass}`}
|
|
1728
|
+
class={`sv-grid-row ${userRowClass} ${rowDropClass(rowIndex)}`}
|
|
1418
1729
|
class:sv-grid-row-selected={isRowSelected(row.id)}
|
|
1730
|
+
class:sv-grid-row-alt={props.zebraRows &&
|
|
1731
|
+
rowIndex % 2 === 1}
|
|
1732
|
+
class:sv-grid-row-draggable={rowDragManagedEffective}
|
|
1419
1733
|
{...getGridRowA11yProps(rowIndex + 1)}
|
|
1734
|
+
{...rowDragAttrs(rowIndex)}
|
|
1420
1735
|
style={`height: ${rowItem.size}px;`}
|
|
1421
1736
|
>
|
|
1422
1737
|
{#if showRowNumbersEffective}
|
|
@@ -1471,6 +1786,10 @@
|
|
|
1471
1786
|
{@const isEditing =
|
|
1472
1787
|
ctrl.editingCell?.rowId === row.id &&
|
|
1473
1788
|
ctrl.editingCell?.columnId === rendered.column.id}
|
|
1789
|
+
{@const inRowEdit =
|
|
1790
|
+
!!fullRowEdit &&
|
|
1791
|
+
fullRowEdit.rowId === row.id &&
|
|
1792
|
+
rendered.column.id in fullRowEdit.draft}
|
|
1474
1793
|
{@const rangeEdges = getCellRangeEdges(
|
|
1475
1794
|
rowIndex,
|
|
1476
1795
|
colIndex,
|
|
@@ -1479,12 +1798,21 @@
|
|
|
1479
1798
|
fillHandleCell &&
|
|
1480
1799
|
fillHandleCell.rowIndex === rowIndex &&
|
|
1481
1800
|
fillHandleCell.colIndex === colIndex}
|
|
1482
|
-
{@const userCellClass = computeCellClass(
|
|
1483
|
-
|
|
1484
|
-
|
|
1801
|
+
{@const userCellClass = computeCellClass(
|
|
1802
|
+
row,
|
|
1803
|
+
rendered.column,
|
|
1804
|
+
)}
|
|
1805
|
+
{@const cellTooltip = computeCellTooltip(
|
|
1806
|
+
row,
|
|
1807
|
+
rendered.column,
|
|
1808
|
+
)}
|
|
1809
|
+
{@const cellNote = computeCellNote(
|
|
1810
|
+
row,
|
|
1811
|
+
rendered.column,
|
|
1812
|
+
)}
|
|
1485
1813
|
<td
|
|
1486
1814
|
class={`sv-grid-cell ${userCellClass}`}
|
|
1487
|
-
class:sv-grid-cell-editing={isEditing}
|
|
1815
|
+
class:sv-grid-cell-editing={isEditing || inRowEdit}
|
|
1488
1816
|
class:sv-grid-cell-active={activeCell.rowIndex ===
|
|
1489
1817
|
rowIndex && activeCell.colIndex === colIndex}
|
|
1490
1818
|
class:sv-grid-cell-has-fill-handle={hasFillHandle}
|
|
@@ -1519,7 +1847,20 @@
|
|
|
1519
1847
|
emitCellDoubleClick(rowIndex, colIndex)}
|
|
1520
1848
|
onclick={() => onCellClick(rowIndex, colIndex)}
|
|
1521
1849
|
oncontextmenu={(event) =>
|
|
1522
|
-
openContextMenu(
|
|
1850
|
+
openContextMenu(
|
|
1851
|
+
event,
|
|
1852
|
+
rowIndex,
|
|
1853
|
+
colIndex,
|
|
1854
|
+
rendered.column.id,
|
|
1855
|
+
)}
|
|
1856
|
+
use:cellFlashAction={{
|
|
1857
|
+
rowId: row.id,
|
|
1858
|
+
value: cellValue,
|
|
1859
|
+
active: !!rendered.column.columnDef.cellFlash,
|
|
1860
|
+
className: flashClassFor(
|
|
1861
|
+
rendered.column.columnDef.cellFlash,
|
|
1862
|
+
),
|
|
1863
|
+
}}
|
|
1523
1864
|
{...getGridCellA11yProps({
|
|
1524
1865
|
id: getGridCellDomId("svgrid", rowIndex, colIndex),
|
|
1525
1866
|
rowIndex: rowIndex + 1,
|
|
@@ -1527,10 +1868,16 @@
|
|
|
1527
1868
|
selected: isRowSelected(row.id),
|
|
1528
1869
|
})}
|
|
1529
1870
|
>
|
|
1530
|
-
{#if
|
|
1871
|
+
{#if inRowEdit}
|
|
1872
|
+
{@render fullRowEditor(rendered.column, row)}
|
|
1873
|
+
{:else if isEditing}
|
|
1531
1874
|
{@render editorBody(rendered.column, row)}
|
|
1532
1875
|
{:else}
|
|
1533
|
-
{@render cellBodyWithFormat(
|
|
1876
|
+
{@render cellBodyWithFormat(
|
|
1877
|
+
row,
|
|
1878
|
+
rendered.column,
|
|
1879
|
+
cellValue,
|
|
1880
|
+
)}
|
|
1534
1881
|
{/if}
|
|
1535
1882
|
{#if !isEditing && fillHandleCell && fillHandleCell.rowIndex === rowIndex && fillHandleCell.colIndex === colIndex}
|
|
1536
1883
|
<!-- Excel-style fill handle: drag down/right to
|
|
@@ -1551,12 +1898,15 @@
|
|
|
1551
1898
|
class="sv-grid-cell-note-corner"
|
|
1552
1899
|
aria-label="Note"
|
|
1553
1900
|
onpointerenter={(event) => {
|
|
1554
|
-
event.stopPropagation()
|
|
1555
|
-
showTooltipFor(
|
|
1901
|
+
event.stopPropagation();
|
|
1902
|
+
showTooltipFor(
|
|
1903
|
+
event.currentTarget as HTMLElement,
|
|
1904
|
+
cellNote,
|
|
1905
|
+
);
|
|
1556
1906
|
}}
|
|
1557
1907
|
onpointerleave={(event) => {
|
|
1558
|
-
event.stopPropagation()
|
|
1559
|
-
hideTooltip()
|
|
1908
|
+
event.stopPropagation();
|
|
1909
|
+
hideTooltip();
|
|
1560
1910
|
}}
|
|
1561
1911
|
></span>
|
|
1562
1912
|
{/if}
|
|
@@ -1611,9 +1961,13 @@
|
|
|
1611
1961
|
{:else}
|
|
1612
1962
|
{@const userRowClass = computeRowClass(row, rowIndex)}
|
|
1613
1963
|
<tr
|
|
1614
|
-
class={`sv-grid-row ${userRowClass}`}
|
|
1964
|
+
class={`sv-grid-row ${userRowClass} ${rowDropClass(rowIndex)}`}
|
|
1615
1965
|
class:sv-grid-row-selected={isRowSelected(row.id)}
|
|
1966
|
+
class:sv-grid-row-alt={props.zebraRows &&
|
|
1967
|
+
rowIndex % 2 === 1}
|
|
1968
|
+
class:sv-grid-row-draggable={rowDragManagedEffective}
|
|
1616
1969
|
{...getGridRowA11yProps(rowIndex + 1)}
|
|
1970
|
+
{...rowDragAttrs(rowIndex)}
|
|
1617
1971
|
>
|
|
1618
1972
|
{#if showRowNumbersEffective}
|
|
1619
1973
|
<td
|
|
@@ -1667,16 +2021,26 @@
|
|
|
1667
2021
|
{@const isEditing =
|
|
1668
2022
|
ctrl.editingCell?.rowId === row.id &&
|
|
1669
2023
|
ctrl.editingCell?.columnId === rendered.column.id}
|
|
2024
|
+
{@const inRowEdit =
|
|
2025
|
+
!!fullRowEdit &&
|
|
2026
|
+
fullRowEdit.rowId === row.id &&
|
|
2027
|
+
rendered.column.id in fullRowEdit.draft}
|
|
1670
2028
|
{@const rangeEdges = getCellRangeEdges(
|
|
1671
2029
|
rowIndex,
|
|
1672
2030
|
colIndex,
|
|
1673
2031
|
)}
|
|
1674
|
-
{@const userCellClass = computeCellClass(
|
|
1675
|
-
|
|
1676
|
-
|
|
2032
|
+
{@const userCellClass = computeCellClass(
|
|
2033
|
+
row,
|
|
2034
|
+
rendered.column,
|
|
2035
|
+
)}
|
|
2036
|
+
{@const cellTooltip = computeCellTooltip(
|
|
2037
|
+
row,
|
|
2038
|
+
rendered.column,
|
|
2039
|
+
)}
|
|
2040
|
+
{@const cellNote = computeCellNote(row, rendered.column)}
|
|
1677
2041
|
<td
|
|
1678
2042
|
class={`sv-grid-cell ${userCellClass}`}
|
|
1679
|
-
class:sv-grid-cell-editing={isEditing}
|
|
2043
|
+
class:sv-grid-cell-editing={isEditing || inRowEdit}
|
|
1680
2044
|
class:sv-grid-cell-active={activeCell.rowIndex ===
|
|
1681
2045
|
rowIndex && activeCell.colIndex === colIndex}
|
|
1682
2046
|
class:sv-grid-cell-cf={hasConditionalFormats}
|
|
@@ -1699,18 +2063,36 @@
|
|
|
1699
2063
|
onpointerdown={(event) =>
|
|
1700
2064
|
onCellPointerDown(rowIndex, colIndex, event)}
|
|
1701
2065
|
onpointerenter={(event) => {
|
|
1702
|
-
onCellPointerEnter(rowIndex, colIndex)
|
|
2066
|
+
onCellPointerEnter(rowIndex, colIndex);
|
|
1703
2067
|
// Column tooltip fires on whole-cell hover.
|
|
1704
2068
|
// Per-cell notes are gated on the corner hot-
|
|
1705
2069
|
// zone below (Excel-style: hover the small
|
|
1706
2070
|
// triangle to read the note).
|
|
1707
|
-
if (cellTooltip)
|
|
2071
|
+
if (cellTooltip)
|
|
2072
|
+
showTooltipFor(
|
|
2073
|
+
event.currentTarget as HTMLElement,
|
|
2074
|
+
cellTooltip,
|
|
2075
|
+
);
|
|
1708
2076
|
}}
|
|
1709
2077
|
onpointerleave={hideTooltip}
|
|
1710
|
-
ondblclick={() =>
|
|
2078
|
+
ondblclick={() =>
|
|
2079
|
+
emitCellDoubleClick(rowIndex, colIndex)}
|
|
1711
2080
|
onclick={() => onCellClick(rowIndex, colIndex)}
|
|
1712
2081
|
oncontextmenu={(event) =>
|
|
1713
|
-
openContextMenu(
|
|
2082
|
+
openContextMenu(
|
|
2083
|
+
event,
|
|
2084
|
+
rowIndex,
|
|
2085
|
+
colIndex,
|
|
2086
|
+
rendered.column.id,
|
|
2087
|
+
)}
|
|
2088
|
+
use:cellFlashAction={{
|
|
2089
|
+
rowId: row.id,
|
|
2090
|
+
value: cellValue,
|
|
2091
|
+
active: !!rendered.column.columnDef.cellFlash,
|
|
2092
|
+
className: flashClassFor(
|
|
2093
|
+
rendered.column.columnDef.cellFlash,
|
|
2094
|
+
),
|
|
2095
|
+
}}
|
|
1714
2096
|
{...getGridCellA11yProps({
|
|
1715
2097
|
id: getGridCellDomId("svgrid", rowIndex, colIndex),
|
|
1716
2098
|
rowIndex: rowIndex + 1,
|
|
@@ -1718,10 +2100,16 @@
|
|
|
1718
2100
|
selected: isRowSelected(row.id),
|
|
1719
2101
|
})}
|
|
1720
2102
|
>
|
|
1721
|
-
{#if
|
|
2103
|
+
{#if inRowEdit}
|
|
2104
|
+
{@render fullRowEditor(rendered.column, row)}
|
|
2105
|
+
{:else if isEditing}
|
|
1722
2106
|
{@render editorBody(rendered.column, row)}
|
|
1723
2107
|
{:else}
|
|
1724
|
-
{@render cellBodyWithFormat(
|
|
2108
|
+
{@render cellBodyWithFormat(
|
|
2109
|
+
row,
|
|
2110
|
+
rendered.column,
|
|
2111
|
+
cellValue,
|
|
2112
|
+
)}
|
|
1725
2113
|
{/if}
|
|
1726
2114
|
{#if cellNote != null && !isEditing}
|
|
1727
2115
|
<!-- Excel-style per-cell note indicator. The
|
|
@@ -1735,12 +2123,15 @@
|
|
|
1735
2123
|
class="sv-grid-cell-note-corner"
|
|
1736
2124
|
aria-label="Note"
|
|
1737
2125
|
onpointerenter={(event) => {
|
|
1738
|
-
event.stopPropagation()
|
|
1739
|
-
showTooltipFor(
|
|
2126
|
+
event.stopPropagation();
|
|
2127
|
+
showTooltipFor(
|
|
2128
|
+
event.currentTarget as HTMLElement,
|
|
2129
|
+
cellNote,
|
|
2130
|
+
);
|
|
1740
2131
|
}}
|
|
1741
2132
|
onpointerleave={(event) => {
|
|
1742
|
-
event.stopPropagation()
|
|
1743
|
-
hideTooltip()
|
|
2133
|
+
event.stopPropagation();
|
|
2134
|
+
hideTooltip();
|
|
1744
2135
|
}}
|
|
1745
2136
|
></span>
|
|
1746
2137
|
{/if}
|
|
@@ -1760,7 +2151,10 @@
|
|
|
1760
2151
|
</tbody>
|
|
1761
2152
|
{#if props.pinnedBottomRows && props.pinnedBottomRows.length > 0}
|
|
1762
2153
|
<!-- svelte-ignore a11y_no_redundant_roles -->
|
|
1763
|
-
<tbody
|
|
2154
|
+
<tbody
|
|
2155
|
+
class="sv-grid-pinned sv-grid-pinned-bottom-body"
|
|
2156
|
+
role="rowgroup"
|
|
2157
|
+
>
|
|
1764
2158
|
{#each props.pinnedBottomRows as r, i (i)}
|
|
1765
2159
|
{@render pinnedRowBody(r, "bottom", i)}
|
|
1766
2160
|
{/each}
|
|
@@ -1862,7 +2256,7 @@
|
|
|
1862
2256
|
content-size={scrollMetrics.scrollHeight ||
|
|
1863
2257
|
rowDomTotalSize + headerHeight}
|
|
1864
2258
|
value={scrollMetrics.scrollTop}
|
|
1865
|
-
step={typeof props.rowHeight ===
|
|
2259
|
+
step={typeof props.rowHeight === "number" ? props.rowHeight : 30}
|
|
1866
2260
|
style={`top: ${headerHeight}px; height: calc(100% - ${headerHeight + (hasHorizontalOverflow ? 16 : 0)}px);`}
|
|
1867
2261
|
></sv-grid-scrollbar>
|
|
1868
2262
|
{/if}
|
|
@@ -1895,8 +2289,23 @@
|
|
|
1895
2289
|
Enter cycles to the next hit; Esc closes. -->
|
|
1896
2290
|
<div class="sv-grid-find" role="search" aria-label="Find in grid">
|
|
1897
2291
|
<svg class="sv-grid-find-icon" viewBox="0 0 16 16" aria-hidden="true">
|
|
1898
|
-
<circle
|
|
1899
|
-
|
|
2292
|
+
<circle
|
|
2293
|
+
cx="7"
|
|
2294
|
+
cy="7"
|
|
2295
|
+
r="4.5"
|
|
2296
|
+
fill="none"
|
|
2297
|
+
stroke="currentColor"
|
|
2298
|
+
stroke-width="1.5"
|
|
2299
|
+
/>
|
|
2300
|
+
<line
|
|
2301
|
+
x1="10.2"
|
|
2302
|
+
y1="10.2"
|
|
2303
|
+
x2="14"
|
|
2304
|
+
y2="14"
|
|
2305
|
+
stroke="currentColor"
|
|
2306
|
+
stroke-width="1.5"
|
|
2307
|
+
stroke-linecap="round"
|
|
2308
|
+
/>
|
|
1900
2309
|
</svg>
|
|
1901
2310
|
<input
|
|
1902
2311
|
class="sv-grid-find-input"
|
|
@@ -1906,40 +2315,73 @@
|
|
|
1906
2315
|
bind:value={ctrl.findQuery}
|
|
1907
2316
|
oninput={() => (ctrl.findHitIndex = 0)}
|
|
1908
2317
|
onkeydown={(event) => {
|
|
1909
|
-
event.stopPropagation()
|
|
1910
|
-
if (event.key ===
|
|
1911
|
-
event.preventDefault()
|
|
1912
|
-
if (findHits.length === 0) return
|
|
1913
|
-
ctrl.findHitIndex =
|
|
1914
|
-
|
|
1915
|
-
|
|
2318
|
+
event.stopPropagation();
|
|
2319
|
+
if (event.key === "Enter") {
|
|
2320
|
+
event.preventDefault();
|
|
2321
|
+
if (findHits.length === 0) return;
|
|
2322
|
+
ctrl.findHitIndex =
|
|
2323
|
+
(ctrl.findHitIndex +
|
|
2324
|
+
(event.shiftKey ? -1 : 1) +
|
|
2325
|
+
findHits.length) %
|
|
2326
|
+
findHits.length;
|
|
2327
|
+
const hit = findHits[ctrl.findHitIndex];
|
|
2328
|
+
if (hit) {
|
|
2329
|
+
setActiveCell(hit.rowIndex, hit.colIndex);
|
|
2330
|
+
scrollActiveCellIntoView(hit.rowIndex, hit.colIndex);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
if (event.key === "Escape") {
|
|
2334
|
+
event.preventDefault();
|
|
2335
|
+
ctrl.findOpen = false;
|
|
2336
|
+
ctrl.findQuery = "";
|
|
1916
2337
|
}
|
|
1917
|
-
if (event.key === 'Escape') { event.preventDefault(); ctrl.findOpen = false; ctrl.findQuery = '' }
|
|
1918
2338
|
}}
|
|
1919
2339
|
/>
|
|
1920
2340
|
<span class="sv-grid-find-count">
|
|
1921
2341
|
{findHits.length === 0 && ctrl.findQuery.trim()
|
|
1922
|
-
?
|
|
2342
|
+
? "No matches"
|
|
1923
2343
|
: findHits.length === 0
|
|
1924
|
-
?
|
|
2344
|
+
? ""
|
|
1925
2345
|
: `${ctrl.findHitIndex + 1} of ${findHits.length}`}
|
|
1926
2346
|
</span>
|
|
1927
|
-
<button
|
|
2347
|
+
<button
|
|
2348
|
+
type="button"
|
|
2349
|
+
class="sv-grid-find-step"
|
|
2350
|
+
aria-label="Previous match"
|
|
1928
2351
|
disabled={findHits.length === 0}
|
|
1929
2352
|
onclick={() => {
|
|
1930
|
-
ctrl.findHitIndex =
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
2353
|
+
ctrl.findHitIndex =
|
|
2354
|
+
(ctrl.findHitIndex - 1 + findHits.length) % findHits.length;
|
|
2355
|
+
const hit = findHits[ctrl.findHitIndex];
|
|
2356
|
+
if (hit) {
|
|
2357
|
+
setActiveCell(hit.rowIndex, hit.colIndex);
|
|
2358
|
+
scrollActiveCellIntoView(hit.rowIndex, hit.colIndex);
|
|
2359
|
+
}
|
|
2360
|
+
}}>↑</button
|
|
2361
|
+
>
|
|
2362
|
+
<button
|
|
2363
|
+
type="button"
|
|
2364
|
+
class="sv-grid-find-step"
|
|
2365
|
+
aria-label="Next match"
|
|
1935
2366
|
disabled={findHits.length === 0}
|
|
1936
2367
|
onclick={() => {
|
|
1937
|
-
ctrl.findHitIndex = (ctrl.findHitIndex + 1) % findHits.length
|
|
1938
|
-
const hit = findHits[ctrl.findHitIndex]
|
|
1939
|
-
if (hit) {
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
2368
|
+
ctrl.findHitIndex = (ctrl.findHitIndex + 1) % findHits.length;
|
|
2369
|
+
const hit = findHits[ctrl.findHitIndex];
|
|
2370
|
+
if (hit) {
|
|
2371
|
+
setActiveCell(hit.rowIndex, hit.colIndex);
|
|
2372
|
+
scrollActiveCellIntoView(hit.rowIndex, hit.colIndex);
|
|
2373
|
+
}
|
|
2374
|
+
}}>↓</button
|
|
2375
|
+
>
|
|
2376
|
+
<button
|
|
2377
|
+
type="button"
|
|
2378
|
+
class="sv-grid-find-close"
|
|
2379
|
+
aria-label="Close find"
|
|
2380
|
+
onclick={() => {
|
|
2381
|
+
ctrl.findOpen = false;
|
|
2382
|
+
ctrl.findQuery = "";
|
|
2383
|
+
}}>✕</button
|
|
2384
|
+
>
|
|
1943
2385
|
</div>
|
|
1944
2386
|
{/if}
|
|
1945
2387
|
|
|
@@ -1967,25 +2409,11 @@
|
|
|
1967
2409
|
{/if}
|
|
1968
2410
|
|
|
1969
2411
|
{#if toolPanelEnabled}
|
|
1970
|
-
<button
|
|
1971
|
-
type="button"
|
|
1972
|
-
class="sv-grid-tool-panel-toggle"
|
|
1973
|
-
class:is-open={ctrl.toolPanelOpen}
|
|
1974
|
-
aria-label={ctrl.toolPanelOpen ? "Close columns panel" : "Open columns panel"}
|
|
1975
|
-
aria-expanded={ctrl.toolPanelOpen}
|
|
1976
|
-
onclick={() => (ctrl.toolPanelOpen = !ctrl.toolPanelOpen)}
|
|
1977
|
-
title="Columns"
|
|
1978
|
-
>
|
|
1979
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
1980
|
-
<rect x="3" y="4" width="6" height="16" rx="1" />
|
|
1981
|
-
<rect x="11" y="4" width="4" height="16" rx="1" />
|
|
1982
|
-
<rect x="17" y="4" width="4" height="16" rx="1" />
|
|
1983
|
-
</svg>
|
|
1984
|
-
</button>
|
|
1985
2412
|
{#if ctrl.toolPanelOpen}
|
|
1986
|
-
|
|
2413
|
+
{@const panelTab = ctrl.toolPanelTab}
|
|
2414
|
+
<aside class="sv-grid-tool-panel" aria-label="Tool panel">
|
|
1987
2415
|
<div class="sv-grid-tool-panel-head">
|
|
1988
|
-
<span>Columns</span>
|
|
2416
|
+
<span>{panelTab === "filters" ? "Filters" : "Columns"}</span>
|
|
1989
2417
|
<button
|
|
1990
2418
|
type="button"
|
|
1991
2419
|
class="sv-grid-tool-panel-close"
|
|
@@ -1993,46 +2421,144 @@
|
|
|
1993
2421
|
onclick={() => (ctrl.toolPanelOpen = false)}>✕</button
|
|
1994
2422
|
>
|
|
1995
2423
|
</div>
|
|
1996
|
-
<
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2424
|
+
<div class="sv-grid-tool-panel-tabs" role="tablist">
|
|
2425
|
+
<button
|
|
2426
|
+
type="button"
|
|
2427
|
+
class="sv-grid-tool-panel-tab"
|
|
2428
|
+
class:is-active={panelTab === "columns"}
|
|
2429
|
+
role="tab"
|
|
2430
|
+
aria-selected={panelTab === "columns"}
|
|
2431
|
+
onclick={() => (ctrl.toolPanelTab = "columns")}>Columns</button
|
|
2432
|
+
>
|
|
2433
|
+
<button
|
|
2434
|
+
type="button"
|
|
2435
|
+
class="sv-grid-tool-panel-tab"
|
|
2436
|
+
class:is-active={panelTab === "filters"}
|
|
2437
|
+
role="tab"
|
|
2438
|
+
aria-selected={panelTab === "filters"}
|
|
2439
|
+
onclick={() => (ctrl.toolPanelTab = "filters")}>Filters</button
|
|
2440
|
+
>
|
|
2441
|
+
</div>
|
|
2442
|
+
{#if panelTab === "columns"}
|
|
2443
|
+
<ul class="sv-grid-tool-panel-list">
|
|
2444
|
+
{#each toolPanelColumns as column, i (column.id)}
|
|
2445
|
+
{@const visible = !ctrl.hiddenColumns[column.id]}
|
|
2446
|
+
{@const grouped = groupingColumns.includes(column.id)}
|
|
2447
|
+
<li class="sv-grid-tool-panel-item">
|
|
2448
|
+
<label class="sv-grid-tool-panel-vis">
|
|
2449
|
+
<input
|
|
2450
|
+
type="checkbox"
|
|
2451
|
+
checked={visible}
|
|
2452
|
+
onchange={() => toggleColumnVisibleInPanel(column.id)}
|
|
2453
|
+
/>
|
|
2454
|
+
<span class="sv-grid-tool-panel-name"
|
|
2455
|
+
>{toolPanelHeaderLabel(column)}</span
|
|
2456
|
+
>
|
|
2457
|
+
</label>
|
|
2458
|
+
<span class="sv-grid-tool-panel-actions">
|
|
2459
|
+
<button
|
|
2460
|
+
type="button"
|
|
2461
|
+
class="sv-grid-tool-panel-btn"
|
|
2462
|
+
class:is-active={grouped}
|
|
2463
|
+
aria-label={grouped ? "Ungroup" : "Group by"}
|
|
2464
|
+
title={grouped ? "Ungroup" : "Group by this column"}
|
|
2465
|
+
onclick={() => toggleGroupInPanel(column.id)}>⊞</button
|
|
2466
|
+
>
|
|
2467
|
+
<button
|
|
2468
|
+
type="button"
|
|
2469
|
+
class="sv-grid-tool-panel-btn"
|
|
2470
|
+
aria-label="Move up"
|
|
2471
|
+
disabled={i === 0}
|
|
2472
|
+
onclick={() => moveColumnInPanel(column.id, -1)}>↑</button
|
|
2473
|
+
>
|
|
2474
|
+
<button
|
|
2475
|
+
type="button"
|
|
2476
|
+
class="sv-grid-tool-panel-btn"
|
|
2477
|
+
aria-label="Move down"
|
|
2478
|
+
disabled={i === toolPanelColumns.length - 1}
|
|
2479
|
+
onclick={() => moveColumnInPanel(column.id, 1)}>↓</button
|
|
2480
|
+
>
|
|
2481
|
+
</span>
|
|
2482
|
+
</li>
|
|
2483
|
+
{/each}
|
|
2484
|
+
</ul>
|
|
2485
|
+
{:else}
|
|
2486
|
+
<!-- Filters tab: one filter control per filterable column, sharing
|
|
2487
|
+
the same filterMenuValues state as the column menu / filter row. -->
|
|
2488
|
+
<div class="sv-grid-tool-panel-filters">
|
|
2489
|
+
{#each toolPanelColumns as column (column.id)}
|
|
2490
|
+
{#if column.columnDef.field && column.columnDef.filterable !== false}
|
|
2491
|
+
{@const active =
|
|
2492
|
+
filterMenuValues[column.id]?.operator ??
|
|
2493
|
+
defaultOperatorFor(column)}
|
|
2494
|
+
{@const fType = getEditorInputType(
|
|
2495
|
+
column.columnDef.editorType ?? "text",
|
|
2496
|
+
)}
|
|
2497
|
+
<div
|
|
2498
|
+
class="sv-grid-tp-filter"
|
|
2499
|
+
class:is-filtered={isColumnFiltered(column.id)}
|
|
2031
2500
|
>
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2501
|
+
<div class="sv-grid-tp-filter-head">
|
|
2502
|
+
<span class="sv-grid-tp-filter-name"
|
|
2503
|
+
>{toolPanelHeaderLabel(column)}</span
|
|
2504
|
+
>
|
|
2505
|
+
{#if isColumnFiltered(column.id)}
|
|
2506
|
+
<button
|
|
2507
|
+
type="button"
|
|
2508
|
+
class="sv-grid-tp-filter-clear"
|
|
2509
|
+
aria-label={`Clear ${toolPanelHeaderLabel(column)} filter`}
|
|
2510
|
+
title="Clear filter"
|
|
2511
|
+
onclick={() => clearColumnFilter(column.id)}>✕</button
|
|
2512
|
+
>
|
|
2513
|
+
{/if}
|
|
2514
|
+
</div>
|
|
2515
|
+
<select
|
|
2516
|
+
class="sv-grid-tp-filter-op"
|
|
2517
|
+
aria-label={`${toolPanelHeaderLabel(column)} filter condition`}
|
|
2518
|
+
value={active}
|
|
2519
|
+
onchange={(e) =>
|
|
2520
|
+
updateFilterOperator(
|
|
2521
|
+
column.id,
|
|
2522
|
+
e.currentTarget.value as FilterOperator,
|
|
2523
|
+
)}
|
|
2524
|
+
>
|
|
2525
|
+
{#each operatorsForColumn(column) as option (option.value)}
|
|
2526
|
+
<option value={option.value}
|
|
2527
|
+
>{operatorOption(option.value).label}</option
|
|
2528
|
+
>
|
|
2529
|
+
{/each}
|
|
2530
|
+
</select>
|
|
2531
|
+
{#if active !== "isBlank"}
|
|
2532
|
+
<input
|
|
2533
|
+
class="sv-grid-tp-filter-input"
|
|
2534
|
+
type={fType}
|
|
2535
|
+
placeholder={active === "between" ? "From" : "Filter…"}
|
|
2536
|
+
value={filterMenuValues[column.id]?.value ?? ""}
|
|
2537
|
+
oninput={(e) =>
|
|
2538
|
+
updateFilterMenuValue(
|
|
2539
|
+
column.id,
|
|
2540
|
+
e.currentTarget.value,
|
|
2541
|
+
)}
|
|
2542
|
+
/>
|
|
2543
|
+
{#if active === "between"}
|
|
2544
|
+
<input
|
|
2545
|
+
class="sv-grid-tp-filter-input"
|
|
2546
|
+
type={fType}
|
|
2547
|
+
placeholder="To"
|
|
2548
|
+
value={filterMenuValues[column.id]?.valueTo ?? ""}
|
|
2549
|
+
oninput={(e) =>
|
|
2550
|
+
updateFilterMenuValueTo(
|
|
2551
|
+
column.id,
|
|
2552
|
+
e.currentTarget.value,
|
|
2553
|
+
)}
|
|
2554
|
+
/>
|
|
2555
|
+
{/if}
|
|
2556
|
+
{/if}
|
|
2557
|
+
</div>
|
|
2558
|
+
{/if}
|
|
2559
|
+
{/each}
|
|
2560
|
+
</div>
|
|
2561
|
+
{/if}
|
|
2036
2562
|
</aside>
|
|
2037
2563
|
{/if}
|
|
2038
2564
|
{/if}
|