@marianmeres/stuic 2.66.0 → 3.0.1

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 (208) hide show
  1. package/README.md +292 -4
  2. package/dist/README.md +41 -18
  3. package/dist/actions/index.d.ts +1 -0
  4. package/dist/actions/index.js +1 -0
  5. package/dist/actions/popover/README.md +19 -0
  6. package/dist/actions/popover/index.css +6 -9
  7. package/dist/actions/popover/popover.svelte.js +2 -2
  8. package/dist/actions/tooltip/README.md +18 -0
  9. package/dist/actions/tooltip/index.css +5 -8
  10. package/dist/actions/tooltip/tooltip.svelte.js +1 -1
  11. package/dist/actions/typeahead.svelte.d.ts +53 -0
  12. package/dist/actions/typeahead.svelte.js +328 -0
  13. package/dist/base.css +17 -0
  14. package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte +10 -10
  15. package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte.d.ts +4 -3
  16. package/dist/components/AlertConfirmPrompt/Current.svelte +15 -18
  17. package/dist/components/AlertConfirmPrompt/Current.svelte.d.ts +4 -3
  18. package/dist/components/AlertConfirmPrompt/acp-icons.js +5 -4
  19. package/dist/components/AlertConfirmPrompt/index.css +66 -0
  20. package/dist/components/AssetsPreview/AssetsPreview.svelte +91 -73
  21. package/dist/components/AssetsPreview/index.css +61 -0
  22. package/dist/components/Avatar/Avatar.svelte +31 -18
  23. package/dist/components/Avatar/README.md +166 -0
  24. package/dist/components/Avatar/index.css +130 -0
  25. package/dist/components/Backdrop/Backdrop.svelte +7 -2
  26. package/dist/components/Backdrop/README.md +71 -6
  27. package/dist/components/Backdrop/index.css +31 -0
  28. package/dist/components/Button/Button.svelte +116 -124
  29. package/dist/components/Button/Button.svelte.d.ts +35 -24
  30. package/dist/components/Button/README.md +87 -21
  31. package/dist/components/Button/index.css +475 -9
  32. package/dist/components/Button/index.d.ts +1 -1
  33. package/dist/components/Button/index.js +1 -1
  34. package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte +7 -39
  35. package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte.d.ts +0 -1
  36. package/dist/components/ButtonGroupRadio/README.md +82 -4
  37. package/dist/components/ButtonGroupRadio/index.css +158 -14
  38. package/dist/components/Collapsible/Collapsible.svelte +7 -7
  39. package/dist/components/Collapsible/Collapsible.svelte.d.ts +2 -2
  40. package/dist/components/Collapsible/README.md +34 -2
  41. package/dist/components/Collapsible/index.css +40 -0
  42. package/dist/components/CommandMenu/CommandMenu.svelte +18 -26
  43. package/dist/components/CommandMenu/CommandMenu.svelte.d.ts +0 -1
  44. package/dist/components/CommandMenu/README.md +39 -0
  45. package/dist/components/CommandMenu/index.css +47 -2
  46. package/dist/components/DismissibleMessage/DismissibleMessage.svelte +53 -51
  47. package/dist/components/DismissibleMessage/DismissibleMessage.svelte.d.ts +6 -6
  48. package/dist/components/DismissibleMessage/README.md +93 -11
  49. package/dist/components/DismissibleMessage/index.css +128 -8
  50. package/dist/components/DismissibleMessage/index.d.ts +1 -1
  51. package/dist/components/DropdownMenu/DropdownMenu.svelte +14 -51
  52. package/dist/components/DropdownMenu/DropdownMenu.svelte.d.ts +6 -7
  53. package/dist/components/DropdownMenu/README.md +132 -0
  54. package/dist/components/DropdownMenu/index.css +258 -52
  55. package/dist/components/Input/FieldAssets.svelte +8 -5
  56. package/dist/components/Input/FieldCheckbox.svelte +7 -44
  57. package/dist/components/Input/FieldFile.svelte +1 -6
  58. package/dist/components/Input/FieldInput.svelte +9 -1
  59. package/dist/components/Input/FieldInput.svelte.d.ts +2 -0
  60. package/dist/components/Input/FieldOptions.svelte +42 -39
  61. package/dist/components/Input/FieldRadios.svelte +7 -16
  62. package/dist/components/Input/FieldSelect.svelte +1 -1
  63. package/dist/components/Input/FieldSwitch.svelte +1 -5
  64. package/dist/components/Input/FieldTextarea.svelte +1 -1
  65. package/dist/components/Input/README.md +194 -0
  66. package/dist/components/Input/_internal/FieldRadioInternal.svelte +2 -40
  67. package/dist/components/Input/_internal/InputWrap.svelte +8 -48
  68. package/dist/components/Input/index.css +524 -116
  69. package/dist/components/KbdShortcut/KbdShortcut.svelte +4 -12
  70. package/dist/components/KbdShortcut/README.md +34 -0
  71. package/dist/components/KbdShortcut/index.css +55 -0
  72. package/dist/components/ListItemButton/ListItemButton.svelte +37 -74
  73. package/dist/components/ListItemButton/ListItemButton.svelte.d.ts +1 -10
  74. package/dist/components/ListItemButton/README.md +100 -45
  75. package/dist/components/ListItemButton/index.css +173 -52
  76. package/dist/components/ListItemButton/index.d.ts +1 -1
  77. package/dist/components/ListItemButton/index.js +1 -1
  78. package/dist/components/Modal/Modal.svelte +1 -8
  79. package/dist/components/Modal/README.md +29 -0
  80. package/dist/components/Modal/index.css +38 -0
  81. package/dist/components/ModalDialog/ModalDialog.svelte +2 -21
  82. package/dist/components/ModalDialog/README.md +35 -0
  83. package/dist/components/ModalDialog/index.css +59 -0
  84. package/dist/components/Nav/Nav.svelte +732 -0
  85. package/dist/components/Nav/Nav.svelte.d.ts +110 -0
  86. package/dist/components/Nav/README.md +334 -0
  87. package/dist/components/Nav/index.css +318 -0
  88. package/dist/components/Nav/index.d.ts +1 -0
  89. package/dist/components/Nav/index.js +1 -0
  90. package/dist/components/Notifications/Notifications.svelte +44 -129
  91. package/dist/components/Notifications/Notifications.svelte.d.ts +9 -18
  92. package/dist/components/Notifications/README.md +186 -70
  93. package/dist/components/Notifications/index.css +212 -15
  94. package/dist/components/Notifications/notifications-stack.svelte.d.ts +4 -0
  95. package/dist/components/Notifications/notifications-stack.svelte.js +8 -0
  96. package/dist/components/Progress/Progress.svelte +4 -2
  97. package/dist/components/Progress/Progress.svelte.d.ts +1 -0
  98. package/dist/components/Progress/README.md +97 -11
  99. package/dist/components/Progress/_internal/Bar.svelte +4 -15
  100. package/dist/components/Progress/_internal/Bar.svelte.d.ts +1 -1
  101. package/dist/components/Progress/_internal/Circle.svelte +30 -2
  102. package/dist/components/Progress/_internal/Circle.svelte.d.ts +1 -0
  103. package/dist/components/Progress/index.css +50 -4
  104. package/dist/components/Skeleton/README.md +152 -0
  105. package/dist/components/Skeleton/Skeleton.svelte +9 -9
  106. package/dist/components/Skeleton/Skeleton.svelte.d.ts +0 -1
  107. package/dist/components/Skeleton/index.css +72 -45
  108. package/dist/components/Spinner/README.md +149 -37
  109. package/dist/components/Spinner/Spinner.svelte +14 -38
  110. package/dist/components/Spinner/Spinner.svelte.d.ts +2 -1
  111. package/dist/components/Spinner/SpinnerCircle.svelte +6 -34
  112. package/dist/components/Spinner/SpinnerCircle.svelte.d.ts +1 -0
  113. package/dist/components/Spinner/SpinnerCircleOscillate.svelte +10 -5
  114. package/dist/components/Spinner/SpinnerUnicode.svelte +3 -1
  115. package/dist/components/Spinner/SpinnerUnicode.svelte.d.ts +1 -0
  116. package/dist/components/Spinner/index.css +104 -0
  117. package/dist/components/Switch/README.md +45 -14
  118. package/dist/components/Switch/Switch.svelte +23 -48
  119. package/dist/components/Switch/Switch.svelte.d.ts +4 -2
  120. package/dist/components/Switch/index.css +121 -4
  121. package/dist/components/Switch/index.d.ts +1 -2
  122. package/dist/components/Switch/index.js +1 -2
  123. package/dist/components/TabbedMenu/README.md +37 -21
  124. package/dist/components/TabbedMenu/TabbedMenu.svelte +5 -46
  125. package/dist/components/TabbedMenu/TabbedMenu.svelte.d.ts +0 -1
  126. package/dist/components/TabbedMenu/index.css +84 -17
  127. package/dist/components/ThemePreview/README.md +289 -0
  128. package/dist/components/ThemePreview/ThemePreview.svelte +394 -0
  129. package/dist/components/ThemePreview/ThemePreview.svelte.d.ts +35 -0
  130. package/dist/components/ThemePreview/index.css +509 -0
  131. package/dist/components/ThemePreview/index.d.ts +1 -0
  132. package/dist/components/ThemePreview/index.js +1 -0
  133. package/dist/components/TwCheck/README.md +32 -13
  134. package/dist/components/TwCheck/TwCheck.svelte +11 -9
  135. package/dist/components/TwCheck/TwCheck.svelte.d.ts +0 -1
  136. package/dist/components/TwCheck/index.css +17 -2
  137. package/dist/components/TypeaheadInput/TypeaheadInput.svelte +20 -188
  138. package/dist/components/TypeaheadInput/TypeaheadInput.svelte.d.ts +4 -2
  139. package/dist/components/X/X.svelte +12 -5
  140. package/dist/components/X/X.svelte.d.ts +1 -0
  141. package/dist/icons/index.d.ts +1 -0
  142. package/dist/icons/index.js +1 -0
  143. package/dist/index.css +46 -26
  144. package/dist/index.d.ts +2 -0
  145. package/dist/index.js +2 -0
  146. package/dist/themes/blue-orange.css +217 -0
  147. package/dist/themes/blue-orange.d.ts +6 -0
  148. package/dist/themes/blue-orange.js +175 -0
  149. package/dist/themes/cyan-red.css +217 -0
  150. package/dist/themes/cyan-red.d.ts +6 -0
  151. package/dist/themes/cyan-red.js +175 -0
  152. package/dist/themes/cyan-slate.css +217 -0
  153. package/dist/themes/cyan-slate.d.ts +6 -0
  154. package/dist/themes/cyan-slate.js +175 -0
  155. package/dist/themes/emerald-pink.css +217 -0
  156. package/dist/themes/emerald-pink.d.ts +6 -0
  157. package/dist/themes/emerald-pink.js +175 -0
  158. package/dist/themes/fuchsia-emerald.css +217 -0
  159. package/dist/themes/fuchsia-emerald.d.ts +6 -0
  160. package/dist/themes/fuchsia-emerald.js +175 -0
  161. package/dist/themes/gray.css +217 -0
  162. package/dist/themes/gray.d.ts +6 -0
  163. package/dist/themes/gray.js +175 -0
  164. package/dist/themes/indigo-amber.css +217 -0
  165. package/dist/themes/indigo-amber.d.ts +6 -0
  166. package/dist/themes/indigo-amber.js +175 -0
  167. package/dist/themes/neutral.css +217 -0
  168. package/dist/themes/neutral.d.ts +6 -0
  169. package/dist/themes/neutral.js +175 -0
  170. package/dist/themes/pink-emerald.css +217 -0
  171. package/dist/themes/pink-emerald.d.ts +6 -0
  172. package/dist/themes/pink-emerald.js +175 -0
  173. package/dist/themes/purple-yellow.css +217 -0
  174. package/dist/themes/purple-yellow.d.ts +6 -0
  175. package/dist/themes/purple-yellow.js +175 -0
  176. package/dist/themes/rainbow.css +217 -0
  177. package/dist/themes/rainbow.d.ts +6 -0
  178. package/dist/themes/rainbow.js +180 -0
  179. package/dist/themes/red-blue.css +217 -0
  180. package/dist/themes/red-blue.d.ts +6 -0
  181. package/dist/themes/red-blue.js +175 -0
  182. package/dist/themes/red-cyan.css +217 -0
  183. package/dist/themes/red-cyan.d.ts +6 -0
  184. package/dist/themes/red-cyan.js +175 -0
  185. package/dist/themes/rose-teal.css +217 -0
  186. package/dist/themes/rose-teal.d.ts +6 -0
  187. package/dist/themes/rose-teal.js +175 -0
  188. package/dist/themes/sky-amber.css +217 -0
  189. package/dist/themes/sky-amber.d.ts +6 -0
  190. package/dist/themes/sky-amber.js +175 -0
  191. package/dist/themes/slate-cyan.css +217 -0
  192. package/dist/themes/slate-cyan.d.ts +6 -0
  193. package/dist/themes/slate-cyan.js +175 -0
  194. package/dist/themes/tailwind-color-pairs.md +31 -0
  195. package/dist/themes/teal-rose.css +217 -0
  196. package/dist/themes/teal-rose.d.ts +6 -0
  197. package/dist/themes/teal-rose.js +175 -0
  198. package/dist/themes/violet-lime.css +217 -0
  199. package/dist/themes/violet-lime.d.ts +6 -0
  200. package/dist/themes/violet-lime.js +175 -0
  201. package/dist/utils/design-tokens.d.ts +43 -0
  202. package/dist/utils/design-tokens.js +127 -0
  203. package/dist/utils/index.d.ts +1 -0
  204. package/dist/utils/index.js +1 -0
  205. package/dist/utils/storage-abstraction.js +1 -1
  206. package/package.json +14 -11
  207. package/dist/components/Switch/SwitchButton.svelte +0 -135
  208. package/dist/components/Switch/SwitchButton.svelte.d.ts +0 -21
