@urbicon-ui/blocks 6.30.1 → 6.32.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/components/Calendar/Calendar.svelte +17 -21
- package/dist/components/Calendar/CalendarAgendaView.svelte +9 -3
- package/dist/components/Calendar/CalendarDayView.svelte +9 -3
- package/dist/components/Calendar/CalendarGrid.svelte +9 -3
- package/dist/components/Calendar/CalendarWeekGrid.svelte +9 -3
- package/dist/components/Calendar/CalendarYearGrid.svelte +12 -3
- package/dist/components/CommandPalette/commandPalette.variants.js +9 -4
- package/dist/primitives/Accordion/accordion.variants.d.ts +7 -7
- package/dist/primitives/Accordion/accordion.variants.js +4 -4
- package/dist/primitives/Accordion/index.d.ts +2 -2
- package/dist/primitives/Button/Button.svelte +23 -7
- package/dist/primitives/Combobox/Combobox.svelte +11 -3
- package/dist/primitives/Combobox/combobox.variants.d.ts +9 -0
- package/dist/primitives/Combobox/combobox.variants.js +22 -4
- package/dist/primitives/Combobox/index.d.ts +2 -2
- package/dist/primitives/Menu/menu.variants.js +13 -5
- package/dist/primitives/SegmentGroup/SegmentGroup.svelte +4 -4
- package/dist/primitives/SegmentGroup/SegmentItem.svelte +1 -1
- package/dist/primitives/SegmentGroup/index.d.ts +1 -1
- package/dist/primitives/SegmentGroup/segmentgroup.variants.d.ts +4 -4
- package/dist/primitives/SegmentGroup/segmentgroup.variants.js +15 -15
- package/dist/primitives/Select/select.variants.js +21 -7
- package/dist/primitives/Slider/Slider.svelte +2 -2
- package/dist/primitives/Slider/slider.variants.d.ts +14 -14
- package/dist/primitives/Slider/slider.variants.js +13 -13
- package/dist/primitives/Toggle/Toggle.svelte +2 -2
- package/dist/primitives/Toggle/index.d.ts +1 -1
- package/dist/primitives/Toggle/toggle.variants.d.ts +7 -7
- package/dist/primitives/Toggle/toggle.variants.js +32 -32
- package/docs/MIGRATION-v5.md +7 -5
- package/package.json +5 -4
|
@@ -178,10 +178,6 @@
|
|
|
178
178
|
view === 'year' || view === 'agenda' ? 'month' : view
|
|
179
179
|
);
|
|
180
180
|
|
|
181
|
-
// --- Uncontrolled fallback ---
|
|
182
|
-
let internalValue = $state<CalendarProps['value']>(undefined);
|
|
183
|
-
const effectiveValue = $derived(value !== undefined ? value : internalValue);
|
|
184
|
-
|
|
185
181
|
// --- Extra disable predicate (min/max handled by the controller) ---
|
|
186
182
|
const disabledDatesSet = $derived(new Set(disabledDates.map((d) => toIso(d))));
|
|
187
183
|
function checkExtraDisabled(date: Date): boolean {
|
|
@@ -210,7 +206,7 @@
|
|
|
210
206
|
return selectionMode;
|
|
211
207
|
},
|
|
212
208
|
get selection() {
|
|
213
|
-
return
|
|
209
|
+
return value as DateGridSelection | undefined;
|
|
214
210
|
},
|
|
215
211
|
get rangeStart() {
|
|
216
212
|
return undefined;
|
|
@@ -255,12 +251,13 @@
|
|
|
255
251
|
onMonthChange?.(anchor.getMonth(), anchor.getFullYear());
|
|
256
252
|
}
|
|
257
253
|
onDateClick?.(date);
|
|
254
|
+
// Always assign the $bindable — no controlled/uncontrolled split. With
|
|
255
|
+
// `bind:value` the write-back reaches the consumer even from the empty
|
|
256
|
+
// (undefined) initial, the only type-correct "no selection"; without a
|
|
257
|
+
// binding Svelte 5 keeps the write local until the parent passes a new
|
|
258
|
+
// prop value, so uncontrolled and callback-driven usage work unchanged.
|
|
258
259
|
const next = selection as CalendarProps['value'];
|
|
259
|
-
|
|
260
|
-
value = next;
|
|
261
|
-
} else {
|
|
262
|
-
internalValue = next;
|
|
263
|
-
}
|
|
260
|
+
value = next;
|
|
264
261
|
onValueChange?.(next!);
|
|
265
262
|
}
|
|
266
263
|
|
|
@@ -351,10 +348,10 @@
|
|
|
351
348
|
|
|
352
349
|
// --- Derived: selected date for event list ---
|
|
353
350
|
const selectedDateForList = $derived.by(() => {
|
|
354
|
-
if (!
|
|
355
|
-
if (
|
|
356
|
-
if (Array.isArray(
|
|
357
|
-
return
|
|
351
|
+
if (!value) return null;
|
|
352
|
+
if (value instanceof Date) return value;
|
|
353
|
+
if (Array.isArray(value)) return value[value.length - 1] ?? null;
|
|
354
|
+
return value.start;
|
|
358
355
|
});
|
|
359
356
|
|
|
360
357
|
// --- Event helpers ---
|
|
@@ -500,15 +497,14 @@
|
|
|
500
497
|
return selectedDateForList;
|
|
501
498
|
},
|
|
502
499
|
get selectedDates() {
|
|
503
|
-
if (!
|
|
504
|
-
if (
|
|
505
|
-
if (Array.isArray(
|
|
506
|
-
return [
|
|
500
|
+
if (!value) return [];
|
|
501
|
+
if (value instanceof Date) return [value];
|
|
502
|
+
if (Array.isArray(value)) return value;
|
|
503
|
+
return [value.start, value.end];
|
|
507
504
|
},
|
|
508
505
|
get selectedRange() {
|
|
509
|
-
if (!
|
|
510
|
-
|
|
511
|
-
return effectiveValue as DateRange;
|
|
506
|
+
if (!value || value instanceof Date || Array.isArray(value)) return null;
|
|
507
|
+
return value as DateRange;
|
|
512
508
|
},
|
|
513
509
|
get events() {
|
|
514
510
|
return expandedEvents;
|
|
@@ -82,9 +82,15 @@
|
|
|
82
82
|
aria-label={bt('calendar.agendaView')}
|
|
83
83
|
onkeydown={handleKeydown}
|
|
84
84
|
{@attach swipeable({
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
// Direction-gated like the header arrows (the DateGridScaffold pattern): a
|
|
86
|
+
// swipe at the bound is inert — no navDirection flip, no clamped no-op emit.
|
|
87
|
+
onSwipeLeft: () => {
|
|
88
|
+
if (ctx.canGoForward) ctx.navigate(1);
|
|
89
|
+
},
|
|
90
|
+
onSwipeRight: () => {
|
|
91
|
+
if (ctx.canGoBack) ctx.navigate(-1);
|
|
92
|
+
},
|
|
93
|
+
enabled: ctx.swipeable && !ctx.disabled
|
|
88
94
|
})}
|
|
89
95
|
style="overflow: hidden;"
|
|
90
96
|
>
|
|
@@ -54,9 +54,15 @@
|
|
|
54
54
|
aria-label={dateLabel}
|
|
55
55
|
onkeydown={handleKeydown}
|
|
56
56
|
{@attach swipeable({
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
// Direction-gated like the header arrows (the DateGridScaffold pattern): a
|
|
58
|
+
// swipe at the bound is inert — no navDirection flip, no clamped no-op emit.
|
|
59
|
+
onSwipeLeft: () => {
|
|
60
|
+
if (ctx.canGoForward) ctx.navigate(1);
|
|
61
|
+
},
|
|
62
|
+
onSwipeRight: () => {
|
|
63
|
+
if (ctx.canGoBack) ctx.navigate(-1);
|
|
64
|
+
},
|
|
65
|
+
enabled: ctx.swipeable && !ctx.disabled
|
|
60
66
|
})}
|
|
61
67
|
>
|
|
62
68
|
<div class="grid [&>*]:col-start-1 [&>*]:row-start-1">
|
|
@@ -72,9 +72,15 @@
|
|
|
72
72
|
onmouseleave={() => ctx.setHoveredDate(null)}
|
|
73
73
|
style="overflow: hidden; position: relative;"
|
|
74
74
|
{@attach swipeable({
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
// Direction-gated like the header arrows (the DateGridScaffold pattern): a
|
|
76
|
+
// swipe at the bound is inert — no navDirection flip, no clamped no-op emit.
|
|
77
|
+
onSwipeLeft: () => {
|
|
78
|
+
if (ctx.canGoForward) ctx.navigate(1);
|
|
79
|
+
},
|
|
80
|
+
onSwipeRight: () => {
|
|
81
|
+
if (ctx.canGoBack) ctx.navigate(-1);
|
|
82
|
+
},
|
|
83
|
+
enabled: ctx.swipeable && !ctx.disabled
|
|
78
84
|
})}
|
|
79
85
|
>
|
|
80
86
|
<CalendarWeekdayHeader />
|
|
@@ -99,9 +99,15 @@
|
|
|
99
99
|
onkeydown={handleKeydown}
|
|
100
100
|
style="overflow: hidden;"
|
|
101
101
|
{@attach swipeable({
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
// Direction-gated like the header arrows (the DateGridScaffold pattern): a
|
|
103
|
+
// swipe at the bound is inert — no navDirection flip, no clamped no-op emit.
|
|
104
|
+
onSwipeLeft: () => {
|
|
105
|
+
if (ctx.canGoForward) ctx.navigate(1);
|
|
106
|
+
},
|
|
107
|
+
onSwipeRight: () => {
|
|
108
|
+
if (ctx.canGoBack) ctx.navigate(-1);
|
|
109
|
+
},
|
|
110
|
+
enabled: ctx.swipeable && !ctx.disabled
|
|
105
111
|
})}
|
|
106
112
|
>
|
|
107
113
|
<div class="grid [&>*]:col-start-1 [&>*]:row-start-1">
|
|
@@ -85,9 +85,18 @@
|
|
|
85
85
|
onkeydown={handleKeydown}
|
|
86
86
|
style="overflow: hidden;"
|
|
87
87
|
{@attach swipeable({
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
// Direction-gated like the header arrows (the DateGridScaffold pattern): a
|
|
89
|
+
// swipe at the bound is inert — no navDirection flip, no clamped no-op
|
|
90
|
+
// emit. In year view the gate is month-granular (the controller maps year
|
|
91
|
+
// onto month bounds): whenever it blocks, the year step would have clamped
|
|
92
|
+
// back onto the displayed month/year anyway — never a real change.
|
|
93
|
+
onSwipeLeft: () => {
|
|
94
|
+
if (ctx.canGoForward) ctx.navigate(1);
|
|
95
|
+
},
|
|
96
|
+
onSwipeRight: () => {
|
|
97
|
+
if (ctx.canGoBack) ctx.navigate(-1);
|
|
98
|
+
},
|
|
99
|
+
enabled: ctx.swipeable && !ctx.disabled
|
|
91
100
|
})}
|
|
92
101
|
>
|
|
93
102
|
<div class="grid [&>*]:col-start-1 [&>*]:row-start-1">
|
|
@@ -15,11 +15,16 @@ export const commandPaletteVariants = tv({
|
|
|
15
15
|
],
|
|
16
16
|
inputIcon: 'h-4 w-4 shrink-0 text-text-tertiary',
|
|
17
17
|
clearButton: 'text-text-quaternary hover:text-text-secondary',
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
// `p-1` + `space-y-0.5` = the shared listbox rhythm (XC-9): 4px edge
|
|
19
|
+
// inset, 2px item-to-item gap — same as Select/Combobox/Menu panels.
|
|
20
|
+
list: 'max-h-72 overflow-y-auto p-1 space-y-0.5',
|
|
21
|
+
// `px-3` shares the item's horizontal inset; the micro-typo (2xs,
|
|
22
|
+
// quaternary) is the palette's own command-surface voice.
|
|
23
|
+
groupLabel: ['px-3 py-1.5 text-2xs font-semibold uppercase tracking-wide text-text-quaternary'],
|
|
24
|
+
// tier: modify — palette items are selectable actions. Rhythm = the md
|
|
25
|
+
// listbox baseline (px-3 py-2 text-sm min-h-2.5rem, gap-2 — XC-9).
|
|
21
26
|
item: [
|
|
22
|
-
'flex w-full items-center gap-
|
|
27
|
+
'flex w-full items-center gap-2 rounded-modify px-3 py-2 text-sm min-h-[2.5rem]',
|
|
23
28
|
'transition-colors cursor-pointer'
|
|
24
29
|
],
|
|
25
30
|
itemHighlighted: 'bg-primary-subtle text-primary',
|
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
import { type SlotNames, type VariantProps } from '../../utils/variants.js';
|
|
2
2
|
export declare const accordionVariants: ((props?: {
|
|
3
|
-
variant?: "default" | "ghost" | "
|
|
3
|
+
variant?: "default" | "ghost" | "card" | undefined;
|
|
4
4
|
size?: "sm" | "md" | "lg" | undefined;
|
|
5
5
|
} | undefined) => {
|
|
6
6
|
base: (props?: ({
|
|
7
|
-
variant?: "default" | "ghost" | "
|
|
7
|
+
variant?: "default" | "ghost" | "card" | undefined;
|
|
8
8
|
size?: "sm" | "md" | "lg" | undefined;
|
|
9
9
|
} & {
|
|
10
10
|
class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
|
|
11
11
|
}) | undefined) => string;
|
|
12
12
|
item: (props?: ({
|
|
13
|
-
variant?: "default" | "ghost" | "
|
|
13
|
+
variant?: "default" | "ghost" | "card" | undefined;
|
|
14
14
|
size?: "sm" | "md" | "lg" | undefined;
|
|
15
15
|
} & {
|
|
16
16
|
class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
|
|
17
17
|
}) | undefined) => string;
|
|
18
18
|
trigger: (props?: ({
|
|
19
|
-
variant?: "default" | "ghost" | "
|
|
19
|
+
variant?: "default" | "ghost" | "card" | undefined;
|
|
20
20
|
size?: "sm" | "md" | "lg" | undefined;
|
|
21
21
|
} & {
|
|
22
22
|
class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
|
|
23
23
|
}) | undefined) => string;
|
|
24
24
|
chevron: (props?: ({
|
|
25
|
-
variant?: "default" | "ghost" | "
|
|
25
|
+
variant?: "default" | "ghost" | "card" | undefined;
|
|
26
26
|
size?: "sm" | "md" | "lg" | undefined;
|
|
27
27
|
} & {
|
|
28
28
|
class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
|
|
29
29
|
}) | undefined) => string;
|
|
30
30
|
content: (props?: ({
|
|
31
|
-
variant?: "default" | "ghost" | "
|
|
31
|
+
variant?: "default" | "ghost" | "card" | undefined;
|
|
32
32
|
size?: "sm" | "md" | "lg" | undefined;
|
|
33
33
|
} & {
|
|
34
34
|
class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
|
|
35
35
|
}) | undefined) => string;
|
|
36
36
|
contentInner: (props?: ({
|
|
37
|
-
variant?: "default" | "ghost" | "
|
|
37
|
+
variant?: "default" | "ghost" | "card" | undefined;
|
|
38
38
|
size?: "sm" | "md" | "lg" | undefined;
|
|
39
39
|
} & {
|
|
40
40
|
class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
|
|
@@ -27,13 +27,13 @@ export const accordionVariants = tv({
|
|
|
27
27
|
contentInner: 'pb-4 text-text-secondary'
|
|
28
28
|
},
|
|
29
29
|
// Variant contract (see docs/MIGRATION-v5.md §2):
|
|
30
|
-
// default
|
|
31
|
-
//
|
|
32
|
-
// ghost
|
|
30
|
+
// default → hairlines between items (divide-y)
|
|
31
|
+
// card → items as standalone card blocks with spacing, no borders
|
|
32
|
+
// ghost → no separators, hover-tint per item
|
|
33
33
|
variants: {
|
|
34
34
|
variant: {
|
|
35
35
|
default: {},
|
|
36
|
-
|
|
36
|
+
card: {
|
|
37
37
|
base: 'divide-y-0 space-y-2',
|
|
38
38
|
item: 'rounded-contain bg-surface-quiet px-4'
|
|
39
39
|
},
|
|
@@ -33,7 +33,7 @@ export interface AccordionContext {
|
|
|
33
33
|
*
|
|
34
34
|
* @example
|
|
35
35
|
* ```svelte
|
|
36
|
-
* <Accordion type="multiple" variant="
|
|
36
|
+
* <Accordion type="multiple" variant="card" bind:value={openItems}>
|
|
37
37
|
* <AccordionItem value="section-1" title="Section 1">Content</AccordionItem>
|
|
38
38
|
* <AccordionItem value="section-2" title="Section 2">Content</AccordionItem>
|
|
39
39
|
* </Accordion>
|
|
@@ -43,7 +43,7 @@ export interface AccordionProps extends AccordionVariants, Omit<HTMLAttributes<H
|
|
|
43
43
|
/** Allow single or multiple items open at once @default 'single' */
|
|
44
44
|
type?: 'single' | 'multiple';
|
|
45
45
|
/** Visual style @default 'default' */
|
|
46
|
-
variant?: 'default' | '
|
|
46
|
+
variant?: 'default' | 'card' | 'ghost';
|
|
47
47
|
/** Size @default 'md' */
|
|
48
48
|
size?: 'sm' | 'md' | 'lg';
|
|
49
49
|
/** Controlled open item(s) – string for single, string[] for multiple */
|
|
@@ -106,8 +106,23 @@
|
|
|
106
106
|
}
|
|
107
107
|
</script>
|
|
108
108
|
|
|
109
|
+
<!--
|
|
110
|
+
restProps spreads FIRST so component-owned state wins (COMPONENT-API-CONVENTIONS
|
|
111
|
+
§restProps ordering). The ARIA/selection attributes after the spread are
|
|
112
|
+
conditional merges, not plain overrides: an explicit `undefined` after a spread
|
|
113
|
+
REMOVES the attribute in Svelte, so a naive `role={ariaProps.role}` would strip
|
|
114
|
+
a standalone consumer's own `role="link"`. Each merge lets the ButtonGroup
|
|
115
|
+
selection wiring (role/aria-checked/data-value) and the modeled state
|
|
116
|
+
(pressed/disabled/loading) win when the component has something to say, and
|
|
117
|
+
falls back to the consumer's restProps value when it doesn't. Inside a
|
|
118
|
+
selection group aria-pressed is forced off even against restProps — a selection
|
|
119
|
+
role announces via aria-checked, and doubling up is an ARIA violation.
|
|
120
|
+
`type`/`disabled`/`class`/`onclick` are destructured (never in restProps);
|
|
121
|
+
a consumer onclick is composed inside handleClick.
|
|
122
|
+
-->
|
|
109
123
|
<button
|
|
110
124
|
bind:this={buttonElement}
|
|
125
|
+
{...restProps}
|
|
111
126
|
{type}
|
|
112
127
|
disabled={effectiveDisabled}
|
|
113
128
|
class={[
|
|
@@ -118,13 +133,14 @@
|
|
|
118
133
|
: styles.base({ class: [slotClasses?.base, className] })
|
|
119
134
|
]}
|
|
120
135
|
onclick={handleClick}
|
|
121
|
-
role={ariaProps.role}
|
|
122
|
-
aria-checked={ariaProps['aria-checked']}
|
|
123
|
-
data-value={ariaProps['data-value']}
|
|
124
|
-
aria-pressed={ariaProps.role
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
{
|
|
136
|
+
role={ariaProps.role ?? restProps.role}
|
|
137
|
+
aria-checked={ariaProps['aria-checked'] ?? restProps['aria-checked']}
|
|
138
|
+
data-value={ariaProps['data-value'] ?? restProps['data-value']}
|
|
139
|
+
aria-pressed={ariaProps.role
|
|
140
|
+
? undefined
|
|
141
|
+
: effectiveActive || effectivePressed || restProps['aria-pressed']}
|
|
142
|
+
aria-disabled={effectiveDisabled || (restProps['aria-disabled'] ?? false)}
|
|
143
|
+
aria-busy={loading || (restProps['aria-busy'] ?? false)}
|
|
128
144
|
>
|
|
129
145
|
<span
|
|
130
146
|
class={unstyled
|
|
@@ -895,9 +895,17 @@
|
|
|
895
895
|
{@render customOption(opt, selected)}
|
|
896
896
|
{:else}
|
|
897
897
|
<span class="flex-1 truncate">{opt.label}</span>
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
898
|
+
<!--
|
|
899
|
+
Always rendered (reserved space) and faded in via opacity — parity
|
|
900
|
+
with Select's optionCheck mechanic, so selecting never shifts layout.
|
|
901
|
+
-->
|
|
902
|
+
<CheckMarkIcon
|
|
903
|
+
class={unstyled
|
|
904
|
+
? (slotClasses?.optionCheck ?? '')
|
|
905
|
+
: styles.optionCheck({
|
|
906
|
+
class: [slotClasses?.optionCheck, selected ? 'opacity-100' : undefined]
|
|
907
|
+
})}
|
|
908
|
+
/>
|
|
901
909
|
{/if}
|
|
902
910
|
</button>
|
|
903
911
|
{/snippet}
|
|
@@ -105,6 +105,15 @@ export declare const comboboxVariants: ((props?: {
|
|
|
105
105
|
} & {
|
|
106
106
|
class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
|
|
107
107
|
}) | undefined) => string;
|
|
108
|
+
optionCheck: (props?: ({
|
|
109
|
+
tier?: "commit" | "modify" | undefined;
|
|
110
|
+
variant?: "ghost" | "filled" | "outlined" | "underline" | undefined;
|
|
111
|
+
size?: "sm" | "md" | "lg" | "xs" | "xl" | undefined;
|
|
112
|
+
open?: boolean | undefined;
|
|
113
|
+
disabled?: boolean | undefined;
|
|
114
|
+
} & {
|
|
115
|
+
class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
|
|
116
|
+
}) | undefined) => string;
|
|
108
117
|
group: (props?: ({
|
|
109
118
|
tier?: "commit" | "modify" | undefined;
|
|
110
119
|
variant?: "ghost" | "filled" | "outlined" | "underline" | undefined;
|
|
@@ -34,13 +34,21 @@ export const comboboxVariants = tv({
|
|
|
34
34
|
'flex w-full items-center gap-2 rounded-modify px-3 py-2 text-left',
|
|
35
35
|
'text-text-primary cursor-pointer select-none',
|
|
36
36
|
'transition-colors duration-[var(--blocks-duration-fast)]',
|
|
37
|
-
'focus-visible:outline-none'
|
|
37
|
+
'focus-visible:outline-none',
|
|
38
|
+
'disabled:opacity-50 disabled:cursor-not-allowed'
|
|
38
39
|
],
|
|
39
40
|
optionActive: 'bg-surface-hover',
|
|
40
41
|
optionSelected: 'bg-surface-selected font-medium',
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
|
|
42
|
+
// Trailing selected-check (parity with Select `optionCheck`): always
|
|
43
|
+
// rendered with reserved space, fades in via opacity — no layout shift
|
|
44
|
+
// when a row becomes selected. Sized one step with the option per size.
|
|
45
|
+
optionCheck: [
|
|
46
|
+
'shrink-0 text-primary opacity-0 transition-opacity duration-[var(--blocks-duration-fast)]'
|
|
47
|
+
],
|
|
48
|
+
// Grouped options (parity with Select). `group` carries the same
|
|
49
|
+
// item-to-item rhythm as the flat listbox (`space-y-0.5` there lives on
|
|
50
|
+
// the `listbox` slot); the label is a quiet, uppercase section header.
|
|
51
|
+
group: 'space-y-0.5',
|
|
44
52
|
groupLabel: 'px-3 py-1.5 text-xs font-medium text-text-tertiary uppercase tracking-wider',
|
|
45
53
|
noResults: 'px-3 py-4 text-center text-sm text-text-tertiary',
|
|
46
54
|
// Async-search in-flight message (queryFn). Mirrors noResults so both listbox
|
|
@@ -137,11 +145,15 @@ export const comboboxVariants = tv({
|
|
|
137
145
|
// The multi-mode tokenizer (`control`/`search`/`tag`) tracks the same
|
|
138
146
|
// ladder: `control` uses `min-h-*` (not `h-*`) so it grows as tags wrap,
|
|
139
147
|
// and reserves right padding (`pr-*`) for the absolute chevron/clear.
|
|
148
|
+
// Option rows follow the shared listbox item rhythm (XC-9); the group
|
|
149
|
+
// label always shares the option's horizontal inset.
|
|
140
150
|
xs: {
|
|
141
151
|
// `pointer-coarse:text-base` floors the input to 16px on touch-primary
|
|
142
152
|
// devices so iOS Safari doesn't auto-zoom (and never un-zoom) on focus.
|
|
143
153
|
input: 'h-7 px-2 pr-7 text-xs pointer-coarse:text-base',
|
|
144
154
|
option: 'px-2 py-1 text-xs min-h-[1.75rem]',
|
|
155
|
+
optionCheck: 'w-3 h-3',
|
|
156
|
+
groupLabel: 'px-2',
|
|
145
157
|
control: 'min-h-7 gap-1 px-1.5 py-1 pr-7',
|
|
146
158
|
search: 'text-xs pointer-coarse:text-base',
|
|
147
159
|
tag: 'text-xs px-1.5 py-0.5',
|
|
@@ -151,6 +163,8 @@ export const comboboxVariants = tv({
|
|
|
151
163
|
// See `xs` — floor to 16px on touch to avoid iOS Safari focus-zoom.
|
|
152
164
|
input: 'h-8 px-3 pr-8 text-sm pointer-coarse:text-base',
|
|
153
165
|
option: 'px-2 py-1.5 text-sm min-h-[2rem]',
|
|
166
|
+
optionCheck: 'w-3.5 h-3.5',
|
|
167
|
+
groupLabel: 'px-2',
|
|
154
168
|
control: 'min-h-8 gap-1 px-2 py-1 pr-8',
|
|
155
169
|
search: 'text-sm pointer-coarse:text-base',
|
|
156
170
|
tag: 'text-xs px-2 py-0.5',
|
|
@@ -159,6 +173,7 @@ export const comboboxVariants = tv({
|
|
|
159
173
|
md: {
|
|
160
174
|
input: 'h-10 px-3 pr-8 text-base',
|
|
161
175
|
option: 'px-3 py-2 text-sm min-h-[2.5rem]',
|
|
176
|
+
optionCheck: 'w-4 h-4',
|
|
162
177
|
control: 'min-h-10 gap-1.5 px-2 py-1.5 pr-8',
|
|
163
178
|
search: 'text-base',
|
|
164
179
|
tag: 'text-sm px-2 py-0.5',
|
|
@@ -167,6 +182,7 @@ export const comboboxVariants = tv({
|
|
|
167
182
|
lg: {
|
|
168
183
|
input: 'h-12 px-4 pr-10 text-lg',
|
|
169
184
|
option: 'px-3 py-2.5 text-base min-h-[3rem]',
|
|
185
|
+
optionCheck: 'w-5 h-5',
|
|
170
186
|
control: 'min-h-12 gap-2 px-3 py-2 pr-10',
|
|
171
187
|
search: 'text-lg',
|
|
172
188
|
tag: 'text-base px-2.5 py-1',
|
|
@@ -175,6 +191,8 @@ export const comboboxVariants = tv({
|
|
|
175
191
|
xl: {
|
|
176
192
|
input: 'h-14 px-6 pr-12 text-xl',
|
|
177
193
|
option: 'px-4 py-3 text-lg min-h-[3.5rem]',
|
|
194
|
+
optionCheck: 'w-6 h-6',
|
|
195
|
+
groupLabel: 'px-4',
|
|
178
196
|
control: 'min-h-14 gap-2 px-4 py-2.5 pr-12',
|
|
179
197
|
search: 'text-xl',
|
|
180
198
|
tag: 'text-lg px-3 py-1',
|
|
@@ -138,8 +138,8 @@ interface ComboboxBaseProps<T extends SelectValue = string> extends ComboboxVari
|
|
|
138
138
|
unstyled?: boolean;
|
|
139
139
|
/** Per-slot class overrides merged with tv() styles. Slots: base | label | requiredMark |
|
|
140
140
|
* inputWrapper | input | message | helper | listbox | option | optionActive | optionSelected |
|
|
141
|
-
* group | groupLabel | loading | noResults | clear | chevron | control | search |
|
|
142
|
-
* tagLabel | tagRemove */
|
|
141
|
+
* optionCheck | group | groupLabel | loading | noResults | clear | chevron | control | search |
|
|
142
|
+
* tag | tagLabel | tagRemove */
|
|
143
143
|
slotClasses?: Partial<Record<ComboboxSlots, string>>;
|
|
144
144
|
/**
|
|
145
145
|
* Apply a named preset registered via `<BlocksProvider presets={{ Combobox: {...} }}>`.
|
|
@@ -27,7 +27,9 @@ export const menuVariants = tv({
|
|
|
27
27
|
header: ['sticky top-0 z-10 bg-surface-elevated', 'px-2 py-1 border-b border-border-hairline'],
|
|
28
28
|
section: ['px-3 py-1.5 text-xs font-medium text-text-tertiary'],
|
|
29
29
|
divider: ['my-1 h-px bg-border-hairline'],
|
|
30
|
-
|
|
30
|
+
// No extra `py` — the `content` slot's `p-1` is the single edge inset
|
|
31
|
+
// (symmetric 4px, same rhythm as the Select/Combobox listboxes, XC-9).
|
|
32
|
+
items: ['space-y-0.5'],
|
|
31
33
|
// tier: modify — menu items are momentary affordances inside the contain panel.
|
|
32
34
|
item: [
|
|
33
35
|
'flex w-full items-center gap-2 rounded-modify text-left',
|
|
@@ -35,7 +37,9 @@ export const menuVariants = tv({
|
|
|
35
37
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary/50',
|
|
36
38
|
'cursor-pointer select-none'
|
|
37
39
|
],
|
|
38
|
-
|
|
40
|
+
// No `mr-*` — the item's `gap-2` owns the icon↔label distance (8px, the
|
|
41
|
+
// same gap every listbox row in the library uses).
|
|
42
|
+
indicator: ['inline-flex items-center self-center'],
|
|
39
43
|
submenu: ['ml-4 mt-2 border-l border-border-hairline pl-2'],
|
|
40
44
|
footer: [
|
|
41
45
|
'sticky bottom-0 z-10 bg-surface-elevated',
|
|
@@ -57,11 +61,15 @@ export const menuVariants = tv({
|
|
|
57
61
|
content: 'min-w-48'
|
|
58
62
|
}
|
|
59
63
|
},
|
|
60
|
-
// Size for menu items
|
|
64
|
+
// Size for menu items. Typo tracks the Button ladder (text-sm/base/lg —
|
|
65
|
+
// Action-family items read like the Button that opened them); py/min-h
|
|
66
|
+
// sit on the shared listbox baseline (2 / 2.5 / 3 rem, XC-9). The section
|
|
67
|
+
// header always shares the item's horizontal inset (px-3 lives on the
|
|
68
|
+
// `section` slot base for md).
|
|
61
69
|
itemSize: {
|
|
62
|
-
sm: { item: 'px-2 py-1.5 text-sm min-h-[2rem]' },
|
|
70
|
+
sm: { item: 'px-2 py-1.5 text-sm min-h-[2rem]', section: 'px-2' },
|
|
63
71
|
md: { item: 'px-3 py-2 text-base min-h-[2.5rem]' },
|
|
64
|
-
lg: { item: 'px-4 py-2.5 text-lg min-h-[3rem]' }
|
|
72
|
+
lg: { item: 'px-4 py-2.5 text-lg min-h-[3rem]', section: 'px-4' }
|
|
65
73
|
},
|
|
66
74
|
disabled: {
|
|
67
75
|
true: {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
value = $bindable(),
|
|
13
13
|
onValueChange,
|
|
14
14
|
size = 'md',
|
|
15
|
-
|
|
15
|
+
variant = 'default',
|
|
16
16
|
tier,
|
|
17
17
|
fullWidth = false,
|
|
18
18
|
disabled = false,
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
|
|
53
53
|
const variantProps: SegmentGroupVariants = $derived({
|
|
54
54
|
size,
|
|
55
|
-
|
|
55
|
+
variant,
|
|
56
56
|
tier: effectiveTier,
|
|
57
57
|
fullWidth,
|
|
58
58
|
disabled
|
|
@@ -111,8 +111,8 @@
|
|
|
111
111
|
get size() {
|
|
112
112
|
return size;
|
|
113
113
|
},
|
|
114
|
-
get
|
|
115
|
-
return
|
|
114
|
+
get variant() {
|
|
115
|
+
return variant;
|
|
116
116
|
},
|
|
117
117
|
get tier() {
|
|
118
118
|
return effectiveTier;
|
|
@@ -11,7 +11,7 @@ export interface SegmentGroupContext {
|
|
|
11
11
|
selectItem: (value: string) => void;
|
|
12
12
|
isActive: (value: string) => boolean;
|
|
13
13
|
readonly size: NonNullable<SegmentGroupVariants['size']>;
|
|
14
|
-
readonly
|
|
14
|
+
readonly variant: NonNullable<SegmentGroupVariants['variant']>;
|
|
15
15
|
readonly tier: InteractiveTier;
|
|
16
16
|
readonly disabled: boolean;
|
|
17
17
|
readonly unstyled: boolean;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { type SlotNames, type VariantProps } from '../../utils/variants.js';
|
|
2
2
|
export declare const segmentGroupVariants: ((props?: {
|
|
3
3
|
tier?: "commit" | "modify" | undefined;
|
|
4
|
-
|
|
4
|
+
variant?: "text" | "default" | undefined;
|
|
5
5
|
size?: "sm" | "md" | "lg" | undefined;
|
|
6
6
|
fullWidth?: boolean | undefined;
|
|
7
7
|
disabled?: boolean | undefined;
|
|
8
8
|
} | undefined) => {
|
|
9
9
|
base: (props?: ({
|
|
10
10
|
tier?: "commit" | "modify" | undefined;
|
|
11
|
-
|
|
11
|
+
variant?: "text" | "default" | undefined;
|
|
12
12
|
size?: "sm" | "md" | "lg" | undefined;
|
|
13
13
|
fullWidth?: boolean | undefined;
|
|
14
14
|
disabled?: boolean | undefined;
|
|
@@ -17,7 +17,7 @@ export declare const segmentGroupVariants: ((props?: {
|
|
|
17
17
|
}) | undefined) => string;
|
|
18
18
|
indicator: (props?: ({
|
|
19
19
|
tier?: "commit" | "modify" | undefined;
|
|
20
|
-
|
|
20
|
+
variant?: "text" | "default" | undefined;
|
|
21
21
|
size?: "sm" | "md" | "lg" | undefined;
|
|
22
22
|
fullWidth?: boolean | undefined;
|
|
23
23
|
disabled?: boolean | undefined;
|
|
@@ -26,7 +26,7 @@ export declare const segmentGroupVariants: ((props?: {
|
|
|
26
26
|
}) | undefined) => string;
|
|
27
27
|
item: (props?: ({
|
|
28
28
|
tier?: "commit" | "modify" | undefined;
|
|
29
|
-
|
|
29
|
+
variant?: "text" | "default" | undefined;
|
|
30
30
|
size?: "sm" | "md" | "lg" | undefined;
|
|
31
31
|
fullWidth?: boolean | undefined;
|
|
32
32
|
disabled?: boolean | undefined;
|