@svgrid/grid 1.2.22 → 1.2.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/dist/GridFooter.svelte +34 -20
  2. package/dist/GridFooter.svelte.d.ts +8 -0
  3. package/dist/SvAutoComplete.svelte +100 -0
  4. package/dist/SvAutoComplete.svelte.d.ts +16 -0
  5. package/dist/SvButton.svelte +97 -0
  6. package/dist/SvButton.svelte.d.ts +23 -0
  7. package/dist/SvCalendar.svelte +669 -0
  8. package/dist/SvCalendar.svelte.d.ts +70 -0
  9. package/dist/SvCheckBox.svelte +81 -0
  10. package/dist/SvCheckBox.svelte.d.ts +19 -0
  11. package/dist/SvColorInput.svelte +187 -0
  12. package/dist/SvColorInput.svelte.d.ts +15 -0
  13. package/dist/SvComboBox.svelte +130 -0
  14. package/dist/SvComboBox.svelte.d.ts +15 -0
  15. package/dist/SvCountryInput.svelte +123 -0
  16. package/dist/SvCountryInput.svelte.d.ts +14 -0
  17. package/dist/SvDateTimePicker.svelte +322 -0
  18. package/dist/SvDateTimePicker.svelte.d.ts +37 -0
  19. package/dist/SvDropDownList.svelte +143 -0
  20. package/dist/SvDropDownList.svelte.d.ts +15 -0
  21. package/dist/SvForm.svelte +147 -0
  22. package/dist/SvForm.svelte.d.ts +29 -0
  23. package/dist/SvGauge.svelte +99 -0
  24. package/dist/SvGauge.svelte.d.ts +31 -0
  25. package/dist/SvGrid.controller.svelte.d.ts +16 -2
  26. package/dist/SvGrid.controller.svelte.js +63 -8
  27. package/dist/SvGrid.css +84 -31
  28. package/dist/SvGrid.helpers.d.ts +4 -0
  29. package/dist/SvGrid.helpers.js +40 -12
  30. package/dist/SvGrid.svelte +84 -5
  31. package/dist/SvGrid.types.d.ts +36 -0
  32. package/dist/SvGridDropdown.svelte +16 -30
  33. package/dist/SvGridDropdown.svelte.d.ts +4 -0
  34. package/dist/SvListBox.svelte +122 -0
  35. package/dist/SvListBox.svelte.d.ts +19 -0
  36. package/dist/SvMaskedInput.svelte +0 -0
  37. package/dist/SvMaskedInput.svelte.d.ts +15 -0
  38. package/dist/SvNumberInput.svelte +0 -0
  39. package/dist/SvNumberInput.svelte.d.ts +22 -0
  40. package/dist/SvPasswordInput.svelte +110 -0
  41. package/dist/SvPasswordInput.svelte.d.ts +22 -0
  42. package/dist/SvPhoneInput.svelte +0 -0
  43. package/dist/SvPhoneInput.svelte.d.ts +19 -0
  44. package/dist/SvRadioGroup.svelte +108 -0
  45. package/dist/SvRadioGroup.svelte.d.ts +23 -0
  46. package/dist/SvRating.svelte +111 -0
  47. package/dist/SvRating.svelte.d.ts +20 -0
  48. package/dist/SvRepeatButton.svelte +89 -0
  49. package/dist/SvRepeatButton.svelte.d.ts +21 -0
  50. package/dist/SvSlider.svelte +182 -0
  51. package/dist/SvSlider.svelte.d.ts +25 -0
  52. package/dist/SvSwitchButton.svelte +94 -0
  53. package/dist/SvSwitchButton.svelte.d.ts +19 -0
  54. package/dist/SvTabs.svelte +122 -0
  55. package/dist/SvTabs.svelte.d.ts +26 -0
  56. package/dist/SvTagsInput.svelte +81 -0
  57. package/dist/SvTagsInput.svelte.d.ts +19 -0
  58. package/dist/SvTimePicker.svelte +359 -0
  59. package/dist/SvTimePicker.svelte.d.ts +27 -0
  60. package/dist/SvToggleButton.svelte +57 -0
  61. package/dist/SvToggleButton.svelte.d.ts +16 -0
  62. package/dist/SvTree.svelte +210 -0
  63. package/dist/SvTree.svelte.d.ts +29 -0
  64. package/dist/build-api.js +78 -1
  65. package/dist/cdn/svgrid.js +10022 -5904
  66. package/dist/cdn/svgrid.svelte-external.js +8531 -4429
  67. package/dist/cell-render.d.ts +4 -0
  68. package/dist/cell-render.js +35 -1
  69. package/dist/clipboard.js +19 -1
  70. package/dist/collaboration.d.ts +2 -0
  71. package/dist/collaboration.js +4 -0
  72. package/dist/core.d.ts +22 -1
  73. package/dist/countries.d.ts +15 -0
  74. package/dist/countries.js +61 -0
  75. package/dist/datetime/date-core.d.ts +88 -0
  76. package/dist/datetime/date-core.js +165 -0
  77. package/dist/datetime/date-format.d.ts +39 -0
  78. package/dist/datetime/date-format.js +371 -0
  79. package/dist/datetime/date-restrict.d.ts +23 -0
  80. package/dist/datetime/date-restrict.js +50 -0
  81. package/dist/datetime/date-selection.d.ts +25 -0
  82. package/dist/datetime/date-selection.js +94 -0
  83. package/dist/datetime/mask.d.ts +21 -0
  84. package/dist/datetime/mask.js +90 -0
  85. package/dist/editing.d.ts +1 -1
  86. package/dist/editing.js +7 -0
  87. package/dist/editors/cell-editors.d.ts +2 -2
  88. package/dist/editors/cell-editors.js +3 -1
  89. package/dist/export-format.d.ts +178 -0
  90. package/dist/export-format.js +419 -0
  91. package/dist/hyperformula-adapter.d.ts +4 -0
  92. package/dist/hyperformula-adapter.js +15 -6
  93. package/dist/index.d.ts +36 -0
  94. package/dist/index.js +41 -0
  95. package/dist/list-option.d.ts +10 -0
  96. package/dist/list-option.js +7 -0
  97. package/dist/menus.js +18 -2
  98. package/dist/popover.d.ts +48 -0
  99. package/dist/popover.js +65 -0
  100. package/dist/row-resize.d.ts +4 -4
  101. package/dist/row-resize.js +8 -5
  102. package/dist/selection.d.ts +6 -0
  103. package/dist/selection.js +31 -5
  104. package/dist/server-data-source.d.ts +43 -0
  105. package/dist/server-data-source.js +100 -0
  106. package/dist/sparkline.js +8 -4
  107. package/dist/svgrid-wrapper.types.d.ts +26 -1
  108. package/package.json +6 -1
  109. package/src/GridFooter.svelte +34 -20
  110. package/src/SvAutoComplete.svelte +100 -0
  111. package/src/SvButton.svelte +97 -0
  112. package/src/SvCalendar.svelte +669 -0
  113. package/src/SvCalendar.test.ts +211 -0
  114. package/src/SvCheckBox.svelte +81 -0
  115. package/src/SvColorInput.svelte +187 -0
  116. package/src/SvComboBox.svelte +130 -0
  117. package/src/SvCountryInput.svelte +123 -0
  118. package/src/SvDateTimePicker.svelte +322 -0
  119. package/src/SvDateTimePicker.test.ts +136 -0
  120. package/src/SvDropDownList.svelte +143 -0
  121. package/src/SvForm.svelte +147 -0
  122. package/src/SvGauge.svelte +99 -0
  123. package/src/SvGrid.controller.svelte.ts +74 -8
  124. package/src/SvGrid.css +84 -31
  125. package/src/SvGrid.helpers.ts +41 -12
  126. package/src/SvGrid.svelte +84 -5
  127. package/src/SvGrid.types.ts +33 -0
  128. package/src/SvGridDropdown.svelte +16 -30
  129. package/src/SvListBox.svelte +122 -0
  130. package/src/SvMaskedInput.svelte +0 -0
  131. package/src/SvNumberInput.svelte +0 -0
  132. package/src/SvPasswordInput.svelte +110 -0
  133. package/src/SvPhoneInput.svelte +0 -0
  134. package/src/SvRadioGroup.svelte +108 -0
  135. package/src/SvRating.svelte +111 -0
  136. package/src/SvRepeatButton.svelte +89 -0
  137. package/src/SvSlider.svelte +182 -0
  138. package/src/SvSwitchButton.svelte +94 -0
  139. package/src/SvTabs.svelte +122 -0
  140. package/src/SvTagsInput.svelte +81 -0
  141. package/src/SvTimePicker.svelte +359 -0
  142. package/src/SvTimePicker.test.ts +98 -0
  143. package/src/SvToggleButton.svelte +57 -0
  144. package/src/SvTree.svelte +210 -0
  145. package/src/build-api.ts +115 -0
  146. package/src/cell-render.test.ts +38 -0
  147. package/src/cell-render.ts +43 -0
  148. package/src/clipboard.test.ts +18 -0
  149. package/src/clipboard.ts +24 -1
  150. package/src/collaboration.test.ts +30 -0
  151. package/src/collaboration.ts +6 -0
  152. package/src/core.ts +27 -3
  153. package/src/countries.ts +71 -0
  154. package/src/datetime/date-core.test.ts +217 -0
  155. package/src/datetime/date-core.ts +204 -0
  156. package/src/datetime/date-format.test.ts +121 -0
  157. package/src/datetime/date-format.ts +317 -0
  158. package/src/datetime/date-restrict.ts +60 -0
  159. package/src/datetime/date-selection.test.ts +129 -0
  160. package/src/datetime/date-selection.ts +137 -0
  161. package/src/datetime/mask.test.ts +36 -0
  162. package/src/datetime/mask.ts +84 -0
  163. package/src/editing.test.ts +22 -0
  164. package/src/editing.ts +7 -1
  165. package/src/editors/cell-editors.ts +7 -1
  166. package/src/export-data-api.test.ts +126 -0
  167. package/src/export-format.test.ts +107 -0
  168. package/src/export-format.ts +598 -0
  169. package/src/hyperformula-adapter.test.ts +35 -1
  170. package/src/hyperformula-adapter.ts +18 -6
  171. package/src/index.ts +71 -0
  172. package/src/list-option.ts +15 -0
  173. package/src/menus.ts +21 -2
  174. package/src/popover.ts +79 -0
  175. package/src/row-resize.test.ts +25 -0
  176. package/src/row-resize.ts +8 -5
  177. package/src/selection.test.ts +36 -4
  178. package/src/selection.ts +30 -9
  179. package/src/server-data-source.test.ts +201 -0
  180. package/src/server-data-source.ts +148 -0
  181. package/src/sparkline.test.ts +9 -0
  182. package/src/sparkline.ts +9 -4
  183. package/src/svgrid-wrapper.types.ts +28 -1
  184. package/src/svgrid.behavior.test.ts +27 -0
  185. package/src/svgrid.new-features.wrapper.test.ts +27 -1
  186. package/src/ui-buttons.test.ts +105 -0
  187. package/src/ui-composite.test.ts +89 -0
  188. package/src/ui-inputs.test.ts +92 -0
  189. package/src/ui-range.test.ts +61 -0
  190. package/src/ui-selection.test.ts +106 -0
