@marianmeres/stuic 3.0.0 → 3.0.2

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 (156) hide show
  1. package/dist/actions/index.d.ts +1 -0
  2. package/dist/actions/index.js +1 -0
  3. package/dist/actions/typeahead.svelte.d.ts +53 -0
  4. package/dist/actions/typeahead.svelte.js +328 -0
  5. package/dist/base.css +17 -0
  6. package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte +4 -3
  7. package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte.d.ts +4 -3
  8. package/dist/components/AlertConfirmPrompt/Current.svelte +1 -2
  9. package/dist/components/AlertConfirmPrompt/Current.svelte.d.ts +0 -1
  10. package/dist/components/AlertConfirmPrompt/index.css +47 -43
  11. package/dist/components/AssetsPreview/AssetsPreview.svelte +0 -1
  12. package/dist/components/AssetsPreview/AssetsPreview.svelte.d.ts +0 -1
  13. package/dist/components/AssetsPreview/index.css +31 -29
  14. package/dist/components/Avatar/Avatar.svelte +0 -1
  15. package/dist/components/Avatar/Avatar.svelte.d.ts +0 -1
  16. package/dist/components/Avatar/index.css +87 -85
  17. package/dist/components/Backdrop/Backdrop.svelte +0 -1
  18. package/dist/components/Backdrop/Backdrop.svelte.d.ts +0 -1
  19. package/dist/components/Backdrop/index.css +15 -13
  20. package/dist/components/Button/Button.svelte +0 -1
  21. package/dist/components/Button/Button.svelte.d.ts +0 -1
  22. package/dist/components/Button/index.css +431 -429
  23. package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte +0 -1
  24. package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte.d.ts +0 -1
  25. package/dist/components/ButtonGroupRadio/index.css +123 -117
  26. package/dist/components/Collapsible/index.css +17 -15
  27. package/dist/components/CommandMenu/CommandMenu.svelte +7 -4
  28. package/dist/components/CommandMenu/CommandMenu.svelte.d.ts +0 -1
  29. package/dist/components/CommandMenu/index.css +27 -25
  30. package/dist/components/DismissibleMessage/DismissibleMessage.svelte +0 -2
  31. package/dist/components/DismissibleMessage/DismissibleMessage.svelte.d.ts +0 -1
  32. package/dist/components/DismissibleMessage/index.css +116 -110
  33. package/dist/components/DropdownMenu/DropdownMenu.svelte +317 -74
  34. package/dist/components/DropdownMenu/DropdownMenu.svelte.d.ts +19 -1
  35. package/dist/components/DropdownMenu/index.css +236 -170
  36. package/dist/components/DropdownMenu/index.d.ts +1 -1
  37. package/dist/components/HoverExpandableWidth/HoverExpandableWidth.svelte +3 -1
  38. package/dist/components/HoverExpandableWidth/HoverExpandableWidth.svelte.d.ts +1 -0
  39. package/dist/components/Input/FieldInput.svelte +8 -0
  40. package/dist/components/Input/FieldInput.svelte.d.ts +2 -0
  41. package/dist/components/Input/FieldOptions.svelte +1 -1
  42. package/dist/components/Input/index.css +411 -398
  43. package/dist/components/KbdShortcut/KbdShortcut.svelte +4 -12
  44. package/dist/components/KbdShortcut/README.md +34 -0
  45. package/dist/components/KbdShortcut/index.css +55 -0
  46. package/dist/components/ListItemButton/ListItemButton.svelte +0 -1
  47. package/dist/components/ListItemButton/ListItemButton.svelte.d.ts +0 -1
  48. package/dist/components/ListItemButton/index.css +118 -116
  49. package/dist/components/Modal/Modal.svelte +0 -1
  50. package/dist/components/Modal/Modal.svelte.d.ts +0 -1
  51. package/dist/components/Modal/index.css +18 -16
  52. package/dist/components/ModalDialog/index.css +29 -27
  53. package/dist/components/Nav/Nav.svelte +732 -0
  54. package/dist/components/Nav/Nav.svelte.d.ts +110 -0
  55. package/dist/components/Nav/README.md +334 -0
  56. package/dist/components/Nav/index.css +318 -0
  57. package/dist/components/Nav/index.d.ts +1 -0
  58. package/dist/components/Nav/index.js +1 -0
  59. package/dist/components/Notifications/Notifications.svelte +2 -3
  60. package/dist/components/Notifications/Notifications.svelte.d.ts +0 -1
  61. package/dist/components/Notifications/index.css +158 -158
  62. package/dist/components/Notifications/notifications-stack.svelte.d.ts +4 -0
  63. package/dist/components/Notifications/notifications-stack.svelte.js +8 -0
  64. package/dist/components/Progress/Progress.svelte +4 -2
  65. package/dist/components/Progress/Progress.svelte.d.ts +1 -0
  66. package/dist/components/Progress/README.md +86 -15
  67. package/dist/components/Progress/_internal/Bar.svelte +4 -15
  68. package/dist/components/Progress/_internal/Bar.svelte.d.ts +1 -1
  69. package/dist/components/Progress/_internal/Circle.svelte +30 -2
  70. package/dist/components/Progress/_internal/Circle.svelte.d.ts +1 -0
  71. package/dist/components/Progress/index.css +47 -1
  72. package/dist/components/Skeleton/README.md +152 -0
  73. package/dist/components/Skeleton/Skeleton.svelte +6 -7
  74. package/dist/components/Skeleton/Skeleton.svelte.d.ts +0 -1
  75. package/dist/components/Skeleton/index.css +73 -43
  76. package/dist/components/Spinner/README.md +149 -37
  77. package/dist/components/Spinner/Spinner.svelte +14 -38
  78. package/dist/components/Spinner/Spinner.svelte.d.ts +2 -1
  79. package/dist/components/Spinner/SpinnerCircle.svelte +6 -34
  80. package/dist/components/Spinner/SpinnerCircle.svelte.d.ts +1 -0
  81. package/dist/components/Spinner/SpinnerCircleOscillate.svelte +10 -5
  82. package/dist/components/Spinner/SpinnerUnicode.svelte +3 -1
  83. package/dist/components/Spinner/SpinnerUnicode.svelte.d.ts +1 -0
  84. package/dist/components/Spinner/index.css +104 -0
  85. package/dist/components/Switch/README.md +34 -18
  86. package/dist/components/Switch/Switch.svelte +24 -46
  87. package/dist/components/Switch/Switch.svelte.d.ts +4 -2
  88. package/dist/components/Switch/index.css +120 -2
  89. package/dist/components/Switch/index.d.ts +1 -2
  90. package/dist/components/Switch/index.js +1 -2
  91. package/dist/components/TabbedMenu/README.md +28 -17
  92. package/dist/components/TabbedMenu/TabbedMenu.svelte +5 -46
  93. package/dist/components/TabbedMenu/TabbedMenu.svelte.d.ts +0 -1
  94. package/dist/components/TabbedMenu/index.css +85 -3
  95. package/dist/components/ThemePreview/ThemePreview.svelte +86 -33
  96. package/dist/components/ThemePreview/ThemePreview.svelte.d.ts +3 -1
  97. package/dist/components/ThemePreview/index.css +24 -8
  98. package/dist/components/TwCheck/README.md +32 -13
  99. package/dist/components/TwCheck/TwCheck.svelte +11 -9
  100. package/dist/components/TwCheck/TwCheck.svelte.d.ts +0 -1
  101. package/dist/components/TwCheck/index.css +14 -0
  102. package/dist/components/TypeaheadInput/TypeaheadInput.svelte +19 -187
  103. package/dist/components/TypeaheadInput/TypeaheadInput.svelte.d.ts +4 -2
  104. package/dist/icons/index.d.ts +1 -0
  105. package/dist/icons/index.js +1 -0
  106. package/dist/index.css +44 -39
  107. package/dist/index.d.ts +1 -0
  108. package/dist/index.js +1 -0
  109. package/dist/themes/blue-orange.css +246 -156
  110. package/dist/themes/blue-orange.js +24 -0
  111. package/dist/themes/cyan-red.css +246 -156
  112. package/dist/themes/cyan-red.js +24 -0
  113. package/dist/themes/cyan-slate.css +246 -156
  114. package/dist/themes/cyan-slate.js +25 -1
  115. package/dist/themes/emerald-pink.css +246 -156
  116. package/dist/themes/emerald-pink.js +25 -1
  117. package/dist/themes/fuchsia-emerald.css +246 -156
  118. package/dist/themes/fuchsia-emerald.js +25 -1
  119. package/dist/themes/gray.css +246 -156
  120. package/dist/themes/gray.js +24 -0
  121. package/dist/themes/indigo-amber.css +246 -156
  122. package/dist/themes/indigo-amber.js +26 -2
  123. package/dist/themes/neutral.css +246 -156
  124. package/dist/themes/neutral.js +24 -0
  125. package/dist/themes/pink-emerald.css +246 -156
  126. package/dist/themes/pink-emerald.js +25 -1
  127. package/dist/themes/pink-teal.css +253 -0
  128. package/dist/themes/pink-teal.d.ts +6 -0
  129. package/dist/themes/pink-teal.js +175 -0
  130. package/dist/themes/purple-yellow.css +246 -156
  131. package/dist/themes/purple-yellow.js +24 -0
  132. package/dist/themes/rainbow.css +246 -156
  133. package/dist/themes/rainbow.js +25 -1
  134. package/dist/themes/red-blue.css +246 -156
  135. package/dist/themes/red-blue.js +24 -0
  136. package/dist/themes/red-cyan.css +246 -156
  137. package/dist/themes/red-cyan.js +24 -0
  138. package/dist/themes/red-sky.css +253 -0
  139. package/dist/themes/red-sky.d.ts +6 -0
  140. package/dist/themes/red-sky.js +175 -0
  141. package/dist/themes/rose-teal.css +246 -156
  142. package/dist/themes/rose-teal.js +24 -0
  143. package/dist/themes/sky-amber.css +246 -156
  144. package/dist/themes/sky-amber.js +26 -2
  145. package/dist/themes/slate-cyan.css +246 -156
  146. package/dist/themes/slate-cyan.js +25 -1
  147. package/dist/themes/teal-rose.css +246 -156
  148. package/dist/themes/teal-rose.js +24 -0
  149. package/dist/themes/violet-lime.css +246 -156
  150. package/dist/themes/violet-lime.js +27 -3
  151. package/dist/utils/design-tokens.d.ts +1 -1
  152. package/dist/utils/design-tokens.js +44 -3
  153. package/dist/utils/storage-abstraction.js +1 -1
  154. package/package.json +11 -28
  155. package/dist/components/Switch/SwitchButton.svelte +0 -134
  156. package/dist/components/Switch/SwitchButton.svelte.d.ts +0 -21