@@ -0,0 +1,328 @@
1
+ import { ItemCollection } from "@marianmeres/item-collection";
2
+ import { unaccent } from "../utils/unaccent.js";
3
+ import { createClog } from "@marianmeres/clog";
4
+ const DEFAULT_DEBOUNCE_MS = 150;
5
+ const DEFAULT_APPEND_HINT = " [tab]";
6
+ const clog = createClog("typeahead", { color: "auto" });
7
+ /**
8
+ * A Svelte action that adds typeahead/autocomplete functionality to an input element.
9
+ *
10
+ * Creates a "ghost input" behind the main input to show suggestions. Supports keyboard
11
+ * navigation with ArrowUp/Down, accepts suggestions with Tab/Enter/ArrowRight, and
12
+ * handles accent-insensitive matching.
13
+ *
14
+ * @param el - The input element to add typeahead to
15
+ * @param fn - Function returning configuration options
16
+ *
17
+ * @example
18
+ * ```svelte
19
+ * <input
20
+ * bind:value
21
+ * use:typeahead={() => ({
22
+ * getOptions: async (q) => fetchSuggestions(q),
23
+ * renderOptionLabel: (item) => item.name,
24
+ * onSubmit: (v) => console.log('Selected:', v),
25
+ * })}
26
+ * />
27
+ * ```
28
+ */
29
+ export function typeahead(el, fn) {
30
+ let ghostEl = null;
31
+ let options = null;
32
+ let debounceTimer = null;
33
+ let previousKey = "";
34
+ let allowListAll = false;
35
+ // Current resolved options
36
+ let currentOpts = { getOptions: async () => [] };
37
+ // Helper: render option label
38
+ function renderLabel(item) {
39
+ return (currentOpts.renderOptionLabel?.(item) ??
40
+ `${item[currentOpts.itemIdPropName || "id"]}`);
41
+ }
42
+ // Helper: get current suggestion string
43
+ function getSuggestion() {
44
+ const active = options?.active;
45
+ if (!active)
46
+ return "";
47
+ const value = el.value || "";
48
+ const suggestion = renderLabel(active);
49
+ // Preserve user's case, append rest from suggestion
50
+ return value + suggestion.slice(value.length);
51
+ }
52
+ // Helper: get visible suggestion (with hint, or empty if matches value)
53
+ function getVisibleSuggestion() {
54
+ const suggestion = getSuggestion();
55
+ const value = el.value || "";
56
+ if (!suggestion ||
57
+ unaccent(suggestion.toLowerCase()) === unaccent(value.toLowerCase())) {
58
+ return "";
59
+ }
60
+ const hint = currentOpts.appendHint ?? DEFAULT_APPEND_HINT;
61
+ return suggestion ? suggestion + hint : "";
62
+ }
63
+ // Update ghost input value
64
+ function updateGhost() {
65
+ if (ghostEl) {
66
+ ghostEl.value = getVisibleSuggestion();
67
+ }
68
+ }
69
+ // Create ghost input element
70
+ function createGhost() {
71
+ if (ghostEl)
72
+ return;
73
+ ghostEl = document.createElement("input");
74
+ ghostEl.type = "text";
75
+ ghostEl.tabIndex = -1;
76
+ ghostEl.readOnly = true;
77
+ ghostEl.setAttribute("aria-hidden", "true");
78
+ ghostEl.setAttribute("autocomplete", "off");
79
+ // Ensure parent has relative positioning
80
+ const parent = el.parentElement;
81
+ if (parent) {
82
+ const parentPos = getComputedStyle(parent).position;
83
+ if (parentPos === "static") {
84
+ parent.style.position = "relative";
85
+ }
86
+ }
87
+ // Style main input for proper layering
88
+ el.style.position = "relative";
89
+ el.style.zIndex = "10";
90
+ el.style.background = "transparent";
91
+ // Style ghost input
92
+ applyGhostStyles();
93
+ // Insert after main input
94
+ el.insertAdjacentElement("afterend", ghostEl);
95
+ }
96
+ function applyGhostStyles() {
97
+ if (!ghostEl)
98
+ return;
99
+ const computed = getComputedStyle(el);
100
+ // Copy relevant styles from main input
101
+ ghostEl.style.position = "absolute";
102
+ ghostEl.style.inset = "0";
103
+ ghostEl.style.pointerEvents = "none";
104
+ ghostEl.style.opacity = "0.4";
105
+ ghostEl.style.zIndex = "0";
106
+ ghostEl.style.font = computed.font;
107
+ ghostEl.style.padding = computed.padding;
108
+ ghostEl.style.borderColor = "transparent";
109
+ ghostEl.style.background = "transparent";
110
+ ghostEl.style.color = "inherit";
111
+ ghostEl.style.outline = "none";
112
+ ghostEl.style.width = "100%";
113
+ ghostEl.style.boxSizing = "border-box";
114
+ ghostEl.style.transition = "none";
115
+ // Apply custom class if provided
116
+ if (currentOpts.classGhost) {
117
+ ghostEl.className = currentOpts.classGhost;
118
+ }
119
+ }
120
+ // Debounced search
121
+ function scheduleSearch(query) {
122
+ if (debounceTimer)
123
+ clearTimeout(debounceTimer);
124
+ debounceTimer = setTimeout(async () => {
125
+ await performSearch(query);
126
+ }, currentOpts.debounceMs ?? DEFAULT_DEBOUNCE_MS);
127
+ }
128
+ async function performSearch(query) {
129
+ if (!options || !currentOpts.getOptions)
130
+ return;
131
+ options.clear();
132
+ if (!allowListAll && !query) {
133
+ updateGhost();
134
+ return;
135
+ }
136
+ currentOpts.onFetchingChange?.(true);
137
+ try {
138
+ const results = await currentOpts.getOptions(query, []);
139
+ if (!results.length) {
140
+ updateGhost();
141
+ return;
142
+ }
143
+ // Filter for exact "starts with" match
144
+ let found = results;
145
+ if (query) {
146
+ found = results.filter((item) => {
147
+ const label = unaccent(renderLabel(item).toLowerCase());
148
+ return label.startsWith(unaccent(query.toLowerCase()));
149
+ });
150
+ }
151
+ if (!found.length) {
152
+ updateGhost();
153
+ return;
154
+ }
155
+ options.addMany(found);
156
+ options.setActiveFirst();
157
+ updateGhost();
158
+ }
159
+ catch (err) {
160
+ clog.error(err);
161
+ }
162
+ finally {
163
+ currentOpts.onFetchingChange?.(false);
164
+ }
165
+ }
166
+ // Submit handler
167
+ function handleSubmit(value) {
168
+ value = (value || "").trim();
169
+ if (value) {
170
+ currentOpts.onSubmit?.(value);
171
+ }
172
+ allowListAll = false;
173
+ }
174
+ // Set input value (triggers Svelte binding update via input event)
175
+ function setInputValue(newValue) {
176
+ el.value = newValue;
177
+ el.dispatchEvent(new Event("input", { bubbles: true }));
178
+ }
179
+ // Keyboard handler
180
+ function onKeyDown(e) {
181
+ // Ignore with modifier keys
182
+ if (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey)
183
+ return;
184
+ const cursorPos = el.selectionStart ?? 0;
185
+ const value = el.value || "";
186
+ // ArrowRight only applies if cursor at end
187
+ if (value.length && e.key === "ArrowRight" && cursorPos < value.length) {
188
+ return;
189
+ }
190
+ // Tab after Enter should behave normally
191
+ if (previousKey === "Enter" && e.key === "Tab") {
192
+ return;
193
+ }
194
+ // Allow listing all on arrow keys with empty value
195
+ allowListAll =
196
+ !value.length &&
197
+ !currentOpts.noListAllOnEmptyQ &&
198
+ ["ArrowDown", "ArrowUp"].includes(e.key);
199
+ if (allowListAll) {
200
+ scheduleSearch("");
201
+ }
202
+ const suggestion = options?.active ? renderLabel(options.active) : null;
203
+ if (e.key === "ArrowDown") {
204
+ options?.setActiveNext();
205
+ updateGhost();
206
+ e.preventDefault();
207
+ }
208
+ else if (e.key === "ArrowUp") {
209
+ options?.setActivePrevious();
210
+ updateGhost();
211
+ e.preventDefault();
212
+ }
213
+ else if (["ArrowRight", "Tab"].includes(e.key) && suggestion) {
214
+ if (e.key === "Tab" && value !== suggestion) {
215
+ e.preventDefault();
216
+ }
217
+ setInputValue(suggestion);
218
+ // Clear options and ghost immediately after accepting suggestion
219
+ options?.clear();
220
+ updateGhost();
221
+ if (e.key === "Tab")
222
+ handleSubmit(suggestion);
223
+ }
224
+ else if (e.key === "Enter") {
225
+ options?.clear();
226
+ handleSubmit(value);
227
+ }
228
+ else if (e.key === "Backspace" && cursorPos === 0) {
229
+ currentOpts.onDeleteRequest?.();
230
+ }
231
+ previousKey = e.key;
232
+ }
233
+ // Input handler (for value changes)
234
+ function onInput() {
235
+ const value = el.value || "";
236
+ // Quick clear if needed
237
+ if ((!allowListAll && !value) || !options?.active) {
238
+ options?.clear();
239
+ updateGhost();
240
+ }
241
+ else if (options?.active) {
242
+ // Check if current suggestion still matches
243
+ const suggestion = renderLabel(options.active);
244
+ if (!suggestion.toLowerCase().startsWith(value.toLowerCase())) {
245
+ options.clear();
246
+ updateGhost();
247
+ }
248
+ }
249
+ // Schedule new search
250
+ scheduleSearch(value);
251
+ }
252
+ // Blur handler
253
+ function onBlur() {
254
+ handleSubmit(el.value || "");
255
+ }
256
+ // Remove ghost and cleanup
257
+ function destroyGhost() {
258
+ ghostEl?.remove();
259
+ ghostEl = null;
260
+ // Reset main input styles
261
+ el.style.position = "";
262
+ el.style.zIndex = "";
263
+ el.style.background = "";
264
+ }
265
+ // Main effect for setup/cleanup
266
+ $effect(() => {
267
+ const opts = fn?.() || { getOptions: async () => [] };
268
+ currentOpts = {
269
+ enabled: opts.enabled ?? true,
270
+ getOptions: opts.getOptions,
271
+ renderOptionLabel: opts.renderOptionLabel,
272
+ itemIdPropName: opts.itemIdPropName || "id",
273
+ debounceMs: opts.debounceMs ?? DEFAULT_DEBOUNCE_MS,
274
+ onSubmit: opts.onSubmit,
275
+ onDeleteRequest: opts.onDeleteRequest,
276
+ onFetchingChange: opts.onFetchingChange,
277
+ noListAllOnEmptyQ: opts.noListAllOnEmptyQ ?? false,
278
+ appendHint: opts.appendHint ?? DEFAULT_APPEND_HINT,
279
+ classGhost: opts.classGhost,
280
+ onResetGhost: opts.onResetGhost,
281
+ };
282
+ if (!currentOpts.enabled || !currentOpts.getOptions) {
283
+ // Cleanup if disabled or no getOptions provided
284
+ destroyGhost();
285
+ return;
286
+ }
287
+ // Initialize ItemCollection if needed
288
+ if (!options) {
289
+ options = new ItemCollection([], {
290
+ idPropName: currentOpts.itemIdPropName,
291
+ searchable: { getContent: (item) => renderLabel(item) },
292
+ allowNextPrevCycle: true,
293
+ sortFn: (a, b) => {
294
+ const _a = renderLabel(a).toLowerCase();
295
+ const _b = renderLabel(b).toLowerCase();
296
+ return _a.localeCompare(_b);
297
+ },
298
+ });
299
+ }
300
+ // Create ghost input
301
+ createGhost();
302
+ // Provide reset function via callback
303
+ currentOpts.onResetGhost?.(() => {
304
+ // Cancel any pending search
305
+ if (debounceTimer) {
306
+ clearTimeout(debounceTimer);
307
+ debounceTimer = null;
308
+ }
309
+ options?.clear();
310
+ updateGhost();
311
+ });
312
+ // Add event listeners
313
+ el.addEventListener("keydown", onKeyDown);
314
+ el.addEventListener("input", onInput);
315
+ el.addEventListener("blur", onBlur);
316
+ // Set autocomplete off
317
+ el.setAttribute("autocomplete", "off");
318
+ return () => {
319
+ // Cleanup
320
+ el.removeEventListener("keydown", onKeyDown);
321
+ el.removeEventListener("input", onInput);
322
+ el.removeEventListener("blur", onBlur);
323
+ destroyGhost();
324
+ if (debounceTimer)
325
+ clearTimeout(debounceTimer);
326
+ };
327
+ });
328
+ }
package/dist/base.css ADDED
@@ -0,0 +1,17 @@
1
+ /* Base styles for STUIC components */
2
+ @layer base {
3
+ button:not(:disabled),
4
+ [role="button"]:not(:disabled) {
5
+ cursor: pointer;
6
+ }
7
+
8
+ button:disabled,
9
+ [role="button"]:disabled,
10
+ input:disabled {
11
+ cursor: not-allowed !important;
12
+ }
13
+ }
14
+
15
+ .scrollbar-thin {
16
+ scrollbar-width: thin;
17
+ }
@@ -1,5 +1,6 @@
1
1
  <script lang="ts" module>
