@svgrid/grid 1.2.22 → 1.2.24
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 +89 -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 +409 -0
- package/dist/SvCalendar.svelte.d.ts +52 -0
- package/dist/SvCheckBox.svelte +82 -0
- package/dist/SvCheckBox.svelte.d.ts +19 -0
- package/dist/SvColorInput.svelte +160 -0
- package/dist/SvColorInput.svelte.d.ts +15 -0
- package/dist/SvComboBox.svelte +108 -0
- package/dist/SvComboBox.svelte.d.ts +15 -0
- package/dist/SvCountryInput.svelte +125 -0
- package/dist/SvCountryInput.svelte.d.ts +14 -0
- package/dist/SvDateTimePicker.svelte +265 -0
- package/dist/SvDateTimePicker.svelte.d.ts +38 -0
- package/dist/SvDropDownList.svelte +120 -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 +97 -0
- package/dist/SvGauge.svelte.d.ts +26 -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 +89 -0
- package/dist/SvListBox.svelte.d.ts +22 -0
- package/dist/SvMaskedInput.svelte +66 -0
- package/dist/SvMaskedInput.svelte.d.ts +15 -0
- package/dist/SvNumberInput.svelte +116 -0
- package/dist/SvNumberInput.svelte.d.ts +18 -0
- package/dist/SvPasswordInput.svelte +100 -0
- package/dist/SvPasswordInput.svelte.d.ts +18 -0
- package/dist/SvPhoneInput.svelte +87 -0
- package/dist/SvPhoneInput.svelte.d.ts +19 -0
- package/dist/SvRadioGroup.svelte +85 -0
- package/dist/SvRadioGroup.svelte.d.ts +20 -0
- package/dist/SvRating.svelte +91 -0
- package/dist/SvRating.svelte.d.ts +18 -0
- package/dist/SvRepeatButton.svelte +89 -0
- package/dist/SvRepeatButton.svelte.d.ts +21 -0
- package/dist/SvSlider.svelte +155 -0
- package/dist/SvSlider.svelte.d.ts +20 -0
- package/dist/SvSwitchButton.svelte +87 -0
- package/dist/SvSwitchButton.svelte.d.ts +14 -0
- package/dist/SvTabs.svelte +109 -0
- package/dist/SvTabs.svelte.d.ts +26 -0
- package/dist/SvTagsInput.svelte +70 -0
- package/dist/SvTagsInput.svelte.d.ts +14 -0
- package/dist/SvTimePicker.svelte +201 -0
- package/dist/SvTimePicker.svelte.d.ts +30 -0
- package/dist/SvToggleButton.svelte +57 -0
- package/dist/SvToggleButton.svelte.d.ts +16 -0
- package/dist/SvTree.svelte +131 -0
- package/dist/SvTree.svelte.d.ts +27 -0
- package/dist/build-api.js +78 -1
- package/dist/cdn/svgrid.js +11674 -5907
- package/dist/cdn/svgrid.svelte-external.js +10436 -4671
- 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/createAutocomplete.svelte.d.ts +77 -0
- package/dist/createAutocomplete.svelte.js +100 -0
- package/dist/createCalendar.svelte.d.ts +192 -0
- package/dist/createCalendar.svelte.js +402 -0
- package/dist/createCheckbox.svelte.d.ts +45 -0
- package/dist/createCheckbox.svelte.js +26 -0
- package/dist/createColorInput.svelte.d.ts +67 -0
- package/dist/createColorInput.svelte.js +100 -0
- package/dist/createCombobox.svelte.d.ts +102 -0
- package/dist/createCombobox.svelte.js +171 -0
- package/dist/createCountryInput.svelte.d.ts +86 -0
- package/dist/createCountryInput.svelte.js +126 -0
- package/dist/createDateTimePicker.svelte.d.ts +83 -0
- package/dist/createDateTimePicker.svelte.js +202 -0
- package/dist/createDropdownList.svelte.d.ts +78 -0
- package/dist/createDropdownList.svelte.js +117 -0
- package/dist/createGauge.svelte.d.ts +56 -0
- package/dist/createGauge.svelte.js +49 -0
- package/dist/createListbox.svelte.d.ts +79 -0
- package/dist/createListbox.svelte.js +117 -0
- package/dist/createMaskedInput.svelte.d.ts +30 -0
- package/dist/createMaskedInput.svelte.js +62 -0
- package/dist/createNumberInput.svelte.d.ts +72 -0
- package/dist/createNumberInput.svelte.js +167 -0
- package/dist/createPasswordInput.svelte.d.ts +60 -0
- package/dist/createPasswordInput.svelte.js +72 -0
- package/dist/createPhoneInput.svelte.d.ts +46 -0
- package/dist/createPhoneInput.svelte.js +105 -0
- package/dist/createRadioGroup.svelte.d.ts +64 -0
- package/dist/createRadioGroup.svelte.js +73 -0
- package/dist/createRating.svelte.d.ts +70 -0
- package/dist/createRating.svelte.js +85 -0
- package/dist/createSlider.svelte.d.ts +71 -0
- package/dist/createSlider.svelte.js +123 -0
- package/dist/createSwitch.svelte.d.ts +43 -0
- package/dist/createSwitch.svelte.js +41 -0
- package/dist/createTabs.svelte.d.ts +78 -0
- package/dist/createTabs.svelte.js +92 -0
- package/dist/createTagsInput.svelte.d.ts +65 -0
- package/dist/createTagsInput.svelte.js +93 -0
- package/dist/createTimePicker.svelte.d.ts +106 -0
- package/dist/createTimePicker.svelte.js +248 -0
- package/dist/createToggle.svelte.d.ts +43 -0
- package/dist/createToggle.svelte.js +39 -0
- package/dist/createTree.svelte.d.ts +96 -0
- package/dist/createTree.svelte.js +194 -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/editor-contract.d.ts +48 -0
- package/dist/editor-contract.js +26 -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 +60 -0
- package/dist/index.js +72 -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 +89 -0
- package/src/SvButton.svelte +97 -0
- package/src/SvCalendar.svelte +409 -0
- package/src/SvCalendar.test.ts +211 -0
- package/src/SvCheckBox.svelte +82 -0
- package/src/SvColorInput.svelte +160 -0
- package/src/SvComboBox.svelte +108 -0
- package/src/SvCountryInput.svelte +125 -0
- package/src/SvDateTimePicker.svelte +265 -0
- package/src/SvDateTimePicker.test.ts +136 -0
- package/src/SvDropDownList.svelte +120 -0
- package/src/SvForm.svelte +147 -0
- package/src/SvGauge.svelte +97 -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 +89 -0
- package/src/SvMaskedInput.svelte +66 -0
- package/src/SvNumberInput.svelte +116 -0
- package/src/SvPasswordInput.svelte +100 -0
- package/src/SvPhoneInput.svelte +87 -0
- package/src/SvRadioGroup.svelte +85 -0
- package/src/SvRating.svelte +91 -0
- package/src/SvRepeatButton.svelte +89 -0
- package/src/SvSlider.svelte +155 -0
- package/src/SvSwitchButton.svelte +87 -0
- package/src/SvTabs.svelte +109 -0
- package/src/SvTagsInput.svelte +70 -0
- package/src/SvTimePicker.svelte +201 -0
- package/src/SvTimePicker.test.ts +98 -0
- package/src/SvToggleButton.svelte +57 -0
- package/src/SvTree.svelte +131 -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/createAutocomplete.svelte.ts +104 -0
- package/src/createCalendar.svelte.ts +488 -0
- package/src/createCheckbox.svelte.ts +74 -0
- package/src/createColorInput.svelte.ts +115 -0
- package/src/createCombobox.svelte.ts +158 -0
- package/src/createCountryInput.svelte.ts +126 -0
- package/src/createDateTimePicker.svelte.ts +216 -0
- package/src/createDropdownList.svelte.ts +125 -0
- package/src/createGauge.svelte.ts +86 -0
- package/src/createListbox.svelte.ts +186 -0
- package/src/createMaskedInput.svelte.ts +81 -0
- package/src/createNumberInput.svelte.ts +193 -0
- package/src/createPasswordInput.svelte.ts +87 -0
- package/src/createPhoneInput.svelte.ts +119 -0
- package/src/createRadioGroup.svelte.ts +132 -0
- package/src/createRating.svelte.ts +145 -0
- package/src/createSlider.svelte.ts +160 -0
- package/src/createSwitch.svelte.ts +78 -0
- package/src/createTabs.svelte.ts +129 -0
- package/src/createTagsInput.svelte.ts +98 -0
- package/src/createTimePicker.svelte.ts +289 -0
- package/src/createToggle.svelte.ts +69 -0
- package/src/createTree.svelte.ts +216 -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/editor-contract.test.ts +32 -0
- package/src/editor-contract.ts +60 -0
- 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 +110 -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 +118 -0
- package/src/ui-range.test.ts +61 -0
- package/src/ui-selection.test.ts +119 -0
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()}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvListBox - an inline single/multi-select list (WAI-ARIA listbox) with
|
|
4
|
+
* roving highlight + full keyboard. Parity: Smart `smart-list-box`. Controlled
|
|
5
|
+
* via `value` (scalar or array) + `onChange`.
|
|
6
|
+
*/
|
|
7
|
+
import type { ListOption } from './list-option'
|
|
8
|
+
import { createListbox } from './createListbox.svelte'
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
options: ReadonlyArray<ListOption>
|
|
12
|
+
value?: string | number | Array<string | number> | null
|
|
13
|
+
onChange?: (value: any) => void
|
|
14
|
+
multiple?: boolean
|
|
15
|
+
disabled?: boolean
|
|
16
|
+
/** Visible height in rows before scrolling. */
|
|
17
|
+
rows?: number
|
|
18
|
+
ariaLabel?: string
|
|
19
|
+
/** Form field name; emits a hidden input per selected value so the listbox
|
|
20
|
+
* posts in a native <form> (multiple selection -> repeated name). */
|
|
21
|
+
name?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let { options, value = null, onChange, multiple = false, disabled = false, rows = 7, ariaLabel, name }: Props = $props()
|
|
25
|
+
|
|
26
|
+
// The styled listbox is just a renderer over the headless core.
|
|
27
|
+
const lb = createListbox({
|
|
28
|
+
options: () => options,
|
|
29
|
+
value: () => value,
|
|
30
|
+
onChange: (v) => onChange?.(v),
|
|
31
|
+
multiple: () => multiple,
|
|
32
|
+
disabled: () => disabled,
|
|
33
|
+
ariaLabel: () => ariaLabel,
|
|
34
|
+
})
|
|
35
|
+
const isSel = (o: ListOption) => lb.isSelected(o.value)
|
|
36
|
+
|
|
37
|
+
let listEl: HTMLUListElement | null = null
|
|
38
|
+
// Scroll-into-view is a render concern, so it lives here (not in the core).
|
|
39
|
+
$effect(() => {
|
|
40
|
+
const i = lb.activeIndex
|
|
41
|
+
queueMicrotask(() => listEl?.querySelector<HTMLElement>(`[data-idx="${i}"]`)?.scrollIntoView({ block: 'nearest' }))
|
|
42
|
+
})
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<div style="display: contents">
|
|
46
|
+
<ul
|
|
47
|
+
bind:this={listEl}
|
|
48
|
+
class="sv-listbox"
|
|
49
|
+
style:--sv-rows={rows}
|
|
50
|
+
{...lb.rootProps()}
|
|
51
|
+
>
|
|
52
|
+
{#each options as opt, i (opt.value)}
|
|
53
|
+
<!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
|
|
54
|
+
<li
|
|
55
|
+
class="sv-listbox__opt"
|
|
56
|
+
class:is-selected={isSel(opt)}
|
|
57
|
+
class:is-active={lb.isActive(i)}
|
|
58
|
+
class:is-disabled={opt.disabled}
|
|
59
|
+
{...lb.optionProps(i)}
|
|
60
|
+
>
|
|
61
|
+
{#if multiple}<span class="sv-listbox__check" aria-hidden="true">{isSel(opt) ? '✓' : ''}</span>{/if}
|
|
62
|
+
<span class="sv-listbox__label">{opt.label}</span>
|
|
63
|
+
</li>
|
|
64
|
+
{/each}
|
|
65
|
+
</ul>
|
|
66
|
+
{#if name}{#each lb.selectedValues as v (v)}<input type="hidden" {name} value={v} />{/each}{/if}
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<style>
|
|
70
|
+
.sv-listbox {
|
|
71
|
+
--_accent: var(--sg-accent, #2563eb);
|
|
72
|
+
margin: 0; padding: 4px; list-style: none;
|
|
73
|
+
max-height: calc(var(--sv-rows, 7) * 32px + 8px); overflow-y: auto;
|
|
74
|
+
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
75
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
76
|
+
width: 220px; outline: none;
|
|
77
|
+
}
|
|
78
|
+
.sv-listbox:focus-visible { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
79
|
+
.sv-listbox__opt {
|
|
80
|
+
display: flex; align-items: center; gap: 8px; height: 32px; padding: 0 10px;
|
|
81
|
+
border-radius: 6px; cursor: pointer; font-size: 13px;
|
|
82
|
+
}
|
|
83
|
+
.sv-listbox__opt.is-active { background: var(--sg-row-hover-bg, #f1f5f9); }
|
|
84
|
+
.sv-listbox__opt.is-selected { background: color-mix(in srgb, var(--_accent) 14%, transparent); color: var(--_accent); font-weight: 600; }
|
|
85
|
+
.sv-listbox__opt.is-selected.is-active { background: color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
86
|
+
.sv-listbox__opt.is-disabled { opacity: 0.4; cursor: not-allowed; }
|
|
87
|
+
.sv-listbox__check { width: 14px; text-align: center; color: var(--_accent); }
|
|
88
|
+
.sv-listbox__label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
89
|
+
</style>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvMaskedInput - a pattern-masked text input (#=digit, A=letter, *=alnum;
|
|
4
|
+
* other chars are literals). Parity: Smart `smart-masked-text-box`. Emits both
|
|
5
|
+
* the masked display value and the raw (unmasked) value.
|
|
6
|
+
*
|
|
7
|
+
* The styled renderer over the headless `createMaskedInput` core; the mask
|
|
8
|
+
* formatting + state live in the core, spread here via prop-getters.
|
|
9
|
+
*/
|
|
10
|
+
import { unmask } from './datetime/mask'
|
|
11
|
+
import { createMaskedInput } from './createMaskedInput.svelte'
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
value?: string
|
|
15
|
+
/** Called with (masked, raw, complete). */
|
|
16
|
+
onChange?: (masked: string, raw: string, complete: boolean) => void
|
|
17
|
+
mask?: string
|
|
18
|
+
placeholder?: string
|
|
19
|
+
disabled?: boolean
|
|
20
|
+
readonly?: boolean
|
|
21
|
+
name?: string
|
|
22
|
+
size?: 'sm' | 'md' | 'lg'
|
|
23
|
+
ariaLabel?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
value = '',
|
|
28
|
+
onChange,
|
|
29
|
+
mask = '',
|
|
30
|
+
placeholder,
|
|
31
|
+
disabled = false,
|
|
32
|
+
readonly = false,
|
|
33
|
+
name,
|
|
34
|
+
size = 'md',
|
|
35
|
+
ariaLabel,
|
|
36
|
+
}: Props = $props()
|
|
37
|
+
|
|
38
|
+
const mi = createMaskedInput({
|
|
39
|
+
value: () => value,
|
|
40
|
+
onChange: (m, r, c) => onChange?.(m, r, c),
|
|
41
|
+
mask: () => mask,
|
|
42
|
+
placeholder: () => placeholder,
|
|
43
|
+
disabled: () => disabled,
|
|
44
|
+
readonly: () => readonly,
|
|
45
|
+
ariaLabel: () => ariaLabel,
|
|
46
|
+
})
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<input class="sv-masked sv-masked--{size}" {...mi.inputProps()} />
|
|
50
|
+
{#if name}<input type="hidden" {name} value={mask ? unmask(mi.masked, mask) : mi.masked} />{/if}
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
.sv-masked {
|
|
54
|
+
--_accent: var(--sg-accent, #2563eb);
|
|
55
|
+
box-sizing: border-box; width: 200px;
|
|
56
|
+
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
57
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
58
|
+
font: inherit; padding: 0 10px; outline: none;
|
|
59
|
+
font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
|
|
60
|
+
}
|
|
61
|
+
.sv-masked--sm { height: 28px; font-size: 12px; }
|
|
62
|
+
.sv-masked--md { height: 34px; font-size: 13px; }
|
|
63
|
+
.sv-masked--lg { height: 40px; font-size: 15px; }
|
|
64
|
+
.sv-masked:focus { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
65
|
+
.sv-masked:disabled { opacity: 0.6; }
|
|
66
|
+
</style>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvNumberInput - a numeric input with min/max/step, spinner buttons, optional
|
|
4
|
+
* thousands grouping + precision, prefix/suffix. Parity: Smart `smart-number-input`.
|
|
5
|
+
* Emits number | null. The SvGrid number cell editor, standalone too.
|
|
6
|
+
*
|
|
7
|
+
* This is the styled renderer over the headless `createNumberInput` core - the
|
|
8
|
+
* same split as the grid (`createSvGrid` / `<SvGrid>`): parse/format/clamp,
|
|
9
|
+
* spinner + keyboard and ARIA all come from the core via prop-getters.
|
|
10
|
+
*/
|
|
11
|
+
import SvRepeatButton from './SvRepeatButton.svelte'
|
|
12
|
+
import { editorErrorId, type SvEditorProps } from './editor-contract'
|
|
13
|
+
import { createNumberInput } from './createNumberInput.svelte'
|
|
14
|
+
|
|
15
|
+
type Props = SvEditorProps & {
|
|
16
|
+
value?: number | null
|
|
17
|
+
onChange?: (value: number | null) => void
|
|
18
|
+
min?: number
|
|
19
|
+
max?: number
|
|
20
|
+
step?: number
|
|
21
|
+
precision?: number
|
|
22
|
+
/** Group thousands (1,234) on display. */
|
|
23
|
+
grouping?: boolean
|
|
24
|
+
prefix?: string
|
|
25
|
+
suffix?: string
|
|
26
|
+
placeholder?: string
|
|
27
|
+
spinButtons?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let {
|
|
31
|
+
value = null,
|
|
32
|
+
onChange,
|
|
33
|
+
min = -Infinity,
|
|
34
|
+
max = Infinity,
|
|
35
|
+
step = 1,
|
|
36
|
+
precision,
|
|
37
|
+
grouping = false,
|
|
38
|
+
prefix = '',
|
|
39
|
+
suffix = '',
|
|
40
|
+
placeholder,
|
|
41
|
+
disabled = false,
|
|
42
|
+
readonly = false,
|
|
43
|
+
spinButtons = true,
|
|
44
|
+
name,
|
|
45
|
+
size = 'md',
|
|
46
|
+
ariaLabel,
|
|
47
|
+
invalid = false,
|
|
48
|
+
required = false,
|
|
49
|
+
error,
|
|
50
|
+
id,
|
|
51
|
+
}: Props = $props()
|
|
52
|
+
|
|
53
|
+
const num = createNumberInput({
|
|
54
|
+
value: () => value,
|
|
55
|
+
onChange: (v) => onChange?.(v),
|
|
56
|
+
min: () => min,
|
|
57
|
+
max: () => max,
|
|
58
|
+
step: () => step,
|
|
59
|
+
precision: () => precision,
|
|
60
|
+
grouping: () => grouping,
|
|
61
|
+
prefix: () => prefix,
|
|
62
|
+
suffix: () => suffix,
|
|
63
|
+
placeholder: () => placeholder,
|
|
64
|
+
disabled: () => disabled,
|
|
65
|
+
readonly: () => readonly,
|
|
66
|
+
id: () => id,
|
|
67
|
+
invalid: () => invalid,
|
|
68
|
+
required: () => required,
|
|
69
|
+
error: () => error,
|
|
70
|
+
ariaLabel: () => ariaLabel,
|
|
71
|
+
})
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<div class="sv-field">
|
|
75
|
+
<div class="sv-num sv-num--{size}" class:is-disabled={disabled} class:is-invalid={invalid}>
|
|
76
|
+
<input class="sv-num__input" {...num.inputProps()} />
|
|
77
|
+
{#if spinButtons}
|
|
78
|
+
<div class="sv-num__spin">
|
|
79
|
+
<SvRepeatButton size="sm" variant="ghost" ariaLabel="Increment" onclick={num.increment}>
|
|
80
|
+
<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M6 15l6-6 6 6" /></svg>
|
|
81
|
+
</SvRepeatButton>
|
|
82
|
+
<SvRepeatButton size="sm" variant="ghost" ariaLabel="Decrement" onclick={num.decrement}>
|
|
83
|
+
<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6" /></svg>
|
|
84
|
+
</SvRepeatButton>
|
|
85
|
+
</div>
|
|
86
|
+
{/if}
|
|
87
|
+
{#if name}<input type="hidden" {name} value={value ?? ''} />{/if}
|
|
88
|
+
</div>
|
|
89
|
+
{#if error}<span class="sv-field__error" id={editorErrorId(id)} role="alert">{error}</span>{/if}
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<style>
|
|
93
|
+
/* Shared field wrapper: stacks the control box above an optional error line. */
|
|
94
|
+
.sv-field { display: inline-flex; flex-direction: column; gap: 3px; }
|
|
95
|
+
.sv-field__error { font-size: 11.5px; font-weight: 500; color: var(--sg-danger, #dc2626); line-height: 1.35; }
|
|
96
|
+
.sv-num {
|
|
97
|
+
--_accent: var(--sg-accent, #2563eb);
|
|
98
|
+
display: inline-flex; align-items: stretch; width: 150px;
|
|
99
|
+
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
100
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
101
|
+
overflow: hidden;
|
|
102
|
+
}
|
|
103
|
+
.sv-num:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
104
|
+
.sv-num.is-invalid { border-color: var(--sg-danger, #dc2626); }
|
|
105
|
+
.sv-num.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
|
|
106
|
+
.sv-num.is-disabled { opacity: 0.6; }
|
|
107
|
+
.sv-num__input {
|
|
108
|
+
flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit; font: inherit;
|
|
109
|
+
text-align: right; padding: 0 8px;
|
|
110
|
+
}
|
|
111
|
+
.sv-num--sm { height: 28px; font-size: 12px; }
|
|
112
|
+
.sv-num--md { height: 34px; font-size: 13px; }
|
|
113
|
+
.sv-num--lg { height: 40px; font-size: 15px; }
|
|
114
|
+
.sv-num__spin { display: flex; flex-direction: column; border-left: 1px solid var(--sg-border, #e2e8f0); }
|
|
115
|
+
.sv-num__spin :global(.sv-repeat) { flex: 1; padding: 0 6px; border-radius: 0; }
|
|
116
|
+
</style>
|