sveltacular 1.0.32 → 1.0.33
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/forms/index.d.ts +2 -0
- package/dist/forms/index.js +2 -0
- package/dist/forms/reference-box/index.d.ts +2 -0
- package/dist/forms/reference-box/index.js +1 -0
- package/dist/forms/reference-box/reference-box.d.ts +8 -0
- package/dist/forms/reference-box/reference-box.js +1 -0
- package/dist/forms/reference-box/reference-box.svelte +709 -0
- package/dist/forms/reference-box/reference-box.svelte.d.ts +23 -0
- package/dist/forms/tag-box/tag-box.svelte +15 -6
- package/dist/forms/tag-box/tag-box.svelte.d.ts +1 -0
- package/dist/generic/chip/chip.svelte +37 -3
- package/dist/generic/chip/chip.svelte.d.ts +7 -0
- package/dist/icons/icon-data.js +9 -0
- package/dist/icons/types.d.ts +1 -1
- package/package.json +1 -1
package/dist/forms/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { default as NewOrExistingCombo } from './combo/new-or-existing-combo.sve
|
|
|
10
10
|
export { default as NumberBox } from './number-box/number-box.svelte';
|
|
11
11
|
export { default as NumberRangeBox } from './number-range-box/number-range-box.svelte';
|
|
12
12
|
export { default as PhoneBox } from './phone-box/phone-box.svelte';
|
|
13
|
+
export { default as ReferenceBox } from './reference-box/reference-box.svelte';
|
|
13
14
|
export { default as Slider } from './slider/slider.svelte';
|
|
14
15
|
export { default as TagBox } from './tag-box/tag-box.svelte';
|
|
15
16
|
export { default as TextArea } from './text-area/text-area.svelte';
|
|
@@ -20,6 +21,7 @@ export * from './check-box/index.js';
|
|
|
20
21
|
export * from './list-box/index.js';
|
|
21
22
|
export * from './phone-box/index.js';
|
|
22
23
|
export * from './radio-group/index.js';
|
|
24
|
+
export * from './reference-box/index.js';
|
|
23
25
|
export { default as Form } from './form.svelte';
|
|
24
26
|
export { default as FormActions } from './form-actions/form-actions.svelte';
|
|
25
27
|
export { default as FormField } from './form-field/form-field.svelte';
|
package/dist/forms/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export { default as NewOrExistingCombo } from './combo/new-or-existing-combo.sve
|
|
|
11
11
|
export { default as NumberBox } from './number-box/number-box.svelte';
|
|
12
12
|
export { default as NumberRangeBox } from './number-range-box/number-range-box.svelte';
|
|
13
13
|
export { default as PhoneBox } from './phone-box/phone-box.svelte';
|
|
14
|
+
export { default as ReferenceBox } from './reference-box/reference-box.svelte';
|
|
14
15
|
export { default as Slider } from './slider/slider.svelte';
|
|
15
16
|
export { default as TagBox } from './tag-box/tag-box.svelte';
|
|
16
17
|
export { default as TextArea } from './text-area/text-area.svelte';
|
|
@@ -22,6 +23,7 @@ export * from './check-box/index.js';
|
|
|
22
23
|
export * from './list-box/index.js';
|
|
23
24
|
export * from './phone-box/index.js';
|
|
24
25
|
export * from './radio-group/index.js';
|
|
26
|
+
export * from './reference-box/index.js';
|
|
25
27
|
// Form structure components
|
|
26
28
|
export { default as Form } from './form.svelte';
|
|
27
29
|
export { default as FormActions } from './form-actions/form-actions.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as ReferenceBox } from './reference-box.svelte';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ReferenceItem = {
|
|
2
|
+
id: string | number;
|
|
3
|
+
name: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
};
|
|
6
|
+
export type SearchFunction = (text: string) => Promise<ReferenceItem[]>;
|
|
7
|
+
export type CreateNewFunction = (inputName: string) => Promise<ReferenceItem | null>;
|
|
8
|
+
export type LinkBuilderFunction = (item: ReferenceItem) => string | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { uniqueId } from '../../helpers/unique-id.js';
|
|
3
|
+
import FormField, { type FormFieldFeedback } from '../form-field/form-field.svelte';
|
|
4
|
+
import Chip from '../../generic/chip/chip.svelte';
|
|
5
|
+
import Menu from '../../generic/menu/menu.svelte';
|
|
6
|
+
import type { FormFieldSizeOptions, MenuOption } from '../../types/form.js';
|
|
7
|
+
import { onMount } from 'svelte';
|
|
8
|
+
import { browser } from '$app/environment';
|
|
9
|
+
import debounce from '../../helpers/debounce.js';
|
|
10
|
+
import type {
|
|
11
|
+
ReferenceItem,
|
|
12
|
+
SearchFunction,
|
|
13
|
+
CreateNewFunction,
|
|
14
|
+
LinkBuilderFunction
|
|
15
|
+
} from './reference-box.js';
|
|
16
|
+
import Prompt from '../../modals/prompt.svelte';
|
|
17
|
+
import { ucfirst } from '../../helpers/ucfirst.js';
|
|
18
|
+
import Icon from '../../icons/icon.svelte';
|
|
19
|
+
|
|
20
|
+
const id = uniqueId();
|
|
21
|
+
const listboxId = `${id}-listbox`;
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
value = $bindable([] as ReferenceItem[]),
|
|
25
|
+
items = [] as ReferenceItem[],
|
|
26
|
+
search = undefined as SearchFunction | undefined,
|
|
27
|
+
createNew = undefined as CreateNewFunction | undefined,
|
|
28
|
+
linkBuilder = undefined as LinkBuilderFunction | undefined,
|
|
29
|
+
resourceName = undefined as string | undefined,
|
|
30
|
+
placeholder = 'Search and add items...',
|
|
31
|
+
required = false,
|
|
32
|
+
disabled = false,
|
|
33
|
+
size = 'full' as FormFieldSizeOptions,
|
|
34
|
+
label = undefined as string | undefined,
|
|
35
|
+
helperText = undefined as string | undefined,
|
|
36
|
+
feedback = undefined as FormFieldFeedback | undefined,
|
|
37
|
+
maxItems = undefined as number | undefined,
|
|
38
|
+
onChange = undefined as ((value: ReferenceItem[]) => void) | undefined
|
|
39
|
+
}: {
|
|
40
|
+
value?: ReferenceItem[];
|
|
41
|
+
items?: ReferenceItem[];
|
|
42
|
+
search?: SearchFunction | undefined;
|
|
43
|
+
createNew?: CreateNewFunction | undefined;
|
|
44
|
+
linkBuilder?: LinkBuilderFunction | undefined;
|
|
45
|
+
resourceName?: string | undefined;
|
|
46
|
+
placeholder?: string;
|
|
47
|
+
required?: boolean;
|
|
48
|
+
disabled?: boolean;
|
|
49
|
+
size?: FormFieldSizeOptions;
|
|
50
|
+
label?: string;
|
|
51
|
+
helperText?: string;
|
|
52
|
+
feedback?: FormFieldFeedback | undefined;
|
|
53
|
+
maxItems?: number | undefined;
|
|
54
|
+
onChange?: ((value: ReferenceItem[]) => void) | undefined;
|
|
55
|
+
} = $props();
|
|
56
|
+
|
|
57
|
+
let searchText = $state('');
|
|
58
|
+
let isMenuOpen = $state(false);
|
|
59
|
+
let highlightIndex = $state(-1);
|
|
60
|
+
let inputElement: HTMLInputElement | null = $state(null);
|
|
61
|
+
let containerElement: HTMLDivElement | null = $state(null);
|
|
62
|
+
let invalidAttempt = $state(false);
|
|
63
|
+
let localItems = $state<ReferenceItem[]>([]);
|
|
64
|
+
let isLoading = $state(false);
|
|
65
|
+
let showPrompt = $state(false);
|
|
66
|
+
let isCreating = $state(false);
|
|
67
|
+
let createError = $state<string | null>(null);
|
|
68
|
+
let promptKey = $state(0);
|
|
69
|
+
|
|
70
|
+
// Use local items when search function is provided, otherwise use static items
|
|
71
|
+
let currentItems = $derived(search ? localItems : items);
|
|
72
|
+
|
|
73
|
+
// Convert ReferenceItem[] to MenuOption[] for Menu component
|
|
74
|
+
let menuOptions = $derived.by(() => {
|
|
75
|
+
return currentItems
|
|
76
|
+
.filter((item) => {
|
|
77
|
+
// Don't show already selected items
|
|
78
|
+
return !value.some((v) => v.id === item.id);
|
|
79
|
+
})
|
|
80
|
+
.map((item, index) => ({
|
|
81
|
+
value: String(item.id),
|
|
82
|
+
name: item.name,
|
|
83
|
+
index
|
|
84
|
+
}));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Filter suggestions based on current input
|
|
88
|
+
let filteredSuggestions = $derived.by(() => {
|
|
89
|
+
if (!searchText.trim()) {
|
|
90
|
+
return menuOptions;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const searchLower = searchText.trim().toLowerCase();
|
|
94
|
+
return menuOptions.filter((option) => {
|
|
95
|
+
const optionText = option.name.toLowerCase();
|
|
96
|
+
return optionText.includes(searchLower);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Check if an item already exists (by id)
|
|
101
|
+
function itemExists(item: ReferenceItem): boolean {
|
|
102
|
+
return value.some((v) => v.id === item.id);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check if max items reached
|
|
106
|
+
function isMaxItemsReached(): boolean {
|
|
107
|
+
return maxItems !== undefined && value.length >= maxItems;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function addItem(itemToAdd: ReferenceItem) {
|
|
111
|
+
// Check max items
|
|
112
|
+
if (isMaxItemsReached()) {
|
|
113
|
+
showInvalidFeedback();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check duplicate
|
|
118
|
+
if (itemExists(itemToAdd)) {
|
|
119
|
+
showInvalidFeedback();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Add the item
|
|
124
|
+
value = [...value, itemToAdd];
|
|
125
|
+
searchText = '';
|
|
126
|
+
invalidAttempt = false;
|
|
127
|
+
isMenuOpen = false;
|
|
128
|
+
highlightIndex = -1;
|
|
129
|
+
onChange?.(value);
|
|
130
|
+
|
|
131
|
+
// Focus back on input
|
|
132
|
+
if (browser && inputElement) {
|
|
133
|
+
inputElement.focus();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function showInvalidFeedback() {
|
|
138
|
+
invalidAttempt = true;
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
invalidAttempt = false;
|
|
141
|
+
}, 500);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function removeItem(itemToRemove: ReferenceItem) {
|
|
145
|
+
value = value.filter((item) => item.id !== itemToRemove.id);
|
|
146
|
+
onChange?.(value);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
150
|
+
if (disabled) return;
|
|
151
|
+
|
|
152
|
+
// Escape - close dropdown
|
|
153
|
+
if (event.key === 'Escape') {
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
closeDropdown();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Enter - add item or select from dropdown
|
|
160
|
+
if (event.key === 'Enter') {
|
|
161
|
+
event.preventDefault();
|
|
162
|
+
if (isMenuOpen && highlightIndex >= 0 && filteredSuggestions[highlightIndex]) {
|
|
163
|
+
const selectedOption = filteredSuggestions[highlightIndex];
|
|
164
|
+
const selectedItem = currentItems.find((item) => String(item.id) === selectedOption.value);
|
|
165
|
+
if (selectedItem) {
|
|
166
|
+
addItem(selectedItem);
|
|
167
|
+
}
|
|
168
|
+
} else if (isMenuOpen && createNew && highlightIndex === filteredSuggestions.length) {
|
|
169
|
+
// "Create new..." is highlighted
|
|
170
|
+
openCreatePrompt();
|
|
171
|
+
} else if (isMenuOpen && filteredSuggestions.length > 0) {
|
|
172
|
+
// Auto-select first item if available
|
|
173
|
+
const firstItem = currentItems.find(
|
|
174
|
+
(item) => String(item.id) === filteredSuggestions[0].value
|
|
175
|
+
);
|
|
176
|
+
if (firstItem) {
|
|
177
|
+
addItem(firstItem);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Tab - select highlighted suggestion or add current text
|
|
184
|
+
if (event.key === 'Tab') {
|
|
185
|
+
if (isMenuOpen && highlightIndex >= 0 && filteredSuggestions[highlightIndex]) {
|
|
186
|
+
event.preventDefault();
|
|
187
|
+
const selectedOption = filteredSuggestions[highlightIndex];
|
|
188
|
+
const selectedItem = currentItems.find((item) => String(item.id) === selectedOption.value);
|
|
189
|
+
if (selectedItem) {
|
|
190
|
+
addItem(selectedItem);
|
|
191
|
+
}
|
|
192
|
+
} else if (isMenuOpen && createNew && highlightIndex === filteredSuggestions.length) {
|
|
193
|
+
// "Create new..." is highlighted
|
|
194
|
+
event.preventDefault();
|
|
195
|
+
openCreatePrompt();
|
|
196
|
+
} else if (isMenuOpen && filteredSuggestions.length > 0) {
|
|
197
|
+
event.preventDefault();
|
|
198
|
+
const firstItem = currentItems.find(
|
|
199
|
+
(item) => String(item.id) === filteredSuggestions[0].value
|
|
200
|
+
);
|
|
201
|
+
if (firstItem) {
|
|
202
|
+
addItem(firstItem);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Arrow Down - navigate suggestions
|
|
209
|
+
if (event.key === 'ArrowDown') {
|
|
210
|
+
event.preventDefault();
|
|
211
|
+
if (!isMenuOpen && (filteredSuggestions.length > 0 || createNew)) {
|
|
212
|
+
openDropdown();
|
|
213
|
+
}
|
|
214
|
+
// Allow highlighting "Create new..." option if it exists
|
|
215
|
+
const maxIndex = createNew ? filteredSuggestions.length : filteredSuggestions.length - 1;
|
|
216
|
+
if (maxIndex >= 0) {
|
|
217
|
+
highlightIndex = Math.min(highlightIndex + 1, maxIndex);
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Arrow Up - navigate suggestions
|
|
223
|
+
if (event.key === 'ArrowUp') {
|
|
224
|
+
event.preventDefault();
|
|
225
|
+
if (isMenuOpen) {
|
|
226
|
+
// Allow highlighting "Create new..." option if it exists
|
|
227
|
+
const maxIndex = createNew ? filteredSuggestions.length : filteredSuggestions.length - 1;
|
|
228
|
+
if (maxIndex >= 0) {
|
|
229
|
+
highlightIndex = Math.max(highlightIndex - 1, 0);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function handleInput(event: Event) {
|
|
237
|
+
const input = event.target as HTMLInputElement;
|
|
238
|
+
const inputValue = input.value;
|
|
239
|
+
searchText = inputValue;
|
|
240
|
+
|
|
241
|
+
// Clear invalid feedback when user types
|
|
242
|
+
invalidAttempt = false;
|
|
243
|
+
|
|
244
|
+
// Open dropdown when typing if there are items or search function
|
|
245
|
+
if (inputValue.trim() && (currentItems.length > 0 || search)) {
|
|
246
|
+
openDropdown();
|
|
247
|
+
// Auto-highlight first item
|
|
248
|
+
highlightIndex = 0;
|
|
249
|
+
} else {
|
|
250
|
+
closeDropdown();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Trigger search if search function is provided
|
|
254
|
+
if (search && inputValue.trim()) {
|
|
255
|
+
triggerSearch();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function openDropdown() {
|
|
260
|
+
if (!disabled && (filteredSuggestions.length > 0 || createNew)) {
|
|
261
|
+
isMenuOpen = true;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function closeDropdown() {
|
|
266
|
+
isMenuOpen = false;
|
|
267
|
+
highlightIndex = -1;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function onSelectFromMenu(item: MenuOption) {
|
|
271
|
+
const selectedItem = currentItems.find((i) => String(i.id) === item.value);
|
|
272
|
+
if (selectedItem) {
|
|
273
|
+
addItem(selectedItem);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Get ARIA active descendant
|
|
278
|
+
let activeDescendant = $derived(
|
|
279
|
+
highlightIndex >= 0 && filteredSuggestions[highlightIndex]
|
|
280
|
+
? `${listboxId}-option-${highlightIndex}`
|
|
281
|
+
: highlightIndex === filteredSuggestions.length && createNew
|
|
282
|
+
? `${listboxId}-option-create`
|
|
283
|
+
: undefined
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
// Check if we should show "Create new..." option
|
|
287
|
+
let showCreateNew = $derived(!!createNew && isMenuOpen);
|
|
288
|
+
|
|
289
|
+
// Check if there are no results when searching
|
|
290
|
+
let hasNoResults = $derived(
|
|
291
|
+
searchText.trim() && filteredSuggestions.length === 0 && !isLoading && !createNew
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// Debounced search function
|
|
295
|
+
const triggerSearch = debounce(async () => {
|
|
296
|
+
if (search && searchText.trim()) {
|
|
297
|
+
isLoading = true;
|
|
298
|
+
try {
|
|
299
|
+
localItems = await search(searchText);
|
|
300
|
+
} finally {
|
|
301
|
+
isLoading = false;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}, 300);
|
|
305
|
+
|
|
306
|
+
// Handle creating new item
|
|
307
|
+
const handleCreateNew = async (name: string) => {
|
|
308
|
+
if (!createNew) return;
|
|
309
|
+
|
|
310
|
+
isCreating = true;
|
|
311
|
+
createError = null;
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const result = await createNew(name);
|
|
315
|
+
|
|
316
|
+
if (result) {
|
|
317
|
+
// Add to local items if using search (for display in dropdown)
|
|
318
|
+
if (search) {
|
|
319
|
+
localItems = [...localItems, result];
|
|
320
|
+
}
|
|
321
|
+
// Note: For static items, the parent should update the items prop
|
|
322
|
+
// The newly created item will be added to value, which is what matters
|
|
323
|
+
|
|
324
|
+
// Add the newly created item
|
|
325
|
+
addItem(result);
|
|
326
|
+
showPrompt = false;
|
|
327
|
+
} else {
|
|
328
|
+
// Handle error - show message to user
|
|
329
|
+
createError = 'Failed to create new item';
|
|
330
|
+
// Keep prompt open so user can try again
|
|
331
|
+
}
|
|
332
|
+
} catch (error) {
|
|
333
|
+
createError = error instanceof Error ? error.message : 'An error occurred';
|
|
334
|
+
// Keep prompt open so user can try again
|
|
335
|
+
} finally {
|
|
336
|
+
isCreating = false;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const openCreatePrompt = () => {
|
|
341
|
+
createError = null;
|
|
342
|
+
// Increment key to force Prompt to remount and reset its value
|
|
343
|
+
promptKey++;
|
|
344
|
+
showPrompt = true;
|
|
345
|
+
// Close dropdown when opening prompt
|
|
346
|
+
isMenuOpen = false;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// Reset error when prompt closes
|
|
350
|
+
$effect(() => {
|
|
351
|
+
if (!showPrompt) {
|
|
352
|
+
createError = null;
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Close dropdown when clicking outside
|
|
357
|
+
onMount(() => {
|
|
358
|
+
const handleClickOutside = (e: MouseEvent) => {
|
|
359
|
+
if (containerElement && !containerElement.contains(e.target as Node)) {
|
|
360
|
+
closeDropdown();
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
if (browser) {
|
|
365
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
366
|
+
return () => {
|
|
367
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
</script>
|
|
372
|
+
|
|
373
|
+
<FormField {size} {label} {id} {required} {disabled} {helperText} {feedback}>
|
|
374
|
+
<!-- ARIA live region for screen reader announcements -->
|
|
375
|
+
<div class="sr-only" role="status" aria-live="polite" aria-atomic="true">
|
|
376
|
+
{#if isMenuOpen && isLoading}
|
|
377
|
+
Searching...
|
|
378
|
+
{:else if isMenuOpen && filteredSuggestions.length > 0}
|
|
379
|
+
{filteredSuggestions.length}
|
|
380
|
+
{filteredSuggestions.length === 1 ? 'result' : 'results'} available
|
|
381
|
+
{:else if invalidAttempt}
|
|
382
|
+
{#if isMaxItemsReached()}
|
|
383
|
+
Maximum {maxItems} items reached
|
|
384
|
+
{:else}
|
|
385
|
+
Item already exists
|
|
386
|
+
{/if}
|
|
387
|
+
{/if}
|
|
388
|
+
</div>
|
|
389
|
+
|
|
390
|
+
<div class="reference-box" bind:this={containerElement}>
|
|
391
|
+
<div class="input-container">
|
|
392
|
+
<div
|
|
393
|
+
class="input {disabled ? 'disabled' : 'enabled'} {invalidAttempt
|
|
394
|
+
? 'invalid'
|
|
395
|
+
: ''} {isMenuOpen ? 'open' : ''}"
|
|
396
|
+
>
|
|
397
|
+
<input
|
|
398
|
+
{id}
|
|
399
|
+
type="text"
|
|
400
|
+
bind:value={searchText}
|
|
401
|
+
bind:this={inputElement}
|
|
402
|
+
{placeholder}
|
|
403
|
+
onkeydown={handleKeydown}
|
|
404
|
+
oninput={handleInput}
|
|
405
|
+
{disabled}
|
|
406
|
+
{required}
|
|
407
|
+
role="combobox"
|
|
408
|
+
aria-expanded={isMenuOpen}
|
|
409
|
+
aria-controls={listboxId}
|
|
410
|
+
aria-autocomplete="list"
|
|
411
|
+
aria-activedescendant={activeDescendant}
|
|
412
|
+
aria-haspopup="listbox"
|
|
413
|
+
aria-label="Reference input"
|
|
414
|
+
aria-busy={isLoading}
|
|
415
|
+
/>
|
|
416
|
+
{#if isLoading}
|
|
417
|
+
<div class="loading-indicator" aria-hidden="true">
|
|
418
|
+
<div class="spinner"></div>
|
|
419
|
+
</div>
|
|
420
|
+
{/if}
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
|
|
424
|
+
<!-- Autocomplete dropdown -->
|
|
425
|
+
{#if isMenuOpen}
|
|
426
|
+
<div class="dropdown">
|
|
427
|
+
{#if hasNoResults}
|
|
428
|
+
<div class="no-results" role="status">No results found</div>
|
|
429
|
+
{:else}
|
|
430
|
+
<Menu
|
|
431
|
+
items={filteredSuggestions}
|
|
432
|
+
open={isMenuOpen}
|
|
433
|
+
closeAfterSelect={true}
|
|
434
|
+
{searchText}
|
|
435
|
+
onSelect={onSelectFromMenu}
|
|
436
|
+
size="full"
|
|
437
|
+
bind:highlightIndex
|
|
438
|
+
{listboxId}
|
|
439
|
+
/>
|
|
440
|
+
{/if}
|
|
441
|
+
{#if showCreateNew}
|
|
442
|
+
<button
|
|
443
|
+
type="button"
|
|
444
|
+
class="create-new"
|
|
445
|
+
class:selected={highlightIndex === filteredSuggestions.length}
|
|
446
|
+
onclick={openCreatePrompt}
|
|
447
|
+
role="option"
|
|
448
|
+
id={listboxId ? `${listboxId}-option-create` : undefined}
|
|
449
|
+
aria-selected={highlightIndex === filteredSuggestions.length}
|
|
450
|
+
>
|
|
451
|
+
<Icon type="plus" size="sm" />
|
|
452
|
+
<span>
|
|
453
|
+
{#if resourceName}
|
|
454
|
+
Create new {resourceName}...
|
|
455
|
+
{:else}
|
|
456
|
+
Create new...
|
|
457
|
+
{/if}
|
|
458
|
+
</span>
|
|
459
|
+
</button>
|
|
460
|
+
{/if}
|
|
461
|
+
</div>
|
|
462
|
+
{/if}
|
|
463
|
+
|
|
464
|
+
{#if value.length > 0}
|
|
465
|
+
<div class="items">
|
|
466
|
+
{#each value as item}
|
|
467
|
+
{@const linkUrl = linkBuilder ? linkBuilder(item) : undefined}
|
|
468
|
+
<span class="item">
|
|
469
|
+
<Chip
|
|
470
|
+
label={item.name}
|
|
471
|
+
removable={!disabled}
|
|
472
|
+
onRemove={() => removeItem(item)}
|
|
473
|
+
link={linkUrl ? { url: linkUrl, target: '_blank' } : undefined}
|
|
474
|
+
tooltip={item.description}
|
|
475
|
+
>
|
|
476
|
+
{item.description}
|
|
477
|
+
</Chip>
|
|
478
|
+
</span>
|
|
479
|
+
{/each}
|
|
480
|
+
</div>
|
|
481
|
+
{/if}
|
|
482
|
+
</div>
|
|
483
|
+
</FormField>
|
|
484
|
+
|
|
485
|
+
{#key promptKey}
|
|
486
|
+
<Prompt
|
|
487
|
+
bind:open={showPrompt}
|
|
488
|
+
title={resourceName ? `Create New ${ucfirst(resourceName)}` : 'Create New'}
|
|
489
|
+
placeholder="Enter name"
|
|
490
|
+
required={true}
|
|
491
|
+
okText={resourceName ? `Create ${ucfirst(resourceName)}` : 'Create'}
|
|
492
|
+
cancelText="Cancel"
|
|
493
|
+
onOk={handleCreateNew}
|
|
494
|
+
onCancel={() => {
|
|
495
|
+
createError = null;
|
|
496
|
+
}}
|
|
497
|
+
>
|
|
498
|
+
{#if createError}
|
|
499
|
+
<div class="create-error" role="alert">
|
|
500
|
+
{createError}
|
|
501
|
+
</div>
|
|
502
|
+
{/if}
|
|
503
|
+
{#if isCreating}
|
|
504
|
+
<div class="creating-indicator" aria-live="polite">Creating...</div>
|
|
505
|
+
{/if}
|
|
506
|
+
</Prompt>
|
|
507
|
+
{/key}
|
|
508
|
+
|
|
509
|
+
<style>.reference-box {
|
|
510
|
+
display: flex;
|
|
511
|
+
flex-direction: column;
|
|
512
|
+
gap: var(--spacing-sm);
|
|
513
|
+
width: 100%;
|
|
514
|
+
position: relative;
|
|
515
|
+
}
|
|
516
|
+
.reference-box .input-container {
|
|
517
|
+
display: flex;
|
|
518
|
+
gap: var(--spacing-sm);
|
|
519
|
+
align-items: stretch;
|
|
520
|
+
}
|
|
521
|
+
.reference-box .input-container .input {
|
|
522
|
+
display: flex;
|
|
523
|
+
align-items: center;
|
|
524
|
+
justify-content: flex-start;
|
|
525
|
+
position: relative;
|
|
526
|
+
width: 100%;
|
|
527
|
+
min-height: 2.125rem;
|
|
528
|
+
border-radius: var(--radius-md);
|
|
529
|
+
border: var(--border-thin) solid var(--form-input-border);
|
|
530
|
+
background-color: var(--form-input-bg);
|
|
531
|
+
color: var(--form-input-fg);
|
|
532
|
+
font-size: var(--font-md);
|
|
533
|
+
font-weight: 500;
|
|
534
|
+
line-height: 2rem;
|
|
535
|
+
padding: 0;
|
|
536
|
+
transition: background-color var(--transition-base) var(--ease-in-out), border-color var(--transition-base) var(--ease-in-out), color var(--transition-base) var(--ease-in-out), fill var(--transition-base) var(--ease-in-out), stroke var(--transition-base) var(--ease-in-out), box-shadow var(--transition-base) var(--ease-in-out);
|
|
537
|
+
user-select: none;
|
|
538
|
+
white-space: nowrap;
|
|
539
|
+
flex: 1;
|
|
540
|
+
}
|
|
541
|
+
.reference-box .input-container .input.disabled {
|
|
542
|
+
opacity: 0.5;
|
|
543
|
+
cursor: not-allowed;
|
|
544
|
+
}
|
|
545
|
+
.reference-box .input-container .input.open {
|
|
546
|
+
border-color: var(--focus-ring, #007bff);
|
|
547
|
+
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
|
548
|
+
}
|
|
549
|
+
.reference-box .input-container .input.invalid {
|
|
550
|
+
border-color: var(--danger, #dc3545);
|
|
551
|
+
animation: shake 0.3s ease-in-out;
|
|
552
|
+
box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.25);
|
|
553
|
+
}
|
|
554
|
+
.reference-box .input-container .input input {
|
|
555
|
+
background-color: transparent;
|
|
556
|
+
border: none;
|
|
557
|
+
line-height: 2rem;
|
|
558
|
+
height: 2rem;
|
|
559
|
+
font-size: var(--font-md);
|
|
560
|
+
width: 100%;
|
|
561
|
+
flex-grow: 1;
|
|
562
|
+
padding: 0 var(--spacing-base);
|
|
563
|
+
margin: 0;
|
|
564
|
+
}
|
|
565
|
+
.reference-box .input-container .input input:focus {
|
|
566
|
+
outline: none;
|
|
567
|
+
}
|
|
568
|
+
.reference-box .input-container .input input:disabled {
|
|
569
|
+
cursor: not-allowed;
|
|
570
|
+
}
|
|
571
|
+
.reference-box .input-container .input input::placeholder {
|
|
572
|
+
color: var(--form-input-placeholder);
|
|
573
|
+
}
|
|
574
|
+
.reference-box .input-container .input .loading-indicator {
|
|
575
|
+
position: absolute;
|
|
576
|
+
right: var(--spacing-base);
|
|
577
|
+
width: 1.25rem;
|
|
578
|
+
height: 1.25rem;
|
|
579
|
+
display: flex;
|
|
580
|
+
align-items: center;
|
|
581
|
+
justify-content: center;
|
|
582
|
+
z-index: 2;
|
|
583
|
+
}
|
|
584
|
+
.reference-box .input-container .input .loading-indicator .spinner {
|
|
585
|
+
width: 1rem;
|
|
586
|
+
height: 1rem;
|
|
587
|
+
border: 2px solid var(--form-input-border);
|
|
588
|
+
border-top-color: var(--form-input-fg);
|
|
589
|
+
border-radius: 50%;
|
|
590
|
+
animation: spin 0.8s linear infinite;
|
|
591
|
+
}
|
|
592
|
+
.reference-box .dropdown {
|
|
593
|
+
position: absolute;
|
|
594
|
+
top: calc(100% - var(--spacing-sm));
|
|
595
|
+
left: 0;
|
|
596
|
+
width: 100%;
|
|
597
|
+
z-index: 1000;
|
|
598
|
+
margin-top: 0.25rem;
|
|
599
|
+
}
|
|
600
|
+
.reference-box .dropdown .no-results {
|
|
601
|
+
padding: 1rem;
|
|
602
|
+
text-align: center;
|
|
603
|
+
color: var(--text-muted, #6c757d);
|
|
604
|
+
font-size: var(--font-sm, 0.875rem);
|
|
605
|
+
background-color: var(--form-input-bg);
|
|
606
|
+
border: var(--border-thin) solid var(--form-input-border);
|
|
607
|
+
border-radius: var(--radius-md);
|
|
608
|
+
}
|
|
609
|
+
.reference-box .dropdown .create-new {
|
|
610
|
+
width: 100%;
|
|
611
|
+
display: flex;
|
|
612
|
+
align-items: center;
|
|
613
|
+
gap: 0.5rem;
|
|
614
|
+
padding: 0.5rem 1rem;
|
|
615
|
+
border: none;
|
|
616
|
+
background-color: var(--form-input-bg);
|
|
617
|
+
color: var(--form-input-fg);
|
|
618
|
+
font-size: var(--font-sm, 0.875rem);
|
|
619
|
+
cursor: pointer;
|
|
620
|
+
border-top: var(--border-thin) solid var(--form-input-border);
|
|
621
|
+
transition: background-color var(--transition-base) var(--ease-in-out), color var(--transition-base) var(--ease-in-out);
|
|
622
|
+
border: var(--border-thin) solid var(--form-input-border);
|
|
623
|
+
border-top: none;
|
|
624
|
+
border-radius: 0 0 var(--radius-md) var(--radius-md);
|
|
625
|
+
}
|
|
626
|
+
.reference-box .dropdown .create-new:hover, .reference-box .dropdown .create-new.selected {
|
|
627
|
+
background: var(--form-input-selected-bg, #003c75);
|
|
628
|
+
color: var(--form-input-selected-fg, white);
|
|
629
|
+
}
|
|
630
|
+
.reference-box .dropdown .create-new:focus {
|
|
631
|
+
outline: none;
|
|
632
|
+
}
|
|
633
|
+
.reference-box .dropdown .create-new:focus-visible {
|
|
634
|
+
outline: 2px solid var(--focus-ring, #007bff);
|
|
635
|
+
outline-offset: -2px;
|
|
636
|
+
}
|
|
637
|
+
.reference-box .dropdown .create-new span {
|
|
638
|
+
flex: 1;
|
|
639
|
+
}
|
|
640
|
+
.reference-box .dropdown :global(.menu) {
|
|
641
|
+
font-size: var(--font-sm, 0.875rem);
|
|
642
|
+
border-radius: var(--radius-md);
|
|
643
|
+
}
|
|
644
|
+
.reference-box .dropdown :global(.menu) :global(li) :global(div) {
|
|
645
|
+
padding: 0.25rem 0.5rem;
|
|
646
|
+
line-height: 1.25;
|
|
647
|
+
font-size: var(--font-sm, 0.875rem);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.sr-only {
|
|
651
|
+
position: absolute;
|
|
652
|
+
width: 1px;
|
|
653
|
+
height: 1px;
|
|
654
|
+
padding: 0;
|
|
655
|
+
margin: -1px;
|
|
656
|
+
overflow: hidden;
|
|
657
|
+
clip: rect(0, 0, 0, 0);
|
|
658
|
+
white-space: nowrap;
|
|
659
|
+
border-width: 0;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
@keyframes shake {
|
|
663
|
+
0%, 100% {
|
|
664
|
+
transform: translateX(0);
|
|
665
|
+
}
|
|
666
|
+
10%, 30%, 50%, 70%, 90% {
|
|
667
|
+
transform: translateX(-4px);
|
|
668
|
+
}
|
|
669
|
+
20%, 40%, 60%, 80% {
|
|
670
|
+
transform: translateX(4px);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
@keyframes spin {
|
|
674
|
+
from {
|
|
675
|
+
transform: rotate(0deg);
|
|
676
|
+
}
|
|
677
|
+
to {
|
|
678
|
+
transform: rotate(360deg);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
.create-error {
|
|
682
|
+
color: var(--color-danger, #dc3545);
|
|
683
|
+
font-size: var(--font-sm, 0.875rem);
|
|
684
|
+
margin-top: 0.5rem;
|
|
685
|
+
padding: 0.5rem;
|
|
686
|
+
background-color: var(--color-danger-bg, #f8d7da);
|
|
687
|
+
border-radius: var(--radius-sm, 0.25rem);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.creating-indicator {
|
|
691
|
+
color: var(--text-muted, #6c757d);
|
|
692
|
+
font-size: var(--font-sm, 0.875rem);
|
|
693
|
+
margin-top: 0.5rem;
|
|
694
|
+
font-style: italic;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.item {
|
|
698
|
+
display: inline-block;
|
|
699
|
+
vertical-align: middle;
|
|
700
|
+
line-height: 1;
|
|
701
|
+
margin-right: var(--spacing-xs);
|
|
702
|
+
margin-bottom: var(--spacing-xs);
|
|
703
|
+
padding: 0;
|
|
704
|
+
border: none;
|
|
705
|
+
background: none;
|
|
706
|
+
font: inherit;
|
|
707
|
+
color: inherit;
|
|
708
|
+
text-align: left;
|
|
709
|
+
}</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type FormFieldFeedback } from '../form-field/form-field.svelte';
|
|
2
|
+
import type { FormFieldSizeOptions } from '../../types/form.js';
|
|
3
|
+
import type { ReferenceItem, SearchFunction, CreateNewFunction, LinkBuilderFunction } from './reference-box.js';
|
|
4
|
+
type $$ComponentProps = {
|
|
5
|
+
value?: ReferenceItem[];
|
|
6
|
+
items?: ReferenceItem[];
|
|
7
|
+
search?: SearchFunction | undefined;
|
|
8
|
+
createNew?: CreateNewFunction | undefined;
|
|
9
|
+
linkBuilder?: LinkBuilderFunction | undefined;
|
|
10
|
+
resourceName?: string | undefined;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
size?: FormFieldSizeOptions;
|
|
15
|
+
label?: string;
|
|
16
|
+
helperText?: string;
|
|
17
|
+
feedback?: FormFieldFeedback | undefined;
|
|
18
|
+
maxItems?: number | undefined;
|
|
19
|
+
onChange?: ((value: ReferenceItem[]) => void) | undefined;
|
|
20
|
+
};
|
|
21
|
+
declare const ReferenceBox: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
22
|
+
type ReferenceBox = ReturnType<typeof ReferenceBox>;
|
|
23
|
+
export default ReferenceBox;
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
strict = false,
|
|
26
26
|
caseInsensitive = true,
|
|
27
27
|
maxTags = undefined as number | undefined,
|
|
28
|
-
onChange = undefined as ((value: string[]) => void) | undefined
|
|
28
|
+
onChange = undefined as ((value: string[]) => void) | undefined,
|
|
29
|
+
deleteOnBackspace = false
|
|
29
30
|
}: {
|
|
30
31
|
value?: string[];
|
|
31
32
|
placeholder?: string;
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
caseInsensitive?: boolean;
|
|
43
44
|
maxTags?: number | undefined;
|
|
44
45
|
onChange?: ((value: string[]) => void) | undefined;
|
|
46
|
+
deleteOnBackspace?: boolean;
|
|
45
47
|
} = $props();
|
|
46
48
|
|
|
47
49
|
let newTag = $state('');
|
|
@@ -118,7 +120,7 @@
|
|
|
118
120
|
|
|
119
121
|
function addTag(tagToAdd?: string) {
|
|
120
122
|
const tag = (tagToAdd || newTag).trim();
|
|
121
|
-
|
|
123
|
+
|
|
122
124
|
// Prevent empty tags
|
|
123
125
|
if (!tag) {
|
|
124
126
|
newTag = '';
|
|
@@ -151,7 +153,7 @@
|
|
|
151
153
|
isMenuOpen = false;
|
|
152
154
|
highlightIndex = -1;
|
|
153
155
|
onChange?.(value);
|
|
154
|
-
|
|
156
|
+
|
|
155
157
|
// Focus back on input
|
|
156
158
|
if (browser && inputElement) {
|
|
157
159
|
inputElement.focus();
|
|
@@ -208,7 +210,9 @@
|
|
|
208
210
|
|
|
209
211
|
// Backspace - remove last tag if input is empty
|
|
210
212
|
if (event.key === 'Backspace' && newTag === '' && value.length > 0) {
|
|
211
|
-
|
|
213
|
+
if (deleteOnBackspace) {
|
|
214
|
+
removeTag(value[value.length - 1]);
|
|
215
|
+
}
|
|
212
216
|
return;
|
|
213
217
|
}
|
|
214
218
|
|
|
@@ -312,7 +316,8 @@
|
|
|
312
316
|
<!-- ARIA live region for screen reader announcements -->
|
|
313
317
|
<div class="sr-only" role="status" aria-live="polite" aria-atomic="true">
|
|
314
318
|
{#if isMenuOpen && filteredSuggestions.length > 0}
|
|
315
|
-
{filteredSuggestions.length}
|
|
319
|
+
{filteredSuggestions.length}
|
|
320
|
+
{filteredSuggestions.length === 1 ? 'suggestion' : 'suggestions'} available
|
|
316
321
|
{:else if invalidTagAttempt}
|
|
317
322
|
{#if isMaxTagsReached()}
|
|
318
323
|
Maximum {maxTags} tags reached
|
|
@@ -326,7 +331,11 @@
|
|
|
326
331
|
|
|
327
332
|
<div class="tag-box" bind:this={containerElement}>
|
|
328
333
|
<div class="input-container">
|
|
329
|
-
<div
|
|
334
|
+
<div
|
|
335
|
+
class="input {disabled ? 'disabled' : 'enabled'} {invalidTagAttempt
|
|
336
|
+
? 'invalid'
|
|
337
|
+
: ''} {isMenuOpen ? 'open' : ''}"
|
|
338
|
+
>
|
|
330
339
|
<input
|
|
331
340
|
{id}
|
|
332
341
|
type="text"
|
|
@@ -16,6 +16,7 @@ type $$ComponentProps = {
|
|
|
16
16
|
caseInsensitive?: boolean;
|
|
17
17
|
maxTags?: number | undefined;
|
|
18
18
|
onChange?: ((value: string[]) => void) | undefined;
|
|
19
|
+
deleteOnBackspace?: boolean;
|
|
19
20
|
};
|
|
20
21
|
declare const TagBox: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
21
22
|
type TagBox = ReturnType<typeof TagBox>;
|
|
@@ -1,23 +1,43 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import Icon from '../../icons/icon.svelte';
|
|
2
3
|
import type { ComponentSize } from '../../types/size.js';
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
3
5
|
|
|
4
6
|
let {
|
|
5
7
|
label,
|
|
8
|
+
tooltip = undefined,
|
|
6
9
|
removable = false,
|
|
7
10
|
size = 'md' as ComponentSize,
|
|
8
11
|
variant = 'standard' as 'standard' | 'positive' | 'negative',
|
|
9
|
-
onRemove = undefined
|
|
12
|
+
onRemove = undefined,
|
|
13
|
+
link = undefined,
|
|
14
|
+
children = undefined
|
|
10
15
|
}: {
|
|
11
16
|
label: string;
|
|
17
|
+
tooltip?: string;
|
|
18
|
+
link?: { url: string; target?: string };
|
|
12
19
|
removable?: boolean;
|
|
13
20
|
size?: ComponentSize;
|
|
14
21
|
variant?: 'standard' | 'positive' | 'negative';
|
|
15
22
|
onRemove?: (() => void) | undefined;
|
|
23
|
+
children?: Snippet;
|
|
16
24
|
} = $props();
|
|
17
25
|
</script>
|
|
18
26
|
|
|
19
|
-
<div class="chip {size} {variant}">
|
|
27
|
+
<div class="chip {size} {variant}" title={tooltip}>
|
|
20
28
|
<span class="label">{label}</span>
|
|
29
|
+
{#if children}
|
|
30
|
+
<span class="children">
|
|
31
|
+
{@render children?.()}
|
|
32
|
+
</span>
|
|
33
|
+
{/if}
|
|
34
|
+
|
|
35
|
+
{#if link}
|
|
36
|
+
<a class="link" href={link.url} target={link.target || '_blank'} rel="noopener noreferrer">
|
|
37
|
+
<Icon type="external-link" size="xs" />
|
|
38
|
+
</a>
|
|
39
|
+
{/if}
|
|
40
|
+
|
|
21
41
|
{#if removable}
|
|
22
42
|
<button type="button" class="remove" onclick={onRemove} aria-label="Remove {label}"> × </button>
|
|
23
43
|
{/if}
|
|
@@ -32,10 +52,24 @@
|
|
|
32
52
|
background-color: var(--chip-bg, #e0e0e0);
|
|
33
53
|
color: var(--chip-fg, #000);
|
|
34
54
|
font-size: 0.875rem;
|
|
35
|
-
font-weight: 500;
|
|
36
55
|
}
|
|
37
56
|
.chip .label {
|
|
38
57
|
line-height: 1.5;
|
|
58
|
+
font-weight: 500;
|
|
59
|
+
}
|
|
60
|
+
.chip .children {
|
|
61
|
+
font-size: 80%;
|
|
62
|
+
font-style: italic;
|
|
63
|
+
opacity: 0.5;
|
|
64
|
+
}
|
|
65
|
+
.chip .link {
|
|
66
|
+
display: inline-block;
|
|
67
|
+
vertical-align: middle;
|
|
68
|
+
line-height: 1;
|
|
69
|
+
margin-left: 0.25rem;
|
|
70
|
+
margin-right: 0.25rem;
|
|
71
|
+
padding: 0;
|
|
72
|
+
border: none;
|
|
39
73
|
}
|
|
40
74
|
.chip .remove {
|
|
41
75
|
background: none;
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { ComponentSize } from '../../types/size.js';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
type $$ComponentProps = {
|
|
3
4
|
label: string;
|
|
5
|
+
tooltip?: string;
|
|
6
|
+
link?: {
|
|
7
|
+
url: string;
|
|
8
|
+
target?: string;
|
|
9
|
+
};
|
|
4
10
|
removable?: boolean;
|
|
5
11
|
size?: ComponentSize;
|
|
6
12
|
variant?: 'standard' | 'positive' | 'negative';
|
|
7
13
|
onRemove?: (() => void) | undefined;
|
|
14
|
+
children?: Snippet;
|
|
8
15
|
};
|
|
9
16
|
declare const Chip: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
17
|
type Chip = ReturnType<typeof Chip>;
|
package/dist/icons/icon-data.js
CHANGED
|
@@ -229,6 +229,15 @@ export const iconRegistry = {
|
|
|
229
229
|
}
|
|
230
230
|
]
|
|
231
231
|
},
|
|
232
|
+
'external-link': {
|
|
233
|
+
viewBox: '0 0 122.6 122.88',
|
|
234
|
+
fill: 'currentColor',
|
|
235
|
+
paths: [
|
|
236
|
+
{
|
|
237
|
+
d: 'M110.6,72.58c0-3.19,2.59-5.78,5.78-5.78c3.19,0,5.78,2.59,5.78,5.78v33.19c0,4.71-1.92,8.99-5.02,12.09 c-3.1,3.1-7.38,5.02-12.09,5.02H17.11c-4.71,0-8.99-1.92-12.09-5.02c-3.1-3.1-5.02-7.38-5.02-12.09V17.19 C0,12.48,1.92,8.2,5.02,5.1C8.12,2,12.4,0.08,17.11,0.08h32.98c3.19,0,5.78,2.59,5.78,5.78c0,3.19-2.59,5.78-5.78,5.78H17.11 c-1.52,0-2.9,0.63-3.91,1.63c-1.01,1.01-1.63,2.39-1.63,3.91v88.58c0,1.52,0.63,2.9,1.63,3.91c1.01,1.01,2.39,1.63,3.91,1.63h87.95 c1.52,0,2.9-0.63,3.91-1.63s1.63-2.39,1.63-3.91V72.58L110.6,72.58z M112.42,17.46L54.01,76.6c-2.23,2.27-5.89,2.3-8.16,0.07 c-2.27-2.23-2.3-5.89-0.07-8.16l56.16-56.87H78.56c-3.19,0-5.78-2.59-5.78-5.78c0-3.19,2.59-5.78,5.78-5.78h26.5 c5.12,0,11.72-0.87,15.65,3.1c2.48,2.51,1.93,22.52,1.61,34.11c-0.08,3-0.15,5.29-0.15,6.93c0,3.19-2.59,5.78-5.78,5.78 c-3.19,0-5.78-2.59-5.78-5.78c0-0.31,0.08-3.32,0.19-7.24C110.96,30.94,111.93,22.94,112.42,17.46L112.42,17.46z'
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
},
|
|
232
241
|
eye: {
|
|
233
242
|
viewBox: '0 0 20 14',
|
|
234
243
|
fill: 'none',
|
package/dist/icons/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type-safe string union of all available icon types
|
|
3
3
|
*/
|
|
4
|
-
export type IconType = 'angle-right' | 'angle-up' | 'angle-left' | 'angle-down' | 'arrow-left' | 'arrow-right' | 'arrow-up' | 'arrow-down' | 'check' | 'clipboard' | 'close' | 'copy' | 'download' | 'edit' | 'envelope' | 'envelope-full' | 'export' | 'eye' | 'folder-open' | 'hamburger' | 'heart' | 'heart-full' | 'home' | 'home-full' | 'import' | 'info' | 'link' | 'minus' | 'mobile-phone' | 'phone' | 'plus' | 'print' | 'search' | 'settings' | 'sortable' | 'star' | 'star-full' | 'trash' | 'triangle-up' | 'triangle-down' | 'triangle-left' | 'triangle-right' | 'triangle-up-down' | 'upload' | 'user' | 'warning';
|
|
4
|
+
export type IconType = 'angle-right' | 'angle-up' | 'angle-left' | 'angle-down' | 'arrow-left' | 'arrow-right' | 'arrow-up' | 'arrow-down' | 'check' | 'clipboard' | 'close' | 'copy' | 'download' | 'edit' | 'envelope' | 'envelope-full' | 'export' | 'external-link' | 'eye' | 'folder-open' | 'hamburger' | 'heart' | 'heart-full' | 'home' | 'home-full' | 'import' | 'info' | 'link' | 'minus' | 'mobile-phone' | 'phone' | 'plus' | 'print' | 'search' | 'settings' | 'sortable' | 'star' | 'star-full' | 'trash' | 'triangle-up' | 'triangle-down' | 'triangle-left' | 'triangle-right' | 'triangle-up-down' | 'upload' | 'user' | 'warning';
|