@svgrid/grid 2.2.16 → 2.2.18
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
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
*
|
|
6
6
|
* The styled renderer over the headless `createPasswordInput` core: the reveal
|
|
7
7
|
* toggle + strength heuristic come from the core, spread here via prop-getters.
|
|
8
|
+
* The box, size, invalid state and focus ring are owned by SvField's `frame`
|
|
9
|
+
* chrome; the reveal eye lives in the trailing slot and the strength meter sits
|
|
10
|
+
* below the field.
|
|
8
11
|
*/
|
|
12
|
+
import type { Snippet } from 'svelte'
|
|
9
13
|
import SvField from './SvField.svelte'
|
|
10
14
|
import { nextEditorId, resolveMessages, type SvEditorProps } from './editor-contract'
|
|
11
15
|
import { createPasswordInput } from './createPasswordInput.svelte'
|
|
@@ -28,6 +32,12 @@
|
|
|
28
32
|
autocomplete?: string
|
|
29
33
|
/** Override the built-in strings (reveal toggle + strength labels). */
|
|
30
34
|
messages?: Partial<PasswordMessages>
|
|
35
|
+
/** Leading adornment (icon) inside the field. */
|
|
36
|
+
leading?: Snippet
|
|
37
|
+
/** Stretch to the container width. */
|
|
38
|
+
block?: boolean
|
|
39
|
+
/** Control width in px (ignored when `block`). Default 220. */
|
|
40
|
+
width?: number
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
let {
|
|
@@ -48,8 +58,12 @@
|
|
|
48
58
|
hint,
|
|
49
59
|
dir,
|
|
50
60
|
id,
|
|
61
|
+
loading = false,
|
|
51
62
|
autocomplete = 'current-password',
|
|
52
63
|
messages,
|
|
64
|
+
leading,
|
|
65
|
+
block = false,
|
|
66
|
+
width = 220,
|
|
53
67
|
}: Props = $props()
|
|
54
68
|
|
|
55
69
|
const autoId = nextEditorId('sv-pass')
|
|
@@ -76,20 +90,40 @@
|
|
|
76
90
|
})
|
|
77
91
|
</script>
|
|
78
92
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
{#snippet eye()}
|
|
94
|
+
{#if revealable}
|
|
95
|
+
<button class="sv-pw__eye" {...pw.toggleProps()}>
|
|
96
|
+
{#if pw.revealed}
|
|
97
|
+
<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>
|
|
98
|
+
{:else}
|
|
99
|
+
<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>
|
|
100
|
+
{/if}
|
|
101
|
+
</button>
|
|
102
|
+
{/if}
|
|
103
|
+
{/snippet}
|
|
104
|
+
|
|
105
|
+
<div class="sv-pw" class:sv-pw--block={block}>
|
|
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
|
+
{width}
|
|
121
|
+
{leading}
|
|
122
|
+
trailing={revealable ? eye : undefined}
|
|
123
|
+
>
|
|
82
124
|
<input class="sv-pw__input" {...pw.inputProps()} />
|
|
83
|
-
{#if
|
|
84
|
-
|
|
85
|
-
{#if pw.revealed}
|
|
86
|
-
<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>
|
|
87
|
-
{:else}
|
|
88
|
-
<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>
|
|
89
|
-
{/if}
|
|
90
|
-
</button>
|
|
91
|
-
{/if}
|
|
92
|
-
</div>
|
|
125
|
+
{#if name}<input type="hidden" {name} value={value} />{/if}
|
|
126
|
+
</SvField>
|
|
93
127
|
{#if showStrength}
|
|
94
128
|
<div class="sv-pw__meter" aria-hidden="true">
|
|
95
129
|
{#each Array(4) as _, i (i)}
|
|
@@ -98,27 +132,13 @@
|
|
|
98
132
|
</div>
|
|
99
133
|
<span class="sv-pw__strength is-lvl{pw.strength}">{pw.strengthLabel}</span>
|
|
100
134
|
{/if}
|
|
101
|
-
{#if name}<input type="hidden" {name} value={value} />{/if}
|
|
102
135
|
</div>
|
|
103
|
-
</SvField>
|
|
104
136
|
|
|
105
137
|
<style>
|
|
106
|
-
.sv-pw {
|
|
107
|
-
.sv-pw
|
|
108
|
-
.sv-
|
|
109
|
-
|
|
110
|
-
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
111
|
-
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
112
|
-
}
|
|
113
|
-
.sv-pw__field:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
114
|
-
.sv-pw__field.is-invalid { border-color: var(--sg-danger, #dc2626); }
|
|
115
|
-
.sv-pw__field.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
|
|
116
|
-
.sv-pw__input { flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit; font: inherit; padding: 0 10px; }
|
|
117
|
-
.sv-pw--sm .sv-pw__field { height: 28px; font-size: 12px; }
|
|
118
|
-
.sv-pw--md .sv-pw__field { height: 34px; font-size: 13px; }
|
|
119
|
-
.sv-pw--lg .sv-pw__field { height: 40px; font-size: 15px; }
|
|
120
|
-
.sv-pw__eye { display: grid; place-items: center; width: 32px; align-self: stretch; background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; }
|
|
121
|
-
.sv-pw__eye:hover { color: var(--_accent); }
|
|
138
|
+
.sv-pw { display: inline-flex; flex-direction: column; gap: 5px; }
|
|
139
|
+
.sv-pw--block { display: flex; width: 100%; }
|
|
140
|
+
.sv-pw__eye { display: grid; place-items: center; width: 26px; align-self: center; background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; padding: 0; }
|
|
141
|
+
.sv-pw__eye:hover { color: var(--sg-accent, #2563eb); }
|
|
122
142
|
.sv-pw__meter { display: flex; gap: 4px; }
|
|
123
143
|
.sv-pw__bar { flex: 1; height: 4px; border-radius: 2px; background: var(--sg-border, #e2e8f0); transition: background 0.15s; }
|
|
124
144
|
.sv-pw__bar.on.is-lvl1 { background: var(--sg-danger, #dc2626); }
|
|
@@ -1,3 +1,14 @@
|
|
|
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
|
+
* The styled renderer over the headless `createPasswordInput` core: the reveal
|
|
6
|
+
* toggle + strength heuristic come from the core, spread here via prop-getters.
|
|
7
|
+
* The box, size, invalid state and focus ring are owned by SvField's `frame`
|
|
8
|
+
* chrome; the reveal eye lives in the trailing slot and the strength meter sits
|
|
9
|
+
* below the field.
|
|
10
|
+
*/
|
|
11
|
+
import type { Snippet } from 'svelte';
|
|
1
12
|
import { type SvEditorProps } from './editor-contract';
|
|
2
13
|
/** User-facing strings for the password input (localizable via `messages`). */
|
|
3
14
|
type PasswordMessages = {
|
|
@@ -19,6 +30,12 @@ type Props = SvEditorProps & {
|
|
|
19
30
|
autocomplete?: string;
|
|
20
31
|
/** Override the built-in strings (reveal toggle + strength labels). */
|
|
21
32
|
messages?: Partial<PasswordMessages>;
|
|
33
|
+
/** Leading adornment (icon) inside the field. */
|
|
34
|
+
leading?: Snippet;
|
|
35
|
+
/** Stretch to the container width. */
|
|
36
|
+
block?: boolean;
|
|
37
|
+
/** Control width in px (ignored when `block`). Default 220. */
|
|
38
|
+
width?: number;
|
|
22
39
|
};
|
|
23
40
|
declare const SvPasswordInput: import("svelte").Component<Props, {}, "value">;
|
|
24
41
|
type SvPasswordInput = ReturnType<typeof SvPasswordInput>;
|
package/dist/SvPhoneInput.svelte
CHANGED
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
placeholder?: string
|
|
25
25
|
/** Override the built-in strings (e.g. the country selector aria-label). */
|
|
26
26
|
messages?: Partial<PhoneMessages>
|
|
27
|
+
/** Stretch to the container width. */
|
|
28
|
+
block?: boolean
|
|
29
|
+
/** Control width in px (ignored when `block`). Default 250. */
|
|
30
|
+
width?: number
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
let {
|
|
@@ -43,7 +47,10 @@
|
|
|
43
47
|
hint,
|
|
44
48
|
dir,
|
|
45
49
|
id,
|
|
50
|
+
loading = false,
|
|
46
51
|
messages,
|
|
52
|
+
block = false,
|
|
53
|
+
width = 250,
|
|
47
54
|
}: Props = $props()
|
|
48
55
|
|
|
49
56
|
const autoId = nextEditorId('sv-phone')
|
|
@@ -68,53 +75,57 @@
|
|
|
68
75
|
})
|
|
69
76
|
</script>
|
|
70
77
|
|
|
71
|
-
|
|
72
|
-
<
|
|
73
|
-
<div class="sv-phone__country">
|
|
78
|
+
{#snippet countryChip()}
|
|
79
|
+
<span class="sv-phone__country">
|
|
74
80
|
<span class="sv-phone__flag" aria-hidden="true">{ph.flag}</span>
|
|
75
81
|
<span class="sv-phone__dial">{ph.dial}</span>
|
|
82
|
+
<svg class="sv-phone__chev" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
|
76
83
|
<select class="sv-phone__select" {...ph.selectProps()}>
|
|
77
84
|
{#each COUNTRIES as c (c.code)}
|
|
78
85
|
<option value={c.code}>{flagEmoji(c.code)} {c.name} ({c.dial})</option>
|
|
79
86
|
{/each}
|
|
80
87
|
</select>
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
</span>
|
|
89
|
+
{/snippet}
|
|
90
|
+
|
|
91
|
+
<SvField
|
|
92
|
+
frame
|
|
93
|
+
id={uid}
|
|
94
|
+
{label}
|
|
95
|
+
{hint}
|
|
96
|
+
{error}
|
|
97
|
+
{required}
|
|
98
|
+
{dir}
|
|
99
|
+
{size}
|
|
100
|
+
{invalid}
|
|
101
|
+
{disabled}
|
|
102
|
+
{readonly}
|
|
103
|
+
{loading}
|
|
104
|
+
{block}
|
|
105
|
+
{width}
|
|
106
|
+
leading={countryChip}
|
|
107
|
+
>
|
|
83
108
|
<input class="sv-phone__number" {...ph.inputProps()} />
|
|
84
109
|
{#if name}<input type="hidden" {name} value={ph.value} />{/if}
|
|
85
|
-
</div>
|
|
86
110
|
</SvField>
|
|
87
111
|
|
|
88
112
|
<style>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
display: inline-flex; align-items: stretch; width: 250px;
|
|
92
|
-
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
93
|
-
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
94
|
-
overflow: hidden;
|
|
95
|
-
}
|
|
96
|
-
.sv-phone:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
97
|
-
.sv-phone.is-invalid { border-color: var(--sg-danger, #dc2626); }
|
|
98
|
-
.sv-phone.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
|
|
99
|
-
.sv-phone.is-disabled { opacity: 0.6; }
|
|
113
|
+
/* A compact country chip in the leading slot: flag + dial + chevron, with the
|
|
114
|
+
native <select> as an invisible overlay covering it. */
|
|
100
115
|
.sv-phone__country {
|
|
101
116
|
position: relative; display: inline-flex; align-items: center; gap: 4px;
|
|
102
|
-
padding-
|
|
103
|
-
background: var(--sg-header-bg, #f8fafc); cursor: pointer;
|
|
117
|
+
padding-inline-end: 8px; border-inline-end: 1px solid var(--sg-border, #e2e8f0); cursor: pointer;
|
|
104
118
|
}
|
|
105
119
|
.sv-phone__flag { font-size: 15px; }
|
|
106
120
|
.sv-phone__dial { font-size: 13px; font-weight: 600; }
|
|
107
121
|
.sv-phone__chev { color: var(--sg-muted, #64748b); }
|
|
108
122
|
/* The native <select> is an invisible overlay, but its option popup is drawn by
|
|
109
|
-
the OS using the control's own color/background - so theme them explicitly
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
the OS using the control's own color/background - so theme them explicitly (a
|
|
124
|
+
2-class selector, so it beats SvField frame's generic `select` reset) or dark
|
|
125
|
+
themes get light text on the default white popup. */
|
|
126
|
+
.sv-phone__country .sv-phone__select {
|
|
127
|
+
position: absolute; inset: 0; opacity: 0; cursor: pointer; font-size: 13px; padding: 0;
|
|
113
128
|
color: var(--sg-fg, #0f172a); background: var(--sg-bg, #fff);
|
|
114
129
|
}
|
|
115
|
-
.sv-phone__select option { color: var(--sg-fg, #0f172a); background: var(--sg-bg, #fff); }
|
|
116
|
-
.sv-phone__number { flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit; font: inherit; padding: 0 10px; }
|
|
117
|
-
.sv-phone--sm { height: 28px; font-size: 12px; }
|
|
118
|
-
.sv-phone--md { height: 34px; font-size: 13px; }
|
|
119
|
-
.sv-phone--lg { height: 40px; font-size: 15px; }
|
|
130
|
+
.sv-phone__country .sv-phone__select option { color: var(--sg-fg, #0f172a); background: var(--sg-bg, #fff); }
|
|
120
131
|
</style>
|
|
@@ -12,6 +12,10 @@ type Props = SvEditorProps & {
|
|
|
12
12
|
placeholder?: string;
|
|
13
13
|
/** Override the built-in strings (e.g. the country selector aria-label). */
|
|
14
14
|
messages?: Partial<PhoneMessages>;
|
|
15
|
+
/** Stretch to the container width. */
|
|
16
|
+
block?: boolean;
|
|
17
|
+
/** Control width in px (ignored when `block`). Default 250. */
|
|
18
|
+
width?: number;
|
|
15
19
|
};
|
|
16
20
|
declare const SvPhoneInput: import("svelte").Component<Props, {}, "value">;
|
|
17
21
|
type SvPhoneInput = ReturnType<typeof SvPhoneInput>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvPopconfirm - a lightweight confirm step in a popover anchored to its
|
|
4
|
+
* trigger, for a low-stakes destructive action ("Delete this row?") where a
|
|
5
|
+
* full modal dialog would be too heavy. Click the anchor to reveal a short
|
|
6
|
+
* message plus Cancel / Confirm buttons; confirming runs `onConfirm` and closes.
|
|
7
|
+
* Built on SvPopover, so it is portalled, positioned by the shared engine, and
|
|
8
|
+
* dismisses on Escape / outside-click.
|
|
9
|
+
*
|
|
10
|
+
* ```svelte
|
|
11
|
+
* <SvPopconfirm title="Delete this row?" confirmVariant="danger"
|
|
12
|
+
* onConfirm={() => remove(row)}>
|
|
13
|
+
* {#snippet anchor()}<SvButton variant="danger" size="sm">Delete</SvButton>{/snippet}
|
|
14
|
+
* </SvPopconfirm>
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import type { Snippet } from 'svelte'
|
|
18
|
+
import SvPopover from './SvPopover.svelte'
|
|
19
|
+
import SvButton from './SvButton.svelte'
|
|
20
|
+
import type { Placement } from './positioning'
|
|
21
|
+
|
|
22
|
+
type Props = {
|
|
23
|
+
/** Controlled open state (bindable). */
|
|
24
|
+
open?: boolean
|
|
25
|
+
onOpenChange?: (open: boolean) => void
|
|
26
|
+
/** Bold prompt, e.g. "Delete this row?". */
|
|
27
|
+
title?: string
|
|
28
|
+
/** Optional longer explanation under the title. */
|
|
29
|
+
description?: string
|
|
30
|
+
confirmLabel?: string
|
|
31
|
+
cancelLabel?: string
|
|
32
|
+
/** Variant for the confirm button (use `danger` for destructive actions). */
|
|
33
|
+
confirmVariant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger'
|
|
34
|
+
/** Runs when the user confirms; the popover then closes. */
|
|
35
|
+
onConfirm?: () => void
|
|
36
|
+
/** Runs when the user cancels (or dismisses); the popover then closes. */
|
|
37
|
+
onCancel?: () => void
|
|
38
|
+
/** Preferred placement; flips when there is no room. Default `top`. */
|
|
39
|
+
placement?: Placement
|
|
40
|
+
ariaLabel?: string
|
|
41
|
+
/** The trigger that opens the confirm. */
|
|
42
|
+
anchor?: Snippet
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let {
|
|
46
|
+
open = $bindable(false),
|
|
47
|
+
onOpenChange,
|
|
48
|
+
title,
|
|
49
|
+
description,
|
|
50
|
+
confirmLabel = 'Confirm',
|
|
51
|
+
cancelLabel = 'Cancel',
|
|
52
|
+
confirmVariant = 'primary',
|
|
53
|
+
onConfirm,
|
|
54
|
+
onCancel,
|
|
55
|
+
placement = 'top',
|
|
56
|
+
ariaLabel,
|
|
57
|
+
anchor,
|
|
58
|
+
}: Props = $props()
|
|
59
|
+
|
|
60
|
+
function confirm() { onConfirm?.(); open = false }
|
|
61
|
+
function cancel() { onCancel?.(); open = false }
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<SvPopover bind:open {onOpenChange} trigger="click" {placement} minWidth={230} ariaLabel={ariaLabel ?? title} {anchor}>
|
|
65
|
+
<div class="sv-popconfirm">
|
|
66
|
+
{#if title}<div class="sv-popconfirm__title">{title}</div>{/if}
|
|
67
|
+
{#if description}<div class="sv-popconfirm__desc">{description}</div>{/if}
|
|
68
|
+
<div class="sv-popconfirm__actions">
|
|
69
|
+
<SvButton size="sm" variant="ghost" onclick={cancel}>{cancelLabel}</SvButton>
|
|
70
|
+
<SvButton size="sm" variant={confirmVariant} onclick={confirm}>{confirmLabel}</SvButton>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</SvPopover>
|
|
74
|
+
|
|
75
|
+
<style>
|
|
76
|
+
.sv-popconfirm__title { font-weight: 650; font-size: 13px; color: var(--sg-fg, #0f172a); }
|
|
77
|
+
.sv-popconfirm__desc { margin-top: 4px; font-size: 12.5px; color: var(--sg-muted, #64748b); line-height: 1.45; }
|
|
78
|
+
.sv-popconfirm__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; }
|
|
79
|
+
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvPopconfirm - a lightweight confirm step in a popover anchored to its
|
|
3
|
+
* trigger, for a low-stakes destructive action ("Delete this row?") where a
|
|
4
|
+
* full modal dialog would be too heavy. Click the anchor to reveal a short
|
|
5
|
+
* message plus Cancel / Confirm buttons; confirming runs `onConfirm` and closes.
|
|
6
|
+
* Built on SvPopover, so it is portalled, positioned by the shared engine, and
|
|
7
|
+
* dismisses on Escape / outside-click.
|
|
8
|
+
*
|
|
9
|
+
* ```svelte
|
|
10
|
+
* <SvPopconfirm title="Delete this row?" confirmVariant="danger"
|
|
11
|
+
* onConfirm={() => remove(row)}>
|
|
12
|
+
* {#snippet anchor()}<SvButton variant="danger" size="sm">Delete</SvButton>{/snippet}
|
|
13
|
+
* </SvPopconfirm>
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import type { Snippet } from 'svelte';
|
|
17
|
+
import type { Placement } from './positioning';
|
|
18
|
+
type Props = {
|
|
19
|
+
/** Controlled open state (bindable). */
|
|
20
|
+
open?: boolean;
|
|
21
|
+
onOpenChange?: (open: boolean) => void;
|
|
22
|
+
/** Bold prompt, e.g. "Delete this row?". */
|
|
23
|
+
title?: string;
|
|
24
|
+
/** Optional longer explanation under the title. */
|
|
25
|
+
description?: string;
|
|
26
|
+
confirmLabel?: string;
|
|
27
|
+
cancelLabel?: string;
|
|
28
|
+
/** Variant for the confirm button (use `danger` for destructive actions). */
|
|
29
|
+
confirmVariant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
|
|
30
|
+
/** Runs when the user confirms; the popover then closes. */
|
|
31
|
+
onConfirm?: () => void;
|
|
32
|
+
/** Runs when the user cancels (or dismisses); the popover then closes. */
|
|
33
|
+
onCancel?: () => void;
|
|
34
|
+
/** Preferred placement; flips when there is no room. Default `top`. */
|
|
35
|
+
placement?: Placement;
|
|
36
|
+
ariaLabel?: string;
|
|
37
|
+
/** The trigger that opens the confirm. */
|
|
38
|
+
anchor?: Snippet;
|
|
39
|
+
};
|
|
40
|
+
declare const SvPopconfirm: import("svelte").Component<Props, {}, "open">;
|
|
41
|
+
type SvPopconfirm = ReturnType<typeof SvPopconfirm>;
|
|
42
|
+
export default SvPopconfirm;
|
package/dist/SvPopover.svelte
CHANGED
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* SvPopover - a floating panel anchored to a trigger, portalled to <body> so it
|
|
4
4
|
* escapes any `overflow:hidden` ancestor. Open on click (default), hover, or
|
|
5
|
-
* manually via `open`.
|
|
6
|
-
*
|
|
5
|
+
* manually via `open`. Positioned by the shared engine (`computePosition`): full
|
|
6
|
+
* placement matrix + flip + shift + an optional arrow, kept in sync with
|
|
7
|
+
* `autoUpdate`. Closes on outside-click / Escape via the dismissable layer
|
|
8
|
+
* stack, and animates in. The panel is hoverable (interactive) under the hover
|
|
9
|
+
* trigger. Parity: Smart popover.
|
|
7
10
|
*
|
|
8
11
|
* ```svelte
|
|
9
|
-
* <SvPopover>
|
|
12
|
+
* <SvPopover placement="bottom" arrow>
|
|
10
13
|
* {#snippet anchor()}<button>Open</button>{/snippet}
|
|
11
14
|
* <p>Panel content</p>
|
|
12
15
|
* </SvPopover>
|
|
13
16
|
* ```
|
|
14
17
|
*/
|
|
15
18
|
import type { Snippet } from 'svelte'
|
|
16
|
-
import {
|
|
19
|
+
import { portalToBody, popIn } from './popover'
|
|
20
|
+
import { computePosition, autoUpdate, type Placement, type ComputePositionResult } from './positioning'
|
|
17
21
|
import { createDismissableLayer } from './a11y/dismissable'
|
|
18
22
|
|
|
19
23
|
type Props = {
|
|
@@ -22,7 +26,17 @@
|
|
|
22
26
|
onOpenChange?: (open: boolean) => void
|
|
23
27
|
/** How the anchor opens the panel. `manual` = only `open` drives it. */
|
|
24
28
|
trigger?: 'click' | 'hover' | 'manual'
|
|
25
|
-
/**
|
|
29
|
+
/** Preferred placement (flips when there is no room). Default `bottom-start`. */
|
|
30
|
+
placement?: Placement
|
|
31
|
+
/** Main-axis gap between trigger and panel. Default 8 (room for the arrow). */
|
|
32
|
+
offset?: number
|
|
33
|
+
/** Render a pointer arrow toward the trigger. Default true. */
|
|
34
|
+
arrow?: boolean
|
|
35
|
+
/** Hover trigger: delay (ms) before opening. Default 0. */
|
|
36
|
+
openDelay?: number
|
|
37
|
+
/** Hover trigger: delay (ms) before closing after leave. Default 120. */
|
|
38
|
+
closeDelay?: number
|
|
39
|
+
/** Estimated panel height, used before the panel has been measured. */
|
|
26
40
|
estimatedHeight?: number
|
|
27
41
|
/** Force a minimum panel width (else it hugs its content). */
|
|
28
42
|
minWidth?: number
|
|
@@ -39,6 +53,11 @@
|
|
|
39
53
|
open = $bindable(false),
|
|
40
54
|
onOpenChange,
|
|
41
55
|
trigger = 'click',
|
|
56
|
+
placement = 'bottom-start',
|
|
57
|
+
offset = 8,
|
|
58
|
+
arrow = true,
|
|
59
|
+
openDelay = 0,
|
|
60
|
+
closeDelay = 120,
|
|
42
61
|
estimatedHeight = 220,
|
|
43
62
|
minWidth,
|
|
44
63
|
closeOnOutsideClick = true,
|
|
@@ -47,43 +66,60 @@
|
|
|
47
66
|
children,
|
|
48
67
|
}: Props = $props()
|
|
49
68
|
|
|
69
|
+
const ARROW = 10
|
|
70
|
+
|
|
50
71
|
let anchorEl = $state<HTMLSpanElement | null>(null)
|
|
51
72
|
let panelEl = $state<HTMLDivElement | null>(null)
|
|
52
|
-
let
|
|
73
|
+
let pos = $state<ComputePositionResult>({
|
|
74
|
+
x: 0, y: 0, placement, side: 'bottom', align: 'start', maxWidth: 0, maxHeight: 0,
|
|
75
|
+
})
|
|
53
76
|
|
|
54
77
|
function setOpen(v: boolean) { if (v !== open) { open = v; onOpenChange?.(v) } }
|
|
55
78
|
function toggle() { setOpen(!open) }
|
|
56
79
|
|
|
57
80
|
function updatePos() {
|
|
58
81
|
if (!anchorEl) return
|
|
59
|
-
|
|
82
|
+
const r = anchorEl.getBoundingClientRect()
|
|
83
|
+
const f = panelEl?.getBoundingClientRect()
|
|
84
|
+
const floating = {
|
|
85
|
+
width: f?.width || minWidth || r.width,
|
|
86
|
+
height: f?.height || estimatedHeight,
|
|
87
|
+
}
|
|
88
|
+
pos = computePosition(
|
|
89
|
+
{ x: r.left, y: r.top, width: r.width, height: r.height },
|
|
90
|
+
floating,
|
|
91
|
+
{ placement, offset, padding: 8, minMainAxis: 96, arrow: arrow ? { size: ARROW } : undefined },
|
|
92
|
+
)
|
|
60
93
|
}
|
|
61
94
|
|
|
95
|
+
// Reposition whenever the trigger/panel resize or an ancestor scrolls, and wire
|
|
96
|
+
// Escape + outside-click dismissal via the shared layer stack (so a popover
|
|
97
|
+
// nested in a dialog closes top-first). Re-runs once `panelEl` mounts.
|
|
62
98
|
$effect(() => {
|
|
63
|
-
if (!open) return
|
|
64
|
-
updatePos
|
|
65
|
-
const rp = () => updatePos()
|
|
66
|
-
window.addEventListener('scroll', rp, true)
|
|
67
|
-
window.addEventListener('resize', rp)
|
|
68
|
-
// Escape + outside-click dismissal via the shared layer stack (so a popover
|
|
69
|
-
// nested in a dialog closes top-first). The trigger is passed alongside the
|
|
70
|
-
// panel so clicking it toggles rather than dismiss-then-reopen.
|
|
99
|
+
if (!open || !anchorEl || !panelEl) return
|
|
100
|
+
const stop = autoUpdate(anchorEl, panelEl, updatePos)
|
|
71
101
|
const layer = createDismissableLayer({
|
|
72
102
|
element: () => [anchorEl, panelEl],
|
|
73
103
|
onDismiss: () => setOpen(false),
|
|
74
104
|
closeOnOutside: closeOnOutsideClick,
|
|
75
105
|
})
|
|
76
106
|
layer.activate()
|
|
77
|
-
return () => {
|
|
78
|
-
window.removeEventListener('scroll', rp, true)
|
|
79
|
-
window.removeEventListener('resize', rp)
|
|
80
|
-
layer.release()
|
|
81
|
-
}
|
|
107
|
+
return () => { stop(); layer.release() }
|
|
82
108
|
})
|
|
83
109
|
|
|
84
|
-
let
|
|
85
|
-
|
|
86
|
-
function
|
|
110
|
+
let openTimer: ReturnType<typeof setTimeout> | undefined
|
|
111
|
+
let closeTimer: ReturnType<typeof setTimeout> | undefined
|
|
112
|
+
function onEnter() {
|
|
113
|
+
if (trigger !== 'hover') return
|
|
114
|
+
clearTimeout(closeTimer)
|
|
115
|
+
if (open) return
|
|
116
|
+
openTimer = setTimeout(() => setOpen(true), openDelay)
|
|
117
|
+
}
|
|
118
|
+
function onLeave() {
|
|
119
|
+
if (trigger !== 'hover') return
|
|
120
|
+
clearTimeout(openTimer)
|
|
121
|
+
closeTimer = setTimeout(() => setOpen(false), closeDelay)
|
|
122
|
+
}
|
|
87
123
|
|
|
88
124
|
/** Fallback accessible name for the panel when `ariaLabel` isn't given: the
|
|
89
125
|
* anchor's own aria-label, else its trimmed text content. */
|
|
@@ -112,11 +148,11 @@
|
|
|
112
148
|
bind:this={panelEl}
|
|
113
149
|
class="sv-pop__panel"
|
|
114
150
|
use:portalToBody
|
|
115
|
-
use:popIn={{ up:
|
|
151
|
+
use:popIn={{ up: pos.side === 'top' }}
|
|
116
152
|
style:position="fixed"
|
|
117
|
-
style:top={`${
|
|
118
|
-
style:left={`${
|
|
119
|
-
style:min-width={minWidth ? `${
|
|
153
|
+
style:top={`${pos.y}px`}
|
|
154
|
+
style:left={`${pos.x}px`}
|
|
155
|
+
style:min-width={minWidth ? `${minWidth}px` : undefined}
|
|
120
156
|
role="dialog"
|
|
121
157
|
aria-label={ariaLabel ?? anchorFallbackLabel()}
|
|
122
158
|
onpointerenter={onEnter}
|
|
@@ -124,16 +160,38 @@
|
|
|
124
160
|
onfocusin={onEnter}
|
|
125
161
|
onfocusout={onLeave}
|
|
126
162
|
>
|
|
127
|
-
{
|
|
163
|
+
{#if arrow}
|
|
164
|
+
<span
|
|
165
|
+
class="sv-pop__arrow sv-pop__arrow--{pos.side}"
|
|
166
|
+
style:left={pos.arrow?.x != null ? `${pos.arrow.x}px` : undefined}
|
|
167
|
+
style:top={pos.arrow?.y != null ? `${pos.arrow.y}px` : undefined}
|
|
168
|
+
></span>
|
|
169
|
+
{/if}
|
|
170
|
+
<div class="sv-pop__content" style:max-height={pos.maxHeight ? `${pos.maxHeight}px` : undefined}>
|
|
171
|
+
{@render children?.()}
|
|
172
|
+
</div>
|
|
128
173
|
</div>
|
|
129
174
|
{/if}
|
|
130
175
|
|
|
131
176
|
<style>
|
|
132
177
|
.sv-pop__anchor { display: inline-flex; }
|
|
133
178
|
:global(.sv-pop__panel) {
|
|
134
|
-
z-index: 2147483646; max-width: min(92vw, 360px);
|
|
179
|
+
z-index: 2147483646; max-width: min(92vw, 360px);
|
|
135
180
|
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
136
181
|
border: 1px solid var(--sg-border, #e2e8f0); border-radius: 10px;
|
|
137
182
|
box-shadow: 0 16px 48px -12px rgba(15, 23, 42, 0.35); font-size: 13px; line-height: 1.55;
|
|
138
183
|
}
|
|
184
|
+
:global(.sv-pop__content) { padding: 12px 14px; overflow: auto; }
|
|
185
|
+
/* Arrow: a rotated square straddling the panel edge, two borders showing so it
|
|
186
|
+
reads as a continuation of the panel outline. Not clipped (sibling of the
|
|
187
|
+
scrollable content, panel itself has no overflow). */
|
|
188
|
+
:global(.sv-pop__arrow) {
|
|
189
|
+
position: absolute; width: 10px; height: 10px;
|
|
190
|
+
background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #e2e8f0);
|
|
191
|
+
transform: rotate(45deg);
|
|
192
|
+
}
|
|
193
|
+
:global(.sv-pop__arrow--bottom) { top: -6px; border-right: 0; border-bottom: 0; }
|
|
194
|
+
:global(.sv-pop__arrow--top) { bottom: -6px; border-left: 0; border-top: 0; }
|
|
195
|
+
:global(.sv-pop__arrow--right) { left: -6px; border-top: 0; border-right: 0; }
|
|
196
|
+
:global(.sv-pop__arrow--left) { right: -6px; border-bottom: 0; border-left: 0; }
|
|
139
197
|
</style>
|
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SvPopover - a floating panel anchored to a trigger, portalled to <body> so it
|
|
3
3
|
* escapes any `overflow:hidden` ancestor. Open on click (default), hover, or
|
|
4
|
-
* manually via `open`.
|
|
5
|
-
*
|
|
4
|
+
* manually via `open`. Positioned by the shared engine (`computePosition`): full
|
|
5
|
+
* placement matrix + flip + shift + an optional arrow, kept in sync with
|
|
6
|
+
* `autoUpdate`. Closes on outside-click / Escape via the dismissable layer
|
|
7
|
+
* stack, and animates in. The panel is hoverable (interactive) under the hover
|
|
8
|
+
* trigger. Parity: Smart popover.
|
|
6
9
|
*
|
|
7
10
|
* ```svelte
|
|
8
|
-
* <SvPopover>
|
|
11
|
+
* <SvPopover placement="bottom" arrow>
|
|
9
12
|
* {#snippet anchor()}<button>Open</button>{/snippet}
|
|
10
13
|
* <p>Panel content</p>
|
|
11
14
|
* </SvPopover>
|
|
12
15
|
* ```
|
|
13
16
|
*/
|
|
14
17
|
import type { Snippet } from 'svelte';
|
|
18
|
+
import { type Placement } from './positioning';
|
|
15
19
|
type Props = {
|
|
16
20
|
/** Controlled open state (bindable). */
|
|
17
21
|
open?: boolean;
|
|
18
22
|
onOpenChange?: (open: boolean) => void;
|
|
19
23
|
/** How the anchor opens the panel. `manual` = only `open` drives it. */
|
|
20
24
|
trigger?: 'click' | 'hover' | 'manual';
|
|
21
|
-
/**
|
|
25
|
+
/** Preferred placement (flips when there is no room). Default `bottom-start`. */
|
|
26
|
+
placement?: Placement;
|
|
27
|
+
/** Main-axis gap between trigger and panel. Default 8 (room for the arrow). */
|
|
28
|
+
offset?: number;
|
|
29
|
+
/** Render a pointer arrow toward the trigger. Default true. */
|
|
30
|
+
arrow?: boolean;
|
|
31
|
+
/** Hover trigger: delay (ms) before opening. Default 0. */
|
|
32
|
+
openDelay?: number;
|
|
33
|
+
/** Hover trigger: delay (ms) before closing after leave. Default 120. */
|
|
34
|
+
closeDelay?: number;
|
|
35
|
+
/** Estimated panel height, used before the panel has been measured. */
|
|
22
36
|
estimatedHeight?: number;
|
|
23
37
|
/** Force a minimum panel width (else it hugs its content). */
|
|
24
38
|
minWidth?: number;
|
package/dist/SvRadioGroup.svelte
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
onChange,
|
|
26
26
|
name,
|
|
27
27
|
disabled = false,
|
|
28
|
+
readonly = false,
|
|
28
29
|
orientation = 'vertical',
|
|
29
30
|
size = 'md',
|
|
30
31
|
ariaLabel,
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
const rg = createRadioGroup({
|
|
45
46
|
options: () => options,
|
|
46
47
|
value: () => value,
|
|
47
|
-
onChange: (v) => onChange?.(v),
|
|
48
|
+
onChange: (v) => { if (readonly) return; onChange?.(v) },
|
|
48
49
|
disabled: () => disabled,
|
|
49
50
|
// A visible field `label` also names the group for assistive tech.
|
|
50
51
|
ariaLabel: () => ariaLabel ?? label,
|
|
@@ -60,7 +61,9 @@
|
|
|
60
61
|
<div
|
|
61
62
|
class="sv-radiogroup sv-radiogroup--{orientation} sv-radiogroup--{size}"
|
|
62
63
|
class:is-invalid={invalid}
|
|
64
|
+
class:is-readonly={readonly}
|
|
63
65
|
{...rg.groupProps()}
|
|
66
|
+
aria-readonly={readonly ? 'true' : undefined}
|
|
64
67
|
>
|
|
65
68
|
{#each options as opt (opt.value)}
|
|
66
69
|
<button
|
|
@@ -88,6 +91,7 @@
|
|
|
88
91
|
.sv-radiogroup--md .sv-radio { font-size: 13px; }
|
|
89
92
|
.sv-radiogroup--lg .sv-radio { font-size: 15px; }
|
|
90
93
|
.sv-radio[disabled] { opacity: 0.5; cursor: not-allowed; }
|
|
94
|
+
.sv-radiogroup.is-readonly .sv-radio { cursor: default; }
|
|
91
95
|
.sv-radio__dot {
|
|
92
96
|
width: 17px; height: 17px; flex: none; border-radius: 50%;
|
|
93
97
|
border: 1.5px solid var(--sg-border, #cbd5e1); background: var(--sg-input-bg, #fff);
|