@svgrid/grid 1.2.22 → 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/dist/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/dist/SvGrid.types.d.ts
CHANGED
|
@@ -92,6 +92,35 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
|
|
|
92
92
|
showPagination?: boolean;
|
|
93
93
|
/** Initial page size when pagination is enabled. Defaults to 10. */
|
|
94
94
|
pageSize?: number;
|
|
95
|
+
/** Page-size choices shown in the footer's selector. Defaults to `[10, 25, 50, 100]`. */
|
|
96
|
+
pageSizeOptions?: number[];
|
|
97
|
+
/**
|
|
98
|
+
* Where the pagination footer sits: `'bottom'` (default), `'top'`, or `'both'`.
|
|
99
|
+
* The status bar (when enabled) always stays at the bottom.
|
|
100
|
+
*/
|
|
101
|
+
paginationPosition?: 'top' | 'bottom' | 'both';
|
|
102
|
+
/**
|
|
103
|
+
* Server-side pagination. When `true`, the grid renders its native
|
|
104
|
+
* pagination footer from the `rowCount` / `pageIndex` you provide (rather
|
|
105
|
+
* than counting the local rows), does NOT slice `data` (the rows you pass are
|
|
106
|
+
* treated as the current page), and emits `onPaginationChange` when the user
|
|
107
|
+
* pages or changes page size. Pair with `externalSort` / `externalFilter`
|
|
108
|
+
* and a server data source. Requires `showPagination` / `pageable` to show
|
|
109
|
+
* the footer. Controlled: you own `pageIndex` + `pageSize` + `rowCount`.
|
|
110
|
+
*/
|
|
111
|
+
externalPagination?: boolean;
|
|
112
|
+
/** Total rows on the server, for the footer's range + page count. Used with `externalPagination`. */
|
|
113
|
+
rowCount?: number;
|
|
114
|
+
/** Current 0-based page index. Used with `externalPagination` (controlled). */
|
|
115
|
+
pageIndex?: number;
|
|
116
|
+
/**
|
|
117
|
+
* Fires when the user changes page or page size while `externalPagination`
|
|
118
|
+
* is on. Fetch that page and update `data` / `rowCount` / `pageIndex`.
|
|
119
|
+
*/
|
|
120
|
+
onPaginationChange?: (pagination: {
|
|
121
|
+
pageIndex: number;
|
|
122
|
+
pageSize: number;
|
|
123
|
+
}) => void;
|
|
95
124
|
virtualization?: boolean;
|
|
96
125
|
/** Row height in pixels. Pass a function `(rowIndex) => px` for
|
|
97
126
|
* per-row variable heights (e.g. an interactive row-resize feature).
|
|
@@ -137,6 +166,13 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
|
|
|
137
166
|
showFilterMenu?: boolean;
|
|
138
167
|
showFilterRow?: boolean;
|
|
139
168
|
enableCellSelection?: boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Highlight the row under the pointer. Default **false** - the hover tint can
|
|
171
|
+
* compete with the cell selection / fill marquee. Set `true` to opt in; when
|
|
172
|
+
* on, the hover no longer paints over selected cells so the selection stays
|
|
173
|
+
* visible.
|
|
174
|
+
*/
|
|
175
|
+
enableRowHover?: boolean;
|
|
140
176
|
/**
|
|
141
177
|
* Prepend a header row (the column labels) to copied cell ranges, so pasting
|
|
142
178
|
* 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()}
|
|
@@ -28,6 +28,10 @@ type Props = {
|
|
|
28
28
|
/** Add a typeahead input at the top of the popover that filters the
|
|
29
29
|
* option list as the user types. Used by the rich-select editor. */
|
|
30
30
|
searchable?: boolean;
|
|
31
|
+
/** Focus the trigger and open the panel on mount. True for in-cell editing
|
|
32
|
+
* (you clicked into the cell to edit); set false for form fields, which
|
|
33
|
+
* should stay closed and unfocused until the user acts. Default true. */
|
|
34
|
+
autoOpen?: boolean;
|
|
31
35
|
/** Called with the new full value (scalar for single, array for multi). */
|
|
32
36
|
onChange?: (next: unknown) => void;
|
|
33
37
|
/** Called when the user finalizes the selection (single pick, Enter, blur with selection). */
|
|
@@ -0,0 +1,122 @@
|
|
|
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
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
options: ReadonlyArray<ListOption>
|
|
11
|
+
value?: string | number | Array<string | number> | null
|
|
12
|
+
onChange?: (value: any) => void
|
|
13
|
+
multiple?: boolean
|
|
14
|
+
disabled?: boolean
|
|
15
|
+
/** Visible height in rows before scrolling. */
|
|
16
|
+
rows?: number
|
|
17
|
+
ariaLabel?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let { options, value = null, onChange, multiple = false, disabled = false, rows = 7, ariaLabel }: Props = $props()
|
|
21
|
+
|
|
22
|
+
const selected = $derived<Array<string | number>>(
|
|
23
|
+
multiple ? (Array.isArray(value) ? value : value == null ? [] : [value]) : value == null ? [] : [value as string | number],
|
|
24
|
+
)
|
|
25
|
+
const enabledIdx = $derived(options.map((o, i) => (o.disabled ? -1 : i)).filter((i) => i >= 0))
|
|
26
|
+
let active = $state(0)
|
|
27
|
+
$effect(() => {
|
|
28
|
+
// Keep the active index on an enabled option.
|
|
29
|
+
if (!options[active] || options[active]!.disabled) active = enabledIdx[0] ?? 0
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
function isSel(o: ListOption) { return selected.includes(o.value) }
|
|
33
|
+
|
|
34
|
+
function pick(i: number) {
|
|
35
|
+
const o = options[i]
|
|
36
|
+
if (!o || o.disabled || disabled) return
|
|
37
|
+
active = i
|
|
38
|
+
if (multiple) {
|
|
39
|
+
const set = new Set(selected)
|
|
40
|
+
set.has(o.value) ? set.delete(o.value) : set.add(o.value)
|
|
41
|
+
onChange?.([...set])
|
|
42
|
+
} else {
|
|
43
|
+
onChange?.(o.value)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function move(delta: number) {
|
|
48
|
+
const pos = enabledIdx.indexOf(active)
|
|
49
|
+
const next = enabledIdx[(pos + delta + enabledIdx.length) % enabledIdx.length]
|
|
50
|
+
if (next != null) { active = next; scrollActive() }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let listEl: HTMLUListElement | null = null
|
|
54
|
+
function scrollActive() {
|
|
55
|
+
queueMicrotask(() => listEl?.querySelector<HTMLElement>(`[data-idx="${active}"]`)?.scrollIntoView({ block: 'nearest' }))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function onKeydown(e: KeyboardEvent) {
|
|
59
|
+
if (disabled) return
|
|
60
|
+
switch (e.key) {
|
|
61
|
+
case 'ArrowDown': e.preventDefault(); move(1); break
|
|
62
|
+
case 'ArrowUp': e.preventDefault(); move(-1); break
|
|
63
|
+
case 'Home': e.preventDefault(); active = enabledIdx[0] ?? 0; scrollActive(); break
|
|
64
|
+
case 'End': e.preventDefault(); active = enabledIdx.at(-1) ?? 0; scrollActive(); break
|
|
65
|
+
case ' ':
|
|
66
|
+
case 'Enter': e.preventDefault(); pick(active); break
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<ul
|
|
72
|
+
bind:this={listEl}
|
|
73
|
+
class="sv-listbox"
|
|
74
|
+
role="listbox"
|
|
75
|
+
aria-multiselectable={multiple}
|
|
76
|
+
aria-label={ariaLabel}
|
|
77
|
+
aria-disabled={disabled}
|
|
78
|
+
tabindex={disabled ? -1 : 0}
|
|
79
|
+
style:--sv-rows={rows}
|
|
80
|
+
onkeydown={onKeydown}
|
|
81
|
+
>
|
|
82
|
+
{#each options as opt, i (opt.value)}
|
|
83
|
+
<!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
|
|
84
|
+
<li
|
|
85
|
+
class="sv-listbox__opt"
|
|
86
|
+
class:is-selected={isSel(opt)}
|
|
87
|
+
class:is-active={i === active}
|
|
88
|
+
class:is-disabled={opt.disabled}
|
|
89
|
+
role="option"
|
|
90
|
+
aria-selected={isSel(opt)}
|
|
91
|
+
aria-disabled={opt.disabled}
|
|
92
|
+
data-idx={i}
|
|
93
|
+
onclick={() => pick(i)}
|
|
94
|
+
onpointermove={() => { if (!opt.disabled) active = i }}
|
|
95
|
+
>
|
|
96
|
+
{#if multiple}<span class="sv-listbox__check" aria-hidden="true">{isSel(opt) ? '✓' : ''}</span>{/if}
|
|
97
|
+
<span class="sv-listbox__label">{opt.label}</span>
|
|
98
|
+
</li>
|
|
99
|
+
{/each}
|
|
100
|
+
</ul>
|
|
101
|
+
|
|
102
|
+
<style>
|
|
103
|
+
.sv-listbox {
|
|
104
|
+
--_accent: var(--sg-accent, #2563eb);
|
|
105
|
+
margin: 0; padding: 4px; list-style: none;
|
|
106
|
+
max-height: calc(var(--sv-rows, 7) * 32px + 8px); overflow-y: auto;
|
|
107
|
+
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
108
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
109
|
+
width: 220px; outline: none;
|
|
110
|
+
}
|
|
111
|
+
.sv-listbox:focus-visible { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
112
|
+
.sv-listbox__opt {
|
|
113
|
+
display: flex; align-items: center; gap: 8px; height: 32px; padding: 0 10px;
|
|
114
|
+
border-radius: 6px; cursor: pointer; font-size: 13px;
|
|
115
|
+
}
|
|
116
|
+
.sv-listbox__opt.is-active { background: var(--sg-row-hover-bg, #f1f5f9); }
|
|
117
|
+
.sv-listbox__opt.is-selected { background: color-mix(in srgb, var(--_accent) 14%, transparent); color: var(--_accent); font-weight: 600; }
|
|
118
|
+
.sv-listbox__opt.is-selected.is-active { background: color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
119
|
+
.sv-listbox__opt.is-disabled { opacity: 0.4; cursor: not-allowed; }
|
|
120
|
+
.sv-listbox__check { width: 14px; text-align: center; color: var(--_accent); }
|
|
121
|
+
.sv-listbox__label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
122
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvListBox - an inline single/multi-select list (WAI-ARIA listbox) with
|
|
3
|
+
* roving highlight + full keyboard. Parity: Smart `smart-list-box`. Controlled
|
|
4
|
+
* via `value` (scalar or array) + `onChange`.
|
|
5
|
+
*/
|
|
6
|
+
import type { ListOption } from './list-option';
|
|
7
|
+
type Props = {
|
|
8
|
+
options: ReadonlyArray<ListOption>;
|
|
9
|
+
value?: string | number | Array<string | number> | null;
|
|
10
|
+
onChange?: (value: any) => void;
|
|
11
|
+
multiple?: boolean;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** Visible height in rows before scrolling. */
|
|
14
|
+
rows?: number;
|
|
15
|
+
ariaLabel?: string;
|
|
16
|
+
};
|
|
17
|
+
declare const SvListBox: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type SvListBox = ReturnType<typeof SvListBox>;
|
|
19
|
+
export default SvListBox;
|
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
value?: string;
|
|
3
|
+
/** Called with (masked, raw, complete). */
|
|
4
|
+
onChange?: (masked: string, raw: string, complete: boolean) => void;
|
|
5
|
+
mask?: string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
readonly?: boolean;
|
|
9
|
+
name?: string;
|
|
10
|
+
size?: 'sm' | 'md' | 'lg';
|
|
11
|
+
ariaLabel?: string;
|
|
12
|
+
};
|
|
13
|
+
declare const SvMaskedInput: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type SvMaskedInput = ReturnType<typeof SvMaskedInput>;
|
|
15
|
+
export default SvMaskedInput;
|
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
value?: number | null;
|
|
3
|
+
onChange?: (value: number | null) => void;
|
|
4
|
+
min?: number;
|
|
5
|
+
max?: number;
|
|
6
|
+
step?: number;
|
|
7
|
+
precision?: number;
|
|
8
|
+
/** Group thousands (1,234) on display. */
|
|
9
|
+
grouping?: boolean;
|
|
10
|
+
prefix?: string;
|
|
11
|
+
suffix?: string;
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
readonly?: boolean;
|
|
15
|
+
spinButtons?: boolean;
|
|
16
|
+
name?: string;
|
|
17
|
+
size?: 'sm' | 'md' | 'lg';
|
|
18
|
+
ariaLabel?: string;
|
|
19
|
+
};
|
|
20
|
+
declare const SvNumberInput: import("svelte").Component<Props, {}, "">;
|
|
21
|
+
type SvNumberInput = ReturnType<typeof SvNumberInput>;
|
|
22
|
+
export default SvNumberInput;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvPasswordInput - a password field with a reveal toggle and an optional
|
|
4
|
+
* strength meter. Parity: Smart `smart-password-input`. Emits the string value.
|
|
5
|
+
*/
|
|
6
|
+
type Props = {
|
|
7
|
+
value?: string
|
|
8
|
+
onChange?: (value: string) => void
|
|
9
|
+
placeholder?: string
|
|
10
|
+
disabled?: boolean
|
|
11
|
+
readonly?: boolean
|
|
12
|
+
/** Show the show/hide eye toggle. */
|
|
13
|
+
revealable?: boolean
|
|
14
|
+
/** Show a 4-level strength meter. */
|
|
15
|
+
showStrength?: boolean
|
|
16
|
+
name?: string
|
|
17
|
+
size?: 'sm' | 'md' | 'lg'
|
|
18
|
+
ariaLabel?: string
|
|
19
|
+
autocomplete?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
value = '',
|
|
24
|
+
onChange,
|
|
25
|
+
placeholder,
|
|
26
|
+
disabled = false,
|
|
27
|
+
readonly = false,
|
|
28
|
+
revealable = true,
|
|
29
|
+
showStrength = false,
|
|
30
|
+
name,
|
|
31
|
+
size = 'md',
|
|
32
|
+
ariaLabel,
|
|
33
|
+
autocomplete = 'current-password',
|
|
34
|
+
}: Props = $props()
|
|
35
|
+
|
|
36
|
+
let revealed = $state(false)
|
|
37
|
+
|
|
38
|
+
// Simple strength heuristic: length + character-class variety (0-4).
|
|
39
|
+
const strength = $derived.by(() => {
|
|
40
|
+
const v = value
|
|
41
|
+
if (!v) return 0
|
|
42
|
+
let score = 0
|
|
43
|
+
if (v.length >= 8) score++
|
|
44
|
+
if (/[a-z]/.test(v) && /[A-Z]/.test(v)) score++
|
|
45
|
+
if (/\d/.test(v)) score++
|
|
46
|
+
if (/[^a-zA-Z0-9]/.test(v)) score++
|
|
47
|
+
return score
|
|
48
|
+
})
|
|
49
|
+
const strengthLabel = ['', 'Weak', 'Fair', 'Good', 'Strong']
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<div class="sv-pw sv-pw--{size}" class:is-disabled={disabled}>
|
|
53
|
+
<div class="sv-pw__field">
|
|
54
|
+
<input
|
|
55
|
+
class="sv-pw__input"
|
|
56
|
+
type={revealed ? 'text' : 'password'}
|
|
57
|
+
value={value}
|
|
58
|
+
{placeholder}
|
|
59
|
+
{disabled}
|
|
60
|
+
{readonly}
|
|
61
|
+
autocomplete={autocomplete as AutoFill}
|
|
62
|
+
aria-label={ariaLabel}
|
|
63
|
+
oninput={(e) => onChange?.((e.currentTarget as HTMLInputElement).value)}
|
|
64
|
+
/>
|
|
65
|
+
{#if revealable}
|
|
66
|
+
<button type="button" class="sv-pw__eye" aria-label={revealed ? 'Hide password' : 'Show password'} aria-pressed={revealed} onclick={() => (revealed = !revealed)} disabled={disabled} tabindex="-1">
|
|
67
|
+
{#if revealed}
|
|
68
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" /><path d="M1 1l22 22" /></svg>
|
|
69
|
+
{:else}
|
|
70
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" /><circle cx="12" cy="12" r="3" /></svg>
|
|
71
|
+
{/if}
|
|
72
|
+
</button>
|
|
73
|
+
{/if}
|
|
74
|
+
</div>
|
|
75
|
+
{#if showStrength}
|
|
76
|
+
<div class="sv-pw__meter" aria-hidden="true">
|
|
77
|
+
{#each Array(4) as _, i (i)}
|
|
78
|
+
<span class="sv-pw__bar is-lvl{strength}" class:on={i < strength}></span>
|
|
79
|
+
{/each}
|
|
80
|
+
</div>
|
|
81
|
+
<span class="sv-pw__strength is-lvl{strength}">{strengthLabel[strength]}</span>
|
|
82
|
+
{/if}
|
|
83
|
+
{#if name}<input type="hidden" {name} value={value} />{/if}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<style>
|
|
87
|
+
.sv-pw { --_accent: var(--sg-accent, #2563eb); display: inline-flex; flex-direction: column; gap: 5px; width: 220px; }
|
|
88
|
+
.sv-pw.is-disabled { opacity: 0.6; }
|
|
89
|
+
.sv-pw__field {
|
|
90
|
+
display: flex; align-items: center;
|
|
91
|
+
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
92
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
93
|
+
}
|
|
94
|
+
.sv-pw__field:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
95
|
+
.sv-pw__input { flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit; font: inherit; padding: 0 10px; }
|
|
96
|
+
.sv-pw--sm .sv-pw__field { height: 28px; font-size: 12px; }
|
|
97
|
+
.sv-pw--md .sv-pw__field { height: 34px; font-size: 13px; }
|
|
98
|
+
.sv-pw--lg .sv-pw__field { height: 40px; font-size: 15px; }
|
|
99
|
+
.sv-pw__eye { display: grid; place-items: center; width: 32px; align-self: stretch; background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; }
|
|
100
|
+
.sv-pw__eye:hover { color: var(--_accent); }
|
|
101
|
+
.sv-pw__meter { display: flex; gap: 4px; }
|
|
102
|
+
.sv-pw__bar { flex: 1; height: 4px; border-radius: 2px; background: var(--sg-border, #e2e8f0); transition: background 0.15s; }
|
|
103
|
+
.sv-pw__bar.on.is-lvl1 { background: #dc2626; }
|
|
104
|
+
.sv-pw__bar.on.is-lvl2 { background: #f59e0b; }
|
|
105
|
+
.sv-pw__bar.on.is-lvl3 { background: #eab308; }
|
|
106
|
+
.sv-pw__bar.on.is-lvl4 { background: #16a34a; }
|
|
107
|
+
.sv-pw__strength { font-size: 11px; font-weight: 600; color: var(--sg-muted, #64748b); }
|
|
108
|
+
.sv-pw__strength.is-lvl1 { color: #dc2626; }
|
|
109
|
+
.sv-pw__strength.is-lvl4 { color: #16a34a; }
|
|
110
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvPasswordInput - a password field with a reveal toggle and an optional
|
|
3
|
+
* strength meter. Parity: Smart `smart-password-input`. Emits the string value.
|
|
4
|
+
*/
|
|
5
|
+
type Props = {
|
|
6
|
+
value?: string;
|
|
7
|
+
onChange?: (value: string) => void;
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
readonly?: boolean;
|
|
11
|
+
/** Show the show/hide eye toggle. */
|
|
12
|
+
revealable?: boolean;
|
|
13
|
+
/** Show a 4-level strength meter. */
|
|
14
|
+
showStrength?: boolean;
|
|
15
|
+
name?: string;
|
|
16
|
+
size?: 'sm' | 'md' | 'lg';
|
|
17
|
+
ariaLabel?: string;
|
|
18
|
+
autocomplete?: string;
|
|
19
|
+
};
|
|
20
|
+
declare const SvPasswordInput: import("svelte").Component<Props, {}, "">;
|
|
21
|
+
type SvPasswordInput = ReturnType<typeof SvPasswordInput>;
|
|
22
|
+
export default SvPasswordInput;
|