@@ -8,4 +8,5 @@ export * from "./popover/popover.svelte.js";
8
8
  export * from "./resizable-width.svelte.js";
9
9
  export * from "./tooltip/tooltip.svelte.js";
10
10
  export * from "./trim.svelte.js";
11
+ export * from "./typeahead.svelte.js";
11
12
  export * from "./validate.svelte.js";
@@ -8,4 +8,5 @@ export * from "./popover/popover.svelte.js";
8
8
  export * from "./resizable-width.svelte.js";
9
9
  export * from "./tooltip/tooltip.svelte.js";
10
10
  export * from "./trim.svelte.js";
11
+ export * from "./typeahead.svelte.js";
11
12
  export * from "./validate.svelte.js";
@@ -0,0 +1,53 @@
1
+ import type { Item } from "@marianmeres/item-collection";
2
+ /**
3
+ * Configuration options for the typeahead action.
4
+ */
5
+ export interface TypeaheadOptions<T extends Item = Item> {
6
+ /** Whether the typeahead functionality is enabled (default: true) */
7
+ enabled?: boolean;
8
+ /** Async function to fetch options based on the current query (required when enabled) */
9
+ getOptions?: (query: string, current: T[]) => Promise<T[]>;
10
+ /** Custom function to render the label for an item */
11
+ renderOptionLabel?: (item: T) => string;
12
+ /** Property name to use as item ID (default: "id") */
13
+ itemIdPropName?: string;
14
+ /** Debounce delay in milliseconds (default: 150) */
15
+ debounceMs?: number;
16
+ /** Callback when value is submitted (Enter, Tab, blur) */
17
+ onSubmit?: (value: string) => void;
18
+ /** Callback when Backspace is pressed at cursor position 0 */
19
+ onDeleteRequest?: () => void;
20
+ /** Callback when fetching state changes */
21
+ onFetchingChange?: (isFetching: boolean) => void;
22
+ /** Disable showing all options when arrow keys pressed on empty input */
23
+ noListAllOnEmptyQ?: boolean;
24
+ /** Hint text appended to the visible suggestion (default: " [tab]") */
25
+ appendHint?: string;
26
+ /** CSS class for the ghost input element */
27
+ classGhost?: string;
28
+ /** Callback that provides a reset function to clear ghost text programmatically */
29
+ onResetGhost?: (resetFn: () => void) => void;
30
+ }
31
+ /**
32
+ * A Svelte action that adds typeahead/autocomplete functionality to an input element.
33
+ *
34
+ * Creates a "ghost input" behind the main input to show suggestions. Supports keyboard
35
+ * navigation with ArrowUp/Down, accepts suggestions with Tab/Enter/ArrowRight, and
36
+ * handles accent-insensitive matching.
37
+ *
38
+ * @param el - The input element to add typeahead to
39
+ * @param fn - Function returning configuration options
40
+ *
41
+ * @example
42
+ * ```svelte
43
+ * <input
44
+ * bind:value
45
+ * use:typeahead={() => ({
46
+ * getOptions: async (q) => fetchSuggestions(q),
47
+ * renderOptionLabel: (item) => item.name,
48
+ * onSubmit: (v) => console.log('Selected:', v),
49
+ * })}
50
+ * />
51
+ * ```
52
+ */
53
+ export declare function typeahead<T extends Item = Item>(el: HTMLInputElement, fn?: () => TypeaheadOptions<T>): void;
@@ -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
- intentButtonCancel?: string;
25
- intentButtonCustom?: string;
26
- intentButtonPrimary?: 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>
@@ -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
- intentButtonCancel?: string;
23
- intentButtonCustom?: string;
24
- intentButtonPrimary?: 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
  }
