@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.
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/typeahead.svelte.d.ts +53 -0
- package/dist/actions/typeahead.svelte.js +328 -0
- package/dist/base.css +17 -0
- package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte +4 -3
- package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte.d.ts +4 -3
- package/dist/components/AlertConfirmPrompt/Current.svelte +1 -2
- package/dist/components/AlertConfirmPrompt/Current.svelte.d.ts +0 -1
- package/dist/components/AlertConfirmPrompt/index.css +47 -43
- package/dist/components/AssetsPreview/AssetsPreview.svelte +0 -1
- package/dist/components/AssetsPreview/AssetsPreview.svelte.d.ts +0 -1
- package/dist/components/AssetsPreview/index.css +31 -29
- package/dist/components/Avatar/Avatar.svelte +0 -1
- package/dist/components/Avatar/Avatar.svelte.d.ts +0 -1
- package/dist/components/Avatar/index.css +87 -85
- package/dist/components/Backdrop/Backdrop.svelte +0 -1
- package/dist/components/Backdrop/Backdrop.svelte.d.ts +0 -1
- package/dist/components/Backdrop/index.css +15 -13
- package/dist/components/Button/Button.svelte +0 -1
- package/dist/components/Button/Button.svelte.d.ts +0 -1
- package/dist/components/Button/index.css +431 -429
- package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte +0 -1
- package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte.d.ts +0 -1
- package/dist/components/ButtonGroupRadio/index.css +123 -117
- package/dist/components/Collapsible/index.css +17 -15
- package/dist/components/CommandMenu/CommandMenu.svelte +7 -4
- package/dist/components/CommandMenu/CommandMenu.svelte.d.ts +0 -1
- package/dist/components/CommandMenu/index.css +27 -25
- package/dist/components/DismissibleMessage/DismissibleMessage.svelte +0 -2
- package/dist/components/DismissibleMessage/DismissibleMessage.svelte.d.ts +0 -1
- package/dist/components/DismissibleMessage/index.css +116 -110
- package/dist/components/DropdownMenu/DropdownMenu.svelte +317 -74
- package/dist/components/DropdownMenu/DropdownMenu.svelte.d.ts +19 -1
- package/dist/components/DropdownMenu/index.css +236 -170
- package/dist/components/DropdownMenu/index.d.ts +1 -1
- package/dist/components/HoverExpandableWidth/HoverExpandableWidth.svelte +3 -1
- package/dist/components/HoverExpandableWidth/HoverExpandableWidth.svelte.d.ts +1 -0
- package/dist/components/Input/FieldInput.svelte +8 -0
- package/dist/components/Input/FieldInput.svelte.d.ts +2 -0
- package/dist/components/Input/FieldOptions.svelte +1 -1
- package/dist/components/Input/index.css +411 -398
- package/dist/components/KbdShortcut/KbdShortcut.svelte +4 -12
- package/dist/components/KbdShortcut/README.md +34 -0
- package/dist/components/KbdShortcut/index.css +55 -0
- package/dist/components/ListItemButton/ListItemButton.svelte +0 -1
- package/dist/components/ListItemButton/ListItemButton.svelte.d.ts +0 -1
- package/dist/components/ListItemButton/index.css +118 -116
- package/dist/components/Modal/Modal.svelte +0 -1
- package/dist/components/Modal/Modal.svelte.d.ts +0 -1
- package/dist/components/Modal/index.css +18 -16
- package/dist/components/ModalDialog/index.css +29 -27
- package/dist/components/Nav/Nav.svelte +732 -0
- package/dist/components/Nav/Nav.svelte.d.ts +110 -0
- package/dist/components/Nav/README.md +334 -0
- package/dist/components/Nav/index.css +318 -0
- package/dist/components/Nav/index.d.ts +1 -0
- package/dist/components/Nav/index.js +1 -0
- package/dist/components/Notifications/Notifications.svelte +2 -3
- package/dist/components/Notifications/Notifications.svelte.d.ts +0 -1
- package/dist/components/Notifications/index.css +158 -158
- package/dist/components/Notifications/notifications-stack.svelte.d.ts +4 -0
- package/dist/components/Notifications/notifications-stack.svelte.js +8 -0
- package/dist/components/Progress/Progress.svelte +4 -2
- package/dist/components/Progress/Progress.svelte.d.ts +1 -0
- package/dist/components/Progress/README.md +86 -15
- package/dist/components/Progress/_internal/Bar.svelte +4 -15
- package/dist/components/Progress/_internal/Bar.svelte.d.ts +1 -1
- package/dist/components/Progress/_internal/Circle.svelte +30 -2
- package/dist/components/Progress/_internal/Circle.svelte.d.ts +1 -0
- package/dist/components/Progress/index.css +47 -1
- package/dist/components/Skeleton/README.md +152 -0
- package/dist/components/Skeleton/Skeleton.svelte +6 -7
- package/dist/components/Skeleton/Skeleton.svelte.d.ts +0 -1
- package/dist/components/Skeleton/index.css +73 -43
- package/dist/components/Spinner/README.md +149 -37
- package/dist/components/Spinner/Spinner.svelte +14 -38
- package/dist/components/Spinner/Spinner.svelte.d.ts +2 -1
- package/dist/components/Spinner/SpinnerCircle.svelte +6 -34
- package/dist/components/Spinner/SpinnerCircle.svelte.d.ts +1 -0
- package/dist/components/Spinner/SpinnerCircleOscillate.svelte +10 -5
- package/dist/components/Spinner/SpinnerUnicode.svelte +3 -1
- package/dist/components/Spinner/SpinnerUnicode.svelte.d.ts +1 -0
- package/dist/components/Spinner/index.css +104 -0
- package/dist/components/Switch/README.md +34 -18
- package/dist/components/Switch/Switch.svelte +24 -46
- package/dist/components/Switch/Switch.svelte.d.ts +4 -2
- package/dist/components/Switch/index.css +120 -2
- package/dist/components/Switch/index.d.ts +1 -2
- package/dist/components/Switch/index.js +1 -2
- package/dist/components/TabbedMenu/README.md +28 -17
- package/dist/components/TabbedMenu/TabbedMenu.svelte +5 -46
- package/dist/components/TabbedMenu/TabbedMenu.svelte.d.ts +0 -1
- package/dist/components/TabbedMenu/index.css +85 -3
- package/dist/components/ThemePreview/ThemePreview.svelte +86 -33
- package/dist/components/ThemePreview/ThemePreview.svelte.d.ts +3 -1
- package/dist/components/ThemePreview/index.css +24 -8
- package/dist/components/TwCheck/README.md +32 -13
- package/dist/components/TwCheck/TwCheck.svelte +11 -9
- package/dist/components/TwCheck/TwCheck.svelte.d.ts +0 -1
- package/dist/components/TwCheck/index.css +14 -0
- package/dist/components/TypeaheadInput/TypeaheadInput.svelte +19 -187
- package/dist/components/TypeaheadInput/TypeaheadInput.svelte.d.ts +4 -2
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.css +44 -39
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/themes/blue-orange.css +246 -156
- package/dist/themes/blue-orange.js +24 -0
- package/dist/themes/cyan-red.css +246 -156
- package/dist/themes/cyan-red.js +24 -0
- package/dist/themes/cyan-slate.css +246 -156
- package/dist/themes/cyan-slate.js +25 -1
- package/dist/themes/emerald-pink.css +246 -156
- package/dist/themes/emerald-pink.js +25 -1
- package/dist/themes/fuchsia-emerald.css +246 -156
- package/dist/themes/fuchsia-emerald.js +25 -1
- package/dist/themes/gray.css +246 -156
- package/dist/themes/gray.js +24 -0
- package/dist/themes/indigo-amber.css +246 -156
- package/dist/themes/indigo-amber.js +26 -2
- package/dist/themes/neutral.css +246 -156
- package/dist/themes/neutral.js +24 -0
- package/dist/themes/pink-emerald.css +246 -156
- package/dist/themes/pink-emerald.js +25 -1
- package/dist/themes/pink-teal.css +253 -0
- package/dist/themes/pink-teal.d.ts +6 -0
- package/dist/themes/pink-teal.js +175 -0
- package/dist/themes/purple-yellow.css +246 -156
- package/dist/themes/purple-yellow.js +24 -0
- package/dist/themes/rainbow.css +246 -156
- package/dist/themes/rainbow.js +25 -1
- package/dist/themes/red-blue.css +246 -156
- package/dist/themes/red-blue.js +24 -0
- package/dist/themes/red-cyan.css +246 -156
- package/dist/themes/red-cyan.js +24 -0
- package/dist/themes/red-sky.css +253 -0
- package/dist/themes/red-sky.d.ts +6 -0
- package/dist/themes/red-sky.js +175 -0
- package/dist/themes/rose-teal.css +246 -156
- package/dist/themes/rose-teal.js +24 -0
- package/dist/themes/sky-amber.css +246 -156
- package/dist/themes/sky-amber.js +26 -2
- package/dist/themes/slate-cyan.css +246 -156
- package/dist/themes/slate-cyan.js +25 -1
- package/dist/themes/teal-rose.css +246 -156
- package/dist/themes/teal-rose.js +24 -0
- package/dist/themes/violet-lime.css +246 -156
- package/dist/themes/violet-lime.js +27 -3
- package/dist/utils/design-tokens.d.ts +1 -1
- package/dist/utils/design-tokens.js +44 -3
- package/dist/utils/storage-abstraction.js +1 -1
- package/package.json +11 -28
- package/dist/components/Switch/SwitchButton.svelte +0 -134
- package/dist/components/Switch/SwitchButton.svelte.d.ts +0 -21
package/dist/actions/index.d.ts
CHANGED
package/dist/actions/index.js
CHANGED
|
@@ -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?:
|
|
25
|
-
intentButtonCustom?:
|
|
26
|
-
intentButtonPrimary?:
|
|
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?:
|
|
23
|
-
intentButtonCustom?:
|
|
24
|
-
intentButtonPrimary?:
|
|
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
|
|
249
|
+
<Spinner />
|
|
251
250
|
</div>
|
|
252
251
|
{/if}
|
|
253
252
|
</div>
|
|
@@ -6,57 +6,61 @@
|
|
|
6
6
|
|
|
7
7
|
:root {
|
|
8
8
|
--stuic-acp-icon-box-radius: 9999px;
|
|
9
|
-
--stuic-acp-spinner-overlay-opacity: 0.
|
|
9
|
+
--stuic-acp-spinner-overlay-opacity: 0.8;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
@layer components {
|
|
13
|
+
/* ============================================================================
|
|
14
|
+
ICON BOX - colors set by variant
|
|
15
|
+
============================================================================ */
|
|
15
16
|
|
|
16
|
-
.stuic-acp .icon-box {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
24
|
-
|
|
23
|
+
/* ============================================================================
|
|
24
|
+
SPINNER OVERLAY - uses surface color
|
|
25
|
+
============================================================================ */
|
|
25
26
|
|
|
26
|
-
.stuic-acp .spinner-box {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
/* ============================================================================
|
|
36
|
+
VARIANT COLOR MAPPING
|
|
37
|
+
Each variant sets internal CSS vars for the icon box.
|
|
38
|
+
============================================================================ */
|
|
38
39
|
|
|
39
|
-
/*
|
|
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:
|
|
47
|
-
.stuic-acp[data-variant=
|
|
48
|
-
|
|
49
|
-
|
|
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:
|
|
53
|
-
.stuic-acp[data-variant=
|
|
54
|
-
|
|
55
|
-
|
|
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=
|
|
60
|
-
|
|
61
|
-
|
|
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
|
}
|
|
@@ -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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
}
|