@celar-ui/svelte 1.3.0 → 1.4.0
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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/inputs/TagInput.svelte +174 -0
- package/dist/inputs/TagInput.svelte.d.ts +12 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export { default as RadioGroup } from './inputs/RadioGroup.svelte';
|
|
|
29
29
|
export { default as RadioItem } from './inputs/RadioItem.svelte';
|
|
30
30
|
export { default as Switch } from './inputs/Switch.svelte';
|
|
31
31
|
export { default as Slider } from './inputs/Slider.svelte';
|
|
32
|
+
export { default as TagInput } from './inputs/TagInput.svelte';
|
|
32
33
|
export { default as AppBar } from './navigation/AppBar.svelte';
|
|
33
34
|
export { default as NavigationBar } from './navigation/NavigationBar.svelte';
|
|
34
35
|
export { default as NavigationBarButton } from './navigation/NavigationBarButton.svelte';
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ export { default as RadioGroup } from './inputs/RadioGroup.svelte';
|
|
|
30
30
|
export { default as RadioItem } from './inputs/RadioItem.svelte';
|
|
31
31
|
export { default as Switch } from './inputs/Switch.svelte';
|
|
32
32
|
export { default as Slider } from './inputs/Slider.svelte';
|
|
33
|
+
export { default as TagInput } from './inputs/TagInput.svelte';
|
|
33
34
|
export { default as AppBar } from './navigation/AppBar.svelte';
|
|
34
35
|
export { default as NavigationBar } from './navigation/NavigationBar.svelte';
|
|
35
36
|
export { default as NavigationBarButton } from './navigation/NavigationBarButton.svelte';
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Badge from '../misc/Badge.svelte';
|
|
3
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
interface TagInputProps extends Omit<HTMLInputAttributes, 'value'> {
|
|
6
|
+
tags?: string[];
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
maxTags?: number;
|
|
9
|
+
allowDuplicates?: boolean;
|
|
10
|
+
onTagAdd?: (tag: string) => void;
|
|
11
|
+
onTagRemove?: (tag: string, index: number) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
tags = $bindable([]),
|
|
16
|
+
placeholder = 'Add tags...',
|
|
17
|
+
maxTags,
|
|
18
|
+
allowDuplicates = false,
|
|
19
|
+
onTagAdd,
|
|
20
|
+
onTagRemove,
|
|
21
|
+
...rest
|
|
22
|
+
}: TagInputProps = $props();
|
|
23
|
+
|
|
24
|
+
let inputValue = $state('');
|
|
25
|
+
|
|
26
|
+
let inputElement: HTMLInputElement;
|
|
27
|
+
|
|
28
|
+
const addTag = (tag: string) => {
|
|
29
|
+
const trimmedTag = tag.trim();
|
|
30
|
+
if (!trimmedTag) return;
|
|
31
|
+
|
|
32
|
+
if (maxTags !== undefined && tags.length >= maxTags) return;
|
|
33
|
+
if (!allowDuplicates && tags.includes(trimmedTag)) return;
|
|
34
|
+
|
|
35
|
+
tags.push(trimmedTag);
|
|
36
|
+
onTagAdd?.(trimmedTag);
|
|
37
|
+
inputValue = '';
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const removeTag = (index: number) => {
|
|
41
|
+
const removedTag = tags[index];
|
|
42
|
+
tags.splice(index, 1);
|
|
43
|
+
onTagRemove?.(removedTag, index);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const handleKeydown = (event: KeyboardEvent) => {
|
|
47
|
+
if (event.key === 'Enter' || event.key === ',') {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
addTag(inputValue);
|
|
50
|
+
} else if (event.key === 'Backspace' && !inputValue && tags.length > 0) {
|
|
51
|
+
removeTag(tags.length - 1);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const handleInputBlur = () => {
|
|
56
|
+
if (inputValue) {
|
|
57
|
+
addTag(inputValue);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const focusInputElement = () => {
|
|
62
|
+
inputElement?.focus();
|
|
63
|
+
};
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<div
|
|
67
|
+
data-tag-input
|
|
68
|
+
onclick={focusInputElement}
|
|
69
|
+
onkeyup={focusInputElement}
|
|
70
|
+
role="button"
|
|
71
|
+
tabindex="0"
|
|
72
|
+
>
|
|
73
|
+
{#each tags as tag, index (index)}
|
|
74
|
+
<Badge size="large">
|
|
75
|
+
{tag}
|
|
76
|
+
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
data-tag-remove
|
|
80
|
+
onclick={() => removeTag(index)}
|
|
81
|
+
aria-label="Remove {tag}"
|
|
82
|
+
>
|
|
83
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
|
84
|
+
<path
|
|
85
|
+
fill="none"
|
|
86
|
+
stroke="currentColor"
|
|
87
|
+
stroke-linecap="round"
|
|
88
|
+
stroke-linejoin="round"
|
|
89
|
+
stroke-width="2"
|
|
90
|
+
d="M18 6L6 18M6 6l12 12"
|
|
91
|
+
/>
|
|
92
|
+
</svg>
|
|
93
|
+
</button>
|
|
94
|
+
</Badge>
|
|
95
|
+
{/each}
|
|
96
|
+
|
|
97
|
+
<input
|
|
98
|
+
{...rest}
|
|
99
|
+
bind:this={inputElement}
|
|
100
|
+
bind:value={inputValue}
|
|
101
|
+
{placeholder}
|
|
102
|
+
onkeydown={handleKeydown}
|
|
103
|
+
onblur={handleInputBlur}
|
|
104
|
+
disabled={maxTags !== undefined && tags.length >= maxTags}
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<style>[data-tag-input] {
|
|
109
|
+
--color-background: transparent;
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
flex-wrap: wrap;
|
|
113
|
+
gap: var(--gap--sm);
|
|
114
|
+
position: relative;
|
|
115
|
+
width: 100%;
|
|
116
|
+
box-sizing: border-box;
|
|
117
|
+
transition-duration: var(--transition-dur);
|
|
118
|
+
transition-property: border-color;
|
|
119
|
+
transition-timing-function: ease-in-out;
|
|
120
|
+
border: 1px solid var(--color-border);
|
|
121
|
+
border-radius: var(--radius);
|
|
122
|
+
background-color: var(--color-background);
|
|
123
|
+
padding: var(--gap--md);
|
|
124
|
+
cursor: text;
|
|
125
|
+
}
|
|
126
|
+
[data-tag-input]:focus-within {
|
|
127
|
+
border: 1px solid var(--color-primary);
|
|
128
|
+
}
|
|
129
|
+
[data-tag-input] input {
|
|
130
|
+
flex: 1;
|
|
131
|
+
border: none;
|
|
132
|
+
outline: none;
|
|
133
|
+
background: transparent;
|
|
134
|
+
font-size: inherit;
|
|
135
|
+
font-family: inherit;
|
|
136
|
+
color: inherit;
|
|
137
|
+
}
|
|
138
|
+
[data-tag-input] input::-moz-placeholder {
|
|
139
|
+
opacity: 1;
|
|
140
|
+
color: rgba(var(--color-onSurface--rgb), 0.7);
|
|
141
|
+
}
|
|
142
|
+
[data-tag-input] input::placeholder {
|
|
143
|
+
opacity: 1;
|
|
144
|
+
color: rgba(var(--color-onSurface--rgb), 0.7);
|
|
145
|
+
}
|
|
146
|
+
[data-tag-input] input:disabled {
|
|
147
|
+
opacity: 0.5;
|
|
148
|
+
cursor: not-allowed;
|
|
149
|
+
}
|
|
150
|
+
[data-tag-input] [data-tag-remove] {
|
|
151
|
+
display: inline-flex;
|
|
152
|
+
justify-content: center;
|
|
153
|
+
align-items: center;
|
|
154
|
+
background-color: transparent;
|
|
155
|
+
border: none;
|
|
156
|
+
color: inherit;
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
padding: var(--gap--xs);
|
|
159
|
+
border-radius: 50%;
|
|
160
|
+
aspect-ratio: 1;
|
|
161
|
+
transition-duration: var(--transition-dur);
|
|
162
|
+
transition-property: background-color;
|
|
163
|
+
transition-timing-function: ease-in-out;
|
|
164
|
+
margin-left: var(--gap--sm);
|
|
165
|
+
}
|
|
166
|
+
[data-tag-input] [data-tag-remove]:hover {
|
|
167
|
+
background-color: var(--color-surfaceContainer);
|
|
168
|
+
}
|
|
169
|
+
[data-tag-input] [data-tag-remove]:focus {
|
|
170
|
+
outline: 1px solid var(--color-border);
|
|
171
|
+
}
|
|
172
|
+
[data-tag-input] [data-tag-remove] svg {
|
|
173
|
+
width: var(--gap);
|
|
174
|
+
}</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
interface TagInputProps extends Omit<HTMLInputAttributes, 'value'> {
|
|
3
|
+
tags?: string[];
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
maxTags?: number;
|
|
6
|
+
allowDuplicates?: boolean;
|
|
7
|
+
onTagAdd?: (tag: string) => void;
|
|
8
|
+
onTagRemove?: (tag: string, index: number) => void;
|
|
9
|
+
}
|
|
10
|
+
declare const TagInput: import("svelte").Component<TagInputProps, {}, "tags">;
|
|
11
|
+
type TagInput = ReturnType<typeof TagInput>;
|
|
12
|
+
export default TagInput;
|