@@ -11,7 +11,6 @@
11
11
  AlertConfirmPromptType,
12
12
  type AlertConfirmPromptStack,
13
13
  } from "./alert-confirm-prompt-stack.svelte.js";
14
- import "./index.css";
15
14
 
16
15
  const { ALERT, CONFIRM, PROMPT } = AlertConfirmPromptType;
17
16
  const isFn = (v: any) => typeof v === "function";
@@ -247,7 +246,7 @@
247
246
  </menu>
248
247
  {#if isPending}
249
248
  <div class={twMerge("spinner-box", _classSpinnerBox, classSpinnerBox)}>
250
- <Spinner class="w-8" />
249
+ <Spinner />
251
250
  </div>
252
251
  {/if}
253
252
  </div>
@@ -1,6 +1,5 @@
1
1
  import type { IntentColorKey } from "../../utils/design-tokens.js";
2
2
  import { type AlertConfirmPromptStack } from "./alert-confirm-prompt-stack.svelte.js";
3
- import "./index.css";
4
3
  interface Props {
5
4
  acp?: AlertConfirmPromptStack;
6
5
  isPending?: boolean;
@@ -6,57 +6,61 @@
6
6
 
7
7
  :root {
8
8
  --stuic-acp-icon-box-radius: 9999px;
9
- --stuic-acp-spinner-overlay-opacity: 0.75;
9
+ --stuic-acp-spinner-overlay-opacity: 0.8;
10
10
  }
11
11
 
12
- /* ============================================================================
13
- ICON BOX - colors set by variant
14
- ============================================================================ */
12
+ @layer components {
13
+ /* ============================================================================
14
+ ICON BOX - colors set by variant
15
+ ============================================================================ */
15
16
 
16
- .stuic-acp .icon-box {
17
- background: var(--_icon-bg);
18
- color: var(--_icon-text);
19
- border-radius: var(--stuic-acp-icon-box-radius);
20
- }
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
+ }
21
22
 
22
- /* ============================================================================
23
- SPINNER OVERLAY - uses surface color
24
- ============================================================================ */
23
+ /* ============================================================================
24
+ SPINNER OVERLAY - uses surface color
25
+ ============================================================================ */
25
26
 
26
- .stuic-acp .spinner-box {
27
- background: color-mix(
28
- in srgb,
29
- var(--stuic-color-surface) calc(var(--stuic-acp-spinner-overlay-opacity) * 100%),
30
- transparent
31
- );
32
- }
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
+ }
33
34
 
34
- /* ============================================================================
35
- VARIANT COLOR MAPPING
36
- Each variant sets internal CSS vars for the icon box.
37
- ============================================================================ */
35
+ /* ============================================================================
36
+ VARIANT COLOR MAPPING
37
+ Each variant sets internal CSS vars for the icon box.
38
+ ============================================================================ */
38
39
 
39
- /* Variant: info (default) */
40
- .stuic-acp[data-variant='info'],
41
- .stuic-acp:not([data-variant]) {
42
- --_icon-bg: color-mix(in srgb, var(--stuic-color-info) 15%, transparent);
43
- --_icon-text: var(--stuic-color-info);
44
- }
40
+ /* NOTE: using primary in all except error */
45
41
 
46
- /* Variant: success */
47
- .stuic-acp[data-variant='success'] {
48
- --_icon-bg: color-mix(in srgb, var(--stuic-color-success) 15%, transparent);
49
- --_icon-text: var(--stuic-color-success);
50
- }
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%, var(--stuic-color-background));
46
+ --_icon-text: var(--stuic-color-primary);
47
+ }
51
48
 
