@svgrid/grid 2.2.25 → 2.2.27
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 +199 -145
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +179 -179
- package/dist/GridMenus.svelte +734 -728
- package/dist/SvAutoComplete.svelte +165 -165
- package/dist/SvCalendar.svelte +503 -503
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvCheckBox.svelte +101 -101
- package/dist/SvColorInput.svelte +181 -181
- package/dist/SvComboBox.svelte +275 -275
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +160 -160
- package/dist/SvDrawer.svelte +250 -250
- package/dist/SvDropDownList.svelte +374 -374
- package/dist/SvDurationInput.svelte +126 -126
- package/dist/SvField.svelte +293 -293
- package/dist/SvForm.svelte +437 -437
- package/dist/SvGrid.css +2723 -2719
- package/dist/SvGrid.svelte +3333 -3333
- package/dist/SvGridChart.svelte +1724 -1724
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +696 -696
- package/dist/SvGridSelect.svelte +259 -259
- package/dist/SvGroupCell.svelte +109 -109
- package/dist/SvListBox.svelte +287 -287
- package/dist/SvMaskedInput.svelte +122 -122
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +146 -146
- package/dist/SvMultiSelect.svelte +281 -281
- package/dist/SvNumberInput.svelte +168 -168
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPasswordInput.svelte +151 -151
- package/dist/SvPhoneInput.svelte +131 -131
- package/dist/SvPopover.svelte +197 -197
- package/dist/SvRadioGroup.svelte +107 -107
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvSlider.svelte +200 -200
- package/dist/SvSwitchButton.svelte +107 -107
- package/dist/SvTagsInput.svelte +112 -112
- package/dist/SvTextInput.svelte +147 -147
- package/dist/SvTimePicker.svelte +245 -245
- package/dist/SvToaster.svelte +159 -159
- package/dist/SvToggleButton.svelte +85 -85
- package/dist/SvTooltip.svelte +161 -161
- package/dist/SvTour.svelte +204 -204
- package/dist/SvTree.svelte +437 -437
- package/dist/SvTreeSelect.svelte +233 -233
- package/dist/a11y/dismissable.d.ts +11 -0
- package/dist/a11y/dismissable.js +21 -0
- package/dist/ai.js +3 -3
- package/dist/cdn/GridMenus-BzxEYNtW.js +421 -0
- package/dist/cdn/GridMenus-Dr-nGEXn.js +417 -0
- package/dist/cdn/{SvDateTimePicker-CYC7gRER.js → SvDateTimePicker-B1sDc9Pz.js} +451 -444
- package/dist/cdn/{SvDateTimePicker-DtQKd4Ns.js → SvDateTimePicker-Bh0FTs1E.js} +114 -107
- package/dist/cdn/{SvGridChart-BhnFdLd0.js → SvGridChart-BKQsO5fZ.js} +1 -1
- package/dist/cdn/{SvGridChart-BcLCYvw9.js → SvGridChart-Bgk4hDs8.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-DvjwxLvK.js → SvGridChartPanel-DSkHIQDd.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-Cmx9DHAS.js → SvGridChartPanel-DkCc2Z7b.js} +1 -1
- package/dist/cdn/{SvGridChartView-B40DTJSX.js → SvGridChartView-BrbVaELm.js} +1 -1
- package/dist/cdn/{SvGridChartView-PwqI4s02.js → SvGridChartView-i9rWvSKg.js} +1 -1
- package/dist/cdn/{SvGridDropdown-hpnHvOGt.js → SvGridDropdown-CGxxopQ4.js} +1 -1
- package/dist/cdn/{SvGridDropdown-DjALc_4f.js → SvGridDropdown-D08sVmAy.js} +1 -1
- package/dist/cdn/{src-DysW9qbk.js → src-Cw8d9U8j.js} +5753 -5753
- package/dist/cdn/{src-bYfr_7aR.js → src-cgs1wcUS.js} +8102 -8102
- package/dist/cdn/svgrid.js +9 -9
- package/dist/cdn/svgrid.svelte-external.js +9 -9
- package/dist/chart-export.js +8 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +30 -13
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +179 -179
- package/src/GridMenus.svelte +734 -728
- package/src/SvAutoComplete.svelte +165 -165
- package/src/SvCalendar.svelte +503 -503
- package/src/SvCalendar.test.ts +226 -226
- package/src/SvCarousel.svelte +141 -141
- package/src/SvCheckBox.svelte +101 -101
- package/src/SvColorInput.svelte +181 -181
- package/src/SvComboBox.svelte +275 -275
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +160 -160
- package/src/SvDrawer.svelte +250 -250
- package/src/SvDropDownList.svelte +374 -374
- package/src/SvDurationInput.svelte +126 -126
- package/src/SvField.svelte +293 -293
- package/src/SvForm.svelte +437 -437
- package/src/SvForm.test.ts +411 -411
- package/src/SvGrid.controller.svelte.ts +3239 -3239
- package/src/SvGrid.css +2723 -2719
- package/src/SvGrid.svelte +3333 -3333
- package/src/SvGrid.types.ts +1613 -1613
- package/src/SvGridChart.svelte +1724 -1724
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +696 -696
- package/src/SvGridSelect.svelte +259 -259
- package/src/SvGroupCell.svelte +109 -109
- package/src/SvListBox.svelte +287 -287
- package/src/SvMaskedInput.svelte +122 -122
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -97
- package/src/SvMenuList.svelte +146 -146
- package/src/SvMultiSelect.svelte +281 -281
- package/src/SvNumberInput.svelte +168 -168
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPasswordInput.svelte +151 -151
- package/src/SvPhoneInput.svelte +131 -131
- package/src/SvPopover.svelte +197 -197
- package/src/SvRadioGroup.svelte +107 -107
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvSlider.svelte +200 -200
- package/src/SvSwitchButton.svelte +107 -107
- package/src/SvTagsInput.svelte +112 -112
- package/src/SvTextInput.svelte +147 -147
- package/src/SvTimePicker.svelte +245 -245
- package/src/SvToaster.svelte +159 -159
- package/src/SvToaster.test.ts +95 -95
- package/src/SvToggleButton.svelte +85 -85
- package/src/SvTooltip.svelte +161 -161
- package/src/SvTour.svelte +204 -204
- package/src/SvTree.svelte +437 -437
- package/src/SvTreeSelect.svelte +233 -233
- package/src/a11y/dismissable.test.ts +119 -95
- package/src/a11y/dismissable.ts +114 -94
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/ai.test.ts +502 -502
- package/src/ai.ts +1391 -1391
- package/src/build-api.coverage.test.ts +633 -633
- package/src/build-api.ts +895 -895
- package/src/builtin-editors.grid.test.ts +85 -85
- package/src/cell-formatting.ts +169 -169
- package/src/cell-render.ts +469 -469
- package/src/chart-export.ts +201 -201
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.ts +2296 -2296
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1111 -1111
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCombobox.svelte.ts +191 -191
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +168 -168
- package/src/createForm.svelte.ts +386 -386
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createMenu.svelte.ts +225 -225
- package/src/createPopoverSelect.svelte.ts +213 -213
- package/src/createSlider.svelte.ts +191 -191
- package/src/createTooltip.svelte.ts +144 -144
- package/src/createTree.svelte.ts +319 -319
- package/src/editing.test.ts +859 -859
- package/src/editing.ts +675 -675
- package/src/editor-contract.ts +157 -157
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +598 -598
- package/src/filter-operators.ts +157 -157
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +127 -127
- package/src/index.ts +713 -712
- package/src/js-scroller.svelte.ts +173 -173
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -49
- package/src/list-nav.ts +29 -29
- package/src/list-option.test.ts +56 -56
- package/src/list-option.ts +172 -172
- package/src/menus.test.ts +700 -700
- package/src/menus.ts +652 -652
- package/src/merge-objects.ts +48 -48
- package/src/overlays.test.ts +90 -90
- package/src/render-component.ts +28 -28
- package/src/scheduler-model.test.ts +562 -562
- package/src/scheduler-model.ts +872 -872
- package/src/selection.test.ts +754 -754
- package/src/selection.ts +600 -600
- package/src/server-data-source.test.ts +289 -289
- package/src/server-data-source.ts +413 -413
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +489 -489
- package/src/spreadsheet.ts +312 -312
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/svgrid-wrapper.types.ts +524 -524
- package/src/svgrid.behavior.test.ts +706 -706
- package/src/svgrid.charting.test.ts +534 -534
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.filter-menu-scroll.test.ts +102 -0
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.wrapper.test.ts +40 -40
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +215 -215
- package/src/toast-store.svelte.ts +246 -246
- package/src/toast-store.test.ts +147 -147
- package/src/ui-buttons.test.ts +144 -144
- package/src/ui-inputs.test.ts +118 -118
- package/src/ui-range.test.ts +70 -70
- package/src/ui-selection.test.ts +155 -155
- package/src/ui-tier1.test.ts +142 -142
- package/src/virtual.test.ts +88 -88
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +296 -296
- package/themes/ember.css +71 -71
- package/dist/cdn/GridMenus-GfCAHgkZ.js +0 -420
- package/dist/cdn/GridMenus-GiVNSeDU.js +0 -416
|
@@ -1,151 +1,151 @@
|
|
|
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
|
-
* The styled renderer over the headless `createPasswordInput` core: the reveal
|
|
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.
|
|
11
|
-
*/
|
|
12
|
-
import type { Snippet } from 'svelte'
|
|
13
|
-
import SvField from './SvField.svelte'
|
|
14
|
-
import { nextEditorId, resolveMessages, type SvEditorProps } from './editor-contract'
|
|
15
|
-
import { createPasswordInput } from './createPasswordInput.svelte'
|
|
16
|
-
|
|
17
|
-
/** User-facing strings for the password input (localizable via `messages`). */
|
|
18
|
-
type PasswordMessages = { show: string; hide: string; weak: string; fair: string; good: string; strong: string }
|
|
19
|
-
const DEFAULT_MESSAGES: PasswordMessages = {
|
|
20
|
-
show: 'Show password', hide: 'Hide password',
|
|
21
|
-
weak: 'Weak', fair: 'Fair', good: 'Good', strong: 'Strong',
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
type Props = SvEditorProps & {
|
|
25
|
-
value?: string
|
|
26
|
-
onChange?: (value: string) => void
|
|
27
|
-
placeholder?: string
|
|
28
|
-
/** Show the show/hide eye toggle. */
|
|
29
|
-
revealable?: boolean
|
|
30
|
-
/** Show a 4-level strength meter. */
|
|
31
|
-
showStrength?: boolean
|
|
32
|
-
autocomplete?: string
|
|
33
|
-
/** Override the built-in strings (reveal toggle + strength labels). */
|
|
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
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
let {
|
|
44
|
-
value = $bindable(''),
|
|
45
|
-
onChange,
|
|
46
|
-
placeholder,
|
|
47
|
-
disabled = false,
|
|
48
|
-
readonly = false,
|
|
49
|
-
revealable = true,
|
|
50
|
-
showStrength = false,
|
|
51
|
-
name,
|
|
52
|
-
size = 'md',
|
|
53
|
-
ariaLabel,
|
|
54
|
-
invalid = false,
|
|
55
|
-
required = false,
|
|
56
|
-
error,
|
|
57
|
-
label,
|
|
58
|
-
hint,
|
|
59
|
-
dir,
|
|
60
|
-
id,
|
|
61
|
-
loading = false,
|
|
62
|
-
autocomplete = 'current-password',
|
|
63
|
-
messages,
|
|
64
|
-
leading,
|
|
65
|
-
block = false,
|
|
66
|
-
width = 220,
|
|
67
|
-
}: Props = $props()
|
|
68
|
-
|
|
69
|
-
const autoId = nextEditorId('sv-pass')
|
|
70
|
-
const uid = $derived(id ?? autoId)
|
|
71
|
-
const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
|
|
72
|
-
|
|
73
|
-
const pw = createPasswordInput({
|
|
74
|
-
value: () => value,
|
|
75
|
-
// Write `value` (so `bind:value` works) AND fire `onChange` (callback users).
|
|
76
|
-
onChange: (v) => { value = v; onChange?.(v) },
|
|
77
|
-
placeholder: () => placeholder,
|
|
78
|
-
disabled: () => disabled,
|
|
79
|
-
readonly: () => readonly,
|
|
80
|
-
ariaLabel: () => ariaLabel,
|
|
81
|
-
autocomplete: () => autocomplete,
|
|
82
|
-
showLabel: () => M.show,
|
|
83
|
-
hideLabel: () => M.hide,
|
|
84
|
-
strengthLabels: () => ['', M.weak, M.fair, M.good, M.strong],
|
|
85
|
-
id: () => uid,
|
|
86
|
-
invalid: () => invalid,
|
|
87
|
-
required: () => required,
|
|
88
|
-
error: () => error,
|
|
89
|
-
hint: () => hint,
|
|
90
|
-
})
|
|
91
|
-
</script>
|
|
92
|
-
|
|
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
|
-
>
|
|
124
|
-
<input class="sv-pw__input" {...pw.inputProps()} />
|
|
125
|
-
{#if name}<input type="hidden" {name} value={value} />{/if}
|
|
126
|
-
</SvField>
|
|
127
|
-
{#if showStrength}
|
|
128
|
-
<div class="sv-pw__meter" aria-hidden="true">
|
|
129
|
-
{#each Array(4) as _, i (i)}
|
|
130
|
-
<span class="sv-pw__bar is-lvl{pw.strength}" class:on={i < pw.strength}></span>
|
|
131
|
-
{/each}
|
|
132
|
-
</div>
|
|
133
|
-
<span class="sv-pw__strength is-lvl{pw.strength}">{pw.strengthLabel}</span>
|
|
134
|
-
{/if}
|
|
135
|
-
</div>
|
|
136
|
-
|
|
137
|
-
<style>
|
|
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); }
|
|
142
|
-
.sv-pw__meter { display: flex; gap: 4px; }
|
|
143
|
-
.sv-pw__bar { flex: 1; height: 4px; border-radius: 2px; background: var(--sg-border, #e2e8f0); transition: background 0.15s; }
|
|
144
|
-
.sv-pw__bar.on.is-lvl1 { background: var(--sg-danger, #dc2626); }
|
|
145
|
-
.sv-pw__bar.on.is-lvl2 { background: var(--sg-warning, #f59e0b); }
|
|
146
|
-
.sv-pw__bar.on.is-lvl3 { background: #eab308; }
|
|
147
|
-
.sv-pw__bar.on.is-lvl4 { background: var(--sg-success, #16a34a); }
|
|
148
|
-
.sv-pw__strength { font-size: 11px; font-weight: 600; color: var(--sg-muted, #64748b); }
|
|
149
|
-
.sv-pw__strength.is-lvl1 { color: var(--sg-danger, #dc2626); }
|
|
150
|
-
.sv-pw__strength.is-lvl4 { color: var(--sg-success, #16a34a); }
|
|
151
|
-
</style>
|
|
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
|
+
* The styled renderer over the headless `createPasswordInput` core: the reveal
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
import type { Snippet } from 'svelte'
|
|
13
|
+
import SvField from './SvField.svelte'
|
|
14
|
+
import { nextEditorId, resolveMessages, type SvEditorProps } from './editor-contract'
|
|
15
|
+
import { createPasswordInput } from './createPasswordInput.svelte'
|
|
16
|
+
|
|
17
|
+
/** User-facing strings for the password input (localizable via `messages`). */
|
|
18
|
+
type PasswordMessages = { show: string; hide: string; weak: string; fair: string; good: string; strong: string }
|
|
19
|
+
const DEFAULT_MESSAGES: PasswordMessages = {
|
|
20
|
+
show: 'Show password', hide: 'Hide password',
|
|
21
|
+
weak: 'Weak', fair: 'Fair', good: 'Good', strong: 'Strong',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type Props = SvEditorProps & {
|
|
25
|
+
value?: string
|
|
26
|
+
onChange?: (value: string) => void
|
|
27
|
+
placeholder?: string
|
|
28
|
+
/** Show the show/hide eye toggle. */
|
|
29
|
+
revealable?: boolean
|
|
30
|
+
/** Show a 4-level strength meter. */
|
|
31
|
+
showStrength?: boolean
|
|
32
|
+
autocomplete?: string
|
|
33
|
+
/** Override the built-in strings (reveal toggle + strength labels). */
|
|
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
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let {
|
|
44
|
+
value = $bindable(''),
|
|
45
|
+
onChange,
|
|
46
|
+
placeholder,
|
|
47
|
+
disabled = false,
|
|
48
|
+
readonly = false,
|
|
49
|
+
revealable = true,
|
|
50
|
+
showStrength = false,
|
|
51
|
+
name,
|
|
52
|
+
size = 'md',
|
|
53
|
+
ariaLabel,
|
|
54
|
+
invalid = false,
|
|
55
|
+
required = false,
|
|
56
|
+
error,
|
|
57
|
+
label,
|
|
58
|
+
hint,
|
|
59
|
+
dir,
|
|
60
|
+
id,
|
|
61
|
+
loading = false,
|
|
62
|
+
autocomplete = 'current-password',
|
|
63
|
+
messages,
|
|
64
|
+
leading,
|
|
65
|
+
block = false,
|
|
66
|
+
width = 220,
|
|
67
|
+
}: Props = $props()
|
|
68
|
+
|
|
69
|
+
const autoId = nextEditorId('sv-pass')
|
|
70
|
+
const uid = $derived(id ?? autoId)
|
|
71
|
+
const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
|
|
72
|
+
|
|
73
|
+
const pw = createPasswordInput({
|
|
74
|
+
value: () => value,
|
|
75
|
+
// Write `value` (so `bind:value` works) AND fire `onChange` (callback users).
|
|
76
|
+
onChange: (v) => { value = v; onChange?.(v) },
|
|
77
|
+
placeholder: () => placeholder,
|
|
78
|
+
disabled: () => disabled,
|
|
79
|
+
readonly: () => readonly,
|
|
80
|
+
ariaLabel: () => ariaLabel,
|
|
81
|
+
autocomplete: () => autocomplete,
|
|
82
|
+
showLabel: () => M.show,
|
|
83
|
+
hideLabel: () => M.hide,
|
|
84
|
+
strengthLabels: () => ['', M.weak, M.fair, M.good, M.strong],
|
|
85
|
+
id: () => uid,
|
|
86
|
+
invalid: () => invalid,
|
|
87
|
+
required: () => required,
|
|
88
|
+
error: () => error,
|
|
89
|
+
hint: () => hint,
|
|
90
|
+
})
|
|
91
|
+
</script>
|
|
92
|
+
|
|
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
|
+
>
|
|
124
|
+
<input class="sv-pw__input" {...pw.inputProps()} />
|
|
125
|
+
{#if name}<input type="hidden" {name} value={value} />{/if}
|
|
126
|
+
</SvField>
|
|
127
|
+
{#if showStrength}
|
|
128
|
+
<div class="sv-pw__meter" aria-hidden="true">
|
|
129
|
+
{#each Array(4) as _, i (i)}
|
|
130
|
+
<span class="sv-pw__bar is-lvl{pw.strength}" class:on={i < pw.strength}></span>
|
|
131
|
+
{/each}
|
|
132
|
+
</div>
|
|
133
|
+
<span class="sv-pw__strength is-lvl{pw.strength}">{pw.strengthLabel}</span>
|
|
134
|
+
{/if}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<style>
|
|
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); }
|
|
142
|
+
.sv-pw__meter { display: flex; gap: 4px; }
|
|
143
|
+
.sv-pw__bar { flex: 1; height: 4px; border-radius: 2px; background: var(--sg-border, #e2e8f0); transition: background 0.15s; }
|
|
144
|
+
.sv-pw__bar.on.is-lvl1 { background: var(--sg-danger, #dc2626); }
|
|
145
|
+
.sv-pw__bar.on.is-lvl2 { background: var(--sg-warning, #f59e0b); }
|
|
146
|
+
.sv-pw__bar.on.is-lvl3 { background: #eab308; }
|
|
147
|
+
.sv-pw__bar.on.is-lvl4 { background: var(--sg-success, #16a34a); }
|
|
148
|
+
.sv-pw__strength { font-size: 11px; font-weight: 600; color: var(--sg-muted, #64748b); }
|
|
149
|
+
.sv-pw__strength.is-lvl1 { color: var(--sg-danger, #dc2626); }
|
|
150
|
+
.sv-pw__strength.is-lvl4 { color: var(--sg-success, #16a34a); }
|
|
151
|
+
</style>
|
package/src/SvPhoneInput.svelte
CHANGED
|
@@ -1,131 +1,131 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SvPhoneInput - a country dial-code selector + national number field. Parity:
|
|
4
|
-
* Smart `smart-phone-input` (simplified). Emits an E.164-ish string
|
|
5
|
-
* (`+<dial><digits>`). The country list is `countries.ts` (extend for full set).
|
|
6
|
-
*
|
|
7
|
-
* The styled renderer over the headless `createPhoneInput` core; country/number
|
|
8
|
-
* state + parsing live in the core, spread here via prop-getters.
|
|
9
|
-
*/
|
|
10
|
-
import { COUNTRIES, flagEmoji } from './countries'
|
|
11
|
-
import SvField from './SvField.svelte'
|
|
12
|
-
import { nextEditorId, resolveMessages, type SvEditorProps } from './editor-contract'
|
|
13
|
-
import { createPhoneInput, type PhoneParts } from './createPhoneInput.svelte'
|
|
14
|
-
|
|
15
|
-
/** User-facing strings for the phone input (localizable via `messages`). */
|
|
16
|
-
type PhoneMessages = { country: string }
|
|
17
|
-
const DEFAULT_MESSAGES: PhoneMessages = { country: 'Country' }
|
|
18
|
-
|
|
19
|
-
type Props = SvEditorProps & {
|
|
20
|
-
value?: string
|
|
21
|
-
onChange?: (value: string, parts: PhoneParts) => void
|
|
22
|
-
/** Default country ISO code. */
|
|
23
|
-
country?: string
|
|
24
|
-
placeholder?: string
|
|
25
|
-
/** Override the built-in strings (e.g. the country selector aria-label). */
|
|
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
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let {
|
|
34
|
-
value = $bindable(''),
|
|
35
|
-
onChange,
|
|
36
|
-
country = 'US',
|
|
37
|
-
disabled = false,
|
|
38
|
-
readonly = false,
|
|
39
|
-
placeholder = 'Phone number',
|
|
40
|
-
name,
|
|
41
|
-
size = 'md',
|
|
42
|
-
ariaLabel,
|
|
43
|
-
invalid = false,
|
|
44
|
-
required = false,
|
|
45
|
-
error,
|
|
46
|
-
label,
|
|
47
|
-
hint,
|
|
48
|
-
dir,
|
|
49
|
-
id,
|
|
50
|
-
loading = false,
|
|
51
|
-
messages,
|
|
52
|
-
block = false,
|
|
53
|
-
width = 250,
|
|
54
|
-
}: Props = $props()
|
|
55
|
-
|
|
56
|
-
const autoId = nextEditorId('sv-phone')
|
|
57
|
-
const uid = $derived(id ?? autoId)
|
|
58
|
-
const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
|
|
59
|
-
|
|
60
|
-
const ph = createPhoneInput({
|
|
61
|
-
value: () => value,
|
|
62
|
-
// Write `value` (so `bind:value` works) AND fire `onChange` (callback users).
|
|
63
|
-
onChange: (v, parts) => { value = v; onChange?.(v, parts) },
|
|
64
|
-
country: () => country,
|
|
65
|
-
disabled: () => disabled,
|
|
66
|
-
readonly: () => readonly,
|
|
67
|
-
placeholder: () => placeholder,
|
|
68
|
-
countryLabel: () => M.country,
|
|
69
|
-
id: () => uid,
|
|
70
|
-
invalid: () => invalid,
|
|
71
|
-
required: () => required,
|
|
72
|
-
error: () => error,
|
|
73
|
-
hint: () => hint,
|
|
74
|
-
ariaLabel: () => ariaLabel,
|
|
75
|
-
})
|
|
76
|
-
</script>
|
|
77
|
-
|
|
78
|
-
{#snippet countryChip()}
|
|
79
|
-
<span class="sv-phone__country">
|
|
80
|
-
<span class="sv-phone__flag" aria-hidden="true">{ph.flag}</span>
|
|
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>
|
|
83
|
-
<select class="sv-phone__select" {...ph.selectProps()}>
|
|
84
|
-
{#each COUNTRIES as c (c.code)}
|
|
85
|
-
<option value={c.code}>{flagEmoji(c.code)} {c.name} ({c.dial})</option>
|
|
86
|
-
{/each}
|
|
87
|
-
</select>
|
|
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
|
-
>
|
|
108
|
-
<input class="sv-phone__number" {...ph.inputProps()} />
|
|
109
|
-
{#if name}<input type="hidden" {name} value={ph.value} />{/if}
|
|
110
|
-
</SvField>
|
|
111
|
-
|
|
112
|
-
<style>
|
|
113
|
-
/* A compact country chip in the leading slot: flag + dial + chevron, with the
|
|
114
|
-
native <select> as an invisible overlay covering it. */
|
|
115
|
-
.sv-phone__country {
|
|
116
|
-
position: relative; display: inline-flex; align-items: center; gap: 4px;
|
|
117
|
-
padding-inline-end: 8px; border-inline-end: 1px solid var(--sg-border, #e2e8f0); cursor: pointer;
|
|
118
|
-
}
|
|
119
|
-
.sv-phone__flag { font-size: 15px; }
|
|
120
|
-
.sv-phone__dial { font-size: 13px; font-weight: 600; }
|
|
121
|
-
.sv-phone__chev { color: var(--sg-muted, #64748b); }
|
|
122
|
-
/* The native <select> is an invisible overlay, but its option popup is drawn by
|
|
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;
|
|
128
|
-
color: var(--sg-fg, #0f172a); background: var(--sg-bg, #fff);
|
|
129
|
-
}
|
|
130
|
-
.sv-phone__country .sv-phone__select option { color: var(--sg-fg, #0f172a); background: var(--sg-bg, #fff); }
|
|
131
|
-
</style>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvPhoneInput - a country dial-code selector + national number field. Parity:
|
|
4
|
+
* Smart `smart-phone-input` (simplified). Emits an E.164-ish string
|
|
5
|
+
* (`+<dial><digits>`). The country list is `countries.ts` (extend for full set).
|
|
6
|
+
*
|
|
7
|
+
* The styled renderer over the headless `createPhoneInput` core; country/number
|
|
8
|
+
* state + parsing live in the core, spread here via prop-getters.
|
|
9
|
+
*/
|
|
10
|
+
import { COUNTRIES, flagEmoji } from './countries'
|
|
11
|
+
import SvField from './SvField.svelte'
|
|
12
|
+
import { nextEditorId, resolveMessages, type SvEditorProps } from './editor-contract'
|
|
13
|
+
import { createPhoneInput, type PhoneParts } from './createPhoneInput.svelte'
|
|
14
|
+
|
|
15
|
+
/** User-facing strings for the phone input (localizable via `messages`). */
|
|
16
|
+
type PhoneMessages = { country: string }
|
|
17
|
+
const DEFAULT_MESSAGES: PhoneMessages = { country: 'Country' }
|
|
18
|
+
|
|
19
|
+
type Props = SvEditorProps & {
|
|
20
|
+
value?: string
|
|
21
|
+
onChange?: (value: string, parts: PhoneParts) => void
|
|
22
|
+
/** Default country ISO code. */
|
|
23
|
+
country?: string
|
|
24
|
+
placeholder?: string
|
|
25
|
+
/** Override the built-in strings (e.g. the country selector aria-label). */
|
|
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
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
value = $bindable(''),
|
|
35
|
+
onChange,
|
|
36
|
+
country = 'US',
|
|
37
|
+
disabled = false,
|
|
38
|
+
readonly = false,
|
|
39
|
+
placeholder = 'Phone number',
|
|
40
|
+
name,
|
|
41
|
+
size = 'md',
|
|
42
|
+
ariaLabel,
|
|
43
|
+
invalid = false,
|
|
44
|
+
required = false,
|
|
45
|
+
error,
|
|
46
|
+
label,
|
|
47
|
+
hint,
|
|
48
|
+
dir,
|
|
49
|
+
id,
|
|
50
|
+
loading = false,
|
|
51
|
+
messages,
|
|
52
|
+
block = false,
|
|
53
|
+
width = 250,
|
|
54
|
+
}: Props = $props()
|
|
55
|
+
|
|
56
|
+
const autoId = nextEditorId('sv-phone')
|
|
57
|
+
const uid = $derived(id ?? autoId)
|
|
58
|
+
const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
|
|
59
|
+
|
|
60
|
+
const ph = createPhoneInput({
|
|
61
|
+
value: () => value,
|
|
62
|
+
// Write `value` (so `bind:value` works) AND fire `onChange` (callback users).
|
|
63
|
+
onChange: (v, parts) => { value = v; onChange?.(v, parts) },
|
|
64
|
+
country: () => country,
|
|
65
|
+
disabled: () => disabled,
|
|
66
|
+
readonly: () => readonly,
|
|
67
|
+
placeholder: () => placeholder,
|
|
68
|
+
countryLabel: () => M.country,
|
|
69
|
+
id: () => uid,
|
|
70
|
+
invalid: () => invalid,
|
|
71
|
+
required: () => required,
|
|
72
|
+
error: () => error,
|
|
73
|
+
hint: () => hint,
|
|
74
|
+
ariaLabel: () => ariaLabel,
|
|
75
|
+
})
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
{#snippet countryChip()}
|
|
79
|
+
<span class="sv-phone__country">
|
|
80
|
+
<span class="sv-phone__flag" aria-hidden="true">{ph.flag}</span>
|
|
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>
|
|
83
|
+
<select class="sv-phone__select" {...ph.selectProps()}>
|
|
84
|
+
{#each COUNTRIES as c (c.code)}
|
|
85
|
+
<option value={c.code}>{flagEmoji(c.code)} {c.name} ({c.dial})</option>
|
|
86
|
+
{/each}
|
|
87
|
+
</select>
|
|
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
|
+
>
|
|
108
|
+
<input class="sv-phone__number" {...ph.inputProps()} />
|
|
109
|
+
{#if name}<input type="hidden" {name} value={ph.value} />{/if}
|
|
110
|
+
</SvField>
|
|
111
|
+
|
|
112
|
+
<style>
|
|
113
|
+
/* A compact country chip in the leading slot: flag + dial + chevron, with the
|
|
114
|
+
native <select> as an invisible overlay covering it. */
|
|
115
|
+
.sv-phone__country {
|
|
116
|
+
position: relative; display: inline-flex; align-items: center; gap: 4px;
|
|
117
|
+
padding-inline-end: 8px; border-inline-end: 1px solid var(--sg-border, #e2e8f0); cursor: pointer;
|
|
118
|
+
}
|
|
119
|
+
.sv-phone__flag { font-size: 15px; }
|
|
120
|
+
.sv-phone__dial { font-size: 13px; font-weight: 600; }
|
|
121
|
+
.sv-phone__chev { color: var(--sg-muted, #64748b); }
|
|
122
|
+
/* The native <select> is an invisible overlay, but its option popup is drawn by
|
|
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;
|
|
128
|
+
color: var(--sg-fg, #0f172a); background: var(--sg-bg, #fff);
|
|
129
|
+
}
|
|
130
|
+
.sv-phone__country .sv-phone__select option { color: var(--sg-fg, #0f172a); background: var(--sg-bg, #fff); }
|
|
131
|
+
</style>
|