@dryui/ui 1.4.1 → 1.5.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/combobox/combobox-content.svelte +7 -3
- package/dist/combobox/combobox-input-root.svelte +8 -0
- package/dist/combobox/combobox-input.svelte +156 -52
- package/dist/combobox/combobox-input.svelte.d.ts +3 -0
- package/dist/combobox/combobox-item.svelte +12 -4
- package/dist/combobox/context.svelte.d.ts +1 -0
- package/dist/combobox/index.d.ts +2 -0
- package/dist/diagram/diagram.svelte +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -1
- package/package.json +2 -7
- package/skills/dryui/SKILL.md +14 -0
- package/dist/country-select/country-select-button-input.svelte +0 -173
- package/dist/country-select/country-select-button-input.svelte.d.ts +0 -15
- package/dist/country-select/index.d.ts +0 -2
- package/dist/country-select/index.js +0 -1
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
}: Props = $props();
|
|
26
26
|
|
|
27
27
|
const ctx = getComboboxCtx();
|
|
28
|
+
const getTriggerEl = () => ctx.triggerEl ?? ctx.inputEl;
|
|
28
29
|
|
|
29
30
|
let el = $state<HTMLDivElement | null>(null);
|
|
30
31
|
|
|
@@ -39,7 +40,7 @@
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
const popover = createAnchoredPopover({
|
|
42
|
-
triggerEl:
|
|
43
|
+
triggerEl: getTriggerEl,
|
|
43
44
|
contentEl: () => el ?? null,
|
|
44
45
|
open: () => ctx.open,
|
|
45
46
|
placement: () => placement,
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
escapeKey: false,
|
|
52
53
|
onDismiss: () => ctx.close(),
|
|
53
54
|
contentEl: () => el ?? null,
|
|
54
|
-
triggerEl:
|
|
55
|
+
triggerEl: getTriggerEl
|
|
55
56
|
});
|
|
56
57
|
</script>
|
|
57
58
|
|
|
@@ -87,7 +88,10 @@
|
|
|
87
88
|
margin: 0;
|
|
88
89
|
|
|
89
90
|
display: grid;
|
|
90
|
-
grid-template-columns: minmax(
|
|
91
|
+
grid-template-columns: minmax(
|
|
92
|
+
max(var(--dry-combobox-content-min-inline-size, 12rem), anchor-size(inline)),
|
|
93
|
+
max-content
|
|
94
|
+
);
|
|
91
95
|
background: var(--dry-color-bg-overlay);
|
|
92
96
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
93
97
|
border-radius: var(--dry-radius-md);
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
let inputValue = $state('');
|
|
27
27
|
let activeIndex = $state(-1);
|
|
28
28
|
let inputEl = $state<HTMLInputElement | null>(null);
|
|
29
|
+
let triggerEl = $state<HTMLElement | null>(null);
|
|
29
30
|
|
|
30
31
|
setComboboxCtx({
|
|
31
32
|
get open() {
|
|
@@ -54,6 +55,12 @@
|
|
|
54
55
|
set inputEl(element: HTMLInputElement | null) {
|
|
55
56
|
inputEl = element;
|
|
56
57
|
},
|
|
58
|
+
get triggerEl() {
|
|
59
|
+
return triggerEl;
|
|
60
|
+
},
|
|
61
|
+
set triggerEl(element: HTMLElement | null) {
|
|
62
|
+
triggerEl = element;
|
|
63
|
+
},
|
|
57
64
|
show() {
|
|
58
65
|
if (!disabled) open = true;
|
|
59
66
|
},
|
|
@@ -93,6 +100,7 @@
|
|
|
93
100
|
[data-combobox-wrapper] {
|
|
94
101
|
container-type: inline-size;
|
|
95
102
|
display: grid;
|
|
103
|
+
grid-template-columns: minmax(0, 1fr);
|
|
96
104
|
position: relative;
|
|
97
105
|
}
|
|
98
106
|
</style>
|
|
@@ -1,23 +1,42 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
4
|
import { getComboboxCtx } from './context.svelte.js';
|
|
4
5
|
|
|
5
6
|
interface Props extends Omit<HTMLInputAttributes, 'size'> {
|
|
6
7
|
placeholder?: string;
|
|
7
8
|
size?: 'sm' | 'md' | 'lg';
|
|
9
|
+
leading?: Snippet;
|
|
10
|
+
trailing?: Snippet;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
let {
|
|
11
14
|
class: className,
|
|
15
|
+
style,
|
|
12
16
|
size = 'md',
|
|
13
17
|
placeholder = '',
|
|
18
|
+
leading,
|
|
19
|
+
trailing,
|
|
20
|
+
disabled = false,
|
|
14
21
|
oninput,
|
|
15
22
|
onkeydown,
|
|
16
23
|
onfocus,
|
|
24
|
+
'aria-invalid': ariaInvalid,
|
|
25
|
+
'data-invalid': dataInvalid,
|
|
17
26
|
...rest
|
|
18
27
|
}: Props = $props();
|
|
19
28
|
|
|
20
29
|
const ctx = getComboboxCtx();
|
|
30
|
+
const hasLeading = $derived(Boolean(leading));
|
|
31
|
+
const hasTrailing = $derived(Boolean(trailing));
|
|
32
|
+
const isDisabled = $derived(Boolean(ctx.disabled || disabled));
|
|
33
|
+
const isInvalid = $derived(
|
|
34
|
+
isTruthyInvalidValue(ariaInvalid) || isTruthyInvalidValue(dataInvalid)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
function isTruthyInvalidValue(value: unknown): boolean {
|
|
38
|
+
return value != null && value !== false && value !== 'false';
|
|
39
|
+
}
|
|
21
40
|
|
|
22
41
|
function attachInput(node: HTMLInputElement) {
|
|
23
42
|
ctx.inputEl = node;
|
|
@@ -28,6 +47,15 @@
|
|
|
28
47
|
};
|
|
29
48
|
}
|
|
30
49
|
|
|
50
|
+
function attachTrigger(node: HTMLElement) {
|
|
51
|
+
ctx.triggerEl = node;
|
|
52
|
+
return () => {
|
|
53
|
+
if (ctx.triggerEl === node) {
|
|
54
|
+
ctx.triggerEl = null;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
31
59
|
function getOptionItems(contentEl: HTMLElement | null): HTMLElement[] {
|
|
32
60
|
if (!contentEl) return [];
|
|
33
61
|
return Array.from(
|
|
@@ -42,7 +70,7 @@
|
|
|
42
70
|
function handleInput(e: Event & { currentTarget: HTMLInputElement }) {
|
|
43
71
|
const val = e.currentTarget.value;
|
|
44
72
|
ctx.setInputValue(val);
|
|
45
|
-
ctx.setActiveIndex(-1);
|
|
73
|
+
if (ctx.activeIndex !== -1) ctx.setActiveIndex(-1);
|
|
46
74
|
if (!ctx.open) ctx.show();
|
|
47
75
|
if (oninput) (oninput as (e: Event & { currentTarget: HTMLInputElement }) => void)(e);
|
|
48
76
|
}
|
|
@@ -53,8 +81,8 @@
|
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
function handleKeydown(e: KeyboardEvent & { currentTarget: HTMLInputElement }) {
|
|
56
|
-
|
|
57
|
-
const items = getOptionItems(
|
|
84
|
+
let cachedItems: HTMLElement[] | undefined;
|
|
85
|
+
const items = () => (cachedItems ??= getOptionItems(getContentEl()));
|
|
58
86
|
|
|
59
87
|
switch (e.key) {
|
|
60
88
|
case 'ArrowDown': {
|
|
@@ -63,7 +91,7 @@
|
|
|
63
91
|
ctx.show();
|
|
64
92
|
ctx.setActiveIndex(0);
|
|
65
93
|
} else {
|
|
66
|
-
const next = ctx.activeIndex < items.length - 1 ? ctx.activeIndex + 1 : 0;
|
|
94
|
+
const next = ctx.activeIndex < items().length - 1 ? ctx.activeIndex + 1 : 0;
|
|
67
95
|
ctx.setActiveIndex(next);
|
|
68
96
|
}
|
|
69
97
|
break;
|
|
@@ -72,17 +100,17 @@
|
|
|
72
100
|
e.preventDefault();
|
|
73
101
|
if (!ctx.open) {
|
|
74
102
|
ctx.show();
|
|
75
|
-
ctx.setActiveIndex(items.length - 1);
|
|
103
|
+
ctx.setActiveIndex(items().length - 1);
|
|
76
104
|
} else {
|
|
77
|
-
const prev = ctx.activeIndex > 0 ? ctx.activeIndex - 1 : items.length - 1;
|
|
105
|
+
const prev = ctx.activeIndex > 0 ? ctx.activeIndex - 1 : items().length - 1;
|
|
78
106
|
ctx.setActiveIndex(prev);
|
|
79
107
|
}
|
|
80
108
|
break;
|
|
81
109
|
}
|
|
82
110
|
case 'Enter': {
|
|
83
111
|
e.preventDefault();
|
|
84
|
-
if (ctx.open && ctx.activeIndex >= 0
|
|
85
|
-
items[ctx.activeIndex]
|
|
112
|
+
if (ctx.open && ctx.activeIndex >= 0) {
|
|
113
|
+
items()[ctx.activeIndex]?.click();
|
|
86
114
|
}
|
|
87
115
|
break;
|
|
88
116
|
}
|
|
@@ -93,21 +121,25 @@
|
|
|
93
121
|
break;
|
|
94
122
|
}
|
|
95
123
|
case 'Home': {
|
|
96
|
-
if (!ctx.open || items.length === 0) break;
|
|
124
|
+
if (!ctx.open || items().length === 0) break;
|
|
97
125
|
e.preventDefault();
|
|
98
126
|
ctx.setActiveIndex(0);
|
|
99
127
|
break;
|
|
100
128
|
}
|
|
101
129
|
case 'End': {
|
|
102
|
-
if (!ctx.open
|
|
130
|
+
if (!ctx.open) break;
|
|
131
|
+
const list = items();
|
|
132
|
+
if (list.length === 0) break;
|
|
103
133
|
e.preventDefault();
|
|
104
|
-
ctx.setActiveIndex(
|
|
134
|
+
ctx.setActiveIndex(list.length - 1);
|
|
105
135
|
break;
|
|
106
136
|
}
|
|
107
137
|
case 'Tab': {
|
|
108
138
|
if (ctx.open) {
|
|
109
|
-
if (ctx.activeIndex >= 0
|
|
110
|
-
items[ctx.activeIndex]
|
|
139
|
+
if (ctx.activeIndex >= 0) {
|
|
140
|
+
const item = items()[ctx.activeIndex];
|
|
141
|
+
if (item) item.click();
|
|
142
|
+
else ctx.close();
|
|
111
143
|
} else {
|
|
112
144
|
ctx.close();
|
|
113
145
|
}
|
|
@@ -121,63 +153,106 @@
|
|
|
121
153
|
}
|
|
122
154
|
</script>
|
|
123
155
|
|
|
124
|
-
<
|
|
125
|
-
{@attach
|
|
126
|
-
id={ctx.inputId}
|
|
127
|
-
type="text"
|
|
128
|
-
role="combobox"
|
|
129
|
-
aria-expanded={ctx.open}
|
|
130
|
-
aria-autocomplete="list"
|
|
131
|
-
aria-controls={ctx.contentId}
|
|
132
|
-
aria-activedescendant={ctx.activeIndex >= 0
|
|
133
|
-
? `${ctx.contentId}-item-${ctx.activeIndex}`
|
|
134
|
-
: undefined}
|
|
156
|
+
<label
|
|
157
|
+
{@attach attachTrigger}
|
|
135
158
|
data-combobox-input
|
|
136
159
|
data-size={size}
|
|
137
160
|
data-state={ctx.open ? 'open' : 'closed'}
|
|
138
|
-
disabled={
|
|
139
|
-
data-
|
|
140
|
-
|
|
141
|
-
{
|
|
142
|
-
autocomplete="off"
|
|
161
|
+
data-disabled={isDisabled || undefined}
|
|
162
|
+
data-invalid={isInvalid || undefined}
|
|
163
|
+
data-has-leading={hasLeading || undefined}
|
|
164
|
+
data-has-trailing={hasTrailing || undefined}
|
|
143
165
|
class={className}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
166
|
+
{style}
|
|
167
|
+
>
|
|
168
|
+
{#if leading}
|
|
169
|
+
<span data-part="leading" aria-hidden="true">{@render leading()}</span>
|
|
170
|
+
{/if}
|
|
171
|
+
|
|
172
|
+
<input
|
|
173
|
+
{@attach attachInput}
|
|
174
|
+
id={ctx.inputId}
|
|
175
|
+
type="text"
|
|
176
|
+
role="combobox"
|
|
177
|
+
aria-expanded={ctx.open}
|
|
178
|
+
aria-autocomplete="list"
|
|
179
|
+
aria-controls={ctx.contentId}
|
|
180
|
+
aria-activedescendant={ctx.activeIndex >= 0
|
|
181
|
+
? `${ctx.contentId}-item-${ctx.activeIndex}`
|
|
182
|
+
: undefined}
|
|
183
|
+
aria-invalid={ariaInvalid}
|
|
184
|
+
data-invalid={dataInvalid}
|
|
185
|
+
disabled={isDisabled || undefined}
|
|
186
|
+
data-disabled={isDisabled || undefined}
|
|
187
|
+
value={ctx.inputValue}
|
|
188
|
+
{placeholder}
|
|
189
|
+
autocomplete="off"
|
|
190
|
+
oninput={handleInput}
|
|
191
|
+
onkeydown={handleKeydown}
|
|
192
|
+
onfocus={handleFocus}
|
|
193
|
+
{...rest}
|
|
194
|
+
/>
|
|
195
|
+
|
|
196
|
+
{#if trailing}
|
|
197
|
+
<span data-part="trailing" aria-hidden="true">{@render trailing()}</span>
|
|
198
|
+
{/if}
|
|
199
|
+
</label>
|
|
149
200
|
|
|
150
201
|
<style>
|
|
151
202
|
[data-combobox-input] {
|
|
152
|
-
--
|
|
153
|
-
--
|
|
154
|
-
--
|
|
155
|
-
--
|
|
156
|
-
|
|
157
|
-
|
|
203
|
+
--_dry-combobox-bg: var(--dry-combobox-bg, var(--dry-form-control-bg));
|
|
204
|
+
--_dry-combobox-border: var(--dry-combobox-border, var(--dry-form-control-border));
|
|
205
|
+
--_dry-combobox-color: var(--dry-combobox-color, var(--dry-form-control-color));
|
|
206
|
+
--_dry-combobox-padding-x: var(
|
|
207
|
+
--dry-combobox-padding-x,
|
|
208
|
+
var(--dry-form-control-padding-inline)
|
|
209
|
+
);
|
|
210
|
+
--_dry-combobox-padding-y: var(--dry-combobox-padding-y, var(--dry-form-control-padding-block));
|
|
211
|
+
--_dry-combobox-font-size: var(--dry-combobox-font-size, var(--dry-form-control-font-size));
|
|
212
|
+
--_dry-combobox-affix-gap: var(--dry-combobox-affix-gap, var(--dry-space-2));
|
|
213
|
+
--_dry-combobox-affix-min-inline-size: var(--dry-combobox-affix-min-inline-size, 1.5rem);
|
|
158
214
|
|
|
159
215
|
display: grid;
|
|
160
|
-
|
|
161
|
-
|
|
216
|
+
align-items: center;
|
|
217
|
+
grid-template-columns: minmax(0, 1fr);
|
|
218
|
+
padding-block: var(--_dry-combobox-padding-y);
|
|
219
|
+
padding-inline: var(--_dry-combobox-padding-x);
|
|
220
|
+
font-size: var(--_dry-combobox-font-size);
|
|
162
221
|
line-height: var(--dry-type-small-leading);
|
|
163
222
|
font-family: var(--dry-font-sans);
|
|
164
|
-
color: var(--
|
|
165
|
-
background: var(--
|
|
166
|
-
border: 1px solid var(--
|
|
223
|
+
color: var(--_dry-combobox-color);
|
|
224
|
+
background: var(--_dry-combobox-bg);
|
|
225
|
+
border: 1px solid var(--_dry-combobox-border);
|
|
167
226
|
border-radius: var(--dry-combobox-radius, var(--dry-form-control-radius));
|
|
168
227
|
box-sizing: border-box;
|
|
169
|
-
|
|
228
|
+
column-gap: var(--_dry-combobox-affix-gap);
|
|
229
|
+
cursor: text;
|
|
170
230
|
transition:
|
|
171
231
|
border-color var(--dry-duration-fast) var(--dry-ease-default),
|
|
172
232
|
box-shadow var(--dry-duration-fast) var(--dry-ease-default);
|
|
173
233
|
}
|
|
174
234
|
|
|
235
|
+
[data-combobox-input][data-has-leading] {
|
|
236
|
+
grid-template-columns:
|
|
237
|
+
minmax(var(--_dry-combobox-affix-min-inline-size), max-content)
|
|
238
|
+
minmax(0, 1fr);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
[data-combobox-input][data-has-trailing] {
|
|
242
|
+
grid-template-columns: minmax(0, 1fr) max-content;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
[data-combobox-input][data-has-leading][data-has-trailing] {
|
|
246
|
+
grid-template-columns:
|
|
247
|
+
minmax(var(--_dry-combobox-affix-min-inline-size), max-content)
|
|
248
|
+
minmax(0, 1fr) max-content;
|
|
249
|
+
}
|
|
250
|
+
|
|
175
251
|
[data-combobox-input]:hover:not([data-disabled]) {
|
|
176
252
|
border-color: var(--dry-form-control-border-hover);
|
|
177
253
|
}
|
|
178
254
|
|
|
179
|
-
[data-combobox-input]:focus-
|
|
180
|
-
[data-combobox-input]:focus {
|
|
255
|
+
[data-combobox-input]:focus-within {
|
|
181
256
|
outline: var(--dry-focus-ring);
|
|
182
257
|
outline-offset: -1px;
|
|
183
258
|
border-color: var(--dry-color-stroke-focus);
|
|
@@ -190,7 +265,6 @@
|
|
|
190
265
|
cursor: not-allowed;
|
|
191
266
|
}
|
|
192
267
|
|
|
193
|
-
[data-combobox-input][aria-invalid='true'],
|
|
194
268
|
[data-combobox-input][data-invalid] {
|
|
195
269
|
--dry-combobox-bg: color-mix(
|
|
196
270
|
in srgb,
|
|
@@ -200,17 +274,47 @@
|
|
|
200
274
|
--dry-combobox-border: var(--dry-color-stroke-error);
|
|
201
275
|
}
|
|
202
276
|
|
|
203
|
-
[data-combobox-input][aria-invalid='true']:hover:not([data-disabled]),
|
|
204
277
|
[data-combobox-input][data-invalid]:hover:not([data-disabled]) {
|
|
205
278
|
border-color: var(--dry-color-stroke-error-strong);
|
|
206
279
|
}
|
|
207
280
|
|
|
208
|
-
[data-combobox-input][
|
|
209
|
-
[data-combobox-input][data-invalid]:focus-visible {
|
|
281
|
+
[data-combobox-input][data-invalid]:focus-within {
|
|
210
282
|
outline-color: var(--dry-color-fill-error);
|
|
211
283
|
border-color: var(--dry-color-stroke-error);
|
|
212
284
|
}
|
|
213
285
|
|
|
286
|
+
[data-combobox-input] input {
|
|
287
|
+
appearance: none;
|
|
288
|
+
background: transparent;
|
|
289
|
+
border: 0;
|
|
290
|
+
box-sizing: border-box;
|
|
291
|
+
color: inherit;
|
|
292
|
+
font: inherit;
|
|
293
|
+
line-height: inherit;
|
|
294
|
+
margin: 0;
|
|
295
|
+
outline: none;
|
|
296
|
+
overflow: hidden;
|
|
297
|
+
padding: 0;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
[data-combobox-input] input::placeholder {
|
|
301
|
+
color: var(--dry-form-control-color-placeholder);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
[data-combobox-input] input:focus,
|
|
305
|
+
[data-combobox-input] input:focus-visible {
|
|
306
|
+
outline: none;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
[data-combobox-input] [data-part='leading'],
|
|
310
|
+
[data-combobox-input] [data-part='trailing'] {
|
|
311
|
+
color: var(--dry-color-text-weak);
|
|
312
|
+
display: grid;
|
|
313
|
+
align-items: center;
|
|
314
|
+
justify-items: center;
|
|
315
|
+
line-height: 1;
|
|
316
|
+
}
|
|
317
|
+
|
|
214
318
|
/* Size variants */
|
|
215
319
|
[data-combobox-input][data-size='sm'] {
|
|
216
320
|
--dry-combobox-padding-x: var(--dry-space-2);
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
3
|
interface Props extends Omit<HTMLInputAttributes, 'size'> {
|
|
3
4
|
placeholder?: string;
|
|
4
5
|
size?: 'sm' | 'md' | 'lg';
|
|
6
|
+
leading?: Snippet;
|
|
7
|
+
trailing?: Snippet;
|
|
5
8
|
}
|
|
6
9
|
declare const ComboboxInput: import("svelte").Component<Props, {}, "">;
|
|
7
10
|
type ComboboxInput = ReturnType<typeof ComboboxInput>;
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
const isSelected = $derived(ctx.value === value);
|
|
29
29
|
const isHighlighted = $derived(ctx.activeIndex === index);
|
|
30
|
+
const hasIcon = $derived(Boolean(icon));
|
|
30
31
|
|
|
31
32
|
function handleClick(e: MouseEvent & { currentTarget: HTMLDivElement }) {
|
|
32
33
|
if (disabled) return;
|
|
@@ -64,6 +65,7 @@
|
|
|
64
65
|
data-state={isSelected ? 'selected' : 'unselected'}
|
|
65
66
|
data-highlighted={isHighlighted || undefined}
|
|
66
67
|
data-disabled={disabled || undefined}
|
|
68
|
+
data-has-icon={hasIcon || undefined}
|
|
67
69
|
data-value={value}
|
|
68
70
|
class={className}
|
|
69
71
|
onclick={handleClick}
|
|
@@ -82,10 +84,10 @@
|
|
|
82
84
|
var(--dry-control-radius, var(--dry-radius-sm)),
|
|
83
85
|
var(--dry-space-4)
|
|
84
86
|
);
|
|
87
|
+
--_dry-combobox-item-icon-size: var(--dry-combobox-item-icon-size, 1.5rem);
|
|
85
88
|
|
|
86
89
|
display: grid;
|
|
87
|
-
grid-
|
|
88
|
-
grid-auto-columns: max-content;
|
|
90
|
+
grid-template-columns: minmax(0, 1fr);
|
|
89
91
|
align-items: center;
|
|
90
92
|
padding: var(--dry-space-2) var(--dry-space-3);
|
|
91
93
|
border-radius: var(--dry-combobox-item-radius);
|
|
@@ -97,6 +99,11 @@
|
|
|
97
99
|
min-height: var(--dry-space-10);
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
[data-combobox-item][data-has-icon] {
|
|
103
|
+
column-gap: var(--dry-space-2);
|
|
104
|
+
grid-template-columns: minmax(var(--_dry-combobox-item-icon-size), max-content) minmax(0, 1fr);
|
|
105
|
+
}
|
|
106
|
+
|
|
100
107
|
[data-combobox-item]:hover:not([data-disabled]),
|
|
101
108
|
[data-combobox-item][data-highlighted] {
|
|
102
109
|
background: var(--dry-color-fill);
|
|
@@ -121,8 +128,9 @@
|
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
[data-combobox-item] [data-part='icon'] {
|
|
124
|
-
display:
|
|
131
|
+
display: grid;
|
|
125
132
|
align-items: center;
|
|
126
|
-
|
|
133
|
+
justify-content: center;
|
|
134
|
+
line-height: 1;
|
|
127
135
|
}
|
|
128
136
|
</style>
|
package/dist/combobox/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { ComboboxInputProps as PrimitiveComboboxInputProps } from '@dryui/p
|
|
|
4
4
|
export type { ComboboxRootProps, ComboboxContentProps, ComboboxItemProps, ComboboxEmptyProps } from '@dryui/primitives';
|
|
5
5
|
export interface ComboboxInputProps extends Omit<PrimitiveComboboxInputProps, 'size'> {
|
|
6
6
|
size?: 'sm' | 'md' | 'lg';
|
|
7
|
+
leading?: Snippet;
|
|
8
|
+
trailing?: Snippet;
|
|
7
9
|
}
|
|
8
10
|
export interface ComboboxGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
9
11
|
label: string;
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
<div data-diagram-container data-fit={fit}>
|
|
24
24
|
<svg
|
|
25
|
-
width={fit === 'native' ? vbW :
|
|
25
|
+
width={fit === 'native' ? vbW : '100%'}
|
|
26
26
|
height={fit === 'native' ? vbH : undefined}
|
|
27
27
|
viewBox="0 0 {vbW} {vbH}"
|
|
28
28
|
preserveAspectRatio="xMidYMin meet"
|
package/dist/index.d.ts
CHANGED
|
@@ -248,8 +248,6 @@ export { VideoEmbed } from './video-embed/index.js';
|
|
|
248
248
|
export type { VideoEmbedProps } from './video-embed/index.js';
|
|
249
249
|
export { PhoneInput } from './phone-input/index.js';
|
|
250
250
|
export type { PhoneInputProps } from './phone-input/index.js';
|
|
251
|
-
export { CountrySelect } from './country-select/index.js';
|
|
252
|
-
export type { CountrySelectProps } from './country-select/index.js';
|
|
253
251
|
export { DateTimeInput } from './date-time-input/index.js';
|
|
254
252
|
export type { DateTimeInputProps } from './date-time-input/index.js';
|
|
255
253
|
export { NotificationCenter } from './notification-center/index.js';
|
package/dist/index.js
CHANGED
|
@@ -127,7 +127,6 @@ export { Sparkline } from './sparkline/index.js';
|
|
|
127
127
|
export { FlipCard } from './flip-card/index.js';
|
|
128
128
|
export { VideoEmbed } from './video-embed/index.js';
|
|
129
129
|
export { PhoneInput } from './phone-input/index.js';
|
|
130
|
-
export { CountrySelect } from './country-select/index.js';
|
|
131
130
|
export { DateTimeInput } from './date-time-input/index.js';
|
|
132
131
|
export { NotificationCenter } from './notification-center/index.js';
|
|
133
132
|
export { MegaMenu } from './mega-menu/index.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dryui/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Zero-dependency styled Svelte 5 components with scoped styles and --dry-* CSS variable theming.",
|
|
5
5
|
"author": "Rob Balfre",
|
|
6
6
|
"license": "MIT",
|
|
@@ -195,11 +195,6 @@
|
|
|
195
195
|
"svelte": "./dist/context-menu/index.js",
|
|
196
196
|
"default": "./dist/context-menu/index.js"
|
|
197
197
|
},
|
|
198
|
-
"./country-select": {
|
|
199
|
-
"types": "./dist/country-select/index.d.ts",
|
|
200
|
-
"svelte": "./dist/country-select/index.js",
|
|
201
|
-
"default": "./dist/country-select/index.js"
|
|
202
|
-
},
|
|
203
198
|
"./data-grid": {
|
|
204
199
|
"types": "./dist/data-grid/index.d.ts",
|
|
205
200
|
"svelte": "./dist/data-grid/index.js",
|
|
@@ -784,7 +779,7 @@
|
|
|
784
779
|
"postpack": "bun ../../scripts/postpack-exports.ts"
|
|
785
780
|
},
|
|
786
781
|
"dependencies": {
|
|
787
|
-
"@dryui/primitives": "^1.
|
|
782
|
+
"@dryui/primitives": "^1.5.0"
|
|
788
783
|
},
|
|
789
784
|
"peerDependencies": {
|
|
790
785
|
"svelte": "^5.55.1"
|
package/skills/dryui/SKILL.md
CHANGED
|
@@ -116,6 +116,18 @@ The test: every `<Input>`, `<Select.Root>`, `<Textarea>` is inside a `Field.Root
|
|
|
116
116
|
|
|
117
117
|
The test: search your markup for raw `<input`, `<select>`, `<dialog>`, `<button>`, `<hr>`, `<table>` — each should be a DryUI component instead.
|
|
118
118
|
|
|
119
|
+
## 7. Ask the Svelte MCP for Svelte Questions
|
|
120
|
+
|
|
121
|
+
**DryUI owns components. `@sveltejs/mcp` owns the framework.**
|
|
122
|
+
|
|
123
|
+
For Svelte 5 runes (`$state`, `$derived`, `$effect`, `$props`), snippets, SvelteKit load fns, `+page.server.ts` shape, form actions, and anything Svelte-syntax adjacent: call the official `svelte-autofixer` and `get-documentation` tools from `@sveltejs/mcp` before guessing from memory.
|
|
124
|
+
|
|
125
|
+
- `dryui setup --install` registers `@sveltejs/mcp` by default; pass `--no-svelte-mcp` to skip.
|
|
126
|
+
- If it's not registered, the fallback is the remote endpoint `https://mcp.svelte.dev/mcp` or a one-liner like `claude mcp add -t stdio -s user svelte -- npx -y @sveltejs/mcp`.
|
|
127
|
+
- Scope split: DryUI `ask`/`check` cover component APIs, theming, composition, and validation. Svelte MCP covers the runtime, compiler, and framework idioms.
|
|
128
|
+
|
|
129
|
+
The test: before writing non-trivial Svelte 5 or SvelteKit code, did you either (a) call `svelte-autofixer` / `get-documentation`, or (b) confirm the pattern is already covered by a DryUI recipe via `ask --scope recipe`?
|
|
130
|
+
|
|
119
131
|
## Quick Start
|
|
120
132
|
|
|
121
133
|
**1. Install the CLI** so every subsequent command is short and fast:
|
|
@@ -149,6 +161,8 @@ This works for greenfield (empty directory), brownfield (existing non-SvelteKit
|
|
|
149
161
|
- OpenCode: `npx degit rob-balfre/dryui/packages/ui/skills/dryui .opencode/skills/dryui` + add the `dryui` and `dryui-feedback` local MCP servers in `opencode.json` (OpenCode also loads `.agents/skills/dryui` and reads `AGENTS.md`)
|
|
150
162
|
- Copilot/Cursor/Windsurf: `npx degit rob-balfre/dryui/packages/ui/skills/dryui .agents/skills/dryui` + add MCP config (see https://dryui.dev/tools)
|
|
151
163
|
|
|
164
|
+
**5. Register the Svelte MCP companion.** `dryui setup --install` does this automatically for Copilot, Cursor, OpenCode, Windsurf, and Zed. For Claude Code run `claude mcp add -t stdio -s user svelte -- npx -y @sveltejs/mcp`; for Codex add `[mcp_servers.svelte] command = "npx", args = ["-y", "@sveltejs/mcp"]` to `~/.codex/config.toml`. See rule 7 above.
|
|
165
|
+
|
|
152
166
|
### Manual setup
|
|
153
167
|
|
|
154
168
|
1. `bun add @dryui/ui`
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
|
-
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
-
import { CountrySelect as PrimitiveCountrySelect } from '@dryui/primitives';
|
|
5
|
-
|
|
6
|
-
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'onchange'> {
|
|
7
|
-
value?: string;
|
|
8
|
-
regions?: string[];
|
|
9
|
-
showDialCode?: boolean;
|
|
10
|
-
disabled?: boolean;
|
|
11
|
-
placeholder?: string;
|
|
12
|
-
name?: string;
|
|
13
|
-
onchange?: (code: string) => void;
|
|
14
|
-
children?: Snippet;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
let {
|
|
18
|
-
value = $bindable(''),
|
|
19
|
-
regions,
|
|
20
|
-
showDialCode = false,
|
|
21
|
-
disabled = false,
|
|
22
|
-
placeholder = 'Select country',
|
|
23
|
-
name: formName,
|
|
24
|
-
onchange,
|
|
25
|
-
class: className,
|
|
26
|
-
...rest
|
|
27
|
-
}: Props = $props();
|
|
28
|
-
</script>
|
|
29
|
-
|
|
30
|
-
<span data-country-select-wrapper class={className}>
|
|
31
|
-
<PrimitiveCountrySelect
|
|
32
|
-
bind:value
|
|
33
|
-
{regions}
|
|
34
|
-
{showDialCode}
|
|
35
|
-
{disabled}
|
|
36
|
-
{placeholder}
|
|
37
|
-
name={formName}
|
|
38
|
-
{onchange}
|
|
39
|
-
{...rest}
|
|
40
|
-
/>
|
|
41
|
-
</span>
|
|
42
|
-
|
|
43
|
-
<style>
|
|
44
|
-
[data-country-select-wrapper] {
|
|
45
|
-
display: contents;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
[data-country-select-wrapper] [data-part='country-select'] {
|
|
49
|
-
display: inline-grid;
|
|
50
|
-
grid-template-columns: minmax(14rem, max-content);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
[data-country-select-wrapper] [data-part='control'] {
|
|
54
|
-
display: grid;
|
|
55
|
-
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
56
|
-
align-items: center;
|
|
57
|
-
gap: var(--dry-space-2, 0.5rem);
|
|
58
|
-
min-height: 2.75rem;
|
|
59
|
-
padding-inline: var(--dry-space-3, 0.75rem);
|
|
60
|
-
border: 1px solid var(--dry-color-stroke-weak);
|
|
61
|
-
border-radius: var(--dry-radius-md);
|
|
62
|
-
background: var(--dry-color-bg-raised);
|
|
63
|
-
box-shadow: var(--dry-shadow-xs);
|
|
64
|
-
transition:
|
|
65
|
-
border-color 140ms ease,
|
|
66
|
-
box-shadow 140ms ease,
|
|
67
|
-
background-color 140ms ease;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
[data-country-select-wrapper] [data-part='control'][data-state='open'] {
|
|
71
|
-
border-color: color-mix(
|
|
72
|
-
in srgb,
|
|
73
|
-
var(--dry-color-brand-primary) 52%,
|
|
74
|
-
var(--dry-color-stroke-weak)
|
|
75
|
-
);
|
|
76
|
-
box-shadow: 0 0 0 3px color-mix(in srgb, var(--dry-color-brand-primary) 18%, transparent);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
[data-country-select-wrapper] [data-part='control'][data-disabled] {
|
|
80
|
-
opacity: 0.6;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
[data-country-select-wrapper] [data-part='flag'] {
|
|
84
|
-
font-size: 1rem;
|
|
85
|
-
line-height: 1;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
[data-country-select-wrapper] [data-part='input'] {
|
|
89
|
-
border: none;
|
|
90
|
-
outline: none;
|
|
91
|
-
padding: 0;
|
|
92
|
-
background: transparent;
|
|
93
|
-
color: var(--dry-color-text-strong);
|
|
94
|
-
font-family: var(--dry-font-sans);
|
|
95
|
-
font-size: var(--dry-type-body-size, var(--dry-text-md-size));
|
|
96
|
-
line-height: var(--dry-type-body-leading, var(--dry-text-md-leading));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
[data-country-select-wrapper] [data-part='input']::placeholder {
|
|
100
|
-
color: var(--dry-color-text-weak);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
[data-country-select-wrapper] [data-part='dial-code'] {
|
|
104
|
-
color: var(--dry-color-text-weak);
|
|
105
|
-
font-size: var(--dry-type-small-size, var(--dry-text-sm-size));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
[data-country-select-wrapper] [data-part='dropdown'] {
|
|
109
|
-
display: grid;
|
|
110
|
-
grid-template-columns: minmax(max(14rem, anchor-size(inline)), max-content);
|
|
111
|
-
gap: var(--dry-space-1, 0.25rem);
|
|
112
|
-
max-height: 18rem;
|
|
113
|
-
padding: var(--dry-space-1, 0.25rem);
|
|
114
|
-
border: 1px solid var(--dry-color-stroke-weak);
|
|
115
|
-
border-radius: calc(var(--dry-radius-md) + 0.125rem);
|
|
116
|
-
background: var(--dry-color-bg-raised);
|
|
117
|
-
box-shadow: var(--dry-shadow-lg);
|
|
118
|
-
inset: unset;
|
|
119
|
-
margin: 0;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
[data-country-select-wrapper] [data-part='option'] {
|
|
123
|
-
display: grid;
|
|
124
|
-
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
125
|
-
align-items: center;
|
|
126
|
-
gap: var(--dry-space-2, 0.5rem);
|
|
127
|
-
justify-self: stretch;
|
|
128
|
-
padding: var(--dry-space-2, 0.5rem) var(--dry-space-3, 0.75rem);
|
|
129
|
-
border: none;
|
|
130
|
-
border-radius: calc(var(--dry-radius-sm) + 0.0625rem);
|
|
131
|
-
background: transparent;
|
|
132
|
-
color: var(--dry-color-text-strong);
|
|
133
|
-
font-family: var(--dry-font-sans);
|
|
134
|
-
font-size: var(--dry-type-small-size, var(--dry-text-sm-size));
|
|
135
|
-
line-height: var(--dry-type-small-leading, var(--dry-text-sm-leading));
|
|
136
|
-
text-align: left;
|
|
137
|
-
cursor: pointer;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
[data-country-select-wrapper] [data-part='option']::before {
|
|
141
|
-
content: attr(data-flag);
|
|
142
|
-
font-size: 1rem;
|
|
143
|
-
line-height: 1;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
[data-country-select-wrapper] [data-part='option'][data-dial-code]::after {
|
|
147
|
-
content: attr(data-dial-code);
|
|
148
|
-
color: var(--dry-color-text-weak);
|
|
149
|
-
font-size: var(--dry-type-small-size, var(--dry-text-sm-size));
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
[data-country-select-wrapper] [data-part='option'][data-highlighted],
|
|
153
|
-
[data-country-select-wrapper] [data-part='option']:hover {
|
|
154
|
-
background: color-mix(in srgb, var(--dry-color-brand-primary) 12%, var(--dry-color-bg-raised));
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
[data-country-select-wrapper] [data-part='option'][data-state='selected'] {
|
|
158
|
-
background: color-mix(in srgb, var(--dry-color-brand-primary) 18%, var(--dry-color-bg-raised));
|
|
159
|
-
color: var(--dry-color-text-strong);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
[data-country-select-wrapper] [data-part='country-name'] {
|
|
163
|
-
overflow: hidden;
|
|
164
|
-
text-overflow: ellipsis;
|
|
165
|
-
white-space: nowrap;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
[data-country-select-wrapper] [data-part='empty'] {
|
|
169
|
-
padding: var(--dry-space-2, 0.5rem) var(--dry-space-3, 0.75rem);
|
|
170
|
-
color: var(--dry-color-text-weak);
|
|
171
|
-
font-size: var(--dry-type-small-size, var(--dry-text-sm-size));
|
|
172
|
-
}
|
|
173
|
-
</style>
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { Snippet } from 'svelte';
|
|
2
|
-
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
-
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'onchange'> {
|
|
4
|
-
value?: string;
|
|
5
|
-
regions?: string[];
|
|
6
|
-
showDialCode?: boolean;
|
|
7
|
-
disabled?: boolean;
|
|
8
|
-
placeholder?: string;
|
|
9
|
-
name?: string;
|
|
10
|
-
onchange?: (code: string) => void;
|
|
11
|
-
children?: Snippet;
|
|
12
|
-
}
|
|
13
|
-
declare const CountrySelectButtonInput: import("svelte").Component<Props, {}, "value">;
|
|
14
|
-
type CountrySelectButtonInput = ReturnType<typeof CountrySelectButtonInput>;
|
|
15
|
-
export default CountrySelectButtonInput;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as CountrySelect } from './country-select-button-input.svelte';
|