52
- /* Variant: warn */
53
- .stuic-acp[data-variant='warn'] {
54
- --_icon-bg: color-mix(in srgb, var(--stuic-color-warning) 15%, transparent);
55
- --_icon-text: var(--stuic-color-warning);
56
- }
49
+ /* Variant: success */
50
+ .stuic-acp[data-variant="success"] {
51
+ --_icon-bg: color-mix(in srgb, var(--stuic-color-primary) 15%, var(--stuic-color-background));
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%, var(--stuic-color-background));
58
+ --_icon-text: var(--stuic-color-primary);
59
+ }
57
60
 
58
- /* Variant: error */
59
- .stuic-acp[data-variant='error'] {
60
- --_icon-bg: color-mix(in srgb, var(--stuic-color-destructive) 15%, transparent);
61
- --_icon-text: var(--stuic-color-destructive);
61
+ /* Variant: error */
62
+ .stuic-acp[data-variant="error"] {
63
+ --_icon-bg: color-mix(in srgb, var(--stuic-color-destructive) 15%, var(--stuic-color-background));
64
+ --_icon-text: var(--stuic-color-destructive);
65
+ }
62
66
  }
@@ -171,7 +171,6 @@
171
171
  </script>
172
172
 
173
173
  <script lang="ts">
174
- import "./index.css";
175
174
  import Button from "../Button/Button.svelte";