2
2
  import type { AlertConfirmPromptStack } from "./alert-confirm-prompt-stack.svelte.js";
3
+ import type { IntentColorKey } from "../../utils/design-tokens.js";
3
4
 
4
5
  export interface Props {
5
6
  acp?: AlertConfirmPromptStack;
@@ -21,9 +22,9 @@
21
22
  classButtonCancel?: string;
22
23
  classButtonCustom?: string;
23
24
  classButtonPrimary?: string;
24
- variantButtonCancel?: string;
25
- variantButtonCustom?: string;
26
- variantButtonPrimary?: string;
25
+ intentButtonCancel?: IntentColorKey;
26
+ intentButtonCustom?: IntentColorKey;
27
+ intentButtonPrimary?: IntentColorKey;
27
28
  classSpinnerBox?: string;
28
29
  defaultIcons?: Partial<
29
30
  Record<"info" | "success" | "warn" | "error" | "spinner", () => string | undefined>
@@ -34,7 +35,6 @@
34
35
  <script lang="ts">
35
36
  import { twMerge } from "../../utils/tw-merge.js";
36
37
  import ModalDialog from "../ModalDialog/ModalDialog.svelte";
37
- import { isTHCNotEmpty } from "../Thc/Thc.svelte";
38
38
  import { AlertConfirmPromptType } from "./alert-confirm-prompt-stack.svelte.js";
39
39
  import Current from "./Current.svelte";
40
40
 
@@ -63,9 +63,9 @@
63
63
  classButtonCancel,
64
64
  classButtonCustom,
65
65
  classButtonPrimary,
66
- variantButtonCancel,
67
- variantButtonCustom,
68
- variantButtonPrimary,
66
+ intentButtonCancel,
67
+ intentButtonCustom,
68
+ intentButtonPrimary,
69
69
  }: Props = $props();
