@svgrid/grid 3.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -88
- package/README.md +199 -199
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +181 -181
- package/dist/SvAutoComplete.svelte +169 -169
- package/dist/SvAvatar.svelte +75 -75
- package/dist/SvCalendar.svelte +503 -503
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvCheckBox.svelte +102 -102
- package/dist/SvCircularProgress.svelte +109 -109
- package/dist/SvColorInput.svelte +181 -181
- package/dist/SvComboBox.svelte +279 -279
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +163 -163
- package/dist/SvDrawer.svelte +254 -254
- package/dist/SvDropDownList.svelte +378 -378
- package/dist/SvDurationInput.svelte +126 -126
- package/dist/SvField.svelte +293 -293
- package/dist/SvForm.svelte +437 -437
- package/dist/SvGridCellEditor.svelte +748 -744
- package/dist/SvGridChart.svelte +1716 -1716
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +728 -728
- package/dist/SvGridSelect.svelte +270 -270
- package/dist/SvGroupCell.svelte +116 -116
- package/dist/SvListBox.svelte +334 -334
- package/dist/SvMaskedInput.svelte +122 -122
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +146 -146
- package/dist/SvMultiSelect.svelte +293 -293
- package/dist/SvNumberInput.svelte +172 -172
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPasswordInput.svelte +151 -151
- package/dist/SvPhoneInput.svelte +133 -133
- package/dist/SvPopover.svelte +197 -197
- package/dist/SvProgress.svelte +116 -116
- package/dist/SvRadioGroup.svelte +107 -107
- package/dist/SvRating.svelte +112 -112
- package/dist/SvResult.svelte +73 -73
- package/dist/SvRichText.svelte +211 -211
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvSlider.svelte +203 -203
- package/dist/SvSwitchButton.svelte +108 -108
- package/dist/SvTagsInput.svelte +115 -115
- package/dist/SvTextInput.svelte +147 -147
- package/dist/SvTimePicker.svelte +245 -245
- package/dist/SvToaster.svelte +159 -159
- package/dist/SvToggleButton.svelte +85 -85
- package/dist/SvTooltip.svelte +161 -161
- package/dist/SvTour.svelte +208 -208
- package/dist/SvTree.svelte +444 -444
- package/dist/SvTreeSelect.svelte +239 -239
- package/dist/cdn/{GridMenus-DMihUtma.js → GridMenus-CYzOvmNQ.js} +3 -3
- package/dist/cdn/{GridMenus-nmNDj1a3.js → GridMenus-DFB6iwF8.js} +3 -3
- package/dist/cdn/{SvDateRangeInput-DflbiP7N.js → SvDateRangeInput-CaOuMs8O.js} +1 -1
- package/dist/cdn/{SvDateRangeInput-Bh0A0JkF.js → SvDateRangeInput-DMLKmGEc.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-Bivn8dAP.js → SvDateTimePicker-CCbDZNZB.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-BWwpfB_o.js → SvDateTimePicker-sonaH0oh.js} +1 -1
- package/dist/cdn/{SvGridCellEditor-Ba7rY3eu.js → SvGridCellEditor-BWnTi2N7.js} +2 -2
- package/dist/cdn/{SvGridCellEditor-AXL8cHGO.js → SvGridCellEditor-CjEJMJKc.js} +2 -2
- package/dist/cdn/{SvGridChart-CwhFz7GV.js → SvGridChart-BEJmNNx9.js} +1 -1
- package/dist/cdn/{SvGridChart-Bs2GIR2Q.js → SvGridChart-BwVos976.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-1WXuStzM.js → SvGridChartPanel-BOknOkrP.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-e5_nqNP9.js → SvGridChartPanel-D2PjII4O.js} +1 -1
- package/dist/cdn/{SvGridChartView-SmPW10dI.js → SvGridChartView-00LPUHL1.js} +1 -1
- package/dist/cdn/{SvGridChartView-eSPuJE6g.js → SvGridChartView-BhUEJ5ki.js} +1 -1
- package/dist/cdn/{SvGridDropdown-B1ZcLpgs.js → SvGridDropdown-D0VdjeR8.js} +1 -1
- package/dist/cdn/{SvGridDropdown-CtdbHcIS.js → SvGridDropdown-D13MtJ2j.js} +1 -1
- package/dist/cdn/{date-format-DZT7T1wf.js → date-format-BNii4zeD.js} +2 -2
- package/dist/cdn/{date-format-CcP1tafP.js → date-format-BnnHlqGw.js} +2 -2
- package/dist/cdn/{src-B_YS5AOc.js → src-C8QjSaq-.js} +42 -44
- package/dist/cdn/{src-DYXXpuSk.js → src-D0hO2eYN.js} +41 -43
- package/dist/cdn/svgrid.js +7 -7
- package/dist/cdn/svgrid.svelte-external.js +7 -7
- package/dist/chart-export.js +8 -8
- package/dist/editing.js +22 -13
- package/package.json +11 -11
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +181 -181
- package/src/SvAutoComplete.svelte +169 -169
- package/src/SvAvatar.svelte +75 -75
- package/src/SvCalendar.svelte +503 -503
- package/src/SvCalendar.test.ts +226 -226
- package/src/SvCarousel.svelte +141 -141
- package/src/SvCheckBox.svelte +102 -102
- package/src/SvCircularProgress.svelte +109 -109
- package/src/SvColorInput.svelte +181 -181
- package/src/SvComboBox.svelte +279 -279
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +163 -163
- package/src/SvDrawer.svelte +254 -254
- package/src/SvDropDownList.svelte +378 -378
- package/src/SvDurationInput.svelte +126 -126
- package/src/SvField.svelte +293 -293
- package/src/SvForm.svelte +437 -437
- package/src/SvForm.test.ts +411 -411
- package/src/SvGrid.types.ts +2092 -2092
- package/src/SvGridCellEditor.svelte +748 -744
- package/src/SvGridChart.svelte +1716 -1716
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +728 -728
- package/src/SvGridSelect.svelte +270 -270
- package/src/SvGroupCell.svelte +116 -116
- package/src/SvListBox.svelte +334 -334
- package/src/SvMaskedInput.svelte +122 -122
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -97
- package/src/SvMenuList.svelte +146 -146
- package/src/SvMultiSelect.svelte +293 -293
- package/src/SvNumberInput.svelte +172 -172
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPasswordInput.svelte +151 -151
- package/src/SvPhoneInput.svelte +133 -133
- package/src/SvPopover.svelte +197 -197
- package/src/SvProgress.svelte +116 -116
- package/src/SvRadioGroup.svelte +107 -107
- package/src/SvRating.svelte +112 -112
- package/src/SvResult.svelte +73 -73
- package/src/SvRichText.svelte +211 -211
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvSlider.svelte +203 -203
- package/src/SvSwitchButton.svelte +108 -108
- package/src/SvTagsInput.svelte +115 -115
- package/src/SvTextInput.svelte +147 -147
- package/src/SvTimePicker.svelte +245 -245
- package/src/SvToaster.svelte +159 -159
- package/src/SvToaster.test.ts +95 -95
- package/src/SvToggleButton.svelte +85 -85
- package/src/SvTooltip.svelte +161 -161
- package/src/SvTour.svelte +208 -208
- package/src/SvTree.svelte +444 -444
- package/src/SvTreeSelect.svelte +239 -239
- package/src/a11y/dismissable.test.ts +119 -119
- package/src/a11y/dismissable.ts +114 -114
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/ai.test.ts +502 -502
- package/src/ai.ts +1419 -1419
- package/src/build-api.coverage.test.ts +633 -633
- package/src/build-api.ts +846 -846
- package/src/builtin-editors.grid.test.ts +83 -83
- package/src/cell-formatting.ts +171 -171
- package/src/cell-render.test.ts +513 -513
- package/src/cell-render.ts +496 -496
- package/src/cell-values.ts +148 -148
- package/src/chart-export.ts +202 -202
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.ts +2321 -2321
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/column-groups.ts +78 -78
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1865 -1865
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCombobox.svelte.ts +191 -191
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +168 -168
- package/src/createForm.svelte.ts +386 -386
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createListbox.svelte.ts +250 -250
- package/src/createMenu.svelte.ts +224 -224
- package/src/createPopoverSelect.svelte.ts +213 -213
- package/src/createSlider.svelte.ts +191 -191
- package/src/createTooltip.svelte.ts +144 -144
- package/src/createTree.svelte.ts +322 -322
- package/src/datetime/date-core.ts +206 -206
- package/src/datetime/date-restrict.ts +61 -61
- package/src/datetime/timezone.ts +135 -135
- package/src/dock-manager-model.ts +596 -596
- package/src/dock-model.ts +374 -374
- package/src/editing.test.ts +974 -974
- package/src/editing.ts +618 -609
- package/src/editor-contract.ts +171 -171
- package/src/editor-registry.grid.test.ts +144 -144
- package/src/editor-registry.ts +122 -122
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +601 -601
- package/src/filter-operators.ts +160 -160
- package/src/filtering/excel-filters.ts +325 -325
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +127 -127
- package/src/group-display.test.ts +167 -167
- package/src/group-display.ts +200 -200
- package/src/headless.ts +87 -87
- package/src/js-scroller.svelte.ts +173 -173
- package/src/keyboard-handlers.ts +270 -270
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -49
- package/src/list-nav.ts +29 -29
- package/src/list-option.test.ts +56 -56
- package/src/list-option.ts +179 -179
- package/src/menus.ts +597 -597
- package/src/merge-objects.ts +48 -48
- package/src/overlays.test.ts +90 -90
- package/src/positioning.ts +268 -268
- package/src/render-component.ts +28 -28
- package/src/row-drag.test.ts +401 -401
- package/src/row-drag.ts +419 -419
- package/src/row-resize.test.ts +524 -524
- package/src/row-resize.ts +228 -228
- package/src/scheduler-ical.ts +181 -181
- package/src/scheduler-model.test.ts +562 -562
- package/src/scheduler-model.ts +873 -873
- package/src/selection.test.ts +885 -885
- package/src/server-data-source.test.ts +383 -383
- package/src/server-data-source.ts +469 -469
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +488 -488
- package/src/spreadsheet.ts +312 -312
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/summaries.ts +113 -113
- package/src/svgrid-wrapper.types.ts +563 -563
- package/src/svgrid.async-editor-options.test.ts +273 -273
- package/src/svgrid.auto-row-height.test.ts +204 -204
- package/src/svgrid.behavior.test.ts +910 -910
- package/src/svgrid.charting.test.ts +534 -534
- package/src/svgrid.comments-autocomplete.test.ts +127 -127
- package/src/svgrid.context-menu.test.ts +147 -147
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.filter-depth.test.ts +163 -163
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +382 -377
- package/src/svgrid.filter-menu-scroll.test.ts +112 -112
- package/src/svgrid.grand-total.test.ts +188 -188
- package/src/svgrid.group-display-mode.test.ts +171 -171
- package/src/svgrid.group-footers.test.ts +121 -121
- package/src/svgrid.group-pagination.test.ts +153 -153
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.tree-data.test.ts +186 -186
- package/src/svgrid.wrapper.test.ts +63 -63
- package/src/svgriddropdown.async-panel.svelte.test.ts +195 -195
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +288 -288
- package/src/toast-store.svelte.ts +250 -250
- package/src/toast-store.test.ts +147 -147
- package/src/tree-row-model.test.ts +168 -168
- package/src/ui-buttons.test.ts +144 -144
- package/src/ui-inputs.test.ts +118 -118
- package/src/ui-localization.test.ts +113 -113
- package/src/ui-range.test.ts +70 -70
- package/src/ui-selection.test.ts +155 -155
- package/src/ui-tier1.test.ts +142 -142
- package/src/virtual.test.ts +88 -88
- 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 +322 -322
|
@@ -1,744 +1,748 @@
|
|
|
1
|
-
<script lang="ts" generics="TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData">
|
|
2
|
-
/**
|
|
3
|
-
* The in-cell and full-row editor UI, lifted out of SvGrid.svelte.
|
|
4
|
-
*
|
|
5
|
-
* Editing is off by default (`editable` / `enableInlineEditing` both default
|
|
6
|
-
* to false) and this markup only ever renders while a cell is actually being
|
|
7
|
-
* edited, so it has no business sitting in the base bundle. SvGrid.svelte
|
|
8
|
-
* loads it with `import()` the moment editing is enabled - well before any
|
|
9
|
-
* double-click can happen - so behaviour is unchanged either way.
|
|
10
|
-
*
|
|
11
|
-
* Keep `SvGridDropdown` and `SvDateTimePicker` DYNAMIC below. Importing
|
|
12
|
-
* either statically would drag ~22 KB into this chunk for every text edit,
|
|
13
|
-
* and a static import from anywhere reachable by SvGrid.svelte has silently
|
|
14
|
-
* defeated a lazy boundary in this codebase before.
|
|
15
|
-
*/
|
|
16
|
-
import {
|
|
17
|
-
type CellEditorOption,
|
|
18
|
-
type Column,
|
|
19
|
-
type EditorContext,
|
|
20
|
-
type Row,
|
|
21
|
-
type RowData,
|
|
22
|
-
type TableFeatures,
|
|
23
|
-
} from "./index";
|
|
24
|
-
import {
|
|
25
|
-
dateToTimeString,
|
|
26
|
-
getEditableInputValue,
|
|
27
|
-
getEditorClass,
|
|
28
|
-
getEditorInputType,
|
|
29
|
-
getCellEditorInputType,
|
|
30
|
-
isNumericEditorInput,
|
|
31
|
-
timeStringToDate,
|
|
32
|
-
toValueArray,
|
|
33
|
-
} from "./SvGrid.helpers";
|
|
34
|
-
import {
|
|
35
|
-
getCellEditor,
|
|
36
|
-
hasCellEditor,
|
|
37
|
-
resolveEditorProps,
|
|
38
|
-
type CellEditorContext,
|
|
39
|
-
} from "./editor-registry";
|
|
40
|
-
import type { SvGridController } from "./SvGrid.controller.svelte";
|
|
41
|
-
|
|
42
|
-
let {
|
|
43
|
-
ctrl,
|
|
44
|
-
column,
|
|
45
|
-
row,
|
|
46
|
-
fullRow = false,
|
|
47
|
-
}: {
|
|
48
|
-
ctrl: SvGridController<TFeatures, TData>;
|
|
49
|
-
column: Column<TData>;
|
|
50
|
-
row: Row<TData>;
|
|
51
|
-
/** Render the full-row editor for this cell instead of the single-cell one. */
|
|
52
|
-
fullRow?: boolean;
|
|
53
|
-
} = $props();
|
|
54
|
-
|
|
55
|
-
// Heavy editors stay behind their own `import()` so a text edit never pays
|
|
56
|
-
// for the dropdown or the date picker.
|
|
57
|
-
let DropdownEditor = $state<typeof import("./SvGridDropdown.svelte").default | null>(null);
|
|
58
|
-
let DateEditor = $state<typeof import("./SvDateTimePicker.svelte").default | null>(null);
|
|
59
|
-
$effect(() => {
|
|
60
|
-
const t = ctrl.editingCell?.editorType;
|
|
61
|
-
if (
|
|
62
|
-
(t === "list" || t === "chips" || t === "select" || t === "rich-select") &&
|
|
63
|
-
!DropdownEditor
|
|
64
|
-
)
|
|
65
|
-
import("./SvGridDropdown.svelte").then((m) => (DropdownEditor = m.default));
|
|
66
|
-
if ((t === "date" || t === "datetime" || t === "time") && !DateEditor)
|
|
67
|
-
import("./SvDateTimePicker.svelte").then((m) => (DateEditor = m.default));
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const grid = $derived(ctrl.grid);
|
|
71
|
-
const fullRowEdit = $derived(ctrl.fullRowEdit);
|
|
72
|
-
const getColumnEditorOptions = $derived(ctrl.getColumnEditorOptions);
|
|
73
|
-
const areEditorOptionsLoading = $derived(ctrl.areEditorOptionsLoading);
|
|
74
|
-
const saveEditingCell = $derived(ctrl.saveEditingCell);
|
|
75
|
-
const updateEditingCellValue = $derived(ctrl.updateEditingCellValue);
|
|
76
|
-
const onEditorKeyDown = $derived(ctrl.onEditorKeyDown);
|
|
77
|
-
const focusOnMount = $derived(ctrl.focusOnMount);
|
|
78
|
-
const toggleCheckboxWithKeyboard = $derived(ctrl.toggleCheckboxWithKeyboard);
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Focus a button-based editor on mount. `focusOnMount` is for text controls -
|
|
82
|
-
* it also places the caret - and the checkbox and rating editors, being
|
|
83
|
-
* <button>s, had no equivalent and so never took focus at all. Their own key
|
|
84
|
-
* handlers therefore never fired: neither could be operated from the
|
|
85
|
-
* keyboard, and Escape never reached them, so the cell stayed stuck in edit
|
|
86
|
-
* mode until the user clicked somewhere else.
|
|
87
|
-
*
|
|
88
|
-
* `enabled` lets a group of buttons (the rating stars) pick which one holds
|
|
89
|
-
* focus, since an action cannot be applied conditionally.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
e.preventDefault();
|
|
103
|
-
ctrl.
|
|
104
|
-
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
{
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
{
|
|
292
|
-
|
|
293
|
-
{
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
{
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
{@
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
},
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
{
|
|
418
|
-
|
|
419
|
-
{@
|
|
420
|
-
|
|
421
|
-
{@const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
//
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
{
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
event.
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
{
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
updateEditingCellValue(
|
|
701
|
-
(event.currentTarget as HTMLInputElement).value,
|
|
702
|
-
)
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
{@const
|
|
716
|
-
{@const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
//
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
{
|
|
1
|
+
<script lang="ts" generics="TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData">
|
|
2
|
+
/**
|
|
3
|
+
* The in-cell and full-row editor UI, lifted out of SvGrid.svelte.
|
|
4
|
+
*
|
|
5
|
+
* Editing is off by default (`editable` / `enableInlineEditing` both default
|
|
6
|
+
* to false) and this markup only ever renders while a cell is actually being
|
|
7
|
+
* edited, so it has no business sitting in the base bundle. SvGrid.svelte
|
|
8
|
+
* loads it with `import()` the moment editing is enabled - well before any
|
|
9
|
+
* double-click can happen - so behaviour is unchanged either way.
|
|
10
|
+
*
|
|
11
|
+
* Keep `SvGridDropdown` and `SvDateTimePicker` DYNAMIC below. Importing
|
|
12
|
+
* either statically would drag ~22 KB into this chunk for every text edit,
|
|
13
|
+
* and a static import from anywhere reachable by SvGrid.svelte has silently
|
|
14
|
+
* defeated a lazy boundary in this codebase before.
|
|
15
|
+
*/
|
|
16
|
+
import {
|
|
17
|
+
type CellEditorOption,
|
|
18
|
+
type Column,
|
|
19
|
+
type EditorContext,
|
|
20
|
+
type Row,
|
|
21
|
+
type RowData,
|
|
22
|
+
type TableFeatures,
|
|
23
|
+
} from "./index";
|
|
24
|
+
import {
|
|
25
|
+
dateToTimeString,
|
|
26
|
+
getEditableInputValue,
|
|
27
|
+
getEditorClass,
|
|
28
|
+
getEditorInputType,
|
|
29
|
+
getCellEditorInputType,
|
|
30
|
+
isNumericEditorInput,
|
|
31
|
+
timeStringToDate,
|
|
32
|
+
toValueArray,
|
|
33
|
+
} from "./SvGrid.helpers";
|
|
34
|
+
import {
|
|
35
|
+
getCellEditor,
|
|
36
|
+
hasCellEditor,
|
|
37
|
+
resolveEditorProps,
|
|
38
|
+
type CellEditorContext,
|
|
39
|
+
} from "./editor-registry";
|
|
40
|
+
import type { SvGridController } from "./SvGrid.controller.svelte";
|
|
41
|
+
|
|
42
|
+
let {
|
|
43
|
+
ctrl,
|
|
44
|
+
column,
|
|
45
|
+
row,
|
|
46
|
+
fullRow = false,
|
|
47
|
+
}: {
|
|
48
|
+
ctrl: SvGridController<TFeatures, TData>;
|
|
49
|
+
column: Column<TData>;
|
|
50
|
+
row: Row<TData>;
|
|
51
|
+
/** Render the full-row editor for this cell instead of the single-cell one. */
|
|
52
|
+
fullRow?: boolean;
|
|
53
|
+
} = $props();
|
|
54
|
+
|
|
55
|
+
// Heavy editors stay behind their own `import()` so a text edit never pays
|
|
56
|
+
// for the dropdown or the date picker.
|
|
57
|
+
let DropdownEditor = $state<typeof import("./SvGridDropdown.svelte").default | null>(null);
|
|
58
|
+
let DateEditor = $state<typeof import("./SvDateTimePicker.svelte").default | null>(null);
|
|
59
|
+
$effect(() => {
|
|
60
|
+
const t = ctrl.editingCell?.editorType;
|
|
61
|
+
if (
|
|
62
|
+
(t === "list" || t === "chips" || t === "select" || t === "rich-select") &&
|
|
63
|
+
!DropdownEditor
|
|
64
|
+
)
|
|
65
|
+
import("./SvGridDropdown.svelte").then((m) => (DropdownEditor = m.default));
|
|
66
|
+
if ((t === "date" || t === "datetime" || t === "time") && !DateEditor)
|
|
67
|
+
import("./SvDateTimePicker.svelte").then((m) => (DateEditor = m.default));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const grid = $derived(ctrl.grid);
|
|
71
|
+
const fullRowEdit = $derived(ctrl.fullRowEdit);
|
|
72
|
+
const getColumnEditorOptions = $derived(ctrl.getColumnEditorOptions);
|
|
73
|
+
const areEditorOptionsLoading = $derived(ctrl.areEditorOptionsLoading);
|
|
74
|
+
const saveEditingCell = $derived(ctrl.saveEditingCell);
|
|
75
|
+
const updateEditingCellValue = $derived(ctrl.updateEditingCellValue);
|
|
76
|
+
const onEditorKeyDown = $derived(ctrl.onEditorKeyDown);
|
|
77
|
+
const focusOnMount = $derived(ctrl.focusOnMount);
|
|
78
|
+
const toggleCheckboxWithKeyboard = $derived(ctrl.toggleCheckboxWithKeyboard);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Focus a button-based editor on mount. `focusOnMount` is for text controls -
|
|
82
|
+
* it also places the caret - and the checkbox and rating editors, being
|
|
83
|
+
* <button>s, had no equivalent and so never took focus at all. Their own key
|
|
84
|
+
* handlers therefore never fired: neither could be operated from the
|
|
85
|
+
* keyboard, and Escape never reached them, so the cell stayed stuck in edit
|
|
86
|
+
* mode until the user clicked somewhere else.
|
|
87
|
+
*
|
|
88
|
+
* `enabled` lets a group of buttons (the rating stars) pick which one holds
|
|
89
|
+
* focus, since an action cannot be applied conditionally.
|
|
90
|
+
*
|
|
91
|
+
* Synchronous, for the same reason as `focusOnMount`: a deferred frame left
|
|
92
|
+
* the cell marked as editing with nothing inside it focused, and keystrokes
|
|
93
|
+
* in that window were dropped by both the grid root and the editor.
|
|
94
|
+
*/
|
|
95
|
+
function focusControlOnMount(node: HTMLElement, enabled: boolean = true) {
|
|
96
|
+
if (!enabled) return;
|
|
97
|
+
node.focus({ preventScroll: true });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function fullRowKeydown(e: KeyboardEvent) {
|
|
101
|
+
if (e.key === "Enter") {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
ctrl.commitFullRowEdit();
|
|
104
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
105
|
+
} else if (e.key === "Escape") {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
ctrl.cancelFullRowEdit();
|
|
108
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function optionValueOf(o: unknown): string {
|
|
112
|
+
return typeof o === "object" && o !== null && "value" in o
|
|
113
|
+
? String((o as { value: unknown }).value)
|
|
114
|
+
: String(o);
|
|
115
|
+
}
|
|
116
|
+
function optionLabelOf(o: unknown): string {
|
|
117
|
+
return typeof o === "object" && o !== null && "label" in o
|
|
118
|
+
? String((o as { label: unknown }).label)
|
|
119
|
+
: String(o);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Build the interaction context handed to a CUSTOM cell editor registered via
|
|
123
|
+
// `registerCellEditor`. Maps the grid's editing lifecycle onto the uniform
|
|
124
|
+
// change / commit / cancel contract every registered editor speaks.
|
|
125
|
+
function buildRegisteredEditorContext(): CellEditorContext {
|
|
126
|
+
const cell = ctrl.editingCell;
|
|
127
|
+
return {
|
|
128
|
+
value: cell?.value,
|
|
129
|
+
rowId: cell?.rowId ?? "",
|
|
130
|
+
columnId: cell?.columnId ?? "",
|
|
131
|
+
onChange: (v: unknown) => updateEditingCellValue(v),
|
|
132
|
+
onCommit: (v?: unknown) => {
|
|
133
|
+
if (v !== undefined) updateEditingCellValue(v);
|
|
134
|
+
saveEditingCell();
|
|
135
|
+
},
|
|
136
|
+
onCancel: () => {
|
|
137
|
+
ctrl.editingCell = null;
|
|
138
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
139
|
+
},
|
|
140
|
+
// Tab out of an editor: commit, then step to the adjacent cell. Without
|
|
141
|
+
// this a registered editor's Tab walks focus out of the grid (#48) - the
|
|
142
|
+
// built-in editors already route through the same helper.
|
|
143
|
+
onCommitAndMove: (v: unknown, direction: 1 | -1) => {
|
|
144
|
+
if (v !== undefined) updateEditingCellValue(v);
|
|
145
|
+
ctrl.commitAndMoveByTab(direction === -1);
|
|
146
|
+
},
|
|
147
|
+
// Close a popover editor without committing. Same effect as cancel today;
|
|
148
|
+
// named separately so an editor can distinguish "dismiss my panel" from
|
|
149
|
+
// "abandon the edit" if that ever diverges.
|
|
150
|
+
onRequestClose: () => {
|
|
151
|
+
ctrl.editingCell = null;
|
|
152
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
153
|
+
},
|
|
154
|
+
inCell: true,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
</script>
|
|
158
|
+
|
|
159
|
+
{#snippet chipsEditor(
|
|
160
|
+
opts: CellEditorOption[],
|
|
161
|
+
multi: boolean,
|
|
162
|
+
selectedArr: Array<string | number>,
|
|
163
|
+
optsLoading: boolean,
|
|
164
|
+
)}
|
|
165
|
+
{#if opts.length > 0}
|
|
166
|
+
<!-- Options-driven chips editor: defer to the custom dropdown,
|
|
167
|
+
which renders the selected values as chips in its trigger and
|
|
168
|
+
pops out a styled listbox. Identical UX to the list editor
|
|
169
|
+
with renderChipsInTrigger flipped on. -->
|
|
170
|
+
{#if DropdownEditor}
|
|
171
|
+
<DropdownEditor
|
|
172
|
+
options={opts}
|
|
173
|
+
loading={optsLoading}
|
|
174
|
+
value={ctrl.editingCell?.value}
|
|
175
|
+
multiple={multi}
|
|
176
|
+
placeholder="Pick…"
|
|
177
|
+
renderChipsInTrigger={true}
|
|
178
|
+
onChange={(next) => {
|
|
179
|
+
ctrl.editingCell = ctrl.editingCell
|
|
180
|
+
? { ...ctrl.editingCell, value: next }
|
|
181
|
+
: ctrl.editingCell;
|
|
182
|
+
}}
|
|
183
|
+
onCommit={() => saveEditingCell()}
|
|
184
|
+
onCancel={() => {
|
|
185
|
+
ctrl.editingCell = null;
|
|
186
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
187
|
+
}}
|
|
188
|
+
/>
|
|
189
|
+
{/if}
|
|
190
|
+
{:else}
|
|
191
|
+
<!-- Free-form chips: typed tags. Enter / comma commits a chip,
|
|
192
|
+
Backspace on empty input removes the last chip, blur saves. -->
|
|
193
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
194
|
+
<div
|
|
195
|
+
class="sv-grid-cell-editor sv-grid-cell-editor-chips"
|
|
196
|
+
role="group"
|
|
197
|
+
tabindex={-1}
|
|
198
|
+
>
|
|
199
|
+
<div class="sv-grid-chips-row">
|
|
200
|
+
{#each selectedArr as v, idx (String(v) + "_" + idx)}
|
|
201
|
+
<span class="sv-grid-chip sv-grid-chip-removable">
|
|
202
|
+
{String(v)}
|
|
203
|
+
<button
|
|
204
|
+
type="button"
|
|
205
|
+
class="sv-grid-chip-remove"
|
|
206
|
+
aria-label="Remove {String(v)}"
|
|
207
|
+
onmousedown={(event) => event.preventDefault()}
|
|
208
|
+
onclick={() => {
|
|
209
|
+
const next = selectedArr.filter((_, i) => i !== idx);
|
|
210
|
+
ctrl.editingCell = ctrl.editingCell
|
|
211
|
+
? {
|
|
212
|
+
...ctrl.editingCell,
|
|
213
|
+
value: multi ? next : (next[0] ?? null),
|
|
214
|
+
}
|
|
215
|
+
: ctrl.editingCell;
|
|
216
|
+
}}>×</button
|
|
217
|
+
>
|
|
218
|
+
</span>
|
|
219
|
+
{/each}
|
|
220
|
+
<input
|
|
221
|
+
use:focusOnMount
|
|
222
|
+
class="sv-grid-chip-input"
|
|
223
|
+
type="text"
|
|
224
|
+
placeholder={multi ? "Type, Enter to add" : "Type a value"}
|
|
225
|
+
onkeydown={(event) => {
|
|
226
|
+
if (event.key === "Enter" || (multi && event.key === ",")) {
|
|
227
|
+
event.preventDefault();
|
|
228
|
+
event.stopPropagation();
|
|
229
|
+
const input = event.currentTarget as HTMLInputElement;
|
|
230
|
+
const raw = input.value.trim();
|
|
231
|
+
if (raw) {
|
|
232
|
+
const next = multi ? [...selectedArr, raw] : [raw];
|
|
233
|
+
ctrl.editingCell = ctrl.editingCell
|
|
234
|
+
? { ...ctrl.editingCell, value: multi ? next : raw }
|
|
235
|
+
: ctrl.editingCell;
|
|
236
|
+
input.value = "";
|
|
237
|
+
if (!multi) saveEditingCell();
|
|
238
|
+
}
|
|
239
|
+
} else if (event.key === "Escape") {
|
|
240
|
+
onEditorKeyDown(event);
|
|
241
|
+
} else if (event.key === "Backspace") {
|
|
242
|
+
const input = event.currentTarget as HTMLInputElement;
|
|
243
|
+
if (input.value === "" && selectedArr.length > 0) {
|
|
244
|
+
event.preventDefault();
|
|
245
|
+
const next = selectedArr.slice(0, -1);
|
|
246
|
+
ctrl.editingCell = ctrl.editingCell
|
|
247
|
+
? {
|
|
248
|
+
...ctrl.editingCell,
|
|
249
|
+
value: multi ? next : (next[0] ?? null),
|
|
250
|
+
}
|
|
251
|
+
: ctrl.editingCell;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}}
|
|
255
|
+
onblur={() => saveEditingCell()}
|
|
256
|
+
/>
|
|
257
|
+
{#if multi}
|
|
258
|
+
<button
|
|
259
|
+
type="button"
|
|
260
|
+
class="sv-grid-chip-commit"
|
|
261
|
+
onmousedown={(event) => event.preventDefault()}
|
|
262
|
+
onclick={() => saveEditingCell()}
|
|
263
|
+
aria-label="Commit chip selection">Done</button
|
|
264
|
+
>
|
|
265
|
+
{/if}
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
{/if}
|
|
269
|
+
{/snippet}
|
|
270
|
+
|
|
271
|
+
{#if fullRow}
|
|
272
|
+
{@const et = column.columnDef.editorType ?? "text"}
|
|
273
|
+
{@const val = fullRowEdit?.draft[column.id]}
|
|
274
|
+
{#if et === "checkbox"}
|
|
275
|
+
<input
|
|
276
|
+
type="checkbox"
|
|
277
|
+
class="sv-grid-fr-editor sv-grid-fr-checkbox"
|
|
278
|
+
checked={Boolean(val)}
|
|
279
|
+
onchange={(e) =>
|
|
280
|
+
ctrl.setFullRowDraft(column.id, e.currentTarget.checked)}
|
|
281
|
+
onkeydown={fullRowKeydown}
|
|
282
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
283
|
+
onclick={(e) => e.stopPropagation()}
|
|
284
|
+
/>
|
|
285
|
+
{:else if et === "list" || et === "select" || et === "rich-select"}
|
|
286
|
+
{@const opts = getColumnEditorOptions(column, row)}
|
|
287
|
+
<select
|
|
288
|
+
class="sv-grid-cell-editor sv-grid-fr-editor"
|
|
289
|
+
value={String(val ?? "")}
|
|
290
|
+
onchange={(e) => ctrl.setFullRowDraft(column.id, e.currentTarget.value)}
|
|
291
|
+
onkeydown={fullRowKeydown}
|
|
292
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
293
|
+
onclick={(e) => e.stopPropagation()}
|
|
294
|
+
>
|
|
295
|
+
{#each opts as o (optionValueOf(o))}
|
|
296
|
+
<option value={optionValueOf(o)}>{optionLabelOf(o)}</option>
|
|
297
|
+
{/each}
|
|
298
|
+
</select>
|
|
299
|
+
{:else}
|
|
300
|
+
{@const inputType =
|
|
301
|
+
et === "number"
|
|
302
|
+
? "number"
|
|
303
|
+
: et === "date"
|
|
304
|
+
? "date"
|
|
305
|
+
: et === "datetime"
|
|
306
|
+
? "datetime-local"
|
|
307
|
+
: et === "time"
|
|
308
|
+
? "time"
|
|
309
|
+
: et === "password"
|
|
310
|
+
? "password"
|
|
311
|
+
: "text"}
|
|
312
|
+
<input
|
|
313
|
+
type={inputType}
|
|
314
|
+
class="sv-grid-cell-editor sv-grid-fr-editor"
|
|
315
|
+
value={String(val ?? "")}
|
|
316
|
+
oninput={(e) => ctrl.setFullRowDraft(column.id, e.currentTarget.value)}
|
|
317
|
+
onkeydown={fullRowKeydown}
|
|
318
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
319
|
+
onclick={(e) => e.stopPropagation()}
|
|
320
|
+
/>
|
|
321
|
+
{/if}
|
|
322
|
+
{:else}
|
|
323
|
+
{#if column.columnDef.cellEditor}
|
|
324
|
+
<!-- Custom editor slot. The columnDef provides a snippet that
|
|
325
|
+
receives the editor context (value + commit + cancel) so the
|
|
326
|
+
consumer fully owns the in-cell UI. -->
|
|
327
|
+
{@const customEditor = column.columnDef
|
|
328
|
+
.cellEditor as unknown as import("svelte").Snippet<
|
|
329
|
+
[EditorContext<TData>]
|
|
330
|
+
>}
|
|
331
|
+
{@render customEditor({
|
|
332
|
+
cell: row.getAllCells().find((c) => c.column.id === column.id)!,
|
|
333
|
+
row,
|
|
334
|
+
column,
|
|
335
|
+
table: grid,
|
|
336
|
+
getValue: () => ctrl.editingCell?.value,
|
|
337
|
+
value: ctrl.editingCell?.value,
|
|
338
|
+
update: (next: unknown) => {
|
|
339
|
+
// Stage the draft without closing. Live-preview controls
|
|
340
|
+
// (sliders, color pickers) call this on every input tick.
|
|
341
|
+
ctrl.editingCell = ctrl.editingCell
|
|
342
|
+
? { ...ctrl.editingCell, value: next }
|
|
343
|
+
: ctrl.editingCell;
|
|
344
|
+
},
|
|
345
|
+
commit: (next?: unknown) => {
|
|
346
|
+
// Write + close. If the caller passed a value, stage it
|
|
347
|
+
// first; otherwise save whatever update() last wrote.
|
|
348
|
+
if (next !== undefined) {
|
|
349
|
+
ctrl.editingCell = ctrl.editingCell
|
|
350
|
+
? { ...ctrl.editingCell, value: next }
|
|
351
|
+
: ctrl.editingCell;
|
|
352
|
+
}
|
|
353
|
+
saveEditingCell();
|
|
354
|
+
},
|
|
355
|
+
cancel: () => {
|
|
356
|
+
ctrl.editingCell = null;
|
|
357
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
358
|
+
},
|
|
359
|
+
})}
|
|
360
|
+
{:else if ctrl.editingCell?.editorType === "checkbox"}
|
|
361
|
+
<button
|
|
362
|
+
use:focusControlOnMount
|
|
363
|
+
type="button"
|
|
364
|
+
class="sv-grid-checkbox"
|
|
365
|
+
role="checkbox"
|
|
366
|
+
aria-checked={Boolean(ctrl.editingCell.value)}
|
|
367
|
+
aria-label="Edit checkbox value"
|
|
368
|
+
onclick={(event) => {
|
|
369
|
+
event.stopPropagation();
|
|
370
|
+
const nextValue = !ctrl.editingCell?.value;
|
|
371
|
+
ctrl.editingCell = ctrl.editingCell
|
|
372
|
+
? { ...ctrl.editingCell, value: nextValue }
|
|
373
|
+
: ctrl.editingCell;
|
|
374
|
+
saveEditingCell();
|
|
375
|
+
}}
|
|
376
|
+
onkeydown={(event) => {
|
|
377
|
+
// Enter / Space are the checkbox's own activation keys. Everything
|
|
378
|
+
// else - Escape to cancel, Tab to commit and move on - goes to the
|
|
379
|
+
// shared editor handler, which this editor used to bypass entirely.
|
|
380
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
381
|
+
toggleCheckboxWithKeyboard(event, () => {
|
|
382
|
+
event.stopPropagation();
|
|
383
|
+
const nextValue = !ctrl.editingCell?.value;
|
|
384
|
+
ctrl.editingCell = ctrl.editingCell
|
|
385
|
+
? { ...ctrl.editingCell, value: nextValue }
|
|
386
|
+
: ctrl.editingCell;
|
|
387
|
+
saveEditingCell();
|
|
388
|
+
});
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
onEditorKeyDown(event);
|
|
392
|
+
}}
|
|
393
|
+
onblur={() => saveEditingCell()}
|
|
394
|
+
></button>
|
|
395
|
+
{:else if ctrl.editingCell?.editorType === "list"}
|
|
396
|
+
{@const opts = getColumnEditorOptions(column, row)}
|
|
397
|
+
{@const optsLoading = areEditorOptionsLoading(column, row)}
|
|
398
|
+
{@const multi = column.columnDef.editorMultiple === true}
|
|
399
|
+
{#if DropdownEditor}
|
|
400
|
+
<DropdownEditor
|
|
401
|
+
options={opts}
|
|
402
|
+
loading={optsLoading}
|
|
403
|
+
value={ctrl.editingCell?.value}
|
|
404
|
+
multiple={multi}
|
|
405
|
+
placeholder="Select…"
|
|
406
|
+
onChange={(next) => {
|
|
407
|
+
ctrl.editingCell = ctrl.editingCell
|
|
408
|
+
? { ...ctrl.editingCell, value: next }
|
|
409
|
+
: ctrl.editingCell;
|
|
410
|
+
}}
|
|
411
|
+
onCommit={() => saveEditingCell()}
|
|
412
|
+
onCancel={() => {
|
|
413
|
+
ctrl.editingCell = null;
|
|
414
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
415
|
+
}}
|
|
416
|
+
/>
|
|
417
|
+
{/if}
|
|
418
|
+
{:else if ctrl.editingCell?.editorType === "chips"}
|
|
419
|
+
{@const opts = getColumnEditorOptions(column, row)}
|
|
420
|
+
{@const optsLoading = areEditorOptionsLoading(column, row)}
|
|
421
|
+
{@const multi = column.columnDef.editorMultiple === true}
|
|
422
|
+
{@const selectedArr = toValueArray(ctrl.editingCell?.value)}
|
|
423
|
+
{@render chipsEditor(opts, multi, selectedArr, optsLoading)}
|
|
424
|
+
{:else if ctrl.editingCell?.editorType === "rating"}
|
|
425
|
+
{@const ratingVal = Math.max(
|
|
426
|
+
0,
|
|
427
|
+
Math.min(5, Math.round(Number(ctrl.editingCell?.value) || 0)),
|
|
428
|
+
)}
|
|
429
|
+
<!-- Focus the star for the current rating (the first one when unrated),
|
|
430
|
+
the radio a radiogroup is expected to enter on. Without it nothing
|
|
431
|
+
in this editor held focus, so `onEditorKeyDown` below never ran and
|
|
432
|
+
Escape could not close the cell. -->
|
|
433
|
+
{@const focusStar = Math.min(5, Math.max(1, ratingVal))}
|
|
434
|
+
<span class="sv-grid-rating-editor" role="radiogroup" aria-label="Rating">
|
|
435
|
+
{#each [1, 2, 3, 4, 5] as n (n)}
|
|
436
|
+
<button
|
|
437
|
+
use:focusControlOnMount={n === focusStar}
|
|
438
|
+
type="button"
|
|
439
|
+
role="radio"
|
|
440
|
+
aria-checked={ratingVal >= n}
|
|
441
|
+
aria-label={`${n} ${n === 1 ? "star" : "stars"}`}
|
|
442
|
+
class={`sv-grid-rating-star ${ratingVal >= n ? "sv-grid-rating-star-on" : ""}`}
|
|
443
|
+
onmousedown={(event) => event.preventDefault()}
|
|
444
|
+
onclick={(event) => {
|
|
445
|
+
event.stopPropagation();
|
|
446
|
+
ctrl.editingCell = ctrl.editingCell
|
|
447
|
+
? { ...ctrl.editingCell, value: n }
|
|
448
|
+
: ctrl.editingCell;
|
|
449
|
+
saveEditingCell();
|
|
450
|
+
}}
|
|
451
|
+
onkeydown={(event) => {
|
|
452
|
+
// Arrows move between the stars, the way a radiogroup is
|
|
453
|
+
// expected to work - without them the editor takes focus (see
|
|
454
|
+
// focusControlOnMount) but Space can only re-pick the star that
|
|
455
|
+
// already holds the value. Enter / Escape / Tab still fall
|
|
456
|
+
// through to the shared handler.
|
|
457
|
+
const key = event.key;
|
|
458
|
+
if (
|
|
459
|
+
key !== "ArrowLeft" &&
|
|
460
|
+
key !== "ArrowRight" &&
|
|
461
|
+
key !== "Home" &&
|
|
462
|
+
key !== "End"
|
|
463
|
+
) {
|
|
464
|
+
onEditorKeyDown(event);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
event.preventDefault();
|
|
468
|
+
event.stopPropagation();
|
|
469
|
+
const current = Math.min(5, Math.max(1, ratingVal));
|
|
470
|
+
const next =
|
|
471
|
+
key === "Home"
|
|
472
|
+
? 1
|
|
473
|
+
: key === "End"
|
|
474
|
+
? 5
|
|
475
|
+
: key === "ArrowLeft"
|
|
476
|
+
? Math.max(1, current - 1)
|
|
477
|
+
: Math.min(5, current + 1);
|
|
478
|
+
ctrl.editingCell = ctrl.editingCell
|
|
479
|
+
? { ...ctrl.editingCell, value: next }
|
|
480
|
+
: ctrl.editingCell;
|
|
481
|
+
const stars = (
|
|
482
|
+
event.currentTarget as HTMLElement
|
|
483
|
+
).parentElement?.querySelectorAll<HTMLElement>(
|
|
484
|
+
".sv-grid-rating-star",
|
|
485
|
+
);
|
|
486
|
+
stars?.[next - 1]?.focus({ preventScroll: true });
|
|
487
|
+
}}>★</button
|
|
488
|
+
>
|
|
489
|
+
{/each}
|
|
490
|
+
<button
|
|
491
|
+
type="button"
|
|
492
|
+
aria-label="Clear rating"
|
|
493
|
+
class="sv-grid-rating-clear"
|
|
494
|
+
onmousedown={(event) => event.preventDefault()}
|
|
495
|
+
onclick={(event) => {
|
|
496
|
+
event.stopPropagation();
|
|
497
|
+
ctrl.editingCell = ctrl.editingCell
|
|
498
|
+
? { ...ctrl.editingCell, value: 0 }
|
|
499
|
+
: ctrl.editingCell;
|
|
500
|
+
saveEditingCell();
|
|
501
|
+
}}>×</button
|
|
502
|
+
>
|
|
503
|
+
</span>
|
|
504
|
+
{:else if ctrl.editingCell?.editorType === "select"}
|
|
505
|
+
<!-- Custom dropdown: opens a themed popover identical in feel to
|
|
506
|
+
the existing 'list' editor (single-select, no typeahead). -->
|
|
507
|
+
{@const selectOpts = getColumnEditorOptions(column, row)}
|
|
508
|
+
{@const optsLoading = areEditorOptionsLoading(column, row)}
|
|
509
|
+
{#if DropdownEditor}
|
|
510
|
+
<DropdownEditor
|
|
511
|
+
options={selectOpts}
|
|
512
|
+
loading={optsLoading}
|
|
513
|
+
value={ctrl.editingCell?.value}
|
|
514
|
+
multiple={false}
|
|
515
|
+
placeholder="Select…"
|
|
516
|
+
onChange={(next) => {
|
|
517
|
+
ctrl.editingCell = ctrl.editingCell
|
|
518
|
+
? { ...ctrl.editingCell, value: next }
|
|
519
|
+
: ctrl.editingCell;
|
|
520
|
+
}}
|
|
521
|
+
onCommit={() => saveEditingCell()}
|
|
522
|
+
onCancel={() => {
|
|
523
|
+
ctrl.editingCell = null;
|
|
524
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
525
|
+
}}
|
|
526
|
+
/>
|
|
527
|
+
{/if}
|
|
528
|
+
{:else if ctrl.editingCell?.editorType === "rich-select"}
|
|
529
|
+
<!-- Searchable combobox: same popover as 'select' with a
|
|
530
|
+
typeahead filter input baked in at the top. -->
|
|
531
|
+
{@const richOpts = getColumnEditorOptions(column, row)}
|
|
532
|
+
{@const optsLoading = areEditorOptionsLoading(column, row)}
|
|
533
|
+
{#if DropdownEditor}
|
|
534
|
+
<DropdownEditor
|
|
535
|
+
options={richOpts}
|
|
536
|
+
loading={optsLoading}
|
|
537
|
+
value={ctrl.editingCell?.value}
|
|
538
|
+
multiple={false}
|
|
539
|
+
searchable={true}
|
|
540
|
+
placeholder="Search…"
|
|
541
|
+
onChange={(next) => {
|
|
542
|
+
ctrl.editingCell = ctrl.editingCell
|
|
543
|
+
? { ...ctrl.editingCell, value: next }
|
|
544
|
+
: ctrl.editingCell;
|
|
545
|
+
}}
|
|
546
|
+
onCommit={() => saveEditingCell()}
|
|
547
|
+
onCancel={() => {
|
|
548
|
+
ctrl.editingCell = null;
|
|
549
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
550
|
+
}}
|
|
551
|
+
/>
|
|
552
|
+
{/if}
|
|
553
|
+
{:else if ctrl.editingCell?.editorType === "textarea"}
|
|
554
|
+
<!-- Multi-line editor. Commits on Tab, Ctrl/Cmd+Enter, or blur.
|
|
555
|
+
Plain Enter inserts a newline (the whole point of textarea).
|
|
556
|
+
Esc cancels. -->
|
|
557
|
+
<textarea
|
|
558
|
+
use:focusOnMount
|
|
559
|
+
class="sv-grid-cell-editor sv-grid-cell-editor-textarea"
|
|
560
|
+
rows="4"
|
|
561
|
+
value={String(ctrl.editingCell?.value ?? "")}
|
|
562
|
+
onpointerdown={(event) => event.stopPropagation()}
|
|
563
|
+
oninput={(event) =>
|
|
564
|
+
updateEditingCellValue(
|
|
565
|
+
(event.currentTarget as HTMLTextAreaElement).value,
|
|
566
|
+
)}
|
|
567
|
+
onkeydown={(event) => {
|
|
568
|
+
event.stopPropagation();
|
|
569
|
+
if (event.key === "Escape") {
|
|
570
|
+
event.preventDefault();
|
|
571
|
+
ctrl.editingCell = null;
|
|
572
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
// Tab and Ctrl/Cmd+Enter both commit. Plain Enter inserts a newline.
|
|
576
|
+
// Tab goes through the shared helper so it advances the active cell
|
|
577
|
+
// like every other editor does (#48).
|
|
578
|
+
if (event.key === "Tab") {
|
|
579
|
+
event.preventDefault();
|
|
580
|
+
ctrl.commitAndMoveByTab(event.shiftKey);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
|
|
584
|
+
event.preventDefault();
|
|
585
|
+
saveEditingCell();
|
|
586
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
587
|
+
}
|
|
588
|
+
}}
|
|
589
|
+
onblur={() => saveEditingCell()}
|
|
590
|
+
></textarea>
|
|
591
|
+
{:else if ctrl.editingCell?.editorType === "autocomplete"}
|
|
592
|
+
<!-- Free-text autocomplete: a text input with a live-filtered
|
|
593
|
+
suggestion list. Typing edits the value freely; clicking a
|
|
594
|
+
suggestion (or blur) commits. Accepts values not in the list. -->
|
|
595
|
+
{@const acOpts = getColumnEditorOptions(column, row)}
|
|
596
|
+
{@const acText = String(ctrl.editingCell?.value ?? "")}
|
|
597
|
+
{@const acFiltered = acText.trim()
|
|
598
|
+
? acOpts.filter((o) =>
|
|
599
|
+
String(o.label).toLowerCase().includes(acText.toLowerCase()),
|
|
600
|
+
)
|
|
601
|
+
: acOpts}
|
|
602
|
+
<div class="sv-grid-autocomplete">
|
|
603
|
+
<input
|
|
604
|
+
use:focusOnMount
|
|
605
|
+
class="sv-grid-cell-editor sv-grid-cell-editor-autocomplete"
|
|
606
|
+
type="text"
|
|
607
|
+
value={acText}
|
|
608
|
+
onpointerdown={(event) => event.stopPropagation()}
|
|
609
|
+
oninput={(event) =>
|
|
610
|
+
updateEditingCellValue(
|
|
611
|
+
(event.currentTarget as HTMLInputElement).value,
|
|
612
|
+
)}
|
|
613
|
+
onblur={() => saveEditingCell()}
|
|
614
|
+
onkeydown={onEditorKeyDown}
|
|
615
|
+
/>
|
|
616
|
+
{#if acFiltered.length > 0}
|
|
617
|
+
<div class="sv-grid-autocomplete-list" role="listbox">
|
|
618
|
+
{#each acFiltered.slice(0, 50) as opt (opt.value)}
|
|
619
|
+
<button
|
|
620
|
+
type="button"
|
|
621
|
+
class="sv-grid-autocomplete-option"
|
|
622
|
+
role="option"
|
|
623
|
+
aria-selected={String(opt.value) === acText}
|
|
624
|
+
onmousedown={(event) => {
|
|
625
|
+
event.preventDefault();
|
|
626
|
+
ctrl.editingCell = ctrl.editingCell
|
|
627
|
+
? { ...ctrl.editingCell, value: opt.value }
|
|
628
|
+
: ctrl.editingCell;
|
|
629
|
+
saveEditingCell();
|
|
630
|
+
}}>{opt.label}</button
|
|
631
|
+
>
|
|
632
|
+
{/each}
|
|
633
|
+
</div>
|
|
634
|
+
{/if}
|
|
635
|
+
</div>
|
|
636
|
+
{:else if ctrl.editingCell?.editorType === "date"}
|
|
637
|
+
<!-- Rich date editor: SvCalendar popover over a formatted input. Opts
|
|
638
|
+
out to the native <input type="date"> via editorType 'date-native'. -->
|
|
639
|
+
{#if DateEditor}
|
|
640
|
+
<DateEditor
|
|
641
|
+
value={ctrl.editingCell?.value as string | number | Date | null}
|
|
642
|
+
formatString="yyyy-MM-dd"
|
|
643
|
+
dropDownDisplayMode="calendar"
|
|
644
|
+
autoOpen
|
|
645
|
+
block
|
|
646
|
+
onChange={(d) => updateEditingCellValue(d)}
|
|
647
|
+
onCommit={() => saveEditingCell()}
|
|
648
|
+
onCancel={() => {
|
|
649
|
+
ctrl.editingCell = null;
|
|
650
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
651
|
+
}}
|
|
652
|
+
/>
|
|
653
|
+
{/if}
|
|
654
|
+
{:else if ctrl.editingCell?.editorType === "datetime"}
|
|
655
|
+
{#if DateEditor}
|
|
656
|
+
<DateEditor
|
|
657
|
+
value={ctrl.editingCell?.value as string | number | Date | null}
|
|
658
|
+
formatString="yyyy-MM-dd HH:mm"
|
|
659
|
+
dropDownDisplayMode="both"
|
|
660
|
+
autoOpen
|
|
661
|
+
block
|
|
662
|
+
onChange={(d) => updateEditingCellValue(d)}
|
|
663
|
+
onCommit={() => saveEditingCell()}
|
|
664
|
+
onCancel={() => {
|
|
665
|
+
ctrl.editingCell = null;
|
|
666
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
667
|
+
}}
|
|
668
|
+
/>
|
|
669
|
+
{/if}
|
|
670
|
+
{:else if ctrl.editingCell?.editorType === "time"}
|
|
671
|
+
<!-- Rich time editor: SvTimePicker dial. Stores the 'HH:MM' string the
|
|
672
|
+
`time` parser expects; opts out via editorType 'time-native'. -->
|
|
673
|
+
{#if DateEditor}
|
|
674
|
+
<DateEditor
|
|
675
|
+
value={timeStringToDate(ctrl.editingCell?.value)}
|
|
676
|
+
formatString="HH:mm"
|
|
677
|
+
dropDownDisplayMode="time"
|
|
678
|
+
autoOpen
|
|
679
|
+
block
|
|
680
|
+
onChange={(d) => updateEditingCellValue(dateToTimeString(d))}
|
|
681
|
+
onCommit={() => saveEditingCell()}
|
|
682
|
+
onCancel={() => {
|
|
683
|
+
ctrl.editingCell = null;
|
|
684
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
685
|
+
}}
|
|
686
|
+
/>
|
|
687
|
+
{/if}
|
|
688
|
+
{:else if ctrl.editingCell?.editorType === "color"}
|
|
689
|
+
<!-- Native <input type="color"> opens its picker in a separate OS
|
|
690
|
+
overlay; once the picker closes, focus stays on the input so
|
|
691
|
+
`blur` never fires on its own. Commit on `change` (which fires
|
|
692
|
+
exactly once when the picker is dismissed) so the chosen color
|
|
693
|
+
is saved without needing the user to click elsewhere. -->
|
|
694
|
+
<input
|
|
695
|
+
use:focusOnMount
|
|
696
|
+
class={getEditorClass("color")}
|
|
697
|
+
type="color"
|
|
698
|
+
value={getEditableInputValue("color", ctrl.editingCell?.value)}
|
|
699
|
+
oninput={(event) =>
|
|
700
|
+
updateEditingCellValue(
|
|
701
|
+
(event.currentTarget as HTMLInputElement).value,
|
|
702
|
+
)}
|
|
703
|
+
onchange={(event) => {
|
|
704
|
+
updateEditingCellValue(
|
|
705
|
+
(event.currentTarget as HTMLInputElement).value,
|
|
706
|
+
);
|
|
707
|
+
saveEditingCell();
|
|
708
|
+
}}
|
|
709
|
+
onblur={() => saveEditingCell()}
|
|
710
|
+
onkeydown={onEditorKeyDown}
|
|
711
|
+
/>
|
|
712
|
+
{:else if hasCellEditor(String(ctrl.editingCell?.editorType ?? ""))}
|
|
713
|
+
<!-- Custom editor registered via `registerCellEditor(type, Component)`:
|
|
714
|
+
mount it and hand it the uniform change / commit / cancel context. -->
|
|
715
|
+
{@const _reg = getCellEditor(String(ctrl.editingCell?.editorType))!}
|
|
716
|
+
{@const CustomCellEditor = _reg.component}
|
|
717
|
+
<CustomCellEditor {...resolveEditorProps(_reg, buildRegisteredEditorContext())} />
|
|
718
|
+
{:else}
|
|
719
|
+
{@const editorType = ctrl.editingCell?.editorType ?? "text"}
|
|
720
|
+
{@const isNumberEditor = getCellEditorInputType(editorType) === "text" &&
|
|
721
|
+
getEditorInputType(editorType) === "number"}
|
|
722
|
+
<input
|
|
723
|
+
use:focusOnMount
|
|
724
|
+
class={getEditorClass(editorType)}
|
|
725
|
+
type={getCellEditorInputType(editorType)}
|
|
726
|
+
inputmode={isNumberEditor ? "decimal" : undefined}
|
|
727
|
+
value={getEditableInputValue(
|
|
728
|
+
editorType,
|
|
729
|
+
ctrl.editingCell?.value,
|
|
730
|
+
)}
|
|
731
|
+
oninput={(event) => {
|
|
732
|
+
const input = event.currentTarget as HTMLInputElement;
|
|
733
|
+
// The number editor is a TEXT input so intermediate values survive
|
|
734
|
+
// (see getCellEditorInputType). That means nothing stops a letter
|
|
735
|
+
// being typed, which type="number" used to - so reject it here and
|
|
736
|
+
// put the last good draft back, rather than letting "12x" reach the
|
|
737
|
+
// commit and coerce to null.
|
|
738
|
+
if (isNumberEditor && !isNumericEditorInput(input.value)) {
|
|
739
|
+
input.value = String(ctrl.editingCell?.value ?? "");
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
updateEditingCellValue(input.value);
|
|
743
|
+
}}
|
|
744
|
+
onblur={() => saveEditingCell()}
|
|
745
|
+
onkeydown={onEditorKeyDown}
|
|
746
|
+
/>
|
|
747
|
+
{/if}
|
|
748
|
+
{/if}
|