@finsweet/webflow-apps-utils 1.0.50 → 1.0.52
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/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/select/Select.stories.d.ts +1 -0
- package/dist/ui/components/select/Select.stories.js +35 -0
- package/dist/ui/components/select/Select.svelte +129 -82
- package/dist/ui/components/select/SelectWithFooterStory.svelte +54 -0
- package/dist/ui/components/select/SelectWithFooterStory.svelte.d.ts +11 -0
- package/dist/ui/components/select/index.d.ts +1 -1
- package/dist/ui/components/select/types.d.ts +8 -0
- package/dist/ui/components/tags/TagsInput.stories.d.ts +103 -0
- package/dist/ui/components/tags/TagsInput.stories.js +435 -0
- package/dist/ui/components/tags/TagsInput.svelte +492 -0
- package/dist/ui/components/tags/TagsInput.svelte.d.ts +4 -0
- package/dist/ui/components/tags/index.d.ts +2 -0
- package/dist/ui/components/tags/index.js +2 -0
- package/dist/ui/components/tags/types.d.ts +123 -0
- package/dist/ui/components/tags/types.js +1 -0
- package/dist/ui/icons/CheckCircleIcon.svelte +1 -1
- package/dist/ui/icons/CheckCircleOutlinedIcon.svelte +1 -1
- package/dist/ui/icons/CloseCircleIcon.svelte +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { TimesIcon } from '../../icons';
|
|
3
|
+
|
|
4
|
+
import Loader from '../Loader.svelte';
|
|
5
|
+
import { Tooltip } from '../tooltip';
|
|
6
|
+
import type { TagsInputProps } from './types.js';
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
value = $bindable([]),
|
|
10
|
+
placeholder = 'Add tag...',
|
|
11
|
+
id = 'tags-input',
|
|
12
|
+
disabled = false,
|
|
13
|
+
loading = false,
|
|
14
|
+
invalid = false,
|
|
15
|
+
readonly = false,
|
|
16
|
+
alert = null,
|
|
17
|
+
maxTags = null,
|
|
18
|
+
minTags = null,
|
|
19
|
+
maxTagLength = null,
|
|
20
|
+
separatorKeys = ['Enter', ','],
|
|
21
|
+
allowDuplicates = false,
|
|
22
|
+
validateTag,
|
|
23
|
+
trimTags = true,
|
|
24
|
+
width = '100%',
|
|
25
|
+
height = 'auto',
|
|
26
|
+
class: className = '',
|
|
27
|
+
onValueChange,
|
|
28
|
+
onTagAdd,
|
|
29
|
+
onTagRemove,
|
|
30
|
+
onInvalidTag,
|
|
31
|
+
onfocus,
|
|
32
|
+
onblur,
|
|
33
|
+
onkeydown,
|
|
34
|
+
children,
|
|
35
|
+
...restProps
|
|
36
|
+
}: TagsInputProps = $props();
|
|
37
|
+
|
|
38
|
+
// Component state
|
|
39
|
+
let inputElement: HTMLInputElement | undefined = $state();
|
|
40
|
+
let inputValue = $state('');
|
|
41
|
+
let isFocused = $state(false);
|
|
42
|
+
|
|
43
|
+
// Derived states
|
|
44
|
+
let isDisabled = $derived(disabled || loading);
|
|
45
|
+
let canAddMore = $derived(maxTags === null || value.length < maxTags);
|
|
46
|
+
let showPlaceholder = $derived(value.length === 0 && !inputValue);
|
|
47
|
+
let hasAlert = $derived(alert?.message);
|
|
48
|
+
|
|
49
|
+
// Validation state
|
|
50
|
+
let isMinTagsInvalid = $derived.by(() => {
|
|
51
|
+
if (minTags === null) return false;
|
|
52
|
+
return value.length < minTags;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Derived alert state for styling
|
|
56
|
+
let alertType = $derived(alert?.type || null);
|
|
57
|
+
let isErrorAlert = $derived(alertType === 'error' || alertType === 'warning');
|
|
58
|
+
let isSuccessAlert = $derived(alertType === 'success');
|
|
59
|
+
|
|
60
|
+
// CSS classes
|
|
61
|
+
let wrapperClasses = $derived(
|
|
62
|
+
`
|
|
63
|
+
tags-input-wrapper
|
|
64
|
+
${isDisabled ? 'disabled' : ''}
|
|
65
|
+
${readonly ? 'readonly' : ''}
|
|
66
|
+
${invalid || isErrorAlert || isMinTagsInvalid ? 'invalid' : ''}
|
|
67
|
+
${isSuccessAlert ? 'success' : ''}
|
|
68
|
+
${isFocused ? 'focused' : ''}
|
|
69
|
+
${loading ? 'loading' : ''}
|
|
70
|
+
${className}
|
|
71
|
+
`
|
|
72
|
+
.trim()
|
|
73
|
+
.replace(/\s+/g, ' ')
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Focus the input element
|
|
78
|
+
*/
|
|
79
|
+
const focusInput = () => {
|
|
80
|
+
if (inputElement && !readonly && !isDisabled) {
|
|
81
|
+
inputElement.focus();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Validates a tag before adding
|
|
87
|
+
*/
|
|
88
|
+
const validateTagValue = (tag: string): { valid: boolean; reason?: string } => {
|
|
89
|
+
const trimmedTag = trimTags ? tag.trim() : tag;
|
|
90
|
+
|
|
91
|
+
if (!trimmedTag) {
|
|
92
|
+
return { valid: false, reason: 'Tag cannot be empty' };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (maxTagLength !== null && trimmedTag.length > maxTagLength) {
|
|
96
|
+
return { valid: false, reason: `Tag exceeds maximum length of ${maxTagLength}` };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!allowDuplicates && value.includes(trimmedTag)) {
|
|
100
|
+
return { valid: false, reason: 'Duplicate tag' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!canAddMore) {
|
|
104
|
+
return { valid: false, reason: `Maximum of ${maxTags} tags allowed` };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (validateTag) {
|
|
108
|
+
const customValidation = validateTag(trimmedTag);
|
|
109
|
+
if (customValidation === false) {
|
|
110
|
+
return { valid: false, reason: 'Invalid tag' };
|
|
111
|
+
}
|
|
112
|
+
if (typeof customValidation === 'string') {
|
|
113
|
+
return { valid: false, reason: customValidation };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { valid: true };
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Adds a new tag
|
|
122
|
+
*/
|
|
123
|
+
const addTag = (rawTag: string) => {
|
|
124
|
+
if (readonly || isDisabled) return;
|
|
125
|
+
|
|
126
|
+
const tag = trimTags ? rawTag.trim() : rawTag;
|
|
127
|
+
const validation = validateTagValue(tag);
|
|
128
|
+
|
|
129
|
+
if (!validation.valid) {
|
|
130
|
+
onInvalidTag?.(tag, validation.reason || 'Invalid tag');
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
inputValue = '';
|
|
135
|
+
value = [...value, tag];
|
|
136
|
+
|
|
137
|
+
onTagAdd?.(tag);
|
|
138
|
+
onValueChange?.(value);
|
|
139
|
+
|
|
140
|
+
return true;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Removes a tag at the specified index
|
|
145
|
+
*/
|
|
146
|
+
const removeTag = (index: number) => {
|
|
147
|
+
if (readonly || isDisabled) return;
|
|
148
|
+
|
|
149
|
+
const removedTag = value[index];
|
|
150
|
+
value = value.filter((_, i) => i !== index);
|
|
151
|
+
|
|
152
|
+
onTagRemove?.(removedTag, index);
|
|
153
|
+
onValueChange?.(value);
|
|
154
|
+
|
|
155
|
+
// Focus back to input after removal
|
|
156
|
+
focusInput();
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Handles input changes
|
|
161
|
+
*/
|
|
162
|
+
const handleInput = (event: Event) => {
|
|
163
|
+
const target = event.target as HTMLInputElement;
|
|
164
|
+
const newValue = target.value;
|
|
165
|
+
|
|
166
|
+
// Check if a separator key was typed (e.g., comma)
|
|
167
|
+
for (const separator of separatorKeys) {
|
|
168
|
+
if (separator.length === 1 && newValue.includes(separator)) {
|
|
169
|
+
const parts = newValue.split(separator);
|
|
170
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
171
|
+
if (parts[i].trim()) {
|
|
172
|
+
addTag(parts[i]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
inputValue = parts[parts.length - 1];
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
inputValue = newValue;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Handles keydown events
|
|
185
|
+
*/
|
|
186
|
+
const handleKeydown = (event: KeyboardEvent) => {
|
|
187
|
+
// Check for separator keys (e.g., Enter)
|
|
188
|
+
if (separatorKeys.includes(event.key)) {
|
|
189
|
+
event.preventDefault();
|
|
190
|
+
if (inputValue.trim()) {
|
|
191
|
+
addTag(inputValue);
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Handle Backspace to remove last tag when input is empty
|
|
197
|
+
if (event.key === 'Backspace' && inputValue === '' && value.length > 0) {
|
|
198
|
+
removeTag(value.length - 1);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Handle Tab to add tag if there's input
|
|
203
|
+
if (event.key === 'Tab' && inputValue.trim()) {
|
|
204
|
+
event.preventDefault();
|
|
205
|
+
addTag(inputValue);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
onkeydown?.(event);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Handles focus events
|
|
214
|
+
*/
|
|
215
|
+
const handleFocus = (event: FocusEvent) => {
|
|
216
|
+
isFocused = true;
|
|
217
|
+
onfocus?.(event);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Handles blur events
|
|
222
|
+
*/
|
|
223
|
+
const handleBlur = (event: FocusEvent) => {
|
|
224
|
+
isFocused = false;
|
|
225
|
+
|
|
226
|
+
// Add any remaining input as a tag on blur
|
|
227
|
+
if (inputValue.trim()) {
|
|
228
|
+
addTag(inputValue);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
onblur?.(event);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Handles wrapper click to focus input
|
|
236
|
+
*/
|
|
237
|
+
const handleWrapperClick = () => {
|
|
238
|
+
focusInput();
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Gets the tooltip background color based on alert type
|
|
243
|
+
*/
|
|
244
|
+
const getTooltipColor = (alertType: string) => {
|
|
245
|
+
switch (alertType) {
|
|
246
|
+
case 'error':
|
|
247
|
+
return 'var(--redBackground)';
|
|
248
|
+
case 'warning':
|
|
249
|
+
return 'var(--orangeBackground)';
|
|
250
|
+
case 'success':
|
|
251
|
+
return 'var(--greenBackground)';
|
|
252
|
+
case 'info':
|
|
253
|
+
default:
|
|
254
|
+
return 'var(--actionPrimaryBackground)';
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
</script>
|
|
258
|
+
|
|
259
|
+
{#snippet tagsInputContent()}
|
|
260
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
261
|
+
<div
|
|
262
|
+
class={wrapperClasses}
|
|
263
|
+
style="width: {width}; min-height: {height};"
|
|
264
|
+
role="group"
|
|
265
|
+
aria-labelledby="{id}-label"
|
|
266
|
+
onclick={handleWrapperClick}
|
|
267
|
+
onkeydown={(e) => e.key === 'Enter' && handleWrapperClick()}
|
|
268
|
+
>
|
|
269
|
+
<div class="tags-input-content">
|
|
270
|
+
{#each value as tag, index (`${index}-${tag}`)}
|
|
271
|
+
<span class="tag" role="listitem">
|
|
272
|
+
<span class="tag-text">{tag}</span>
|
|
273
|
+
{#if !readonly && !isDisabled}
|
|
274
|
+
<button
|
|
275
|
+
type="button"
|
|
276
|
+
class="tag-remove"
|
|
277
|
+
onclick={(e) => {
|
|
278
|
+
e.stopPropagation();
|
|
279
|
+
removeTag(index);
|
|
280
|
+
}}
|
|
281
|
+
aria-label="Remove tag {tag}"
|
|
282
|
+
tabindex={-1}
|
|
283
|
+
>
|
|
284
|
+
<TimesIcon />
|
|
285
|
+
</button>
|
|
286
|
+
{/if}
|
|
287
|
+
</span>
|
|
288
|
+
{/each}
|
|
289
|
+
|
|
290
|
+
{#if !readonly}
|
|
291
|
+
<input
|
|
292
|
+
bind:this={inputElement}
|
|
293
|
+
{id}
|
|
294
|
+
type="text"
|
|
295
|
+
class="tags-input-field"
|
|
296
|
+
placeholder={showPlaceholder ? placeholder : ''}
|
|
297
|
+
value={inputValue}
|
|
298
|
+
disabled={isDisabled || !canAddMore}
|
|
299
|
+
oninput={handleInput}
|
|
300
|
+
onkeydown={handleKeydown}
|
|
301
|
+
onfocus={handleFocus}
|
|
302
|
+
onblur={handleBlur}
|
|
303
|
+
aria-label="Add new tag"
|
|
304
|
+
{...restProps}
|
|
305
|
+
/>
|
|
306
|
+
{/if}
|
|
307
|
+
|
|
308
|
+
{#if loading}
|
|
309
|
+
<div class="tags-input-loader">
|
|
310
|
+
<Loader size={14} color="var(--text2)" />
|
|
311
|
+
</div>
|
|
312
|
+
{/if}
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
{#if children}
|
|
316
|
+
{@render children()}
|
|
317
|
+
{/if}
|
|
318
|
+
</div>
|
|
319
|
+
{/snippet}
|
|
320
|
+
|
|
321
|
+
<Tooltip
|
|
322
|
+
message={hasAlert ? alert?.message || '' : ''}
|
|
323
|
+
placement="top"
|
|
324
|
+
listener="hover"
|
|
325
|
+
listenerout="hover"
|
|
326
|
+
showArrow={true}
|
|
327
|
+
hidden={!hasAlert}
|
|
328
|
+
disabled={!hasAlert || !alert?.message}
|
|
329
|
+
fontColor="var(--actionPrimaryText)"
|
|
330
|
+
width="max-content"
|
|
331
|
+
padding="6px"
|
|
332
|
+
bgColor={getTooltipColor(alert?.type || 'info')}
|
|
333
|
+
class="tags-input-tooltip"
|
|
334
|
+
>
|
|
335
|
+
{#snippet target()}
|
|
336
|
+
{@render tagsInputContent()}
|
|
337
|
+
{/snippet}
|
|
338
|
+
</Tooltip>
|
|
339
|
+
|
|
340
|
+
<style>
|
|
341
|
+
:global(.tags-input-tooltip) {
|
|
342
|
+
padding: 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.tags-input-wrapper {
|
|
346
|
+
position: relative;
|
|
347
|
+
border: 1px solid var(--border3);
|
|
348
|
+
border-radius: var(--border-radius);
|
|
349
|
+
padding: 4px;
|
|
350
|
+
display: flex;
|
|
351
|
+
flex-wrap: wrap;
|
|
352
|
+
align-items: flex-start;
|
|
353
|
+
align-content: flex-start;
|
|
354
|
+
background: var(--background1);
|
|
355
|
+
min-height: 32px;
|
|
356
|
+
box-shadow:
|
|
357
|
+
0px 16px 16px -16px rgba(0, 0, 0, 0.13) inset,
|
|
358
|
+
0px 12px 12px -12px rgba(0, 0, 0, 0.13) inset,
|
|
359
|
+
0px 8px 8px -8px rgba(0, 0, 0, 0.17) inset,
|
|
360
|
+
0px 4px 4px -4px rgba(0, 0, 0, 0.17) inset,
|
|
361
|
+
0px 3px 3px -3px rgba(0, 0, 0, 0.17) inset,
|
|
362
|
+
0px 1px 1px -1px rgba(0, 0, 0, 0.13) inset;
|
|
363
|
+
cursor: text;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.tags-input-wrapper.focused {
|
|
367
|
+
border-color: var(--blueBorder);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.tags-input-wrapper.invalid {
|
|
371
|
+
border-color: var(--redBorder);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.tags-input-wrapper.success {
|
|
375
|
+
border-color: var(--greenBorder);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.tags-input-wrapper.disabled,
|
|
379
|
+
.tags-input-wrapper.readonly {
|
|
380
|
+
cursor: not-allowed;
|
|
381
|
+
opacity: 0.7;
|
|
382
|
+
border-color: var(--border1);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.tags-input-wrapper.loading {
|
|
386
|
+
cursor: wait;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.tags-input-content {
|
|
390
|
+
display: flex;
|
|
391
|
+
flex-wrap: wrap;
|
|
392
|
+
align-items: flex-start;
|
|
393
|
+
align-content: flex-start;
|
|
394
|
+
gap: 4px;
|
|
395
|
+
width: 100%;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.tag {
|
|
399
|
+
position: relative;
|
|
400
|
+
display: flex;
|
|
401
|
+
padding: 4px 8px;
|
|
402
|
+
justify-content: center;
|
|
403
|
+
align-items: center;
|
|
404
|
+
border-radius: var(--border-radius);
|
|
405
|
+
background: var(--actionSecondaryBackground);
|
|
406
|
+
color: var(--text1);
|
|
407
|
+
font-size: var(--font-size-small);
|
|
408
|
+
font-weight: var(--font-weight-normal);
|
|
409
|
+
line-height: 16px;
|
|
410
|
+
letter-spacing: -0.115px;
|
|
411
|
+
box-shadow:
|
|
412
|
+
0 0.5px 1px 0 #000,
|
|
413
|
+
0 0.5px 0.5px 0 rgba(255, 255, 255, 0.12) inset;
|
|
414
|
+
user-select: none;
|
|
415
|
+
max-width: 100%;
|
|
416
|
+
min-width: 0;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.tag-text {
|
|
420
|
+
overflow: hidden;
|
|
421
|
+
text-overflow: ellipsis;
|
|
422
|
+
white-space: nowrap;
|
|
423
|
+
min-width: 0;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.tag-remove {
|
|
427
|
+
position: absolute;
|
|
428
|
+
right: 0;
|
|
429
|
+
top: 0;
|
|
430
|
+
bottom: 0;
|
|
431
|
+
display: flex;
|
|
432
|
+
align-items: center;
|
|
433
|
+
justify-content: center;
|
|
434
|
+
width: max-content;
|
|
435
|
+
padding: 4px;
|
|
436
|
+
border: none;
|
|
437
|
+
background: #464646;
|
|
438
|
+
color: var(--text2);
|
|
439
|
+
cursor: pointer;
|
|
440
|
+
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
|
441
|
+
opacity: 0;
|
|
442
|
+
pointer-events: none;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.tags-input-wrapper:not(.disabled) .tag:hover .tag-remove {
|
|
446
|
+
opacity: 1;
|
|
447
|
+
pointer-events: auto;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.tag-remove:not(:disabled) {
|
|
451
|
+
color: var(--text1);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.tag-remove:disabled {
|
|
455
|
+
cursor: not-allowed;
|
|
456
|
+
opacity: 0.5;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.tag-remove :global(svg) {
|
|
460
|
+
width: 10px;
|
|
461
|
+
height: 10px;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.tags-input-field {
|
|
465
|
+
flex: 1;
|
|
466
|
+
min-width: 60px;
|
|
467
|
+
padding: 4px 8px;
|
|
468
|
+
border: none;
|
|
469
|
+
background: transparent;
|
|
470
|
+
color: var(--text1);
|
|
471
|
+
font-size: var(--font-size-small);
|
|
472
|
+
font-family: inherit;
|
|
473
|
+
line-height: 16px;
|
|
474
|
+
letter-spacing: -0.115px;
|
|
475
|
+
outline: none;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.tags-input-field::placeholder {
|
|
479
|
+
color: var(--text3);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.tags-input-field:disabled {
|
|
483
|
+
cursor: not-allowed;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.tags-input-loader {
|
|
487
|
+
display: flex;
|
|
488
|
+
align-items: center;
|
|
489
|
+
justify-content: center;
|
|
490
|
+
padding: 0 4px;
|
|
491
|
+
}
|
|
492
|
+
</style>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { AlertConfig } from '../input/types.js';
|
|
3
|
+
export interface TagsInputProps {
|
|
4
|
+
/**
|
|
5
|
+
* Array of tag values
|
|
6
|
+
*/
|
|
7
|
+
value?: string[];
|
|
8
|
+
/**
|
|
9
|
+
* Placeholder text when no tags and input is empty
|
|
10
|
+
*/
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Input field id
|
|
14
|
+
*/
|
|
15
|
+
id?: string;
|
|
16
|
+
/**
|
|
17
|
+
* If true, the input field will be disabled
|
|
18
|
+
*/
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* If true, the component will show loading state
|
|
22
|
+
*/
|
|
23
|
+
loading?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* If true, the input field will be invalid
|
|
26
|
+
*/
|
|
27
|
+
invalid?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* If true, the input field will be readonly (no adding/removing tags)
|
|
30
|
+
*/
|
|
31
|
+
readonly?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Defines the alert message to show
|
|
34
|
+
*/
|
|
35
|
+
alert?: AlertConfig | null;
|
|
36
|
+
/**
|
|
37
|
+
* Maximum number of tags allowed
|
|
38
|
+
*/
|
|
39
|
+
maxTags?: number | null;
|
|
40
|
+
/**
|
|
41
|
+
* Minimum number of tags required (for validation display)
|
|
42
|
+
*/
|
|
43
|
+
minTags?: number | null;
|
|
44
|
+
/**
|
|
45
|
+
* Maximum length of each tag
|
|
46
|
+
*/
|
|
47
|
+
maxTagLength?: number | null;
|
|
48
|
+
/**
|
|
49
|
+
* Separator keys to trigger tag creation (default: ['Enter', ','])
|
|
50
|
+
*/
|
|
51
|
+
separatorKeys?: string[];
|
|
52
|
+
/**
|
|
53
|
+
* Whether to allow duplicate tags (default: false)
|
|
54
|
+
*/
|
|
55
|
+
allowDuplicates?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Custom validation function for tags
|
|
58
|
+
* Return true if valid, false or error message string if invalid
|
|
59
|
+
*/
|
|
60
|
+
validateTag?: (tag: string) => boolean | string;
|
|
61
|
+
/**
|
|
62
|
+
* Whether to trim whitespace from tags (default: true)
|
|
63
|
+
*/
|
|
64
|
+
trimTags?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Custom width for the component
|
|
67
|
+
*/
|
|
68
|
+
width?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Custom height for the component
|
|
71
|
+
*/
|
|
72
|
+
height?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Additional CSS classes
|
|
75
|
+
*/
|
|
76
|
+
class?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Event handler for value changes
|
|
79
|
+
*/
|
|
80
|
+
onValueChange?: (tags: string[]) => void;
|
|
81
|
+
/**
|
|
82
|
+
* Event handler for individual tag addition
|
|
83
|
+
*/
|
|
84
|
+
onTagAdd?: (tag: string) => void;
|
|
85
|
+
/**
|
|
86
|
+
* Event handler for individual tag removal
|
|
87
|
+
*/
|
|
88
|
+
onTagRemove?: (tag: string, index: number) => void;
|
|
89
|
+
/**
|
|
90
|
+
* Event handler for invalid tag attempt
|
|
91
|
+
*/
|
|
92
|
+
onInvalidTag?: (tag: string, reason: string) => void;
|
|
93
|
+
/**
|
|
94
|
+
* Event handler for focus events
|
|
95
|
+
*/
|
|
96
|
+
onfocus?: (event: FocusEvent) => void;
|
|
97
|
+
/**
|
|
98
|
+
* Event handler for blur events
|
|
99
|
+
*/
|
|
100
|
+
onblur?: (event: FocusEvent) => void;
|
|
101
|
+
/**
|
|
102
|
+
* Event handler for keydown events
|
|
103
|
+
*/
|
|
104
|
+
onkeydown?: (event: KeyboardEvent) => void;
|
|
105
|
+
/**
|
|
106
|
+
* Children content (if any)
|
|
107
|
+
*/
|
|
108
|
+
children?: Snippet;
|
|
109
|
+
}
|
|
110
|
+
export interface TagAddEvent {
|
|
111
|
+
tag: string;
|
|
112
|
+
}
|
|
113
|
+
export interface TagRemoveEvent {
|
|
114
|
+
tag: string;
|
|
115
|
+
index: number;
|
|
116
|
+
}
|
|
117
|
+
export interface InvalidTagEvent {
|
|
118
|
+
tag: string;
|
|
119
|
+
reason: string;
|
|
120
|
+
}
|
|
121
|
+
export interface TagsValueChangeEvent {
|
|
122
|
+
tags: string[];
|
|
123
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 16 16" fill="none">
|
|
2
2
|
<path
|
|
3
3
|
d="M8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0ZM11.53 10.47L10.47 11.53L8 9.06L5.53 11.53L4.47 10.47L6.94 8L4.47 5.53L5.53 4.47L8 6.94L10.47 4.47L11.53 5.53L9.06 8L11.53 10.47Z"
|
|
4
4
|
fill="currentColor"
|