@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/src/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);
|
|
@@ -175,6 +286,11 @@
|
|
|
175
286
|
const goToPage = $derived(ctrl.goToPage);
|
|
176
287
|
const setPageSize = $derived(ctrl.setPageSize);
|
|
177
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);
|
|
178
294
|
const toggleCheckboxWithKeyboard = $derived(ctrl.toggleCheckboxWithKeyboard);
|
|
179
295
|
const operatorOption = $derived(ctrl.operatorOption);
|
|
180
296
|
const defaultOperatorFor = $derived(ctrl.defaultOperatorFor);
|
|
@@ -442,7 +558,10 @@
|
|
|
442
558
|
<!-- Custom editor slot. The columnDef provides a snippet that
|
|
443
559
|
receives the editor context (value + commit + cancel) so the
|
|
444
560
|
consumer fully owns the in-cell UI. -->
|
|
445
|
-
{@const customEditor = column.columnDef
|
|
561
|
+
{@const customEditor = column.columnDef
|
|
562
|
+
.cellEditor as unknown as import("svelte").Snippet<
|
|
563
|
+
[EditorContext<TData>]
|
|
564
|
+
>}
|
|
446
565
|
{@render customEditor({
|
|
447
566
|
cell: row.getAllCells().find((c) => c.column.id === column.id)!,
|
|
448
567
|
row,
|
|
@@ -453,19 +572,23 @@
|
|
|
453
572
|
update: (next: unknown) => {
|
|
454
573
|
// Stage the draft without closing. Live-preview controls
|
|
455
574
|
// (sliders, color pickers) call this on every input tick.
|
|
456
|
-
ctrl.editingCell = ctrl.editingCell
|
|
575
|
+
ctrl.editingCell = ctrl.editingCell
|
|
576
|
+
? { ...ctrl.editingCell, value: next }
|
|
577
|
+
: ctrl.editingCell;
|
|
457
578
|
},
|
|
458
579
|
commit: (next?: unknown) => {
|
|
459
580
|
// Write + close. If the caller passed a value, stage it
|
|
460
581
|
// first; otherwise save whatever update() last wrote.
|
|
461
582
|
if (next !== undefined) {
|
|
462
|
-
ctrl.editingCell = ctrl.editingCell
|
|
583
|
+
ctrl.editingCell = ctrl.editingCell
|
|
584
|
+
? { ...ctrl.editingCell, value: next }
|
|
585
|
+
: ctrl.editingCell;
|
|
463
586
|
}
|
|
464
|
-
saveEditingCell()
|
|
587
|
+
saveEditingCell();
|
|
465
588
|
},
|
|
466
589
|
cancel: () => {
|
|
467
|
-
ctrl.editingCell = null
|
|
468
|
-
ctrl.gridRootEl?.focus({ preventScroll: true })
|
|
590
|
+
ctrl.editingCell = null;
|
|
591
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
469
592
|
},
|
|
470
593
|
})}
|
|
471
594
|
{:else if ctrl.editingCell?.editorType === "checkbox"}
|
|
@@ -519,12 +642,11 @@
|
|
|
519
642
|
{@const selectedArr = toValueArray(ctrl.editingCell?.value)}
|
|
520
643
|
{@render chipsEditor(opts, multi, selectedArr)}
|
|
521
644
|
{:else if ctrl.editingCell?.editorType === "rating"}
|
|
522
|
-
{@const ratingVal = Math.max(
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
>
|
|
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">
|
|
528
650
|
{#each [1, 2, 3, 4, 5] as n (n)}
|
|
529
651
|
<button
|
|
530
652
|
type="button"
|
|
@@ -540,8 +662,8 @@
|
|
|
540
662
|
: ctrl.editingCell;
|
|
541
663
|
saveEditingCell();
|
|
542
664
|
}}
|
|
543
|
-
onkeydown={onEditorKeyDown}
|
|
544
|
-
|
|
665
|
+
onkeydown={onEditorKeyDown}>★</button
|
|
666
|
+
>
|
|
545
667
|
{/each}
|
|
546
668
|
<button
|
|
547
669
|
type="button"
|
|
@@ -554,8 +676,8 @@
|
|
|
554
676
|
? { ...ctrl.editingCell, value: 0 }
|
|
555
677
|
: ctrl.editingCell;
|
|
556
678
|
saveEditingCell();
|
|
557
|
-
}}
|
|
558
|
-
|
|
679
|
+
}}>×</button
|
|
680
|
+
>
|
|
559
681
|
</span>
|
|
560
682
|
{:else if ctrl.editingCell?.editorType === "select"}
|
|
561
683
|
<!-- Custom dropdown: opens a themed popover identical in feel to
|
|
@@ -613,18 +735,21 @@
|
|
|
613
735
|
(event.currentTarget as HTMLTextAreaElement).value,
|
|
614
736
|
)}
|
|
615
737
|
onkeydown={(event) => {
|
|
616
|
-
event.stopPropagation()
|
|
738
|
+
event.stopPropagation();
|
|
617
739
|
if (event.key === "Escape") {
|
|
618
|
-
event.preventDefault()
|
|
619
|
-
ctrl.editingCell = null
|
|
620
|
-
ctrl.gridRootEl?.focus({ preventScroll: true })
|
|
621
|
-
return
|
|
740
|
+
event.preventDefault();
|
|
741
|
+
ctrl.editingCell = null;
|
|
742
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
743
|
+
return;
|
|
622
744
|
}
|
|
623
745
|
// Tab and Ctrl/Cmd+Enter both commit. Plain Enter inserts a newline.
|
|
624
|
-
if (
|
|
625
|
-
event.
|
|
626
|
-
|
|
627
|
-
|
|
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 });
|
|
628
753
|
}
|
|
629
754
|
}}
|
|
630
755
|
onblur={() => saveEditingCell()}
|
|
@@ -668,8 +793,8 @@
|
|
|
668
793
|
? { ...ctrl.editingCell, value: opt.value }
|
|
669
794
|
: ctrl.editingCell;
|
|
670
795
|
saveEditingCell();
|
|
671
|
-
}}
|
|
672
|
-
>
|
|
796
|
+
}}>{opt.label}</button
|
|
797
|
+
>
|
|
673
798
|
{/each}
|
|
674
799
|
</div>
|
|
675
800
|
{/if}
|
|
@@ -717,6 +842,63 @@
|
|
|
717
842
|
{/if}
|
|
718
843
|
{/snippet}
|
|
719
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
|
+
|
|
720
902
|
{#snippet chipsEditor(
|
|
721
903
|
opts: CellEditorOption[],
|
|
722
904
|
multi: boolean,
|
|
@@ -900,7 +1082,11 @@
|
|
|
900
1082
|
inline editing, no row-selection checkbox, no fill handle.
|
|
901
1083
|
Position-sticky CSS keeps it anchored to the top of the body or
|
|
902
1084
|
the bottom of the viewport while the rest scrolls. -->
|
|
903
|
-
{#snippet pinnedRowBody(
|
|
1085
|
+
{#snippet pinnedRowBody(
|
|
1086
|
+
rowData: TData,
|
|
1087
|
+
where: "top" | "bottom",
|
|
1088
|
+
index: number,
|
|
1089
|
+
)}
|
|
904
1090
|
<tr
|
|
905
1091
|
class={`sv-grid-row sv-grid-pinned-row sv-grid-pinned-row-${where}`}
|
|
906
1092
|
data-pinned-row={where}
|
|
@@ -910,7 +1096,8 @@
|
|
|
910
1096
|
<td
|
|
911
1097
|
class="sv-grid-cell sv-grid-row-number-cell"
|
|
912
1098
|
style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
|
|
913
|
-
|
|
1099
|
+
>{where === "top" ? "↑" : "↓"}</td
|
|
1100
|
+
>
|
|
914
1101
|
{/if}
|
|
915
1102
|
{#if showRowSelectionEffective}
|
|
916
1103
|
<td
|
|
@@ -927,13 +1114,17 @@
|
|
|
927
1114
|
{/if}
|
|
928
1115
|
{#each renderedColumns as rendered (rendered.column.id)}
|
|
929
1116
|
{@const value = getPinnedCellValue(rowData, rendered.column)}
|
|
930
|
-
{@const userCellClass = computePinnedCellClass(
|
|
1117
|
+
{@const userCellClass = computePinnedCellClass(
|
|
1118
|
+
rowData,
|
|
1119
|
+
rendered.column,
|
|
1120
|
+
)}
|
|
931
1121
|
<td
|
|
932
1122
|
class={`sv-grid-cell ${userCellClass}`}
|
|
933
1123
|
data-col-id={rendered.column.id}
|
|
934
1124
|
data-pinned={isColumnPinned(rendered.column.id) ?? undefined}
|
|
935
1125
|
style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
|
|
936
|
-
|
|
1126
|
+
>{formatPinnedValue(rendered.column, value)}</td
|
|
1127
|
+
>
|
|
937
1128
|
{/each}
|
|
938
1129
|
{#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
|
|
939
1130
|
<td
|
|
@@ -956,13 +1147,45 @@
|
|
|
956
1147
|
</label>
|
|
957
1148
|
{/if}
|
|
958
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
|
+
|
|
959
1182
|
<div
|
|
960
1183
|
class="sv-grid-shell"
|
|
961
1184
|
style={`height: ${
|
|
962
1185
|
typeof props.containerHeight === "string"
|
|
963
1186
|
? props.containerHeight
|
|
964
1187
|
: `${props.containerHeight ?? 520}px`
|
|
965
|
-
}; --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;`}
|
|
966
1189
|
>
|
|
967
1190
|
<div
|
|
968
1191
|
class="sv-grid-container sv-grid-container-custom-scrollbars"
|
|
@@ -1018,7 +1241,43 @@
|
|
|
1018
1241
|
style={`width: ${cell.widthPx}px; min-width: ${cell.widthPx}px; max-width: ${cell.widthPx}px;`}
|
|
1019
1242
|
>
|
|
1020
1243
|
{#if !cell.isPlaceholder}
|
|
1021
|
-
|
|
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}
|
|
1022
1281
|
{/if}
|
|
1023
1282
|
</th>
|
|
1024
1283
|
{/each}
|
|
@@ -1079,14 +1338,18 @@
|
|
|
1079
1338
|
)}
|
|
1080
1339
|
<th
|
|
1081
1340
|
class="sv-grid-column"
|
|
1082
|
-
class:is-drag-target-before={colDropOnId ===
|
|
1083
|
-
|
|
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"}
|
|
1084
1345
|
class:is-dragging={colDragId === header.column.id}
|
|
1085
1346
|
data-svgrid-header-col={header.column.id}
|
|
1086
1347
|
data-align={getColumnAlign(rendered.column)}
|
|
1087
1348
|
data-pinned={isColumnPinned(rendered.column.id) ??
|
|
1088
1349
|
undefined}
|
|
1089
|
-
draggable={(props.enableColumnReorder ?? false)
|
|
1350
|
+
draggable={(props.enableColumnReorder ?? false)
|
|
1351
|
+
? true
|
|
1352
|
+
: undefined}
|
|
1090
1353
|
ondragstart={(e) =>
|
|
1091
1354
|
(props.enableColumnReorder ?? false) &&
|
|
1092
1355
|
onColumnHeaderDragStart(e, header.column.id)}
|
|
@@ -1116,7 +1379,9 @@
|
|
|
1116
1379
|
{#if !header.isPlaceholder}
|
|
1117
1380
|
<div class="sv-grid-header-cell">
|
|
1118
1381
|
{#if typeof header.column.columnDef.header === "function"}
|
|
1119
|
-
{@const rendered = header.column.columnDef.header(
|
|
1382
|
+
{@const rendered = header.column.columnDef.header(
|
|
1383
|
+
header.getContext(),
|
|
1384
|
+
)}
|
|
1120
1385
|
<!-- Custom header (snippet/component): rendered
|
|
1121
1386
|
OUTSIDE the sort button so the consumer's
|
|
1122
1387
|
own interactive elements (menu buttons,
|
|
@@ -1129,20 +1394,30 @@
|
|
|
1129
1394
|
role="button"
|
|
1130
1395
|
tabindex="-1"
|
|
1131
1396
|
onclick={(event) => {
|
|
1132
|
-
if (!header.column.getCanSort()) return
|
|
1397
|
+
if (!header.column.getCanSort()) return;
|
|
1133
1398
|
// If the click landed on an interactive
|
|
1134
1399
|
// element inside the custom header, let
|
|
1135
1400
|
// that element handle it.
|
|
1136
|
-
const t = event.target as HTMLElement | null
|
|
1137
|
-
if (
|
|
1138
|
-
|
|
1139
|
-
|
|
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);
|
|
1140
1411
|
}}
|
|
1141
1412
|
onkeydown={(event) => {
|
|
1142
|
-
if (event.key !==
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
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
|
+
);
|
|
1146
1421
|
}}
|
|
1147
1422
|
>
|
|
1148
1423
|
{#if rendered instanceof RenderSnippetConfig}
|
|
@@ -1157,10 +1432,12 @@
|
|
|
1157
1432
|
{#if header.column.getCanSort()}
|
|
1158
1433
|
{#if sortDirection === "asc"}
|
|
1159
1434
|
<span class="sv-grid-header-icon"
|
|
1160
|
-
>{@render icon("sort-asc")}</span
|
|
1435
|
+
>{@render icon("sort-asc")}</span
|
|
1436
|
+
>
|
|
1161
1437
|
{:else if sortDirection === "desc"}
|
|
1162
1438
|
<span class="sv-grid-header-icon"
|
|
1163
|
-
>{@render icon("sort-desc")}</span
|
|
1439
|
+
>{@render icon("sort-desc")}</span
|
|
1440
|
+
>
|
|
1164
1441
|
{/if}
|
|
1165
1442
|
{/if}
|
|
1166
1443
|
</div>
|
|
@@ -1172,7 +1449,8 @@
|
|
|
1172
1449
|
onHeaderSortClick(event, header.column.id)}
|
|
1173
1450
|
>
|
|
1174
1451
|
<span class="sv-grid-header-label">
|
|
1175
|
-
{typeof header.column.columnDef.header ===
|
|
1452
|
+
{typeof header.column.columnDef.header ===
|
|
1453
|
+
"string"
|
|
1176
1454
|
? header.column.columnDef.header
|
|
1177
1455
|
: header.id}
|
|
1178
1456
|
</span>
|
|
@@ -1327,9 +1605,15 @@
|
|
|
1327
1605
|
>
|
|
1328
1606
|
</button>
|
|
1329
1607
|
{#if activeOperator !== "isBlank"}
|
|
1608
|
+
{@const frType = getEditorInputType(
|
|
1609
|
+
rendered.column.columnDef.editorType ?? "text",
|
|
1610
|
+
)}
|
|
1330
1611
|
<input
|
|
1331
1612
|
class="sv-grid-filter-value"
|
|
1332
|
-
|
|
1613
|
+
type={frType}
|
|
1614
|
+
placeholder={activeOperator === "between"
|
|
1615
|
+
? "From"
|
|
1616
|
+
: "Filter…"}
|
|
1333
1617
|
data-svgrid-filter-col={rendered.column.id}
|
|
1334
1618
|
value={filterRowValues[rendered.column.id] ?? ""}
|
|
1335
1619
|
oninput={(event) =>
|
|
@@ -1338,6 +1622,21 @@
|
|
|
1338
1622
|
(event.currentTarget as HTMLInputElement).value,
|
|
1339
1623
|
)}
|
|
1340
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}
|
|
1341
1640
|
{/if}
|
|
1342
1641
|
</div>
|
|
1343
1642
|
</th>
|
|
@@ -1355,14 +1654,24 @@
|
|
|
1355
1654
|
</thead>
|
|
1356
1655
|
{#if props.pinnedTopRows && props.pinnedTopRows.length > 0}
|
|
1357
1656
|
<!-- svelte-ignore a11y_no_redundant_roles -->
|
|
1358
|
-
<tbody
|
|
1657
|
+
<tbody
|
|
1658
|
+
class="sv-grid-pinned sv-grid-pinned-top-body"
|
|
1659
|
+
role="rowgroup"
|
|
1660
|
+
>
|
|
1359
1661
|
{#each props.pinnedTopRows as r, i (i)}
|
|
1360
1662
|
{@render pinnedRowBody(r, "top", i)}
|
|
1361
1663
|
{/each}
|
|
1362
1664
|
</tbody>
|
|
1363
1665
|
{/if}
|
|
1364
1666
|
<!-- svelte-ignore a11y_no_redundant_roles -->
|
|
1365
|
-
<tbody
|
|
1667
|
+
<tbody
|
|
1668
|
+
class="sv-grid-body"
|
|
1669
|
+
role="rowgroup"
|
|
1670
|
+
ondragover={rowDragManagedEffective
|
|
1671
|
+
? onRowsContainerDragOver
|
|
1672
|
+
: undefined}
|
|
1673
|
+
ondrop={rowDragManagedEffective ? onRowsContainerDrop : undefined}
|
|
1674
|
+
>
|
|
1366
1675
|
{#if !allRows.length && !(props.loading && props.loadingOverlay)}
|
|
1367
1676
|
<tr class="sv-grid-row sv-grid-empty-row">
|
|
1368
1677
|
<td
|
|
@@ -1416,10 +1725,13 @@
|
|
|
1416
1725
|
{:else}
|
|
1417
1726
|
{@const userRowClass = computeRowClass(row, rowIndex)}
|
|
1418
1727
|
<tr
|
|
1419
|
-
class={`sv-grid-row ${userRowClass}`}
|
|
1728
|
+
class={`sv-grid-row ${userRowClass} ${rowDropClass(rowIndex)}`}
|
|
1420
1729
|
class:sv-grid-row-selected={isRowSelected(row.id)}
|
|
1421
|
-
class:sv-grid-row-alt={props.zebraRows &&
|
|
1730
|
+
class:sv-grid-row-alt={props.zebraRows &&
|
|
1731
|
+
rowIndex % 2 === 1}
|
|
1732
|
+
class:sv-grid-row-draggable={rowDragManagedEffective}
|
|
1422
1733
|
{...getGridRowA11yProps(rowIndex + 1)}
|
|
1734
|
+
{...rowDragAttrs(rowIndex)}
|
|
1423
1735
|
style={`height: ${rowItem.size}px;`}
|
|
1424
1736
|
>
|
|
1425
1737
|
{#if showRowNumbersEffective}
|
|
@@ -1474,6 +1786,10 @@
|
|
|
1474
1786
|
{@const isEditing =
|
|
1475
1787
|
ctrl.editingCell?.rowId === row.id &&
|
|
1476
1788
|
ctrl.editingCell?.columnId === rendered.column.id}
|
|
1789
|
+
{@const inRowEdit =
|
|
1790
|
+
!!fullRowEdit &&
|
|
1791
|
+
fullRowEdit.rowId === row.id &&
|
|
1792
|
+
rendered.column.id in fullRowEdit.draft}
|
|
1477
1793
|
{@const rangeEdges = getCellRangeEdges(
|
|
1478
1794
|
rowIndex,
|
|
1479
1795
|
colIndex,
|
|
@@ -1482,12 +1798,21 @@
|
|
|
1482
1798
|
fillHandleCell &&
|
|
1483
1799
|
fillHandleCell.rowIndex === rowIndex &&
|
|
1484
1800
|
fillHandleCell.colIndex === colIndex}
|
|
1485
|
-
{@const userCellClass = computeCellClass(
|
|
1486
|
-
|
|
1487
|
-
|
|
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
|
+
)}
|
|
1488
1813
|
<td
|
|
1489
1814
|
class={`sv-grid-cell ${userCellClass}`}
|
|
1490
|
-
class:sv-grid-cell-editing={isEditing}
|
|
1815
|
+
class:sv-grid-cell-editing={isEditing || inRowEdit}
|
|
1491
1816
|
class:sv-grid-cell-active={activeCell.rowIndex ===
|
|
1492
1817
|
rowIndex && activeCell.colIndex === colIndex}
|
|
1493
1818
|
class:sv-grid-cell-has-fill-handle={hasFillHandle}
|
|
@@ -1522,7 +1847,20 @@
|
|
|
1522
1847
|
emitCellDoubleClick(rowIndex, colIndex)}
|
|
1523
1848
|
onclick={() => onCellClick(rowIndex, colIndex)}
|
|
1524
1849
|
oncontextmenu={(event) =>
|
|
1525
|
-
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
|
+
}}
|
|
1526
1864
|
{...getGridCellA11yProps({
|
|
1527
1865
|
id: getGridCellDomId("svgrid", rowIndex, colIndex),
|
|
1528
1866
|
rowIndex: rowIndex + 1,
|
|
@@ -1530,10 +1868,16 @@
|
|
|
1530
1868
|
selected: isRowSelected(row.id),
|
|
1531
1869
|
})}
|
|
1532
1870
|
>
|
|
1533
|
-
{#if
|
|
1871
|
+
{#if inRowEdit}
|
|
1872
|
+
{@render fullRowEditor(rendered.column, row)}
|
|
1873
|
+
{:else if isEditing}
|
|
1534
1874
|
{@render editorBody(rendered.column, row)}
|
|
1535
1875
|
{:else}
|
|
1536
|
-
{@render cellBodyWithFormat(
|
|
1876
|
+
{@render cellBodyWithFormat(
|
|
1877
|
+
row,
|
|
1878
|
+
rendered.column,
|
|
1879
|
+
cellValue,
|
|
1880
|
+
)}
|
|
1537
1881
|
{/if}
|
|
1538
1882
|
{#if !isEditing && fillHandleCell && fillHandleCell.rowIndex === rowIndex && fillHandleCell.colIndex === colIndex}
|
|
1539
1883
|
<!-- Excel-style fill handle: drag down/right to
|
|
@@ -1554,12 +1898,15 @@
|
|
|
1554
1898
|
class="sv-grid-cell-note-corner"
|
|
1555
1899
|
aria-label="Note"
|
|
1556
1900
|
onpointerenter={(event) => {
|
|
1557
|
-
event.stopPropagation()
|
|
1558
|
-
showTooltipFor(
|
|
1901
|
+
event.stopPropagation();
|
|
1902
|
+
showTooltipFor(
|
|
1903
|
+
event.currentTarget as HTMLElement,
|
|
1904
|
+
cellNote,
|
|
1905
|
+
);
|
|
1559
1906
|
}}
|
|
1560
1907
|
onpointerleave={(event) => {
|
|
1561
|
-
event.stopPropagation()
|
|
1562
|
-
hideTooltip()
|
|
1908
|
+
event.stopPropagation();
|
|
1909
|
+
hideTooltip();
|
|
1563
1910
|
}}
|
|
1564
1911
|
></span>
|
|
1565
1912
|
{/if}
|
|
@@ -1614,10 +1961,13 @@
|
|
|
1614
1961
|
{:else}
|
|
1615
1962
|
{@const userRowClass = computeRowClass(row, rowIndex)}
|
|
1616
1963
|
<tr
|
|
1617
|
-
class={`sv-grid-row ${userRowClass}`}
|
|
1964
|
+
class={`sv-grid-row ${userRowClass} ${rowDropClass(rowIndex)}`}
|
|
1618
1965
|
class:sv-grid-row-selected={isRowSelected(row.id)}
|
|
1619
|
-
class:sv-grid-row-alt={props.zebraRows &&
|
|
1966
|
+
class:sv-grid-row-alt={props.zebraRows &&
|
|
1967
|
+
rowIndex % 2 === 1}
|
|
1968
|
+
class:sv-grid-row-draggable={rowDragManagedEffective}
|
|
1620
1969
|
{...getGridRowA11yProps(rowIndex + 1)}
|
|
1970
|
+
{...rowDragAttrs(rowIndex)}
|
|
1621
1971
|
>
|
|
1622
1972
|
{#if showRowNumbersEffective}
|
|
1623
1973
|
<td
|
|
@@ -1671,16 +2021,26 @@
|
|
|
1671
2021
|
{@const isEditing =
|
|
1672
2022
|
ctrl.editingCell?.rowId === row.id &&
|
|
1673
2023
|
ctrl.editingCell?.columnId === rendered.column.id}
|
|
2024
|
+
{@const inRowEdit =
|
|
2025
|
+
!!fullRowEdit &&
|
|
2026
|
+
fullRowEdit.rowId === row.id &&
|
|
2027
|
+
rendered.column.id in fullRowEdit.draft}
|
|
1674
2028
|
{@const rangeEdges = getCellRangeEdges(
|
|
1675
2029
|
rowIndex,
|
|
1676
2030
|
colIndex,
|
|
1677
2031
|
)}
|
|
1678
|
-
{@const userCellClass = computeCellClass(
|
|
1679
|
-
|
|
1680
|
-
|
|
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)}
|
|
1681
2041
|
<td
|
|
1682
2042
|
class={`sv-grid-cell ${userCellClass}`}
|
|
1683
|
-
class:sv-grid-cell-editing={isEditing}
|
|
2043
|
+
class:sv-grid-cell-editing={isEditing || inRowEdit}
|
|
1684
2044
|
class:sv-grid-cell-active={activeCell.rowIndex ===
|
|
1685
2045
|
rowIndex && activeCell.colIndex === colIndex}
|
|
1686
2046
|
class:sv-grid-cell-cf={hasConditionalFormats}
|
|
@@ -1703,18 +2063,36 @@
|
|
|
1703
2063
|
onpointerdown={(event) =>
|
|
1704
2064
|
onCellPointerDown(rowIndex, colIndex, event)}
|
|
1705
2065
|
onpointerenter={(event) => {
|
|
1706
|
-
onCellPointerEnter(rowIndex, colIndex)
|
|
2066
|
+
onCellPointerEnter(rowIndex, colIndex);
|
|
1707
2067
|
// Column tooltip fires on whole-cell hover.
|
|
1708
2068
|
// Per-cell notes are gated on the corner hot-
|
|
1709
2069
|
// zone below (Excel-style: hover the small
|
|
1710
2070
|
// triangle to read the note).
|
|
1711
|
-
if (cellTooltip)
|
|
2071
|
+
if (cellTooltip)
|
|
2072
|
+
showTooltipFor(
|
|
2073
|
+
event.currentTarget as HTMLElement,
|
|
2074
|
+
cellTooltip,
|
|
2075
|
+
);
|
|
1712
2076
|
}}
|
|
1713
2077
|
onpointerleave={hideTooltip}
|
|
1714
|
-
ondblclick={() =>
|
|
2078
|
+
ondblclick={() =>
|
|
2079
|
+
emitCellDoubleClick(rowIndex, colIndex)}
|
|
1715
2080
|
onclick={() => onCellClick(rowIndex, colIndex)}
|
|
1716
2081
|
oncontextmenu={(event) =>
|
|
1717
|
-
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
|
+
}}
|
|
1718
2096
|
{...getGridCellA11yProps({
|
|
1719
2097
|
id: getGridCellDomId("svgrid", rowIndex, colIndex),
|
|
1720
2098
|
rowIndex: rowIndex + 1,
|
|
@@ -1722,10 +2100,16 @@
|
|
|
1722
2100
|
selected: isRowSelected(row.id),
|
|
1723
2101
|
})}
|
|
1724
2102
|
>
|
|
1725
|
-
{#if
|
|
2103
|
+
{#if inRowEdit}
|
|
2104
|
+
{@render fullRowEditor(rendered.column, row)}
|
|
2105
|
+
{:else if isEditing}
|
|
1726
2106
|
{@render editorBody(rendered.column, row)}
|
|
1727
2107
|
{:else}
|
|
1728
|
-
{@render cellBodyWithFormat(
|
|
2108
|
+
{@render cellBodyWithFormat(
|
|
2109
|
+
row,
|
|
2110
|
+
rendered.column,
|
|
2111
|
+
cellValue,
|
|
2112
|
+
)}
|
|
1729
2113
|
{/if}
|
|
1730
2114
|
{#if cellNote != null && !isEditing}
|
|
1731
2115
|
<!-- Excel-style per-cell note indicator. The
|
|
@@ -1739,12 +2123,15 @@
|
|
|
1739
2123
|
class="sv-grid-cell-note-corner"
|
|
1740
2124
|
aria-label="Note"
|
|
1741
2125
|
onpointerenter={(event) => {
|
|
1742
|
-
event.stopPropagation()
|
|
1743
|
-
showTooltipFor(
|
|
2126
|
+
event.stopPropagation();
|
|
2127
|
+
showTooltipFor(
|
|
2128
|
+
event.currentTarget as HTMLElement,
|
|
2129
|
+
cellNote,
|
|
2130
|
+
);
|
|
1744
2131
|
}}
|
|
1745
2132
|
onpointerleave={(event) => {
|
|
1746
|
-
event.stopPropagation()
|
|
1747
|
-
hideTooltip()
|
|
2133
|
+
event.stopPropagation();
|
|
2134
|
+
hideTooltip();
|
|
1748
2135
|
}}
|
|
1749
2136
|
></span>
|
|
1750
2137
|
{/if}
|
|
@@ -1764,7 +2151,10 @@
|
|
|
1764
2151
|
</tbody>
|
|
1765
2152
|
{#if props.pinnedBottomRows && props.pinnedBottomRows.length > 0}
|
|
1766
2153
|
<!-- svelte-ignore a11y_no_redundant_roles -->
|
|
1767
|
-
<tbody
|
|
2154
|
+
<tbody
|
|
2155
|
+
class="sv-grid-pinned sv-grid-pinned-bottom-body"
|
|
2156
|
+
role="rowgroup"
|
|
2157
|
+
>
|
|
1768
2158
|
{#each props.pinnedBottomRows as r, i (i)}
|
|
1769
2159
|
{@render pinnedRowBody(r, "bottom", i)}
|
|
1770
2160
|
{/each}
|
|
@@ -1866,7 +2256,7 @@
|
|
|
1866
2256
|
content-size={scrollMetrics.scrollHeight ||
|
|
1867
2257
|
rowDomTotalSize + headerHeight}
|
|
1868
2258
|
value={scrollMetrics.scrollTop}
|
|
1869
|
-
step={typeof props.rowHeight ===
|
|
2259
|
+
step={typeof props.rowHeight === "number" ? props.rowHeight : 30}
|
|
1870
2260
|
style={`top: ${headerHeight}px; height: calc(100% - ${headerHeight + (hasHorizontalOverflow ? 16 : 0)}px);`}
|
|
1871
2261
|
></sv-grid-scrollbar>
|
|
1872
2262
|
{/if}
|
|
@@ -1899,8 +2289,23 @@
|
|
|
1899
2289
|
Enter cycles to the next hit; Esc closes. -->
|
|
1900
2290
|
<div class="sv-grid-find" role="search" aria-label="Find in grid">
|
|
1901
2291
|
<svg class="sv-grid-find-icon" viewBox="0 0 16 16" aria-hidden="true">
|
|
1902
|
-
<circle
|
|
1903
|
-
|
|
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
|
+
/>
|
|
1904
2309
|
</svg>
|
|
1905
2310
|
<input
|
|
1906
2311
|
class="sv-grid-find-input"
|
|
@@ -1910,40 +2315,73 @@
|
|
|
1910
2315
|
bind:value={ctrl.findQuery}
|
|
1911
2316
|
oninput={() => (ctrl.findHitIndex = 0)}
|
|
1912
2317
|
onkeydown={(event) => {
|
|
1913
|
-
event.stopPropagation()
|
|
1914
|
-
if (event.key ===
|
|
1915
|
-
event.preventDefault()
|
|
1916
|
-
if (findHits.length === 0) return
|
|
1917
|
-
ctrl.findHitIndex =
|
|
1918
|
-
|
|
1919
|
-
|
|
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 = "";
|
|
1920
2337
|
}
|
|
1921
|
-
if (event.key === 'Escape') { event.preventDefault(); ctrl.findOpen = false; ctrl.findQuery = '' }
|
|
1922
2338
|
}}
|
|
1923
2339
|
/>
|
|
1924
2340
|
<span class="sv-grid-find-count">
|
|
1925
2341
|
{findHits.length === 0 && ctrl.findQuery.trim()
|
|
1926
|
-
?
|
|
2342
|
+
? "No matches"
|
|
1927
2343
|
: findHits.length === 0
|
|
1928
|
-
?
|
|
2344
|
+
? ""
|
|
1929
2345
|
: `${ctrl.findHitIndex + 1} of ${findHits.length}`}
|
|
1930
2346
|
</span>
|
|
1931
|
-
<button
|
|
2347
|
+
<button
|
|
2348
|
+
type="button"
|
|
2349
|
+
class="sv-grid-find-step"
|
|
2350
|
+
aria-label="Previous match"
|
|
1932
2351
|
disabled={findHits.length === 0}
|
|
1933
2352
|
onclick={() => {
|
|
1934
|
-
ctrl.findHitIndex =
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
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"
|
|
1939
2366
|
disabled={findHits.length === 0}
|
|
1940
2367
|
onclick={() => {
|
|
1941
|
-
ctrl.findHitIndex = (ctrl.findHitIndex + 1) % findHits.length
|
|
1942
|
-
const hit = findHits[ctrl.findHitIndex]
|
|
1943
|
-
if (hit) {
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
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
|
+
>
|
|
1947
2385
|
</div>
|
|
1948
2386
|
{/if}
|
|
1949
2387
|
|
|
@@ -1971,25 +2409,11 @@
|
|
|
1971
2409
|
{/if}
|
|
1972
2410
|
|
|
1973
2411
|
{#if toolPanelEnabled}
|
|
1974
|
-
<button
|
|
1975
|
-
type="button"
|
|
1976
|
-
class="sv-grid-tool-panel-toggle"
|
|
1977
|
-
class:is-open={ctrl.toolPanelOpen}
|
|
1978
|
-
aria-label={ctrl.toolPanelOpen ? "Close columns panel" : "Open columns panel"}
|
|
1979
|
-
aria-expanded={ctrl.toolPanelOpen}
|
|
1980
|
-
onclick={() => (ctrl.toolPanelOpen = !ctrl.toolPanelOpen)}
|
|
1981
|
-
title="Columns"
|
|
1982
|
-
>
|
|
1983
|
-
<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">
|
|
1984
|
-
<rect x="3" y="4" width="6" height="16" rx="1" />
|
|
1985
|
-
<rect x="11" y="4" width="4" height="16" rx="1" />
|
|
1986
|
-
<rect x="17" y="4" width="4" height="16" rx="1" />
|
|
1987
|
-
</svg>
|
|
1988
|
-
</button>
|
|
1989
2412
|
{#if ctrl.toolPanelOpen}
|
|
1990
|
-
|
|
2413
|
+
{@const panelTab = ctrl.toolPanelTab}
|
|
2414
|
+
<aside class="sv-grid-tool-panel" aria-label="Tool panel">
|
|
1991
2415
|
<div class="sv-grid-tool-panel-head">
|
|
1992
|
-
<span>Columns</span>
|
|
2416
|
+
<span>{panelTab === "filters" ? "Filters" : "Columns"}</span>
|
|
1993
2417
|
<button
|
|
1994
2418
|
type="button"
|
|
1995
2419
|
class="sv-grid-tool-panel-close"
|
|
@@ -1997,46 +2421,144 @@
|
|
|
1997
2421
|
onclick={() => (ctrl.toolPanelOpen = false)}>✕</button
|
|
1998
2422
|
>
|
|
1999
2423
|
</div>
|
|
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
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
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)}
|
|
2035
2500
|
>
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
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}
|
|
2040
2562
|
</aside>
|
|
2041
2563
|
{/if}
|
|
2042
2564
|
{/if}
|