70
70
 
71
71
  let modal = $state<ModalDialog>();
@@ -124,9 +124,9 @@
124
124
  {classButtonCancel}
125
125
  {classButtonCustom}
126
126
  {classButtonPrimary}
127
- {variantButtonCancel}
128
- {variantButtonCustom}
129
- {variantButtonPrimary}
127
+ {intentButtonCancel}
128
+ {intentButtonCustom}
129
+ {intentButtonPrimary}
130
130
  />
131
131
  </ModalDialog>
132
132
  {/if}
@@ -1,4 +1,5 @@
1
1
  import type { AlertConfirmPromptStack } from "./alert-confirm-prompt-stack.svelte.js";
2
+ import type { IntentColorKey } from "../../utils/design-tokens.js";
2
3
  export interface Props {
3
4
  acp?: AlertConfirmPromptStack;
4
5
  forceAsHtml?: boolean;
@@ -19,9 +20,9 @@ export interface Props {
19
20
  classButtonCancel?: string;
20
21
  classButtonCustom?: string;
21
22
  classButtonPrimary?: string;
22
- variantButtonCancel?: string;
23
- variantButtonCustom?: string;
24
- variantButtonPrimary?: string;
23
+ intentButtonCancel?: IntentColorKey;
24
+ intentButtonCustom?: IntentColorKey;
25
+ intentButtonPrimary?: IntentColorKey;
25
26
  classSpinnerBox?: string;
26
27
  defaultIcons?: Partial<Record<"info" | "success" | "warn" | "error" | "spinner", () => string | undefined>>;
27
28
  }
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { tick } from "svelte";
2
+ import type { IntentColorKey } from "../../utils/design-tokens.js";
3
3
  import { twMerge } from "../../utils/tw-merge.js";
4
4
  import Button from "../Button/Button.svelte";
5
5
  import FieldInput from "../Input/FieldInput.svelte";
@@ -37,9 +37,9 @@
37
37
  classButtonCancel?: string;
38
38
  classButtonCustom?: string;
39
39
  classButtonPrimary?: string;
40
- variantButtonCancel?: string;
41
- variantButtonCustom?: string;
42
- variantButtonPrimary?: string;
40
+ intentButtonCancel?: IntentColorKey;
41
+ intentButtonCustom?: IntentColorKey;
42
+ intentButtonPrimary?: IntentColorKey;
43
43
  classSpinnerBox?: string;
44
44
  defaultIcons?: Partial<
45
45
  Record<"info" | "success" | "warn" | "error" | "spinner", () => string | undefined>
@@ -66,9 +66,9 @@
66
66
  classButtonCancel,
67
67
  classButtonCustom,
68
68
  classButtonPrimary,
69
- variantButtonCancel,
70
- variantButtonCustom,
71
- variantButtonPrimary = "primary",
69
+ intentButtonCancel,
70
+ intentButtonCustom,
71
+ intentButtonPrimary = "primary",
72
72
  classSpinnerBox,
73
73
  defaultIcons = acpDefaultIcons,
74
74
  }: Props = $props();
