@svgrid/grid 2.2.16 → 2.2.17
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/README.md +1 -1
- package/dist/GridFooter.svelte +12 -11
- package/dist/SvAnchor.svelte +65 -0
- package/dist/SvAnchor.svelte.d.ts +24 -0
- package/dist/SvAspectRatio.svelte +38 -0
- package/dist/SvAspectRatio.svelte.d.ts +16 -0
- package/dist/SvAutoComplete.svelte +52 -7
- package/dist/SvAutoComplete.svelte.d.ts +4 -1
- package/dist/SvBlockquote.svelte +43 -0
- package/dist/SvBlockquote.svelte.d.ts +16 -0
- package/dist/SvCalendar.svelte +33 -2
- package/dist/SvCalendar.svelte.d.ts +8 -1
- package/dist/SvCheckBox.svelte +6 -2
- package/dist/SvCode.svelte +52 -0
- package/dist/SvCode.svelte.d.ts +19 -0
- package/dist/SvCollapsible.svelte +75 -0
- package/dist/SvCollapsible.svelte.d.ts +27 -0
- package/dist/SvColorInput.svelte +11 -17
- package/dist/SvColorInput.svelte.d.ts +2 -0
- package/dist/SvComboBox.svelte +85 -8
- package/dist/SvComboBox.svelte.d.ts +14 -0
- package/dist/SvCountryInput.svelte +2 -2
- package/dist/SvCountryInput.svelte.d.ts +1 -1
- package/dist/SvDropDownList.svelte +154 -51
- package/dist/SvDropDownList.svelte.d.ts +17 -3
- package/dist/SvDurationInput.svelte +59 -41
- package/dist/SvDurationInput.svelte.d.ts +18 -0
- package/dist/SvField.svelte +203 -12
- package/dist/SvField.svelte.d.ts +45 -8
- package/dist/SvForm.svelte +195 -21
- package/dist/SvForm.svelte.d.ts +23 -0
- package/dist/SvGrid.controller.svelte.d.ts +12 -1
- package/dist/SvGrid.controller.svelte.js +87 -7
- package/dist/SvGrid.svelte +383 -181
- package/dist/SvGrid.types.d.ts +123 -2
- package/dist/SvGridChartView.svelte +70 -0
- package/dist/SvGridChartView.svelte.d.ts +11 -0
- package/dist/SvGridDropdown.svelte +9 -2
- package/dist/SvGridSelect.svelte +5 -2
- package/dist/SvGridSelect.svelte.d.ts +1 -1
- package/dist/SvGroup.svelte +41 -0
- package/dist/SvGroup.svelte.d.ts +27 -0
- package/dist/SvHoverCard.svelte +70 -0
- package/dist/SvHoverCard.svelte.d.ts +41 -0
- package/dist/SvKbd.svelte +51 -0
- package/dist/SvKbd.svelte.d.ts +18 -0
- package/dist/SvList.svelte +55 -0
- package/dist/SvList.svelte.d.ts +23 -0
- package/dist/SvListBox.svelte +166 -77
- package/dist/SvListBox.svelte.d.ts +17 -4
- package/dist/SvLoadingOverlay.svelte +53 -0
- package/dist/SvLoadingOverlay.svelte.d.ts +28 -0
- package/dist/SvMark.svelte +36 -0
- package/dist/SvMark.svelte.d.ts +14 -0
- package/dist/SvMaskedInput.svelte +50 -35
- package/dist/SvMaskedInput.svelte.d.ts +17 -2
- package/dist/SvMenuList.svelte +32 -3
- package/dist/SvMenubar.svelte +200 -0
- package/dist/SvMenubar.svelte.d.ts +18 -0
- package/dist/SvMultiSelect.svelte +7 -4
- package/dist/SvMultiSelect.svelte.d.ts +1 -1
- package/dist/SvNumberInput.svelte +79 -42
- package/dist/SvNumberInput.svelte.d.ts +25 -0
- package/dist/SvOtpInput.svelte +2 -2
- package/dist/SvPasswordInput.svelte +51 -31
- package/dist/SvPasswordInput.svelte.d.ts +17 -0
- package/dist/SvPhoneInput.svelte +39 -28
- package/dist/SvPhoneInput.svelte.d.ts +4 -0
- package/dist/SvPopconfirm.svelte +79 -0
- package/dist/SvPopconfirm.svelte.d.ts +42 -0
- package/dist/SvPopover.svelte +87 -29
- package/dist/SvPopover.svelte.d.ts +18 -4
- package/dist/SvRadioGroup.svelte +5 -1
- package/dist/SvResult.svelte +73 -0
- package/dist/SvResult.svelte.d.ts +28 -0
- package/dist/SvSegmented.svelte +114 -0
- package/dist/SvSegmented.svelte.d.ts +29 -0
- package/dist/SvSimpleGrid.svelte +41 -0
- package/dist/SvSimpleGrid.svelte.d.ts +25 -0
- package/dist/SvSlider.svelte +23 -10
- package/dist/SvSpinner.svelte +43 -0
- package/dist/SvSpinner.svelte.d.ts +20 -0
- package/dist/SvStack.svelte +37 -0
- package/dist/SvStack.svelte.d.ts +24 -0
- package/dist/SvSwitchButton.svelte +6 -1
- package/dist/SvTagsInput.svelte +9 -5
- package/dist/SvText.svelte +76 -0
- package/dist/SvText.svelte.d.ts +31 -0
- package/dist/SvTextInput.svelte +75 -43
- package/dist/SvTextInput.svelte.d.ts +28 -1
- package/dist/SvTimePicker.svelte +32 -2
- package/dist/SvTimePicker.svelte.d.ts +8 -9
- package/dist/SvTitle.svelte +57 -0
- package/dist/SvTitle.svelte.d.ts +24 -0
- package/dist/SvToaster.svelte +41 -6
- package/dist/SvToggleButton.svelte +4 -1
- package/dist/SvTooltip.svelte +68 -34
- package/dist/SvTooltip.svelte.d.ts +14 -2
- package/dist/SvTree.svelte +81 -21
- package/dist/SvTree.svelte.d.ts +3 -2
- package/dist/SvTreeSelect.svelte +5 -2
- package/dist/SvTreeSelect.svelte.d.ts +1 -1
- package/dist/SvVisuallyHidden.svelte +49 -0
- package/dist/SvVisuallyHidden.svelte.d.ts +19 -0
- package/dist/ai.d.ts +289 -0
- package/dist/ai.js +1013 -0
- package/dist/board-view.svelte.d.ts +22 -0
- package/dist/board-view.svelte.js +13 -0
- package/dist/build-api.js +36 -6
- package/dist/cdn/GridMenus-0AK9O7qG.js +420 -0
- package/dist/cdn/GridMenus-BjUoCXoD.js +416 -0
- package/dist/cdn/SvDateTimePicker-CYC7gRER.js +2176 -0
- package/dist/cdn/SvDateTimePicker-DtQKd4Ns.js +2198 -0
- package/dist/cdn/SvGridChart-BcLCYvw9.js +1195 -0
- package/dist/cdn/SvGridChart-BhnFdLd0.js +1203 -0
- package/dist/cdn/SvGridChartPanel-Cmx9DHAS.js +561 -0
- package/dist/cdn/SvGridChartPanel-DvjwxLvK.js +577 -0
- package/dist/cdn/SvGridChartView-B40DTJSX.js +54 -0
- package/dist/cdn/SvGridChartView-PwqI4s02.js +53 -0
- package/dist/cdn/SvGridDropdown-DjALc_4f.js +255 -0
- package/dist/cdn/SvGridDropdown-hpnHvOGt.js +254 -0
- package/dist/cdn/chart-T1usDN0o.js +1212 -0
- package/dist/cdn/disclose-version-DoD9AFD_.js +2958 -0
- package/dist/cdn/export-format-JT5pWVT8.js +561 -0
- package/dist/cdn/popover-P-9P2YYn.js +69 -0
- package/dist/cdn/rolldown-runtime-Dy4uBu1J.js +11 -0
- package/dist/cdn/src-BLhdLmoN.js +25707 -0
- package/dist/cdn/src-BwnUVj3T.js +25623 -0
- package/dist/cdn/svgrid.js +9 -32897
- package/dist/cdn/svgrid.svelte-external.js +9 -30052
- package/dist/cell-formatting.d.ts +2 -2
- package/dist/cell-render.js +4 -4
- package/dist/chart-view.svelte.d.ts +25 -0
- package/dist/chart-view.svelte.js +14 -0
- package/dist/createCombobox.svelte.d.ts +5 -0
- package/dist/createCombobox.svelte.js +21 -5
- package/dist/createDropdownList.svelte.d.ts +3 -0
- package/dist/createDropdownList.svelte.js +8 -2
- package/dist/createForm.svelte.d.ts +18 -0
- package/dist/createForm.svelte.js +51 -6
- package/dist/createMenu.svelte.js +27 -0
- package/dist/createPopoverSelect.svelte.d.ts +3 -0
- package/dist/createPopoverSelect.svelte.js +9 -1
- package/dist/createSlider.svelte.d.ts +8 -21
- package/dist/createSlider.svelte.js +34 -0
- package/dist/createTooltip.svelte.d.ts +12 -0
- package/dist/createTooltip.svelte.js +72 -9
- package/dist/editor-contract.d.ts +15 -0
- package/dist/export-provider.d.ts +27 -0
- package/dist/export-provider.js +9 -0
- package/dist/filter-operators.d.ts +6 -0
- package/dist/filter-operators.js +33 -0
- package/dist/form-field.d.ts +45 -2
- package/dist/grid-messages.d.ts +61 -0
- package/dist/grid-messages.js +72 -0
- package/dist/group-display.d.ts +62 -0
- package/dist/group-display.js +78 -0
- package/dist/index.d.ts +36 -4
- package/dist/index.js +43 -2
- package/dist/js-scroller.svelte.d.ts +35 -0
- package/dist/js-scroller.svelte.js +154 -0
- package/dist/list-option.d.ts +46 -0
- package/dist/list-option.js +53 -0
- package/dist/menus.js +25 -11
- package/dist/pivot-view.svelte.d.ts +72 -0
- package/dist/pivot-view.svelte.js +13 -0
- package/dist/positioning.d.ts +111 -0
- package/dist/positioning.js +191 -0
- package/dist/scheduler-model.d.ts +3 -0
- package/dist/scheduler-model.js +22 -5
- package/dist/svgrid-wrapper.types.d.ts +18 -0
- package/dist/toast-store.svelte.d.ts +54 -0
- package/dist/toast-store.svelte.js +65 -15
- package/package.json +9 -1
- package/src/GridFooter.svelte +12 -11
- package/src/SvAnchor.svelte +65 -0
- package/src/SvAspectRatio.svelte +38 -0
- package/src/SvAutoComplete.svelte +52 -7
- package/src/SvBlockquote.svelte +43 -0
- package/src/SvCalendar.svelte +33 -2
- package/src/SvCalendar.test.ts +15 -0
- package/src/SvCheckBox.svelte +6 -2
- package/src/SvCode.svelte +52 -0
- package/src/SvCollapsible.svelte +75 -0
- package/src/SvColorInput.svelte +11 -17
- package/src/SvComboBox.svelte +85 -8
- package/src/SvCountryInput.svelte +2 -2
- package/src/SvDropDownList.svelte +154 -51
- package/src/SvDurationInput.svelte +59 -41
- package/src/SvField.svelte +203 -12
- package/src/SvForm.svelte +195 -21
- package/src/SvForm.test.ts +112 -0
- package/src/SvGrid.controller.svelte.ts +88 -7
- package/src/SvGrid.svelte +383 -181
- package/src/SvGrid.types.ts +125 -1
- package/src/SvGridChartView.svelte +70 -0
- package/src/SvGridDropdown.svelte +9 -2
- package/src/SvGridSelect.svelte +5 -2
- package/src/SvGroup.svelte +41 -0
- package/src/SvHoverCard.svelte +70 -0
- package/src/SvKbd.svelte +51 -0
- package/src/SvList.svelte +55 -0
- package/src/SvListBox.svelte +166 -77
- package/src/SvLoadingOverlay.svelte +53 -0
- package/src/SvMark.svelte +36 -0
- package/src/SvMaskedInput.svelte +50 -35
- package/src/SvMenuList.svelte +32 -3
- package/src/SvMenubar.svelte +200 -0
- package/src/SvMultiSelect.svelte +7 -4
- package/src/SvNumberInput.svelte +79 -42
- package/src/SvOtpInput.svelte +2 -2
- package/src/SvPasswordInput.svelte +51 -31
- package/src/SvPhoneInput.svelte +39 -28
- package/src/SvPopconfirm.svelte +79 -0
- package/src/SvPopover.svelte +87 -29
- package/src/SvRadioGroup.svelte +5 -1
- package/src/SvResult.svelte +73 -0
- package/src/SvSegmented.svelte +114 -0
- package/src/SvSimpleGrid.svelte +41 -0
- package/src/SvSlider.svelte +23 -10
- package/src/SvSpinner.svelte +43 -0
- package/src/SvStack.svelte +37 -0
- package/src/SvSwitchButton.svelte +6 -1
- package/src/SvTagsInput.svelte +9 -5
- package/src/SvText.svelte +76 -0
- package/src/SvTextInput.svelte +75 -43
- package/src/SvTimePicker.svelte +32 -2
- package/src/SvTitle.svelte +57 -0
- package/src/SvToaster.svelte +41 -6
- package/src/SvToaster.test.ts +32 -0
- package/src/SvToggleButton.svelte +4 -1
- package/src/SvTooltip.svelte +68 -34
- package/src/SvTree.svelte +81 -21
- package/src/SvTreeSelect.svelte +5 -2
- package/src/SvVisuallyHidden.svelte +49 -0
- package/src/ai.test.ts +502 -0
- package/src/ai.ts +1391 -0
- package/src/board-view.svelte.ts +33 -0
- package/src/build-api.coverage.test.ts +101 -0
- package/src/build-api.ts +43 -18
- package/src/builtin-editors.grid.test.ts +1 -1
- package/src/cell-formatting.ts +5 -5
- package/src/cell-render.ts +4 -4
- package/src/chart-view.svelte.ts +36 -0
- package/src/createCombobox.svelte.ts +17 -5
- package/src/createDropdownList.svelte.ts +8 -2
- package/src/createForm.svelte.ts +59 -6
- package/src/createMenu.svelte.ts +23 -0
- package/src/createPopoverSelect.svelte.ts +8 -1
- package/src/createSlider.svelte.ts +20 -0
- package/src/createTooltip.svelte.ts +67 -9
- package/src/editor-contract.ts +16 -0
- package/src/export-provider.ts +40 -0
- package/src/filter-operators.ts +37 -0
- package/src/form-field.ts +42 -2
- package/src/grid-messages.test.ts +24 -0
- package/src/grid-messages.ts +137 -0
- package/src/group-display.test.ts +77 -0
- package/src/group-display.ts +117 -0
- package/src/i18n.grid.test.ts +73 -0
- package/src/index.ts +91 -2
- package/src/js-scroller.svelte.ts +173 -0
- package/src/list-option.test.ts +56 -0
- package/src/list-option.ts +81 -0
- package/src/menus.test.ts +32 -0
- package/src/menus.ts +25 -12
- package/src/pivot-view.svelte.ts +90 -0
- package/src/pivot-view.test.ts +38 -0
- package/src/pivot.grid.test.ts +102 -0
- package/src/positioning.test.ts +141 -0
- package/src/positioning.ts +266 -0
- package/src/scheduler-model.test.ts +16 -0
- package/src/scheduler-model.ts +24 -5
- package/src/svgrid-wrapper.types.ts +23 -0
- package/src/svgrid.charting.test.ts +8 -1
- package/src/svgrid.comments-autocomplete.test.ts +7 -2
- package/src/svgrid.context-menu.test.ts +8 -3
- package/src/toast-store.svelte.ts +98 -0
- package/src/toast-store.test.ts +62 -1
- package/src/ui-breadth.test.ts +138 -0
- package/src/ui-buttons.test.ts +39 -0
- package/src/ui-inputs.test.ts +2 -2
- package/src/ui-range.test.ts +9 -0
- package/src/ui-selection.test.ts +36 -0
- package/src/ui-tier1.test.ts +1 -1
- package/src/virtual.test.ts +20 -6
- package/dist/SvGridBoard.svelte +0 -2339
- package/dist/SvGridBoard.svelte.d.ts +0 -31
- package/src/SvGridBoard.svelte +0 -2339
- package/src/board.test.ts +0 -927
package/src/SvSlider.svelte
CHANGED
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
labels = 'none',
|
|
40
40
|
showValue = false,
|
|
41
41
|
disabled = false,
|
|
42
|
+
readonly = false,
|
|
43
|
+
size = 'md',
|
|
42
44
|
orientation = 'horizontal',
|
|
43
45
|
name,
|
|
44
46
|
ariaLabel,
|
|
@@ -60,7 +62,7 @@
|
|
|
60
62
|
// in the headless `createSlider` core; the component keeps the DOM measuring.
|
|
61
63
|
const slider = createSlider({
|
|
62
64
|
value: () => value,
|
|
63
|
-
onChange: (v) => onChange?.(v),
|
|
65
|
+
onChange: (v) => { if (readonly) return; onChange?.(v) },
|
|
64
66
|
min: () => min,
|
|
65
67
|
max: () => max,
|
|
66
68
|
step: () => step,
|
|
@@ -69,6 +71,11 @@
|
|
|
69
71
|
disabled: () => disabled,
|
|
70
72
|
ariaLabel: () => ariaLabel ?? label,
|
|
71
73
|
rtl: () => resolvedDir === 'rtl',
|
|
74
|
+
id: () => uid,
|
|
75
|
+
invalid: () => invalid,
|
|
76
|
+
required: () => required,
|
|
77
|
+
error: () => error,
|
|
78
|
+
hint: () => hint,
|
|
72
79
|
})
|
|
73
80
|
const lo = $derived(slider.lo)
|
|
74
81
|
const hi = $derived(slider.hi)
|
|
@@ -87,7 +94,7 @@
|
|
|
87
94
|
// The rect is measured here (a DOM concern) and passed INTO the core.
|
|
88
95
|
let trackEl: HTMLDivElement | null = null
|
|
89
96
|
function onTrackDown(e: PointerEvent) {
|
|
90
|
-
if (disabled) return
|
|
97
|
+
if (disabled || readonly) return
|
|
91
98
|
slider.pointerDown({ x: e.clientX, y: e.clientY }, trackEl!.getBoundingClientRect())
|
|
92
99
|
trackEl?.setPointerCapture(e.pointerId)
|
|
93
100
|
}
|
|
@@ -102,8 +109,9 @@
|
|
|
102
109
|
|
|
103
110
|
<SvField id={uid} {label} {hint} {error} {required} {dir}>
|
|
104
111
|
<div
|
|
105
|
-
class="sv-slider sv-slider--{orientation}"
|
|
112
|
+
class="sv-slider sv-slider--{orientation} sv-slider--{size}"
|
|
106
113
|
class:is-disabled={disabled}
|
|
114
|
+
class:is-readonly={readonly}
|
|
107
115
|
dir={resolvedDir}
|
|
108
116
|
>
|
|
109
117
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
@@ -126,14 +134,14 @@
|
|
|
126
134
|
{/each}
|
|
127
135
|
|
|
128
136
|
{#if range}
|
|
129
|
-
<div class="sv-slider__thumb" style:--p={`${pct(lo)}%`} {...slider.thumbProps(0)}>
|
|
137
|
+
<div class="sv-slider__thumb" style:--p={`${pct(lo)}%`} {...slider.thumbProps(0)} aria-readonly={readonly ? 'true' : undefined}>
|
|
130
138
|
{#if showValue}<span class="sv-slider__bubble">{fmt(lo)}</span>{/if}
|
|
131
139
|
</div>
|
|
132
|
-
<div class="sv-slider__thumb" style:--p={`${pct(hi)}%`} {...slider.thumbProps(1)}>
|
|
140
|
+
<div class="sv-slider__thumb" style:--p={`${pct(hi)}%`} {...slider.thumbProps(1)} aria-readonly={readonly ? 'true' : undefined}>
|
|
133
141
|
{#if showValue}<span class="sv-slider__bubble">{fmt(hi)}</span>{/if}
|
|
134
142
|
</div>
|
|
135
143
|
{:else}
|
|
136
|
-
<div class="sv-slider__thumb" style:--p={`${pct(hi)}%`} {...slider.thumbProps(0)}>
|
|
144
|
+
<div class="sv-slider__thumb" style:--p={`${pct(hi)}%`} {...slider.thumbProps(0)} aria-readonly={readonly ? 'true' : undefined}>
|
|
137
145
|
{#if showValue}<span class="sv-slider__bubble">{fmt(hi)}</span>{/if}
|
|
138
146
|
</div>
|
|
139
147
|
{/if}
|
|
@@ -152,15 +160,20 @@
|
|
|
152
160
|
</SvField>
|
|
153
161
|
|
|
154
162
|
<style>
|
|
155
|
-
.sv-slider { --_accent: var(--sg-accent, #2563eb); --_track: var(--sg-border, #e2e8f0); padding: 10px; }
|
|
163
|
+
.sv-slider { --_accent: var(--sg-accent, #2563eb); --_track: var(--sg-border, #e2e8f0); --_th: 16px; --_trk: 6px; padding: 10px; }
|
|
164
|
+
.sv-slider--sm { --_th: 13px; --_trk: 4px; }
|
|
165
|
+
.sv-slider--lg { --_th: 20px; --_trk: 8px; }
|
|
156
166
|
.sv-slider--horizontal { width: 240px; }
|
|
167
|
+
.sv-slider--horizontal.sv-slider--sm { width: 180px; }
|
|
168
|
+
.sv-slider--horizontal.sv-slider--lg { width: 300px; }
|
|
157
169
|
.sv-slider--vertical { height: 200px; width: 40px; }
|
|
158
170
|
.sv-slider.is-disabled { opacity: 0.5; pointer-events: none; }
|
|
171
|
+
.sv-slider.is-readonly .sv-slider__track, .sv-slider.is-readonly .sv-slider__thumb { cursor: default; }
|
|
159
172
|
.sv-slider__track {
|
|
160
173
|
position: relative; background: var(--_track); border-radius: 999px; cursor: pointer; touch-action: none;
|
|
161
174
|
}
|
|
162
|
-
.sv-slider--horizontal .sv-slider__track { height:
|
|
163
|
-
.sv-slider--vertical .sv-slider__track { width:
|
|
175
|
+
.sv-slider--horizontal .sv-slider__track { height: var(--_trk); }
|
|
176
|
+
.sv-slider--vertical .sv-slider__track { width: var(--_trk); height: 100%; margin: 0 auto; }
|
|
164
177
|
.sv-slider__scale { position: relative; height: 15px; margin-top: 8px; font-size: 11px; color: var(--sg-muted, #64748b); font-variant-numeric: tabular-nums; }
|
|
165
178
|
.sv-slider__scale--ends { display: flex; justify-content: space-between; }
|
|
166
179
|
.sv-slider__scale-lbl { position: absolute; inset-inline-start: var(--p); transform: translateX(-50%); white-space: nowrap; }
|
|
@@ -168,7 +181,7 @@
|
|
|
168
181
|
.sv-slider--horizontal .sv-slider__fill { inset-inline-start: var(--a); inset-inline-end: calc(100% - var(--b)); top: 0; bottom: 0; }
|
|
169
182
|
.sv-slider--vertical .sv-slider__fill { bottom: var(--a); top: calc(100% - var(--b)); left: 0; right: 0; }
|
|
170
183
|
.sv-slider__thumb {
|
|
171
|
-
position: absolute; width:
|
|
184
|
+
position: absolute; width: var(--_th); height: var(--_th); border-radius: 50%;
|
|
172
185
|
background: #fff; border: 2px solid var(--_accent); box-shadow: 0 1px 3px rgba(0,0,0,0.25);
|
|
173
186
|
cursor: grab; touch-action: none;
|
|
174
187
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvSpinner - a dedicated indeterminate loading spinner (distinct from the
|
|
4
|
+
* determinate SvProgress bars). Sizeable, colorable, and it respects
|
|
5
|
+
* `prefers-reduced-motion`. Give it a `label` for screen readers.
|
|
6
|
+
*
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <SvSpinner size="lg" label="Loading results" />
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
type Props = {
|
|
12
|
+
/** Diameter in px, or a named size. Default `md` (20px). */
|
|
13
|
+
size?: 'sm' | 'md' | 'lg' | number
|
|
14
|
+
/** Stroke color (defaults to the accent token). */
|
|
15
|
+
color?: string
|
|
16
|
+
/** Accessible label; when omitted the spinner is `aria-hidden`. */
|
|
17
|
+
label?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let { size = 'md', color, label }: Props = $props()
|
|
21
|
+
const px = $derived(typeof size === 'number' ? size : size === 'sm' ? 15 : size === 'lg' ? 28 : 20)
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<span
|
|
25
|
+
class="sv-spinner"
|
|
26
|
+
role={label ? 'status' : undefined}
|
|
27
|
+
aria-label={label}
|
|
28
|
+
aria-hidden={label ? undefined : 'true'}
|
|
29
|
+
style:width={`${px}px`}
|
|
30
|
+
style:height={`${px}px`}
|
|
31
|
+
style:color={color}
|
|
32
|
+
></span>
|
|
33
|
+
|
|
34
|
+
<style>
|
|
35
|
+
.sv-spinner {
|
|
36
|
+
display: inline-block; box-sizing: border-box; flex: none; border-radius: 50%;
|
|
37
|
+
border: 2px solid color-mix(in srgb, currentcolor 25%, transparent);
|
|
38
|
+
border-top-color: currentcolor; color: var(--sg-accent, #2563eb);
|
|
39
|
+
animation: sv-spinner-rot 0.6s linear infinite;
|
|
40
|
+
}
|
|
41
|
+
@keyframes sv-spinner-rot { to { transform: rotate(360deg); } }
|
|
42
|
+
@media (prefers-reduced-motion: reduce) { .sv-spinner { animation-duration: 1.4s; } }
|
|
43
|
+
</style>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvStack - a vertical flex layout primitive: stacks children in a column with a
|
|
4
|
+
* consistent `gap` and optional cross-axis `align`. The building block for form
|
|
5
|
+
* rows, card bodies, and sidebars. (For a horizontal row use SvGroup.)
|
|
6
|
+
*
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <SvStack gap={12}><A /><B /><C /></SvStack>
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
import type { Snippet } from 'svelte'
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
/** Gap between children (px, or any CSS length). Default 12. */
|
|
15
|
+
gap?: number | string
|
|
16
|
+
/** Cross-axis alignment. */
|
|
17
|
+
align?: 'start' | 'center' | 'end' | 'stretch'
|
|
18
|
+
/** Main-axis distribution. */
|
|
19
|
+
justify?: 'start' | 'center' | 'end' | 'between' | 'around'
|
|
20
|
+
/** Wrapping element tag. Default `div`. */
|
|
21
|
+
as?: string
|
|
22
|
+
children?: Snippet
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let { gap = 12, align = 'stretch', justify = 'start', as = 'div', children }: Props = $props()
|
|
26
|
+
const g = $derived(typeof gap === 'number' ? `${gap}px` : gap)
|
|
27
|
+
const alignMap = { start: 'flex-start', center: 'center', end: 'flex-end', stretch: 'stretch' } as const
|
|
28
|
+
const justifyMap = { start: 'flex-start', center: 'center', end: 'flex-end', between: 'space-between', around: 'space-around' } as const
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<svelte:element this={as} class="sv-stack" style:gap={g} style:align-items={alignMap[align]} style:justify-content={justifyMap[justify]}>
|
|
32
|
+
{@render children?.()}
|
|
33
|
+
</svelte:element>
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
.sv-stack { display: flex; flex-direction: column; min-width: 0; }
|
|
37
|
+
</style>
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
checked = false,
|
|
21
21
|
onChange,
|
|
22
22
|
disabled = false,
|
|
23
|
+
readonly = false,
|
|
23
24
|
size = 'md',
|
|
24
25
|
name,
|
|
25
26
|
ariaLabel,
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
// The styled switch is just a renderer over the headless core.
|
|
41
42
|
const sw = createSwitch({
|
|
42
43
|
checked: () => checked,
|
|
43
|
-
onChange: (v) => onChange?.(v),
|
|
44
|
+
onChange: (v) => { if (readonly) return; onChange?.(v) },
|
|
44
45
|
disabled: () => disabled,
|
|
45
46
|
ariaLabel: () => ariaLabel,
|
|
46
47
|
id: () => uid,
|
|
@@ -53,9 +54,12 @@
|
|
|
53
54
|
|
|
54
55
|
<SvField id={uid} {label} {hint} {error} {required} {dir}>
|
|
55
56
|
<button
|
|
57
|
+
role="switch"
|
|
56
58
|
class="sv-switch sv-switch--{size}"
|
|
57
59
|
class:is-on={checked}
|
|
60
|
+
class:is-readonly={readonly}
|
|
58
61
|
{...sw.switchProps()}
|
|
62
|
+
aria-readonly={readonly ? 'true' : undefined}
|
|
59
63
|
>
|
|
60
64
|
<span class="sv-switch__track">
|
|
61
65
|
{#if onLabel || offLabel}
|
|
@@ -77,6 +81,7 @@
|
|
|
77
81
|
.sv-switch--sm { --_h: 18px; --_w: 32px; }
|
|
78
82
|
.sv-switch--lg { --_h: 28px; --_w: 52px; }
|
|
79
83
|
.sv-switch[disabled] { opacity: 0.5; cursor: not-allowed; }
|
|
84
|
+
.sv-switch.is-readonly { cursor: default; }
|
|
80
85
|
.sv-switch__track {
|
|
81
86
|
position: relative; width: var(--_w); height: var(--_h); border-radius: 999px;
|
|
82
87
|
background: var(--sg-border, #cbd5e1); transition: background 0.16s;
|
package/src/SvTagsInput.svelte
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
disabled = false,
|
|
34
34
|
unique = true,
|
|
35
35
|
max = Infinity,
|
|
36
|
+
size = 'md',
|
|
36
37
|
name,
|
|
37
38
|
ariaLabel,
|
|
38
39
|
invalid = false,
|
|
@@ -68,7 +69,7 @@
|
|
|
68
69
|
</script>
|
|
69
70
|
|
|
70
71
|
<SvField id={uid} {label} {hint} {error} {required} {dir}>
|
|
71
|
-
<div class="sv-tags" class:is-disabled={disabled} class:is-invalid={invalid} {...ti.rootProps()}>
|
|
72
|
+
<div class="sv-tags sv-tags--{size}" class:is-disabled={disabled} class:is-invalid={invalid} {...ti.rootProps()}>
|
|
72
73
|
{#each value as tag, i (tag + i)}
|
|
73
74
|
<span class="sv-tags__chip" {...ti.tagProps(i)}>
|
|
74
75
|
<span class="sv-tags__label">{tag}</span>
|
|
@@ -89,20 +90,23 @@
|
|
|
89
90
|
<style>
|
|
90
91
|
.sv-tags {
|
|
91
92
|
--_accent: var(--sg-accent, #2563eb);
|
|
92
|
-
|
|
93
|
+
--_minh: 34px; --_chip-h: 22px; --_fs: 13px;
|
|
94
|
+
display: flex; flex-wrap: wrap; gap: 5px; align-items: center; width: 280px; min-height: var(--_minh); padding: 4px 6px;
|
|
93
95
|
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
94
96
|
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
95
97
|
}
|
|
98
|
+
.sv-tags--sm { --_minh: 28px; --_chip-h: 19px; --_fs: 12px; }
|
|
99
|
+
.sv-tags--lg { --_minh: 40px; --_chip-h: 26px; --_fs: 15px; }
|
|
96
100
|
.sv-tags:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
97
101
|
.sv-tags.is-invalid { border-color: var(--sg-danger, #dc2626); }
|
|
98
102
|
.sv-tags.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
|
|
99
103
|
.sv-tags.is-disabled { opacity: 0.6; }
|
|
100
104
|
.sv-tags__chip {
|
|
101
|
-
display: inline-flex; align-items: center; gap: 4px; height:
|
|
105
|
+
display: inline-flex; align-items: center; gap: 4px; height: var(--_chip-h); padding-block: 0; padding-inline: 8px 4px;
|
|
102
106
|
background: color-mix(in srgb, var(--_accent) 14%, transparent); color: var(--_accent);
|
|
103
|
-
border-radius: 5px; font-size:
|
|
107
|
+
border-radius: 5px; font-size: calc(var(--_fs) - 1px); font-weight: 600;
|
|
104
108
|
}
|
|
105
109
|
.sv-tags__x { background: none; border: 0; color: inherit; cursor: pointer; font-size: 15px; line-height: 1; padding: 0 2px; opacity: 0.7; }
|
|
106
110
|
.sv-tags__x:hover { opacity: 1; }
|
|
107
|
-
.sv-tags__input { flex: 1; min-width: 80px; border: 0; background: none; outline: none; color: inherit; font: inherit; font-size:
|
|
111
|
+
.sv-tags__input { flex: 1; min-width: 80px; border: 0; background: none; outline: none; color: inherit; font: inherit; font-size: var(--_fs); height: 24px; }
|
|
108
112
|
</style>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvText - the workhorse text primitive: a paragraph or inline span with
|
|
4
|
+
* consistent size / weight / colour tokens, optional truncation and line
|
|
5
|
+
* clamping. Themes from the shared `--sg-*` tokens.
|
|
6
|
+
*
|
|
7
|
+
* <SvText size="sm" tone="muted">Last synced 2 minutes ago</SvText>
|
|
8
|
+
* <SvText clamp={2}>A long description that wraps then ellipsises...</SvText>
|
|
9
|
+
*/
|
|
10
|
+
import type { Snippet } from 'svelte'
|
|
11
|
+
|
|
12
|
+
type Props = {
|
|
13
|
+
/** Font size step. */
|
|
14
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
15
|
+
/** Font weight. */
|
|
16
|
+
weight?: 'normal' | 'medium' | 'semibold' | 'bold'
|
|
17
|
+
/** Semantic colour. */
|
|
18
|
+
tone?: 'default' | 'muted' | 'accent' | 'success' | 'warning' | 'error'
|
|
19
|
+
/** Text alignment. */
|
|
20
|
+
align?: 'start' | 'center' | 'end'
|
|
21
|
+
/** Truncate to a single line with an ellipsis. */
|
|
22
|
+
truncate?: boolean
|
|
23
|
+
/** Clamp to N lines with an ellipsis (overrides truncate). */
|
|
24
|
+
clamp?: number
|
|
25
|
+
/** Render inline (`span`) instead of a block (`p`). */
|
|
26
|
+
inline?: boolean
|
|
27
|
+
/** Override the element/tag. */
|
|
28
|
+
as?: string
|
|
29
|
+
children?: Snippet
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
size = 'md', weight = 'normal', tone = 'default', align,
|
|
34
|
+
truncate = false, clamp, inline = false, as, children,
|
|
35
|
+
}: Props = $props()
|
|
36
|
+
|
|
37
|
+
const sizeMap = { xs: '11px', sm: '13px', md: '14px', lg: '16px', xl: '20px' } as const
|
|
38
|
+
const weightMap = { normal: 400, medium: 500, semibold: 600, bold: 700 } as const
|
|
39
|
+
const toneMap = {
|
|
40
|
+
default: 'var(--sg-fg, #0f172a)',
|
|
41
|
+
muted: 'var(--sg-muted, #64748b)',
|
|
42
|
+
accent: 'var(--sg-accent, #2563eb)',
|
|
43
|
+
success: 'var(--sg-success, #16a34a)',
|
|
44
|
+
warning: 'var(--sg-warning, #d97706)',
|
|
45
|
+
error: 'var(--sg-danger, #dc2626)',
|
|
46
|
+
} as const
|
|
47
|
+
const alignMap = { start: 'left', center: 'center', end: 'right' } as const
|
|
48
|
+
|
|
49
|
+
const tag = $derived(as ?? (inline ? 'span' : 'p'))
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<svelte:element
|
|
53
|
+
this={tag}
|
|
54
|
+
class="sv-text"
|
|
55
|
+
class:is-truncate={truncate && !clamp}
|
|
56
|
+
class:is-clamp={clamp != null}
|
|
57
|
+
style:font-size={sizeMap[size]}
|
|
58
|
+
style:font-weight={weightMap[weight]}
|
|
59
|
+
style:color={toneMap[tone]}
|
|
60
|
+
style:text-align={align ? alignMap[align] : undefined}
|
|
61
|
+
style:--sv-text-clamp={clamp}
|
|
62
|
+
>
|
|
63
|
+
{@render children?.()}
|
|
64
|
+
</svelte:element>
|
|
65
|
+
|
|
66
|
+
<style>
|
|
67
|
+
.sv-text { margin: 0; font-family: var(--sg-font, inherit); line-height: 1.5; }
|
|
68
|
+
.sv-text.is-truncate { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
69
|
+
.sv-text.is-clamp {
|
|
70
|
+
display: -webkit-box;
|
|
71
|
+
-webkit-box-orient: vertical;
|
|
72
|
+
-webkit-line-clamp: var(--sv-text-clamp, 2);
|
|
73
|
+
line-clamp: var(--sv-text-clamp, 2);
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
}
|
|
76
|
+
</style>
|
package/src/SvTextInput.svelte
CHANGED
|
@@ -4,9 +4,14 @@
|
|
|
4
4
|
* (label / hint / error / RTL / a11y via SvField). Handles `text`, `email`,
|
|
5
5
|
* `url`, `tel` and `search`. As a grid cell editor it honours the interaction
|
|
6
6
|
* contract: Enter commits, Escape cancels. Parity: Smart `smart-input`.
|
|
7
|
+
*
|
|
8
|
+
* The control box, size, invalid state, clear button and leading/trailing
|
|
9
|
+
* adornments are all owned by SvField's `frame` mode, so they behave identically
|
|
10
|
+
* across the text-input family.
|
|
7
11
|
*/
|
|
12
|
+
import type { Snippet } from 'svelte'
|
|
8
13
|
import SvField from './SvField.svelte'
|
|
9
|
-
import { editorAria, nextEditorId, type SvEditorProps } from './editor-contract'
|
|
14
|
+
import { editorAria, nextEditorId, type EditorAction, type SvEditorProps } from './editor-contract'
|
|
10
15
|
|
|
11
16
|
type Props = SvEditorProps & {
|
|
12
17
|
value?: string
|
|
@@ -23,6 +28,22 @@
|
|
|
23
28
|
autocomplete?: AutoFill
|
|
24
29
|
/** Autofocus + select on mount (used when mounted as a cell editor). */
|
|
25
30
|
autofocus?: boolean
|
|
31
|
+
/** Select the whole value whenever the field is focused. */
|
|
32
|
+
selectOnFocus?: boolean
|
|
33
|
+
/** Leading adornment (icon/button) inside the field. */
|
|
34
|
+
leading?: Snippet
|
|
35
|
+
/** Trailing adornment (icon/button) inside the field. */
|
|
36
|
+
trailing?: Snippet
|
|
37
|
+
/** Plain-text affix at the start (e.g. a `$` or protocol). */
|
|
38
|
+
prefix?: string
|
|
39
|
+
/** Plain-text affix at the end (e.g. a unit). */
|
|
40
|
+
suffix?: string
|
|
41
|
+
/** `floating` animates the label up on focus/value. Default `static`. */
|
|
42
|
+
labelMode?: 'static' | 'floating'
|
|
43
|
+
/** Compact in-field action buttons (lookup/generate/copy...). */
|
|
44
|
+
actions?: EditorAction[]
|
|
45
|
+
/** Stretch to the container width. */
|
|
46
|
+
block?: boolean
|
|
26
47
|
}
|
|
27
48
|
|
|
28
49
|
let {
|
|
@@ -46,8 +67,17 @@
|
|
|
46
67
|
hint,
|
|
47
68
|
dir,
|
|
48
69
|
id,
|
|
70
|
+
loading = false,
|
|
49
71
|
autocomplete,
|
|
50
72
|
autofocus = false,
|
|
73
|
+
selectOnFocus = false,
|
|
74
|
+
leading,
|
|
75
|
+
trailing,
|
|
76
|
+
prefix,
|
|
77
|
+
suffix,
|
|
78
|
+
labelMode = 'static',
|
|
79
|
+
actions,
|
|
80
|
+
block = false,
|
|
51
81
|
}: Props = $props()
|
|
52
82
|
|
|
53
83
|
const autoId = nextEditorId('sv-text')
|
|
@@ -68,48 +98,50 @@
|
|
|
68
98
|
function mountFocus(node: HTMLInputElement) {
|
|
69
99
|
if (autofocus) { node.focus(); node.select() }
|
|
70
100
|
}
|
|
101
|
+
function onFocus(e: FocusEvent) {
|
|
102
|
+
if (selectOnFocus) (e.currentTarget as HTMLInputElement).select()
|
|
103
|
+
}
|
|
71
104
|
</script>
|
|
72
105
|
|
|
73
|
-
<SvField
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
<SvField
|
|
107
|
+
frame
|
|
108
|
+
id={uid}
|
|
109
|
+
{label}
|
|
110
|
+
{hint}
|
|
111
|
+
{error}
|
|
112
|
+
{required}
|
|
113
|
+
{dir}
|
|
114
|
+
{size}
|
|
115
|
+
{invalid}
|
|
116
|
+
{disabled}
|
|
117
|
+
{readonly}
|
|
118
|
+
{loading}
|
|
119
|
+
{block}
|
|
120
|
+
{leading}
|
|
121
|
+
{trailing}
|
|
122
|
+
{prefix}
|
|
123
|
+
{suffix}
|
|
124
|
+
{labelMode}
|
|
125
|
+
filled={!!value}
|
|
126
|
+
{actions}
|
|
127
|
+
clearable={clearable}
|
|
128
|
+
showClear={!!value}
|
|
129
|
+
onclear={() => set('')}
|
|
130
|
+
>
|
|
131
|
+
<input
|
|
132
|
+
use:mountFocus
|
|
133
|
+
class="sv-text__input"
|
|
134
|
+
{type}
|
|
135
|
+
{value}
|
|
136
|
+
placeholder={labelMode === 'floating' ? undefined : placeholder}
|
|
137
|
+
{disabled}
|
|
138
|
+
{readonly}
|
|
139
|
+
{maxlength}
|
|
140
|
+
{autocomplete}
|
|
141
|
+
oninput={onInput}
|
|
142
|
+
onkeydown={onKeydown}
|
|
143
|
+
onfocus={onFocus}
|
|
144
|
+
{...editorAria({ id: uid, invalid, required, error, hint, ariaLabel })}
|
|
145
|
+
/>
|
|
146
|
+
{#if name}<input type="hidden" {name} value={value} />{/if}
|
|
96
147
|
</SvField>
|
|
97
|
-
|
|
98
|
-
<style>
|
|
99
|
-
.sv-text { --_accent: var(--sg-accent, #2563eb); display: inline-flex; flex-direction: column; width: 220px; }
|
|
100
|
-
.sv-text.is-disabled { opacity: 0.6; }
|
|
101
|
-
.sv-text__field {
|
|
102
|
-
display: flex; align-items: center;
|
|
103
|
-
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
104
|
-
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
105
|
-
}
|
|
106
|
-
.sv-text__field:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
107
|
-
.sv-text__field.is-invalid { border-color: var(--sg-danger, #dc2626); }
|
|
108
|
-
.sv-text__field.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
|
|
109
|
-
.sv-text__input { flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit; font: inherit; padding: 0 10px; }
|
|
110
|
-
.sv-text--sm .sv-text__field { height: 28px; font-size: 12px; }
|
|
111
|
-
.sv-text--md .sv-text__field { height: 34px; font-size: 13px; }
|
|
112
|
-
.sv-text--lg .sv-text__field { height: 40px; font-size: 15px; }
|
|
113
|
-
.sv-text__clear { display: grid; place-items: center; width: 28px; align-self: stretch; background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; font-size: 17px; line-height: 1; }
|
|
114
|
-
.sv-text__clear:hover { color: var(--sg-fg, #0f172a); }
|
|
115
|
-
</style>
|
package/src/SvTimePicker.svelte
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* and ARIA; this component keeps the DOM-bound pointer capture + hit-testing.
|
|
12
12
|
* All visuals come from `--sg-*` tokens so every theme applies for free.
|
|
13
13
|
*/
|
|
14
|
-
import
|
|
14
|
+
import SvField from './SvField.svelte'
|
|
15
|
+
import { nextEditorId, resolveMessages, type EditorDir, type EditorSize } from './editor-contract'
|
|
15
16
|
import { createTimePicker, type TimeValue, type TimeFormat, type TimeSelection } from './createTimePicker.svelte'
|
|
16
17
|
|
|
17
18
|
/** User-facing strings (localizable via `messages`). */
|
|
@@ -38,6 +39,16 @@
|
|
|
38
39
|
dir?: EditorDir
|
|
39
40
|
/** Override the built-in strings (group label + AM/PM/Now). */
|
|
40
41
|
messages?: Partial<TimeMessages>
|
|
42
|
+
// Optional field chrome: when any of label/hint/error is set, the dial is
|
|
43
|
+
// wrapped in <SvField> so it gains a label/hint/error line; otherwise it
|
|
44
|
+
// renders bare (as embedded inside SvDateTimePicker's popover).
|
|
45
|
+
label?: string
|
|
46
|
+
hint?: string
|
|
47
|
+
error?: string
|
|
48
|
+
required?: boolean
|
|
49
|
+
invalid?: boolean
|
|
50
|
+
size?: EditorSize
|
|
51
|
+
id?: string
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
let {
|
|
@@ -53,8 +64,18 @@
|
|
|
53
64
|
selection = 'hour',
|
|
54
65
|
dir,
|
|
55
66
|
messages,
|
|
67
|
+
label,
|
|
68
|
+
hint,
|
|
69
|
+
error,
|
|
70
|
+
required = false,
|
|
71
|
+
invalid = false,
|
|
72
|
+
id,
|
|
56
73
|
}: Props = $props()
|
|
57
74
|
|
|
75
|
+
const autoId = nextEditorId('sv-tp')
|
|
76
|
+
const uid = $derived(id ?? autoId)
|
|
77
|
+
const hasChrome = $derived(!!(label || hint || error))
|
|
78
|
+
|
|
58
79
|
const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
|
|
59
80
|
const resolvedDir = $derived(dir === 'ltr' || dir === 'rtl' ? dir : undefined)
|
|
60
81
|
|
|
@@ -107,7 +128,8 @@
|
|
|
107
128
|
}
|
|
108
129
|
</script>
|
|
109
130
|
|
|
110
|
-
|
|
131
|
+
{#snippet dialBody()}
|
|
132
|
+
<div class="sv-tp" class:sv-tp--disabled={disabled} class:sv-tp--invalid={invalid} id={uid} role="group" aria-label={label ?? M.label} dir={resolvedDir}>
|
|
111
133
|
<div class="sv-tp__head">
|
|
112
134
|
<button class="sv-tp__seg" class:is-active={tp.selection === 'hour'} {...tp.segProps('hour')}>
|
|
113
135
|
{String(tp.displayHour).padStart(2, '0')}
|
|
@@ -159,6 +181,13 @@
|
|
|
159
181
|
|
|
160
182
|
{#if name}<input type="hidden" {name} value={`${String(tp.hours).padStart(2, '0')}:${tp.mm}`} />{/if}
|
|
161
183
|
</div>
|
|
184
|
+
{/snippet}
|
|
185
|
+
|
|
186
|
+
{#if hasChrome}
|
|
187
|
+
<SvField id={uid} {label} {hint} {error} {required} {dir}>{@render dialBody()}</SvField>
|
|
188
|
+
{:else}
|
|
189
|
+
{@render dialBody()}
|
|
190
|
+
{/if}
|
|
162
191
|
|
|
163
192
|
<style>
|
|
164
193
|
.sv-tp {
|
|
@@ -175,6 +204,7 @@
|
|
|
175
204
|
border: 1px solid var(--_border); border-radius: calc(var(--_radius) + 4px);
|
|
176
205
|
user-select: none;
|
|
177
206
|
}
|
|
207
|
+
.sv-tp--invalid { border-color: var(--sg-danger, #dc2626); }
|
|
178
208
|
.sv-tp--disabled { opacity: 0.55; pointer-events: none; }
|
|
179
209
|
|
|
180
210
|
.sv-tp__head { display: flex; align-items: center; gap: 2px; font-variant-numeric: tabular-nums; }
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvTitle - a heading primitive. `order` sets the semantic level (h1-h6) and,
|
|
4
|
+
* by default, the visual size; pass `size` to decouple the two (e.g. an h2 that
|
|
5
|
+
* looks like an h4). Themes from the shared `--sg-*` tokens.
|
|
6
|
+
*
|
|
7
|
+
* <SvTitle order={1}>Dashboard</SvTitle>
|
|
8
|
+
* <SvTitle order={3} size={5}>Small but semantic h3</SvTitle>
|
|
9
|
+
*/
|
|
10
|
+
import type { Snippet } from 'svelte'
|
|
11
|
+
|
|
12
|
+
type Level = 1 | 2 | 3 | 4 | 5 | 6
|
|
13
|
+
|
|
14
|
+
type Props = {
|
|
15
|
+
/** Semantic heading level (h1-h6). */
|
|
16
|
+
order?: Level
|
|
17
|
+
/** Visual size step, defaults to `order`. */
|
|
18
|
+
size?: Level
|
|
19
|
+
/** Text alignment. */
|
|
20
|
+
align?: 'start' | 'center' | 'end'
|
|
21
|
+
/** Truncate to a single line with an ellipsis. */
|
|
22
|
+
truncate?: boolean
|
|
23
|
+
children?: Snippet
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let { order = 2, size, align, truncate = false, children }: Props = $props()
|
|
27
|
+
|
|
28
|
+
// px + weight per visual step - a compact, balanced type scale.
|
|
29
|
+
const sizeMap = { 1: '30px', 2: '24px', 3: '20px', 4: '17px', 5: '15px', 6: '13px' } as const
|
|
30
|
+
const weightMap = { 1: 700, 2: 700, 3: 600, 4: 600, 5: 600, 6: 600 } as const
|
|
31
|
+
const alignMap = { start: 'left', center: 'center', end: 'right' } as const
|
|
32
|
+
|
|
33
|
+
const step = $derived(size ?? order)
|
|
34
|
+
const tag = $derived(`h${order}`)
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<svelte:element
|
|
38
|
+
this={tag}
|
|
39
|
+
class="sv-title"
|
|
40
|
+
class:is-truncate={truncate}
|
|
41
|
+
style:font-size={sizeMap[step]}
|
|
42
|
+
style:font-weight={weightMap[step]}
|
|
43
|
+
style:text-align={align ? alignMap[align] : undefined}
|
|
44
|
+
>
|
|
45
|
+
{@render children?.()}
|
|
46
|
+
</svelte:element>
|
|
47
|
+
|
|
48
|
+
<style>
|
|
49
|
+
.sv-title {
|
|
50
|
+
margin: 0;
|
|
51
|
+
font-family: var(--sg-font, inherit);
|
|
52
|
+
color: var(--sg-fg, #0f172a);
|
|
53
|
+
line-height: 1.25;
|
|
54
|
+
letter-spacing: -0.01em;
|
|
55
|
+
}
|
|
56
|
+
.sv-title.is-truncate { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
57
|
+
</style>
|