@svgrid/grid 1.2.21 → 1.2.23
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/GridFooter.svelte +34 -20
- package/dist/GridFooter.svelte.d.ts +8 -0
- package/dist/SvAutoComplete.svelte +100 -0
- package/dist/SvAutoComplete.svelte.d.ts +16 -0
- package/dist/SvButton.svelte +97 -0
- package/dist/SvButton.svelte.d.ts +23 -0
- package/dist/SvCalendar.svelte +669 -0
- package/dist/SvCalendar.svelte.d.ts +70 -0
- package/dist/SvCheckBox.svelte +81 -0
- package/dist/SvCheckBox.svelte.d.ts +19 -0
- package/dist/SvColorInput.svelte +187 -0
- package/dist/SvColorInput.svelte.d.ts +15 -0
- package/dist/SvComboBox.svelte +130 -0
- package/dist/SvComboBox.svelte.d.ts +15 -0
- package/dist/SvCountryInput.svelte +123 -0
- package/dist/SvCountryInput.svelte.d.ts +14 -0
- package/dist/SvDateTimePicker.svelte +322 -0
- package/dist/SvDateTimePicker.svelte.d.ts +37 -0
- package/dist/SvDropDownList.svelte +143 -0
- package/dist/SvDropDownList.svelte.d.ts +15 -0
- package/dist/SvForm.svelte +147 -0
- package/dist/SvForm.svelte.d.ts +29 -0
- package/dist/SvGauge.svelte +99 -0
- package/dist/SvGauge.svelte.d.ts +31 -0
- package/dist/SvGrid.controller.svelte.d.ts +16 -2
- package/dist/SvGrid.controller.svelte.js +63 -8
- package/dist/SvGrid.css +84 -31
- package/dist/SvGrid.helpers.d.ts +4 -0
- package/dist/SvGrid.helpers.js +40 -12
- package/dist/SvGrid.svelte +84 -5
- package/dist/SvGrid.types.d.ts +36 -0
- package/dist/SvGridDropdown.svelte +16 -30
- package/dist/SvGridDropdown.svelte.d.ts +4 -0
- package/dist/SvListBox.svelte +122 -0
- package/dist/SvListBox.svelte.d.ts +19 -0
- package/dist/SvMaskedInput.svelte +0 -0
- package/dist/SvMaskedInput.svelte.d.ts +15 -0
- package/dist/SvNumberInput.svelte +0 -0
- package/dist/SvNumberInput.svelte.d.ts +22 -0
- package/dist/SvPasswordInput.svelte +110 -0
- package/dist/SvPasswordInput.svelte.d.ts +22 -0
- package/dist/SvPhoneInput.svelte +0 -0
- package/dist/SvPhoneInput.svelte.d.ts +19 -0
- package/dist/SvRadioGroup.svelte +108 -0
- package/dist/SvRadioGroup.svelte.d.ts +23 -0
- package/dist/SvRating.svelte +111 -0
- package/dist/SvRating.svelte.d.ts +20 -0
- package/dist/SvRepeatButton.svelte +89 -0
- package/dist/SvRepeatButton.svelte.d.ts +21 -0
- package/dist/SvSlider.svelte +182 -0
- package/dist/SvSlider.svelte.d.ts +25 -0
- package/dist/SvSwitchButton.svelte +94 -0
- package/dist/SvSwitchButton.svelte.d.ts +19 -0
- package/dist/SvTabs.svelte +122 -0
- package/dist/SvTabs.svelte.d.ts +26 -0
- package/dist/SvTagsInput.svelte +81 -0
- package/dist/SvTagsInput.svelte.d.ts +19 -0
- package/dist/SvTimePicker.svelte +359 -0
- package/dist/SvTimePicker.svelte.d.ts +27 -0
- package/dist/SvToggleButton.svelte +57 -0
- package/dist/SvToggleButton.svelte.d.ts +16 -0
- package/dist/SvTree.svelte +210 -0
- package/dist/SvTree.svelte.d.ts +29 -0
- package/dist/build-api.js +78 -1
- package/dist/cdn/svgrid.js +10022 -5904
- package/dist/cdn/svgrid.svelte-external.js +8531 -4429
- package/dist/cell-render.d.ts +4 -0
- package/dist/cell-render.js +35 -1
- package/dist/clipboard.js +19 -1
- package/dist/collaboration.d.ts +2 -0
- package/dist/collaboration.js +4 -0
- package/dist/core.d.ts +22 -1
- package/dist/countries.d.ts +15 -0
- package/dist/countries.js +61 -0
- package/dist/datetime/date-core.d.ts +88 -0
- package/dist/datetime/date-core.js +165 -0
- package/dist/datetime/date-format.d.ts +39 -0
- package/dist/datetime/date-format.js +371 -0
- package/dist/datetime/date-restrict.d.ts +23 -0
- package/dist/datetime/date-restrict.js +50 -0
- package/dist/datetime/date-selection.d.ts +25 -0
- package/dist/datetime/date-selection.js +94 -0
- package/dist/datetime/mask.d.ts +21 -0
- package/dist/datetime/mask.js +90 -0
- package/dist/editing.d.ts +1 -1
- package/dist/editing.js +7 -0
- package/dist/editors/cell-editors.d.ts +2 -2
- package/dist/editors/cell-editors.js +3 -1
- package/dist/export-format.d.ts +178 -0
- package/dist/export-format.js +419 -0
- package/dist/hyperformula-adapter.d.ts +4 -0
- package/dist/hyperformula-adapter.js +15 -6
- package/dist/index.d.ts +36 -0
- package/dist/index.js +41 -0
- package/dist/list-option.d.ts +10 -0
- package/dist/list-option.js +7 -0
- package/dist/menus.js +18 -2
- package/dist/popover.d.ts +48 -0
- package/dist/popover.js +65 -0
- package/dist/row-resize.d.ts +4 -4
- package/dist/row-resize.js +8 -5
- package/dist/selection.d.ts +6 -0
- package/dist/selection.js +31 -5
- package/dist/server-data-source.d.ts +43 -0
- package/dist/server-data-source.js +100 -0
- package/dist/sparkline.js +8 -4
- package/dist/svgrid-wrapper.types.d.ts +26 -1
- package/package.json +6 -1
- package/src/GridFooter.svelte +34 -20
- package/src/SvAutoComplete.svelte +100 -0
- package/src/SvButton.svelte +97 -0
- package/src/SvCalendar.svelte +669 -0
- package/src/SvCalendar.test.ts +211 -0
- package/src/SvCheckBox.svelte +81 -0
- package/src/SvColorInput.svelte +187 -0
- package/src/SvComboBox.svelte +130 -0
- package/src/SvCountryInput.svelte +123 -0
- package/src/SvDateTimePicker.svelte +322 -0
- package/src/SvDateTimePicker.test.ts +136 -0
- package/src/SvDropDownList.svelte +143 -0
- package/src/SvForm.svelte +147 -0
- package/src/SvGauge.svelte +99 -0
- package/src/SvGrid.controller.svelte.ts +74 -8
- package/src/SvGrid.css +84 -31
- package/src/SvGrid.helpers.ts +41 -12
- package/src/SvGrid.svelte +84 -5
- package/src/SvGrid.types.ts +33 -0
- package/src/SvGridDropdown.svelte +16 -30
- package/src/SvListBox.svelte +122 -0
- package/src/SvMaskedInput.svelte +0 -0
- package/src/SvNumberInput.svelte +0 -0
- package/src/SvPasswordInput.svelte +110 -0
- package/src/SvPhoneInput.svelte +0 -0
- package/src/SvRadioGroup.svelte +108 -0
- package/src/SvRating.svelte +111 -0
- package/src/SvRepeatButton.svelte +89 -0
- package/src/SvSlider.svelte +182 -0
- package/src/SvSwitchButton.svelte +94 -0
- package/src/SvTabs.svelte +122 -0
- package/src/SvTagsInput.svelte +81 -0
- package/src/SvTimePicker.svelte +359 -0
- package/src/SvTimePicker.test.ts +98 -0
- package/src/SvToggleButton.svelte +57 -0
- package/src/SvTree.svelte +210 -0
- package/src/build-api.ts +115 -0
- package/src/cell-render.test.ts +38 -0
- package/src/cell-render.ts +43 -0
- package/src/clipboard.test.ts +18 -0
- package/src/clipboard.ts +24 -1
- package/src/collaboration.test.ts +30 -0
- package/src/collaboration.ts +6 -0
- package/src/core.ts +27 -3
- package/src/countries.ts +71 -0
- package/src/datetime/date-core.test.ts +217 -0
- package/src/datetime/date-core.ts +204 -0
- package/src/datetime/date-format.test.ts +121 -0
- package/src/datetime/date-format.ts +317 -0
- package/src/datetime/date-restrict.ts +60 -0
- package/src/datetime/date-selection.test.ts +129 -0
- package/src/datetime/date-selection.ts +137 -0
- package/src/datetime/mask.test.ts +36 -0
- package/src/datetime/mask.ts +84 -0
- package/src/editing.test.ts +22 -0
- package/src/editing.ts +7 -1
- package/src/editors/cell-editors.ts +7 -1
- package/src/export-data-api.test.ts +126 -0
- package/src/export-format.test.ts +107 -0
- package/src/export-format.ts +598 -0
- package/src/hyperformula-adapter.test.ts +35 -1
- package/src/hyperformula-adapter.ts +18 -6
- package/src/index.ts +71 -0
- package/src/list-option.ts +15 -0
- package/src/menus.ts +21 -2
- package/src/popover.ts +79 -0
- package/src/row-resize.test.ts +25 -0
- package/src/row-resize.ts +8 -5
- package/src/selection.test.ts +36 -4
- package/src/selection.ts +30 -9
- package/src/server-data-source.test.ts +201 -0
- package/src/server-data-source.ts +148 -0
- package/src/sparkline.test.ts +9 -0
- package/src/sparkline.ts +9 -4
- package/src/svgrid-wrapper.types.ts +28 -1
- package/src/svgrid.behavior.test.ts +27 -0
- package/src/svgrid.new-features.wrapper.test.ts +27 -1
- package/src/ui-buttons.test.ts +105 -0
- package/src/ui-composite.test.ts +89 -0
- package/src/ui-inputs.test.ts +92 -0
- package/src/ui-range.test.ts +61 -0
- package/src/ui-selection.test.ts +106 -0
package/src/SvGrid.css
CHANGED
|
@@ -348,9 +348,13 @@
|
|
|
348
348
|
.sv-grid-table tr > :last-child.sv-grid-cell {
|
|
349
349
|
border-right: 0;
|
|
350
350
|
}
|
|
351
|
-
/* Row hover tint
|
|
352
|
-
|
|
353
|
-
|
|
351
|
+
/* Row hover tint - opt-in via `enableRowHover` (off by default; the class is
|
|
352
|
+
present unless the grid opts in). Uses `background-color` (not the shorthand)
|
|
353
|
+
so it never resets the fill-marquee `background-image`, and skips selected
|
|
354
|
+
cells so the selection stays visible under a hovered row. */
|
|
355
|
+
.sv-grid-table:not(.sv-grid-no-row-hover)
|
|
356
|
+
tbody tr:hover > .sv-grid-cell:not([data-selected-range="true"]) {
|
|
357
|
+
background-color: var(--sg-row-hover-bg, #eef2ff);
|
|
354
358
|
}
|
|
355
359
|
/* Only the active cell + the cell hosting the fill handle become a
|
|
356
360
|
positioning context (so the absolutely-positioned handle anchors
|
|
@@ -441,8 +445,10 @@
|
|
|
441
445
|
),
|
|
442
446
|
var(--sg-pinned-bg, color-mix(in oklab, var(--sg-header-bg, #f1f5f9) 92%, var(--sg-accent, #2563eb) 8%));
|
|
443
447
|
}
|
|
444
|
-
/* Hovered row keeps the pinned tint distinguishable from the hover.
|
|
445
|
-
|
|
448
|
+
/* Hovered row keeps the pinned tint distinguishable from the hover. Selected
|
|
449
|
+
cells are excluded so the selection stays visible under hover. */
|
|
450
|
+
.sv-grid-table:not(.sv-grid-no-row-hover)
|
|
451
|
+
.sv-grid-row:hover > .sv-grid-cell[data-pinned]:not([data-selected-range="true"]) {
|
|
446
452
|
background:
|
|
447
453
|
linear-gradient(
|
|
448
454
|
color-mix(in srgb, var(--sg-row-hover-bg, #eef2ff) 55%, transparent),
|
|
@@ -1062,18 +1068,52 @@ select.sv-grid-fr-editor {
|
|
|
1062
1068
|
transform-origin: bottom right;
|
|
1063
1069
|
}
|
|
1064
1070
|
|
|
1065
|
-
/*
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
`--sg-fill-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1071
|
+
/* Excel-style fill-handle drag marquee: ONE thin dashed rectangle around the
|
|
1072
|
+
whole target range (source + fill-into area), not a thick per-cell border or
|
|
1073
|
+
fill tint. Drawn as four background-image layers on the boundary cells - no
|
|
1074
|
+
`border`, so it paints on the compositor with zero reflow while dragging.
|
|
1075
|
+
`--sg-fill-marquee` overrides the colour (defaults to a theme-aware grey). */
|
|
1076
|
+
/* Step 1 (LOW specificity): default every edge layer to `none`, then light up
|
|
1077
|
+
the edges this cell sits on. These are custom properties, which the
|
|
1078
|
+
`background:` shorthands on selected / zebra cells do NOT reset - so they can
|
|
1079
|
+
safely live at low specificity and just compete with each other by source
|
|
1080
|
+
order (gradient rules come after the defaults, so they win). */
|
|
1081
|
+
.sv-grid-cell[data-fill-top="true"],
|
|
1082
|
+
.sv-grid-cell[data-fill-bottom="true"],
|
|
1083
|
+
.sv-grid-cell[data-fill-left="true"],
|
|
1084
|
+
.sv-grid-cell[data-fill-right="true"] {
|
|
1085
|
+
--sv-fill-top: none;
|
|
1086
|
+
--sv-fill-bottom: none;
|
|
1087
|
+
--sv-fill-left: none;
|
|
1088
|
+
--sv-fill-right: none;
|
|
1089
|
+
/* Match the selection colour (the grid accent) by default. */
|
|
1090
|
+
--sv-fill-color: var(--sg-fill-marquee, var(--sg-accent, #2563eb));
|
|
1091
|
+
}
|
|
1092
|
+
.sv-grid-cell[data-fill-top="true"] {
|
|
1093
|
+
--sv-fill-top: repeating-linear-gradient(to right, var(--sv-fill-color) 0 3px, transparent 3px 6px);
|
|
1094
|
+
}
|
|
1095
|
+
.sv-grid-cell[data-fill-bottom="true"] {
|
|
1096
|
+
--sv-fill-bottom: repeating-linear-gradient(to right, var(--sv-fill-color) 0 3px, transparent 3px 6px);
|
|
1097
|
+
}
|
|
1098
|
+
.sv-grid-cell[data-fill-left="true"] {
|
|
1099
|
+
--sv-fill-left: repeating-linear-gradient(to bottom, var(--sv-fill-color) 0 3px, transparent 3px 6px);
|
|
1100
|
+
}
|
|
1101
|
+
.sv-grid-cell[data-fill-right="true"] {
|
|
1102
|
+
--sv-fill-right: repeating-linear-gradient(to bottom, var(--sv-fill-color) 0 3px, transparent 3px 6px);
|
|
1103
|
+
}
|
|
1104
|
+
/* Step 2 (HIGH specificity, `.sv-grid-table` prefix): compose the resolved edge
|
|
1105
|
+
layers into `background-image` so it beats the `background:` shorthands on
|
|
1106
|
+
selected / zebra / hover cells (which would otherwise reset background-image).
|
|
1107
|
+
The cell keeps its background-COLOR; this only adds the dashed lines on top. */
|
|
1108
|
+
.sv-grid-table .sv-grid-cell[data-fill-top="true"],
|
|
1109
|
+
.sv-grid-table .sv-grid-cell[data-fill-bottom="true"],
|
|
1110
|
+
.sv-grid-table .sv-grid-cell[data-fill-left="true"],
|
|
1111
|
+
.sv-grid-table .sv-grid-cell[data-fill-right="true"] {
|
|
1112
|
+
background-image: var(--sv-fill-top), var(--sv-fill-bottom),
|
|
1113
|
+
var(--sv-fill-left), var(--sv-fill-right);
|
|
1114
|
+
background-repeat: no-repeat;
|
|
1115
|
+
background-size: 100% 2px, 100% 2px, 2px 100%, 2px 100%;
|
|
1116
|
+
background-position: top left, bottom left, top left, top right;
|
|
1077
1117
|
}
|
|
1078
1118
|
|
|
1079
1119
|
.sv-grid-cell-editing {
|
|
@@ -1338,6 +1378,17 @@ select.sv-grid-fr-editor {
|
|
|
1338
1378
|
.sv-grid-cell-cf {
|
|
1339
1379
|
position: relative;
|
|
1340
1380
|
}
|
|
1381
|
+
/* Declarative cell validation (column `validate` hook). Invalid cells get a
|
|
1382
|
+
* soft red wash and a red inset ring - Handsontable's `htInvalid` look. The
|
|
1383
|
+
* value keeps rendering as-is; the message (if any) shows as the tooltip. */
|
|
1384
|
+
.sv-grid-cell-invalid {
|
|
1385
|
+
background-color: var(--sg-invalid-bg, rgba(239, 68, 68, 0.14)) !important;
|
|
1386
|
+
box-shadow: inset 0 0 0 1px var(--sg-invalid-border, rgba(239, 68, 68, 0.45));
|
|
1387
|
+
color: var(--sg-invalid-fg, #b91c1c);
|
|
1388
|
+
}
|
|
1389
|
+
.sv-grid-cell-invalid.sv-grid-cell-active {
|
|
1390
|
+
box-shadow: inset 0 0 0 2px var(--sg-invalid-border, rgba(239, 68, 68, 0.7));
|
|
1391
|
+
}
|
|
1341
1392
|
.sv-grid-status-bar {
|
|
1342
1393
|
display: flex;
|
|
1343
1394
|
flex-wrap: wrap;
|
|
@@ -2103,32 +2154,34 @@ select.sv-grid-fr-editor {
|
|
|
2103
2154
|
color: var(--sg-fg, #0f172a);
|
|
2104
2155
|
font-size: 13px;
|
|
2105
2156
|
}
|
|
2157
|
+
/* Top-positioned pager: rounded + bordered on the top edge instead. */
|
|
2158
|
+
.sv-grid-pagination-top {
|
|
2159
|
+
border-top: 1px solid var(--sg-border, #e2e8f0);
|
|
2160
|
+
border-bottom: 0;
|
|
2161
|
+
border-radius: var(--sg-radius, 6px) var(--sg-radius, 6px) 0 0;
|
|
2162
|
+
}
|
|
2106
2163
|
.sv-grid-pagination-pagesize {
|
|
2107
2164
|
display: inline-flex;
|
|
2108
2165
|
align-items: center;
|
|
2109
2166
|
gap: 8px;
|
|
2110
2167
|
color: var(--sg-muted, #64748b);
|
|
2111
2168
|
}
|
|
2112
|
-
.sv-grid-pagination-pagesize
|
|
2169
|
+
.sv-grid-pagination-pagesize-dd {
|
|
2170
|
+
position: relative;
|
|
2113
2171
|
height: 28px;
|
|
2114
|
-
|
|
2172
|
+
min-width: 62px;
|
|
2115
2173
|
border: 1px solid var(--sg-input-border, #cbd5e1);
|
|
2116
2174
|
border-radius: 5px;
|
|
2117
2175
|
background: var(--sg-input-bg, #fff);
|
|
2118
|
-
|
|
2119
|
-
|
|
2176
|
+
}
|
|
2177
|
+
.sv-grid-pagination-pagesize-dd .sv-grid-dropdown,
|
|
2178
|
+
.sv-grid-pagination-pagesize-dd .sv-grid-dropdown-trigger {
|
|
2179
|
+
background: transparent;
|
|
2180
|
+
border-radius: 5px;
|
|
2120
2181
|
font-size: 13px;
|
|
2121
|
-
|
|
2122
|
-
appearance: none;
|
|
2123
|
-
-webkit-appearance: none;
|
|
2124
|
-
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
|
|
2125
|
-
linear-gradient(135deg, currentColor 50%, transparent 50%);
|
|
2126
|
-
background-position: calc(100% - 12px) 12px, calc(100% - 8px) 12px;
|
|
2127
|
-
background-size: 4px 4px, 4px 4px;
|
|
2128
|
-
background-repeat: no-repeat;
|
|
2182
|
+
color: var(--sg-fg, #0f172a);
|
|
2129
2183
|
}
|
|
2130
|
-
.sv-grid-pagination-pagesize
|
|
2131
|
-
outline: none;
|
|
2184
|
+
.sv-grid-pagination-pagesize-dd:focus-within {
|
|
2132
2185
|
border-color: var(--sg-accent, #2563eb);
|
|
2133
2186
|
box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-accent, #2563eb) 30%, transparent);
|
|
2134
2187
|
}
|
package/src/SvGrid.helpers.ts
CHANGED
|
@@ -69,19 +69,47 @@ export function toDateTimeLocalInputValue(value: unknown) {
|
|
|
69
69
|
return parsed.toISOString().slice(0, 16);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/** Strip the `-native` opt-out suffix so the native-input helpers treat
|
|
73
|
+
* `date-native` exactly like `date` (the rich pickers own the bare types). */
|
|
74
|
+
function baseEditor(editorType: CellEditorType): string {
|
|
75
|
+
return String(editorType).replace(/-native$/, "");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** 'HH:MM[:SS]' string (or Date) to a Date carrying that time (today's date). */
|
|
79
|
+
export function timeStringToDate(value: unknown): Date | null {
|
|
80
|
+
if (value instanceof Date) return value;
|
|
81
|
+
if (typeof value === "string") {
|
|
82
|
+
const m = value.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?/);
|
|
83
|
+
if (m) {
|
|
84
|
+
const d = new Date();
|
|
85
|
+
d.setHours(Math.min(23, +m[1]!), Math.min(59, +m[2]!), m[3] ? +m[3] : 0, 0);
|
|
86
|
+
return d;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** A Date to an 'HH:MM' string (the `time` editor's stored form). */
|
|
93
|
+
export function dateToTimeString(d: Date | null): string | null {
|
|
94
|
+
if (!d) return null;
|
|
95
|
+
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
72
98
|
export function getEditableInputValue(editorType: CellEditorType, value: unknown) {
|
|
73
|
-
|
|
74
|
-
if (
|
|
99
|
+
const base = baseEditor(editorType);
|
|
100
|
+
if (base === "date") return toDateInputValue(value);
|
|
101
|
+
if (base === "datetime") return toDateTimeLocalInputValue(value);
|
|
75
102
|
return String(value ?? "");
|
|
76
103
|
}
|
|
77
104
|
|
|
78
105
|
export function getEditorInputType(editorType: CellEditorType) {
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
if (
|
|
82
|
-
if (
|
|
83
|
-
if (
|
|
84
|
-
if (
|
|
106
|
+
const base = baseEditor(editorType);
|
|
107
|
+
if (base === "number") return "number";
|
|
108
|
+
if (base === "date") return "date";
|
|
109
|
+
if (base === "datetime") return "datetime-local";
|
|
110
|
+
if (base === "time") return "time";
|
|
111
|
+
if (base === "password") return "password";
|
|
112
|
+
if (base === "color") return "color";
|
|
85
113
|
return "text";
|
|
86
114
|
}
|
|
87
115
|
|
|
@@ -130,13 +158,14 @@ export function colorfulChipStyle(color: string | undefined): string {
|
|
|
130
158
|
}
|
|
131
159
|
|
|
132
160
|
export function getEditorClass(editorType: CellEditorType) {
|
|
133
|
-
|
|
161
|
+
const base = baseEditor(editorType);
|
|
162
|
+
if (base === "number")
|
|
134
163
|
return "sv-grid-cell-editor sv-grid-cell-editor-number";
|
|
135
|
-
if (
|
|
164
|
+
if (base === "date")
|
|
136
165
|
return "sv-grid-cell-editor sv-grid-cell-editor-date";
|
|
137
|
-
if (
|
|
166
|
+
if (base === "datetime")
|
|
138
167
|
return "sv-grid-cell-editor sv-grid-cell-editor-datetime";
|
|
139
|
-
if (
|
|
168
|
+
if (base === "color")
|
|
140
169
|
return "sv-grid-cell-editor sv-grid-cell-editor-color";
|
|
141
170
|
return "sv-grid-cell-editor";
|
|
142
171
|
}
|
package/src/SvGrid.svelte
CHANGED
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
} from "./render-component";
|
|
25
25
|
import { buildSparkline, toSparklineValues } from "./sparkline";
|
|
26
26
|
import SvGridDropdown from "./SvGridDropdown.svelte";
|
|
27
|
+
import SvDateTimePicker from "./SvDateTimePicker.svelte";
|
|
28
|
+
import { timeStringToDate, dateToTimeString } from "./SvGrid.helpers";
|
|
27
29
|
import type {
|
|
28
30
|
Props,
|
|
29
31
|
SelectionPoint,
|
|
@@ -246,6 +248,7 @@
|
|
|
246
248
|
const computeRowClass = $derived(ctrl.computeRowClass);
|
|
247
249
|
const computeCellClass = $derived(ctrl.computeCellClass);
|
|
248
250
|
const computeCellTooltip = $derived(ctrl.computeCellTooltip);
|
|
251
|
+
const computeCellValidity = $derived(ctrl.computeCellValidity);
|
|
249
252
|
const computeCellNote = $derived(ctrl.computeCellNote);
|
|
250
253
|
const getCellDisplayValue = $derived(ctrl.getCellDisplayValue);
|
|
251
254
|
const getColumnAlign = $derived(ctrl.getColumnAlign);
|
|
@@ -266,6 +269,7 @@
|
|
|
266
269
|
const getCellRangeEdges = $derived(ctrl.getCellRangeEdges);
|
|
267
270
|
const fillHandleCell = $derived(ctrl.fillHandleCell);
|
|
268
271
|
const isInFillPreview = $derived(ctrl.isInFillPreview);
|
|
272
|
+
const fillMarqueeEdges = $derived(ctrl.fillMarqueeEdges);
|
|
269
273
|
const startFillDrag = $derived(ctrl.startFillDrag);
|
|
270
274
|
const onCellPointerDown = $derived(ctrl.onCellPointerDown);
|
|
271
275
|
const onCellPointerEnter = $derived(ctrl.onCellPointerEnter);
|
|
@@ -306,7 +310,12 @@
|
|
|
306
310
|
onpointermove={onWindowPointerMove}
|
|
307
311
|
/>
|
|
308
312
|
|
|
309
|
-
{#if props.loading && !props.loadingOverlay}
|
|
313
|
+
{#if props.loading && !props.loadingOverlay && !hasMeasured}
|
|
314
|
+
<!-- Full-screen loading state only on the *initial* load, before the grid
|
|
315
|
+
has ever rendered. Once measured, a `loading` flip (from a server-mode
|
|
316
|
+
sort / filter / page refetch) keeps the table mounted so header inputs
|
|
317
|
+
and focus survive - the empty-row message + optional `loadingOverlay`
|
|
318
|
+
cover the in-place refresh. -->
|
|
310
319
|
<div class="sv-grid-state sv-grid-state-loading" role="status">
|
|
311
320
|
Loading grid data...
|
|
312
321
|
</div>
|
|
@@ -798,6 +807,49 @@
|
|
|
798
807
|
</div>
|
|
799
808
|
{/if}
|
|
800
809
|
</div>
|
|
810
|
+
{:else if ctrl.editingCell?.editorType === "date"}
|
|
811
|
+
<!-- Rich date editor: SvCalendar popover over a formatted input. Opts
|
|
812
|
+
out to the native <input type="date"> via editorType 'date-native'. -->
|
|
813
|
+
<SvDateTimePicker
|
|
814
|
+
value={ctrl.editingCell?.value as string | number | Date | null}
|
|
815
|
+
formatString="yyyy-MM-dd"
|
|
816
|
+
dropDownDisplayMode="calendar"
|
|
817
|
+
autoOpen
|
|
818
|
+
onChange={(d) => updateEditingCellValue(d)}
|
|
819
|
+
onCommit={() => saveEditingCell()}
|
|
820
|
+
onCancel={() => {
|
|
821
|
+
ctrl.editingCell = null;
|
|
822
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
823
|
+
}}
|
|
824
|
+
/>
|
|
825
|
+
{:else if ctrl.editingCell?.editorType === "datetime"}
|
|
826
|
+
<SvDateTimePicker
|
|
827
|
+
value={ctrl.editingCell?.value as string | number | Date | null}
|
|
828
|
+
formatString="yyyy-MM-dd HH:mm"
|
|
829
|
+
dropDownDisplayMode="both"
|
|
830
|
+
autoOpen
|
|
831
|
+
onChange={(d) => updateEditingCellValue(d)}
|
|
832
|
+
onCommit={() => saveEditingCell()}
|
|
833
|
+
onCancel={() => {
|
|
834
|
+
ctrl.editingCell = null;
|
|
835
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
836
|
+
}}
|
|
837
|
+
/>
|
|
838
|
+
{:else if ctrl.editingCell?.editorType === "time"}
|
|
839
|
+
<!-- Rich time editor: SvTimePicker dial. Stores the 'HH:MM' string the
|
|
840
|
+
`time` parser expects; opts out via editorType 'time-native'. -->
|
|
841
|
+
<SvDateTimePicker
|
|
842
|
+
value={timeStringToDate(ctrl.editingCell?.value)}
|
|
843
|
+
formatString="HH:mm"
|
|
844
|
+
dropDownDisplayMode="time"
|
|
845
|
+
autoOpen
|
|
846
|
+
onChange={(d) => updateEditingCellValue(dateToTimeString(d))}
|
|
847
|
+
onCommit={() => saveEditingCell()}
|
|
848
|
+
onCancel={() => {
|
|
849
|
+
ctrl.editingCell = null;
|
|
850
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
851
|
+
}}
|
|
852
|
+
/>
|
|
801
853
|
{:else if ctrl.editingCell?.editorType === "color"}
|
|
802
854
|
<!-- Native <input type="color"> opens its picker in a separate OS
|
|
803
855
|
overlay; once the picker closes, focus stays on the input so
|
|
@@ -1179,6 +1231,10 @@
|
|
|
1179
1231
|
</div>
|
|
1180
1232
|
{/if}
|
|
1181
1233
|
|
|
1234
|
+
{#if hasMeasured && (props.paginationPosition === "top" || props.paginationPosition === "both")}
|
|
1235
|
+
<GridFooter {ctrl} showStatus={false} top pager pageSizeOptions={props.pageSizeOptions} />
|
|
1236
|
+
{/if}
|
|
1237
|
+
|
|
1182
1238
|
<div
|
|
1183
1239
|
class="sv-grid-shell"
|
|
1184
1240
|
style={`height: ${
|
|
@@ -1197,6 +1253,7 @@
|
|
|
1197
1253
|
<table
|
|
1198
1254
|
bind:this={ctrl.gridRootEl}
|
|
1199
1255
|
class="sv-grid-table"
|
|
1256
|
+
class:sv-grid-no-row-hover={props.enableRowHover !== true}
|
|
1200
1257
|
{...getGridRootA11yProps({
|
|
1201
1258
|
activeDescendantId,
|
|
1202
1259
|
rowCount: allRows.length,
|
|
@@ -1795,6 +1852,7 @@
|
|
|
1795
1852
|
rowIndex,
|
|
1796
1853
|
colIndex,
|
|
1797
1854
|
)}
|
|
1855
|
+
{@const fillEdges = fillMarqueeEdges(rowIndex, colIndex)}
|
|
1798
1856
|
{@const hasFillHandle =
|
|
1799
1857
|
fillHandleCell &&
|
|
1800
1858
|
fillHandleCell.rowIndex === rowIndex &&
|
|
@@ -1807,6 +1865,10 @@
|
|
|
1807
1865
|
row,
|
|
1808
1866
|
rendered.column,
|
|
1809
1867
|
)}
|
|
1868
|
+
{@const cellValidity = computeCellValidity(
|
|
1869
|
+
row,
|
|
1870
|
+
rendered.column,
|
|
1871
|
+
)}
|
|
1810
1872
|
{@const cellNote = computeCellNote(
|
|
1811
1873
|
row,
|
|
1812
1874
|
rendered.column,
|
|
@@ -1818,7 +1880,9 @@
|
|
|
1818
1880
|
rowIndex && activeCell.colIndex === colIndex}
|
|
1819
1881
|
class:sv-grid-cell-has-fill-handle={hasFillHandle}
|
|
1820
1882
|
class:sv-grid-cell-cf={hasConditionalFormats}
|
|
1883
|
+
class:sv-grid-cell-invalid={cellValidity.invalid}
|
|
1821
1884
|
class:sv-grid-cell-has-note={cellNote != null}
|
|
1885
|
+
title={cellValidity.message ?? undefined}
|
|
1822
1886
|
data-svgrid-row={rowIndex}
|
|
1823
1887
|
data-svgrid-col={colIndex}
|
|
1824
1888
|
data-col-id={rendered.column.id}
|
|
@@ -1839,6 +1903,10 @@
|
|
|
1839
1903
|
data-fill-preview={isInFillPreview(rowIndex, colIndex)
|
|
1840
1904
|
? "true"
|
|
1841
1905
|
: undefined}
|
|
1906
|
+
data-fill-top={fillEdges?.top ? "true" : undefined}
|
|
1907
|
+
data-fill-bottom={fillEdges?.bottom ? "true" : undefined}
|
|
1908
|
+
data-fill-left={fillEdges?.left ? "true" : undefined}
|
|
1909
|
+
data-fill-right={fillEdges?.right ? "true" : undefined}
|
|
1842
1910
|
style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
|
|
1843
1911
|
onpointerdown={(event) =>
|
|
1844
1912
|
onCellPointerDown(rowIndex, colIndex, event)}
|
|
@@ -2040,6 +2108,10 @@
|
|
|
2040
2108
|
row,
|
|
2041
2109
|
rendered.column,
|
|
2042
2110
|
)}
|
|
2111
|
+
{@const cellValidity = computeCellValidity(
|
|
2112
|
+
row,
|
|
2113
|
+
rendered.column,
|
|
2114
|
+
)}
|
|
2043
2115
|
{@const cellNote = computeCellNote(row, rendered.column)}
|
|
2044
2116
|
<td
|
|
2045
2117
|
class={`sv-grid-cell ${userCellClass}`}
|
|
@@ -2047,6 +2119,7 @@
|
|
|
2047
2119
|
class:sv-grid-cell-active={activeCell.rowIndex ===
|
|
2048
2120
|
rowIndex && activeCell.colIndex === colIndex}
|
|
2049
2121
|
class:sv-grid-cell-cf={hasConditionalFormats}
|
|
2122
|
+
class:sv-grid-cell-invalid={cellValidity.invalid}
|
|
2050
2123
|
class:sv-grid-cell-has-note={cellNote != null}
|
|
2051
2124
|
data-svgrid-row={rowIndex}
|
|
2052
2125
|
data-svgrid-col={colIndex}
|
|
@@ -2070,11 +2143,17 @@
|
|
|
2070
2143
|
// Column tooltip fires on whole-cell hover.
|
|
2071
2144
|
// Per-cell notes are gated on the corner hot-
|
|
2072
2145
|
// zone below (Excel-style: hover the small
|
|
2073
|
-
// triangle to read the note).
|
|
2074
|
-
|
|
2146
|
+
// triangle to read the note). A validation
|
|
2147
|
+
// message (when the cell is invalid) wins over
|
|
2148
|
+
// the plain column tooltip.
|
|
2149
|
+
const tip =
|
|
2150
|
+
cellValidity.invalid && cellValidity.message
|
|
2151
|
+
? cellValidity.message
|
|
2152
|
+
: cellTooltip;
|
|
2153
|
+
if (tip)
|
|
2075
2154
|
showTooltipFor(
|
|
2076
2155
|
event.currentTarget as HTMLElement,
|
|
2077
|
-
|
|
2156
|
+
tip,
|
|
2078
2157
|
);
|
|
2079
2158
|
}}
|
|
2080
2159
|
onpointerleave={hideTooltip}
|
|
@@ -2284,7 +2363,7 @@
|
|
|
2284
2363
|
{/if}
|
|
2285
2364
|
</div>
|
|
2286
2365
|
|
|
2287
|
-
<GridFooter {ctrl} />
|
|
2366
|
+
<GridFooter {ctrl} pager={(props.paginationPosition ?? "bottom") !== "top"} pageSizeOptions={props.pageSizeOptions} />
|
|
2288
2367
|
|
|
2289
2368
|
{#if ctrl.findOpen}
|
|
2290
2369
|
<!-- Find-in-grid overlay. Anchored to the TOP of the grid root so
|
package/src/SvGrid.types.ts
CHANGED
|
@@ -105,6 +105,32 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
|
|
|
105
105
|
showPagination?: boolean;
|
|
106
106
|
/** Initial page size when pagination is enabled. Defaults to 10. */
|
|
107
107
|
pageSize?: number;
|
|
108
|
+
/** Page-size choices shown in the footer's selector. Defaults to `[10, 25, 50, 100]`. */
|
|
109
|
+
pageSizeOptions?: number[];
|
|
110
|
+
/**
|
|
111
|
+
* Where the pagination footer sits: `'bottom'` (default), `'top'`, or `'both'`.
|
|
112
|
+
* The status bar (when enabled) always stays at the bottom.
|
|
113
|
+
*/
|
|
114
|
+
paginationPosition?: 'top' | 'bottom' | 'both';
|
|
115
|
+
/**
|
|
116
|
+
* Server-side pagination. When `true`, the grid renders its native
|
|
117
|
+
* pagination footer from the `rowCount` / `pageIndex` you provide (rather
|
|
118
|
+
* than counting the local rows), does NOT slice `data` (the rows you pass are
|
|
119
|
+
* treated as the current page), and emits `onPaginationChange` when the user
|
|
120
|
+
* pages or changes page size. Pair with `externalSort` / `externalFilter`
|
|
121
|
+
* and a server data source. Requires `showPagination` / `pageable` to show
|
|
122
|
+
* the footer. Controlled: you own `pageIndex` + `pageSize` + `rowCount`.
|
|
123
|
+
*/
|
|
124
|
+
externalPagination?: boolean;
|
|
125
|
+
/** Total rows on the server, for the footer's range + page count. Used with `externalPagination`. */
|
|
126
|
+
rowCount?: number;
|
|
127
|
+
/** Current 0-based page index. Used with `externalPagination` (controlled). */
|
|
128
|
+
pageIndex?: number;
|
|
129
|
+
/**
|
|
130
|
+
* Fires when the user changes page or page size while `externalPagination`
|
|
131
|
+
* is on. Fetch that page and update `data` / `rowCount` / `pageIndex`.
|
|
132
|
+
*/
|
|
133
|
+
onPaginationChange?: (pagination: { pageIndex: number; pageSize: number }) => void;
|
|
108
134
|
virtualization?: boolean;
|
|
109
135
|
/** Row height in pixels. Pass a function `(rowIndex) => px` for
|
|
110
136
|
* per-row variable heights (e.g. an interactive row-resize feature).
|
|
@@ -150,6 +176,13 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
|
|
|
150
176
|
showFilterMenu?: boolean;
|
|
151
177
|
showFilterRow?: boolean;
|
|
152
178
|
enableCellSelection?: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Highlight the row under the pointer. Default **false** - the hover tint can
|
|
181
|
+
* compete with the cell selection / fill marquee. Set `true` to opt in; when
|
|
182
|
+
* on, the hover no longer paints over selected cells so the selection stays
|
|
183
|
+
* visible.
|
|
184
|
+
*/
|
|
185
|
+
enableRowHover?: boolean;
|
|
153
186
|
/**
|
|
154
187
|
* Prepend a header row (the column labels) to copied cell ranges, so pasting
|
|
155
188
|
* into Excel / Sheets includes the headers. Applies per copied range.
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* Tab - commit and let focus escape.
|
|
20
20
|
*/
|
|
21
21
|
import type { CellEditorOption } from './editors/cell-editors'
|
|
22
|
+
import { anchoredRect, portalToBody, type AnchoredRect } from './popover'
|
|
22
23
|
|
|
23
24
|
type Props = {
|
|
24
25
|
options: ReadonlyArray<CellEditorOption>
|
|
@@ -30,6 +31,10 @@
|
|
|
30
31
|
/** Add a typeahead input at the top of the popover that filters the
|
|
31
32
|
* option list as the user types. Used by the rich-select editor. */
|
|
32
33
|
searchable?: boolean
|
|
34
|
+
/** Focus the trigger and open the panel on mount. True for in-cell editing
|
|
35
|
+
* (you clicked into the cell to edit); set false for form fields, which
|
|
36
|
+
* should stay closed and unfocused until the user acts. Default true. */
|
|
37
|
+
autoOpen?: boolean
|
|
33
38
|
/** Called with the new full value (scalar for single, array for multi). */
|
|
34
39
|
onChange?: (next: unknown) => void
|
|
35
40
|
/** Called when the user finalizes the selection (single pick, Enter, blur with selection). */
|
|
@@ -45,6 +50,7 @@
|
|
|
45
50
|
placeholder = 'Select…',
|
|
46
51
|
renderChipsInTrigger = false,
|
|
47
52
|
searchable = false,
|
|
53
|
+
autoOpen = true,
|
|
48
54
|
onChange,
|
|
49
55
|
onCommit,
|
|
50
56
|
onCancel,
|
|
@@ -69,7 +75,7 @@
|
|
|
69
75
|
/** Viewport-anchored panel position. We `position: fixed` the panel so it
|
|
70
76
|
* escapes the grid's overflow:hidden scroll container - otherwise the
|
|
71
77
|
* bottom of the list gets clipped by the pager or the grid's footer. */
|
|
72
|
-
let panelRect = $state<
|
|
78
|
+
let panelRect = $state<AnchoredRect>({
|
|
73
79
|
top: 0,
|
|
74
80
|
left: 0,
|
|
75
81
|
width: 0,
|
|
@@ -134,20 +140,15 @@
|
|
|
134
140
|
* flips upward when there isn't enough room below the trigger. */
|
|
135
141
|
function updatePanelPosition() {
|
|
136
142
|
if (!triggerEl) return
|
|
137
|
-
const r = triggerEl.getBoundingClientRect()
|
|
138
143
|
// Use the smaller of (option count, 10) for the upward-flip math so
|
|
139
144
|
// a 3-option list doesn't think it needs 320px of headroom.
|
|
140
145
|
const visibleCount = Math.min(options.length, 10)
|
|
141
146
|
const estimatedHeight = visibleCount * 32 + (multiple ? 40 : 0) + 8
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
left: r.left,
|
|
148
|
-
width: r.width,
|
|
149
|
-
openUpward,
|
|
150
|
-
}
|
|
147
|
+
panelRect = anchoredRect(triggerEl.getBoundingClientRect(), {
|
|
148
|
+
estimatedHeight,
|
|
149
|
+
// Preserve the original behavior: anchor to the trigger, no clamping.
|
|
150
|
+
clampHorizontal: false,
|
|
151
|
+
})
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
// Keep the panel anchored as the page scrolls or resizes - without this,
|
|
@@ -249,28 +250,13 @@
|
|
|
249
250
|
}
|
|
250
251
|
|
|
251
252
|
function focusTrigger(node: HTMLButtonElement) {
|
|
253
|
+
// Form fields (autoOpen=false) stay closed + unfocused until the user acts;
|
|
254
|
+
// in-cell editing auto-focuses + opens to match clicking into a cell.
|
|
255
|
+
if (!autoOpen) return
|
|
252
256
|
node.focus()
|
|
253
|
-
// Auto-open so the user can immediately pick - matches the
|
|
254
|
-
// expectation set by clicking into an editing cell.
|
|
255
257
|
openPanel()
|
|
256
258
|
}
|
|
257
259
|
|
|
258
|
-
/** Svelte action: detach the element on mount and append it to
|
|
259
|
-
* document.body, then put it back / remove on destroy. This is how
|
|
260
|
-
* we "portal" the panel out of the grid's overflow:hidden scroll
|
|
261
|
-
* container so it can never be clipped, no matter how deeply nested
|
|
262
|
-
* the editing cell is. */
|
|
263
|
-
function portalToBody(node: HTMLElement) {
|
|
264
|
-
document.body.appendChild(node)
|
|
265
|
-
return {
|
|
266
|
-
destroy() {
|
|
267
|
-
if (node.parentNode === document.body) {
|
|
268
|
-
document.body.removeChild(node)
|
|
269
|
-
}
|
|
270
|
-
},
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
260
|
/** Close the panel when the user clicks outside both trigger AND
|
|
275
261
|
* panel - `onfocusout` on the dropdown root no longer catches this
|
|
276
262
|
* because the portal'd panel is a sibling of body, not a descendant
|
|
@@ -388,7 +374,7 @@
|
|
|
388
374
|
style:left={`${panelRect.left}px`}
|
|
389
375
|
style:width={`${panelRect.width}px`}
|
|
390
376
|
style:max-height={panelMax !== null ? `${panelMax}px` : null}
|
|
391
|
-
style:z-index="
|
|
377
|
+
style:z-index="2147483647"
|
|
392
378
|
onpointerdown={(event) => event.stopPropagation()}
|
|
393
379
|
onmousedown={(event) => event.stopPropagation()}
|
|
394
380
|
onclick={(event) => event.stopPropagation()}
|