176
175
  const clog = createClog("AssetsPreview", { color: "auto" });
177
176
 
@@ -26,7 +26,6 @@ export interface Props {
26
26
  clampPan?: boolean;
27
27
  }
28
28
  export declare function getAssetIcon(ext?: string): CallableFunction;
29
- import "./index.css";
30
29
  declare const AssetsPreview: import("svelte").Component<Props, {
31
30
  open: (index?: number) => void;
32
31
  close: () => void;
@@ -27,33 +27,35 @@
27
27
  --stuic-assets-preview-transition: 150ms;
28
28
  }
29
29
 
30
- /* ============================================================================
31
- LABELS (filename display, tooltip)
32
- ============================================================================ */
33
-
34
- .stuic-assets-preview-label {
35
- background: var(--stuic-assets-preview-label-bg);
36
- color: var(--stuic-assets-preview-label-text);
37
- border-radius: var(--stuic-assets-preview-label-radius);
38
- }
39
-
40
- /* ============================================================================
41
- DOT INDICATORS (pagination dots)
42
- ============================================================================ */
43
-
44
- .stuic-assets-preview-dot {
45
- width: var(--stuic-assets-preview-dot-size);
46
- height: var(--stuic-assets-preview-dot-size);
47
- border-radius: 9999px;
48
- background: var(--stuic-assets-preview-dot-bg);
49
- border: 1px solid var(--stuic-assets-preview-dot-border);
50
- transition: background var(--stuic-assets-preview-transition);
51
- }
52
-
53
- .stuic-assets-preview-dot:hover {
54
- background: var(--stuic-assets-preview-dot-bg-hover);
55
- }
56
-
57
- .stuic-assets-preview-dot.active {
58
- background: var(--stuic-assets-preview-dot-bg-active);
30
+ @layer components {
31
+ /* ============================================================================
32
+ LABELS (filename display, tooltip)
33
+ ============================================================================ */
34
+
35
+ .stuic-assets-preview-label {
36
+ background: var(--stuic-assets-preview-label-bg);
37
+ color: var(--stuic-assets-preview-label-text);
38
+ border-radius: var(--stuic-assets-preview-label-radius);
39
+ }
40
+
41
+ /* ============================================================================
42
+ DOT INDICATORS (pagination dots)
43
+ ============================================================================ */
44
+
45
+ .stuic-assets-preview-dot {
46
+ width: var(--stuic-assets-preview-dot-size);
47
+ height: var(--stuic-assets-preview-dot-size);
48
+ border-radius: 9999px;
49
+ background: var(--stuic-assets-preview-dot-bg);
50
+ border: 1px solid var(--stuic-assets-preview-dot-border);
51
+ transition: background var(--stuic-assets-preview-transition);
52
+ }
53
+
54
+ .stuic-assets-preview-dot:hover {
55
+ background: var(--stuic-assets-preview-dot-bg-hover);
56
+ }
57
+
58
+ .stuic-assets-preview-dot.active {
59
+ background: var(--stuic-assets-preview-dot-bg-active);
60
+ }
59
61
  }