@@ -0,0 +1,94 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvSwitchButton - an on/off sliding switch (ARIA `switch`). Parity: Smart
4
+ * `smart-switch-button`. Controlled via `checked` + `onChange`; keyboard
5
+ * togglable; optional inline on/off labels.
6
+ */
7
+ type Props = {
8
+ checked?: boolean
9
+ onChange?: (checked: boolean) => void
10
+ disabled?: boolean
11
+ size?: 'sm' | 'md' | 'lg'
12
+ name?: string
13
+ ariaLabel?: string
14
+ /** Optional labels rendered inside the track. */
15
+ onLabel?: string
16
+ offLabel?: string
17
+ }
18
+
19
+ let {
20
+ checked = false,
21
+ onChange,
22
+ disabled = false,
23
+ size = 'md',
24
+ name,
25
+ ariaLabel,
26
+ onLabel,
27
+ offLabel,
28
+ }: Props = $props()
29
+
30
+ function toggle() {
31
+ if (disabled) return
32
+ onChange?.(!checked)
33
+ }
34
+ function onKeydown(e: KeyboardEvent) {
35
+ if (disabled) return
36
+ if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); toggle() }
37
+ else if (e.key === 'ArrowRight') { if (!checked) onChange?.(true) }
38
+ else if (e.key === 'ArrowLeft') { if (checked) onChange?.(false) }
39
+ }
40
+ </script>
41
+
42
+ <button
43
+ type="button"
44
+ role="switch"
45
+ class="sv-switch sv-switch--{size}"
46
+ class:is-on={checked}
47
+ aria-checked={checked}
48
+ aria-label={ariaLabel}
49
+ {disabled}
50
+ onclick={toggle}
51
+ onkeydown={onKeydown}
52
+ >
53
+ <span class="sv-switch__track">
54
+ {#if onLabel || offLabel}
55
+ <span class="sv-switch__txt sv-switch__txt--on">{onLabel ?? ''}</span>
56
+ <span class="sv-switch__txt sv-switch__txt--off">{offLabel ?? ''}</span>
57
+ {/if}
58
+ <span class="sv-switch__thumb"></span>
59
+ </span>
60
+ {#if name}<input type="hidden" {name} value={checked ? 'true' : 'false'} />{/if}
61
+ </button>
62
+
63
+ <style>
64
+ .sv-switch {
65
+ --_accent: var(--sg-accent, #2563eb);
66
+ --_h: 22px; --_w: 40px;
67
+ display: inline-flex; align-items: center; background: none; border: 0; padding: 2px; cursor: pointer;
68
+ }
69
+ .sv-switch--sm { --_h: 18px; --_w: 32px; }
70
+ .sv-switch--lg { --_h: 28px; --_w: 52px; }
71
+ .sv-switch[disabled] { opacity: 0.5; cursor: not-allowed; }
72
+ .sv-switch__track {
73
+ position: relative; width: var(--_w); height: var(--_h); border-radius: 999px;
74
+ background: var(--sg-border, #cbd5e1); transition: background 0.16s;
75
+ display: inline-flex; align-items: center;
76
+ }
77
+ .sv-switch.is-on .sv-switch__track { background: var(--_accent); }
78
+ .sv-switch__thumb {
79
+ position: absolute; left: 2px; top: 50%; transform: translateY(-50%);
80
+ width: calc(var(--_h) - 4px); height: calc(var(--_h) - 4px); border-radius: 50%;
81
+ background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.3); transition: left 0.16s;
82
+ }
83
+ .sv-switch.is-on .sv-switch__thumb { left: calc(var(--_w) - var(--_h) + 2px); }
84
+ .sv-switch__txt {
85
+ position: absolute; font-size: 9px; font-weight: 700; color: #fff; text-transform: uppercase;
86
+ top: 50%; transform: translateY(-50%);
87
+ }
88
+ .sv-switch__txt--on { left: 6px; opacity: 0; }
89
+ .sv-switch__txt--off { right: 5px; color: var(--sg-muted, #64748b); }
90
+ .sv-switch.is-on .sv-switch__txt--on { opacity: 1; }
91
+ .sv-switch.is-on .sv-switch__txt--off { opacity: 0; }
92
+ .sv-switch:focus-visible { outline: none; }
93
+ .sv-switch:focus-visible .sv-switch__track { outline: 2px solid var(--sg-focus-ring, var(--_accent)); outline-offset: 2px; }
94
+ </style>
@@ -0,0 +1,19 @@
1
+ /**
2
+ * SvSwitchButton - an on/off sliding switch (ARIA `switch`). Parity: Smart
3
+ * `smart-switch-button`. Controlled via `checked` + `onChange`; keyboard
4
+ * togglable; optional inline on/off labels.
5
+ */
6
+ type Props = {
7
+ checked?: boolean;
8
+ onChange?: (checked: boolean) => void;
9
+ disabled?: boolean;
10
+ size?: 'sm' | 'md' | 'lg';
11
+ name?: string;
12
+ ariaLabel?: string;
13
+ /** Optional labels rendered inside the track. */
14
+ onLabel?: string;
15
+ offLabel?: string;
16
+ };
17
+ declare const SvSwitchButton: import("svelte").Component<Props, {}, "">;
18
+ type SvSwitchButton = ReturnType<typeof SvSwitchButton>;
19
+ export default SvSwitchButton;
@@ -0,0 +1,122 @@
1
+ <script lang="ts" module>
2
+ export type TabItem = { id: string; label: string; disabled?: boolean }
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ /**
7
+ * SvTabs - a WAI-ARIA tabs widget with roving tabindex + arrow-key navigation.
8
+ * Parity: Smart `smart-tabs`. Controlled via `value` + `onChange`; the active
9
+ * panel is rendered through the `panel` snippet (receives the active id).
10
+ */
11
+ import type { Snippet } from 'svelte'
12
+
13
+ type Props = {
14
+ tabs: ReadonlyArray<TabItem>
15
+ value?: string
16
+ onChange?: (id: string) => void
17
+ orientation?: 'horizontal' | 'vertical'
18
+ /** 'line' underline (default) or 'pill' segmented look. */
19
+ variant?: 'line' | 'pill'
20
+ /** Activate on focus (automatic) vs. on Enter/Space (manual). Default automatic. */
21
+ activation?: 'automatic' | 'manual'
22
+ panel?: Snippet<[string]>
23
+ ariaLabel?: string
24
+ }
25
+
26
+ let {
27
+ tabs,
28
+ value = $bindable(),
29
+ onChange,
30
+ orientation = 'horizontal',
31
+ variant = 'line',
32
+ activation = 'automatic',
33
+ panel,
34
+ ariaLabel,
35
+ }: Props = $props()
36
+
37
+ const active = $derived(value ?? tabs.find((t) => !t.disabled)?.id ?? tabs[0]?.id ?? '')
38
+ const enabled = $derived(tabs.filter((t) => !t.disabled))
39
+ let tablistEl: HTMLDivElement | null = null
40
+
41
+ function select(id: string) {
42
+ const t = tabs.find((x) => x.id === id)
43
+ if (!t || t.disabled) return
44
+ value = id
45
+ onChange?.(id)
46
+ }
47
+ function focusTab(id: string) {
48
+ queueMicrotask(() => tablistEl?.querySelector<HTMLElement>(`[data-tab="${id}"]`)?.focus())
49
+ }
50
+ function onKeydown(e: KeyboardEvent) {
51
+ const idx = enabled.findIndex((t) => t.id === active)
52
+ let next: string | null = null
53
+ const fwd = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown'
54
+ const back = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp'
55
+ if (e.key === fwd) next = enabled[(idx + 1) % enabled.length]?.id ?? null
56
+ else if (e.key === back) next = enabled[(idx - 1 + enabled.length) % enabled.length]?.id ?? null
57
+ else if (e.key === 'Home') next = enabled[0]?.id ?? null
58
+ else if (e.key === 'End') next = enabled.at(-1)?.id ?? null
59
+ else if ((e.key === 'Enter' || e.key === ' ') && activation === 'manual') { e.preventDefault(); select(active); return }
60
+ else return
61
+ if (next) {
62
+ e.preventDefault()
63
+ focusTab(next)
64
+ if (activation === 'automatic') select(next)
65
+ }
66
+ }
67
+ </script>
68
+
69
+ <div class="sv-tabs sv-tabs--{orientation} sv-tabs--{variant}">
70
+ <div bind:this={tablistEl} class="sv-tabs__list" role="tablist" aria-orientation={orientation} aria-label={ariaLabel}>
71
+ {#each tabs as tab (tab.id)}
72
+ <button
73
+ type="button"
74
+ class="sv-tabs__tab"
75
+ class:is-active={tab.id === active}
76
+ role="tab"
77
+ data-tab={tab.id}
78
+ aria-selected={tab.id === active}
79
+ aria-controls={`panel-${tab.id}`}
80
+ tabindex={tab.id === active ? 0 : -1}
81
+ disabled={tab.disabled}
82
+ onclick={() => select(tab.id)}
83
+ onkeydown={onKeydown}
84
+ >{tab.label}</button>
85
+ {/each}
86
+ </div>
87
+ {#if panel}
88
+ <div class="sv-tabs__panel" role="tabpanel" id={`panel-${active}`} tabindex="0">
89
+ {@render panel(active)}
90
+ </div>
91
+ {/if}
92
+ </div>
93
+
94
+ <style>
95
+ .sv-tabs { --_accent: var(--sg-accent, #2563eb); display: flex; gap: 0; }
96
+ .sv-tabs--horizontal { flex-direction: column; }
97
+ .sv-tabs--vertical { flex-direction: row; }
98
+ .sv-tabs__list { display: flex; gap: 2px; }
99
+ .sv-tabs--horizontal .sv-tabs__list { border-bottom: 1px solid var(--sg-border, #e2e8f0); }
100
+ .sv-tabs--vertical .sv-tabs__list { flex-direction: column; border-right: 1px solid var(--sg-border, #e2e8f0); }
101
+ .sv-tabs--pill .sv-tabs__list { border: 0; background: var(--sg-header-bg, #f1f5f9); padding: 3px; border-radius: 9px; gap: 3px; }
102
+
103
+ .sv-tabs__tab {
104
+ background: none; border: 0; font: inherit; font-size: 13px; font-weight: 600; color: var(--sg-muted, #64748b);
105
+ padding: 9px 14px; cursor: pointer; white-space: nowrap; position: relative;
106
+ }
107
+ .sv-tabs__tab:hover:not(:disabled):not(.is-active) { color: var(--sg-fg, #0f172a); }
108
+ .sv-tabs__tab:disabled { opacity: 0.45; cursor: not-allowed; }
109
+ .sv-tabs__tab:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--_accent)); outline-offset: -2px; border-radius: 6px; }
110
+
111
+ .sv-tabs--line .sv-tabs__tab.is-active { color: var(--_accent); }
112
+ .sv-tabs--line .sv-tabs__tab.is-active::after {
113
+ content: ''; position: absolute; left: 8px; right: 8px; height: 2px; background: var(--_accent);
114
+ }
115
+ .sv-tabs--horizontal.sv-tabs--line .sv-tabs__tab.is-active::after { bottom: -1px; }
116
+ .sv-tabs--vertical.sv-tabs--line .sv-tabs__tab.is-active::after { top: 8px; bottom: 8px; left: auto; right: -1px; width: 2px; height: auto; }
117
+
118
+ .sv-tabs--pill .sv-tabs__tab { border-radius: 7px; padding: 7px 14px; }
119
+ .sv-tabs--pill .sv-tabs__tab.is-active { background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a); box-shadow: 0 1px 2px rgba(0,0,0,0.08); }
120
+
121
+ .sv-tabs__panel { padding: 16px 2px; outline: none; }
122
+ </style>
@@ -0,0 +1,26 @@
1
+ export type TabItem = {
2
+ id: string;
3
+ label: string;
4
+ disabled?: boolean;
5
+ };
6
+ /**
7
+ * SvTabs - a WAI-ARIA tabs widget with roving tabindex + arrow-key navigation.
8
+ * Parity: Smart `smart-tabs`. Controlled via `value` + `onChange`; the active
9
+ * panel is rendered through the `panel` snippet (receives the active id).
10
+ */
11
+ import type { Snippet } from 'svelte';
12
+ type Props = {
13
+ tabs: ReadonlyArray<TabItem>;
14
+ value?: string;
15
+ onChange?: (id: string) => void;
16
+ orientation?: 'horizontal' | 'vertical';
17
+ /** 'line' underline (default) or 'pill' segmented look. */
18
+ variant?: 'line' | 'pill';
19
+ /** Activate on focus (automatic) vs. on Enter/Space (manual). Default automatic. */
20
+ activation?: 'automatic' | 'manual';
21
+ panel?: Snippet<[string]>;
22
+ ariaLabel?: string;
23
+ };
24
+ declare const SvTabs: import("svelte").Component<Props, {}, "value">;
25
+ type SvTabs = ReturnType<typeof SvTabs>;
26
+ export default SvTabs;
@@ -0,0 +1,81 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvTagsInput - editable token/chips input. Type + Enter/comma to add,
4
+ * Backspace to remove the last, click × to remove a chip. Parity: Smart
5
+ * `smart-tags` / `smart-input` token mode. Emits string[].
6
+ */
7
+ type Props = {
8
+ value?: string[]
9
+ onChange?: (tags: string[]) => void
10
+ placeholder?: string
11
+ disabled?: boolean
12
+ /** Reject duplicate tags. Default true. */
13
+ unique?: boolean
14
+ max?: number
15
+ name?: string
16
+ ariaLabel?: string
17
+ }
18
+
19
+ let { value = [], onChange, placeholder = 'Add tag…', disabled = false, unique = true, max = Infinity, name, ariaLabel }: Props = $props()
20
+
21
+ let draft = $state('')
22
+
23
+ function add(raw: string) {
24
+ const tag = raw.trim()
25
+ if (!tag || disabled) return
26
+ if (value.length >= max) return
27
+ if (unique && value.includes(tag)) { draft = ''; return }
28
+ onChange?.([...value, tag])
29
+ draft = ''
30
+ }
31
+ function removeAt(i: number) {
32
+ if (disabled) return
33
+ onChange?.(value.filter((_, idx) => idx !== i))
34
+ }
35
+ function onKeydown(e: KeyboardEvent) {
36
+ if (disabled) return
37
+ if (e.key === 'Enter' || e.key === ',') { e.preventDefault(); add(draft) }
38
+ else if (e.key === 'Backspace' && draft === '' && value.length) { removeAt(value.length - 1) }
39
+ }
40
+ </script>
41
+
42
+ <div class="sv-tags" class:is-disabled={disabled} role="group" aria-label={ariaLabel}>
43
+ {#each value as tag, i (tag + i)}
44
+ <span class="sv-tags__chip">
45
+ <span class="sv-tags__label">{tag}</span>
46
+ {#if !disabled}
47
+ <button type="button" class="sv-tags__x" aria-label={`Remove ${tag}`} onclick={() => removeAt(i)}>&times;</button>
48
+ {/if}
49
+ </span>
50
+ {/each}
51
+ <input
52
+ class="sv-tags__input"
53
+ type="text"
54
+ bind:value={draft}
55
+ placeholder={value.length ? '' : placeholder}
56
+ {disabled}
57
+ aria-label={ariaLabel ?? 'Add tag'}
58
+ onkeydown={onKeydown}
59
+ onblur={() => add(draft)}
60
+ />
61
+ {#if name}<input type="hidden" {name} value={value.join(',')} />{/if}
62
+ </div>
63
+
64
+ <style>
65
+ .sv-tags {
66
+ --_accent: var(--sg-accent, #2563eb);
67
+ display: flex; flex-wrap: wrap; gap: 5px; align-items: center; width: 280px; min-height: 34px; padding: 4px 6px;
68
+ background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
69
+ border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
70
+ }
71
+ .sv-tags:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
72
+ .sv-tags.is-disabled { opacity: 0.6; }
73
+ .sv-tags__chip {
74
+ display: inline-flex; align-items: center; gap: 4px; height: 22px; padding: 0 4px 0 8px;
75
+ background: color-mix(in srgb, var(--_accent) 14%, transparent); color: var(--_accent);
76
+ border-radius: 5px; font-size: 12px; font-weight: 600;
77
+ }
78
+ .sv-tags__x { background: none; border: 0; color: inherit; cursor: pointer; font-size: 15px; line-height: 1; padding: 0 2px; opacity: 0.7; }
79
+ .sv-tags__x:hover { opacity: 1; }
80
+ .sv-tags__input { flex: 1; min-width: 80px; border: 0; background: none; outline: none; color: inherit; font: inherit; font-size: 13px; height: 24px; }
81
+ </style>
@@ -0,0 +1,19 @@
1
+ /**
2
+ * SvTagsInput - editable token/chips input. Type + Enter/comma to add,
3
+ * Backspace to remove the last, click × to remove a chip. Parity: Smart
4
+ * `smart-tags` / `smart-input` token mode. Emits string[].
5
+ */
6
+ type Props = {
7
+ value?: string[];
8
+ onChange?: (tags: string[]) => void;
9
+ placeholder?: string;
10
+ disabled?: boolean;
11
+ /** Reject duplicate tags. Default true. */
12
+ unique?: boolean;
13
+ max?: number;
14
+ name?: string;
15
+ ariaLabel?: string;
16
+ };
17
+ declare const SvTagsInput: import("svelte").Component<Props, {}, "">;
18
+ type SvTagsInput = ReturnType<typeof SvTagsInput>;
19
+ export default SvTagsInput;