@@ -119,11 +119,8 @@
119
119
 
120
120
  const _classIconBox = `size-12 sm:size-10
121
121
  mt-1 mb-4 sm:my-0 sm:mr-5
122
- mx-auto
123
- flex flex-shrink-0 items-center justify-center
124
- rounded-full
125
- bg-neutral-950/10 text-neutral-950/80
126
- dark:bg-neutral-50/20 dark:text-neutral-50/80`;
122
+ mx-auto
123
+ flex flex-shrink-0 items-center justify-center`;
127
124
 
128
125
  const _classContentBox = `mt-3 sm:mt-0 flex-1 h-full flex flex-col`; // overflow-hidden
129
126
 
@@ -137,12 +134,12 @@
137
134
 
138
135
  const _classButton = "min-w-24 text-center w-full sm:w-auto";
139
136
 
140
- const _classSpinnerBox = `absolute inset-0 flex items-center justify-center bg-white/75 dark:bg-black/75`;
137
+ const _classSpinnerBox = `absolute inset-0 flex items-center justify-center`;
141
138
 
142
139
  let hasCustom = $derived(current.labelCustom && typeof current.onCustom === "function");
143
140
  </script>
144
141
 
145
- <div class={twMerge("stuic-acp", _class, classProp)}>
142
+ <div class={twMerge("stuic-acp", _class, classProp)} data-variant={current.variant}>
146
143
  <div class={twMerge("wrap", _classWrap, classWrap)}>
147
144
  {#if iconHtml}
148
145
  <div
@@ -215,7 +212,7 @@
215
212
  <li class={twMerge(_classMenuLi, classMenuLi, hasCustom && classMenuLiCustom)}>
216
213
  <CmpButtonCancel
217
214
  class={twMerge("cancel", _classButton, classButton, classButtonCancel)}
218
- variant={variantButtonCancel}
215
+ intent={intentButtonCancel}
219
216
  disabled={isPending}
220
217
  onclick={createOnClick("cancel", current.onCancel)}
221
218
  >
@@ -227,7 +224,7 @@
227
224
  <li class={twMerge(_classMenuLi, classMenuLi, classMenuLiCustom)}>
228
225
  <CmpButtonCustom
229
226
  class={twMerge("custom", _classButton, classButton, classButtonCustom)}
230
- variant={variantButtonCustom}
227
+ intent={intentButtonCustom}
231
228
  disabled={isPending}
232
229
  onclick={createOnClick("custom", current.onCustom!)}
233
230
  >
@@ -238,7 +235,7 @@
238
235
  <li class={twMerge(_classMenuLi, classMenuLi, hasCustom && classMenuLiCustom)}>
239
236
  <CmpButtonOk
240
237
  class={twMerge("ok", _classButton, classButton, classButtonPrimary)}
241
- variant={variantButtonPrimary}
238
+ intent={intentButtonPrimary}
242
239
  disabled={isPending}
243
240
  onclick={createOnClick("ok", current.onOk)}
244
241
  bind:el={okButtonEl}
@@ -249,7 +246,7 @@
249
246
  </menu>
250
247
  {#if isPending}
251
248
  <div class={twMerge("spinner-box", _classSpinnerBox, classSpinnerBox)}>
252
- <Spinner class="w-8" />
249
+ <Spinner />
253
250
  </div>
254
251
  {/if}
255
252
  </div>
@@ -1,3 +1,4 @@
1
+ import type { IntentColorKey } from "../../utils/design-tokens.js";
1
2
  import { type AlertConfirmPromptStack } from "./alert-confirm-prompt-stack.svelte.js";
2
3
  interface Props {
3
4
  acp?: AlertConfirmPromptStack;
@@ -19,9 +20,9 @@ interface Props {
19
20
  classButtonCancel?: string;
20
21
  classButtonCustom?: string;
21
22
  classButtonPrimary?: string;
22
- variantButtonCancel?: string;
23
- variantButtonCustom?: string;
24
- variantButtonPrimary?: string;
23
+ intentButtonCancel?: IntentColorKey;
24
+ intentButtonCustom?: IntentColorKey;
25
+ intentButtonPrimary?: IntentColorKey;
25
26
  classSpinnerBox?: string;
26
27
  defaultIcons?: Partial<Record<"info" | "success" | "warn" | "error" | "spinner", () => string | undefined>>;
27
28
  }
@@ -1,8 +1,9 @@
1
1
  import { iconAlertInfo, iconAlertSuccess, iconAlertWarning, iconAlertError, iconRefresh, } from "../../icons/index.js";
2
+ const size = 23;
2
3
  export const acpDefaultIcons = {
3
- info: () => iconAlertInfo({ size: 24 }),
4
- success: () => iconAlertSuccess({}),
5
- warn: () => iconAlertWarning({ class: "-mt-[3px]" }), // move up a little because it looks better with the triangle
6
- error: () => iconAlertError({}),
4
+ info: () => iconAlertInfo({ size }),
5
+ success: () => iconAlertSuccess({ size }),
6
+ warn: () => iconAlertWarning({ size, class: "-mt-[2px]" }), // move up a little because it looks better with the triangle
7
+ error: () => iconAlertError({ size }),
7
8
  spinner: () => iconRefresh({ size: 32, class: "opacity-50" }),
8
9
  };
@@ -0,0 +1,66 @@
1
+ /* ============================================================================
2
+ ALERT CONFIRM PROMPT COMPONENT TOKENS
3
+ Override globally: :root { --stuic-acp-icon-box-radius: 0; }
4
+ Override locally: <AlertConfirmPrompt style="--stuic-acp-icon-box-radius: 4px;">
5
+ ============================================================================ */
6
+
7
+ :root {
8
+ --stuic-acp-icon-box-radius: 9999px;
9
+ --stuic-acp-spinner-overlay-opacity: 0.8;
10
+ }
11
+
12
+ @layer components {
13
+ /* ============================================================================
14
+ ICON BOX - colors set by variant
15
+ ============================================================================ */
16
+
17
+ .stuic-acp .icon-box {
18
+ background: var(--_icon-bg);
19
+ color: var(--_icon-text);
20
+ border-radius: var(--stuic-acp-icon-box-radius);
21
+ }
22
+
23
+ /* ============================================================================
24
+ SPINNER OVERLAY - uses surface color
25
+ ============================================================================ */
26
+
27
+ .stuic-acp .spinner-box {
28
+ background: color-mix(
29
+ in srgb,
30
+ var(--stuic-color-surface) calc(var(--stuic-acp-spinner-overlay-opacity) * 100%),
31
+ transparent
32
+ );
33
+ }
34
+
35
+ /* ============================================================================
36
+ VARIANT COLOR MAPPING
37
+ Each variant sets internal CSS vars for the icon box.
38
+ ============================================================================ */
39
+
40
+ /* NOTE: using primary in all except error */
41
+
42
+ /* Variant: info (default) */
43
+ .stuic-acp[data-variant="info"],
44
+ .stuic-acp:not([data-variant]) {
45
+ --_icon-bg: color-mix(in srgb, var(--stuic-color-primary) 15%, transparent);
46
+ --_icon-text: var(--stuic-color-primary);
47
+ }
48
+
49
+ /* Variant: success */
50
+ .stuic-acp[data-variant="success"] {
51
+ --_icon-bg: color-mix(in srgb, var(--stuic-color-primary) 15%, transparent);
52
+ --_icon-text: var(--stuic-color-primary);
53
+ }
54
+
55
+ /* Variant: warn */
56
+ .stuic-acp[data-variant="warn"] {
57
+ --_icon-bg: color-mix(in srgb, var(--stuic-color-primary) 15%, transparent);
58
+ --_icon-text: var(--stuic-color-primary);
59
+ }
60
+
61
+ /* Variant: error */
62
+ .stuic-acp[data-variant="error"] {
63
+ --_icon-bg: color-mix(in srgb, var(--stuic-color-destructive) 15%, transparent);
64
+ --_icon-text: var(--stuic-color-destructive);
65
+ }
66
+ }