@urbicon-ui/blocks 6.32.0 → 6.34.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/CalendarAgendaView.svelte +6 -2
- package/dist/components/Calendar/CalendarDayView.svelte +6 -2
- package/dist/internal/date-grid/date-grid.svelte.js +5 -3
- package/dist/mint/README.md +33 -0
- package/dist/mint/compose.d.ts +7 -1
- package/dist/mint/compose.js +35 -8
- package/dist/mint/engine.d.ts +34 -0
- package/dist/mint/engine.js +106 -0
- package/dist/mint/index.d.ts +2 -2
- package/dist/mint/index.js +1 -1
- package/dist/mint/micro-interactions.d.ts +9 -3
- package/dist/mint/micro-interactions.js +18 -87
- package/dist/mint/presets.d.ts +7 -1
- package/dist/mint/presets.js +9 -3
- package/dist/mint/registry.d.ts +41 -2
- package/dist/mint/registry.js +79 -26
- package/dist/mint/ripple.js +1 -1
- package/dist/primitives/Button/Button.svelte +5 -1
- package/dist/primitives/ButtonGroup/ButtonGroup.svelte +29 -5
- package/dist/primitives/Toolbar/Toolbar.svelte +11 -1
- package/docs/MIGRATION-v5.md +6 -4
- package/package.json +3 -3
|
@@ -62,14 +62,18 @@
|
|
|
62
62
|
const agendaKey = $derived(`${ctx.displayedYear}-${ctx.displayedMonth}`);
|
|
63
63
|
|
|
64
64
|
function handleKeydown(e: KeyboardEvent) {
|
|
65
|
+
// Direction-gated like the swipes and header arrows (the DateGridScaffold
|
|
66
|
+
// pattern): an arrow key at the bound is inert — no navDirection flip, no
|
|
67
|
+
// clamped no-op onMonthChange emit. Disabled calendars ignore keys entirely.
|
|
68
|
+
if (ctx.disabled) return;
|
|
65
69
|
switch (e.key) {
|
|
66
70
|
case 'ArrowLeft':
|
|
67
71
|
e.preventDefault();
|
|
68
|
-
ctx.navigate(-1);
|
|
72
|
+
if (ctx.canGoBack) ctx.navigate(-1);
|
|
69
73
|
break;
|
|
70
74
|
case 'ArrowRight':
|
|
71
75
|
e.preventDefault();
|
|
72
|
-
ctx.navigate(1);
|
|
76
|
+
if (ctx.canGoForward) ctx.navigate(1);
|
|
73
77
|
break;
|
|
74
78
|
}
|
|
75
79
|
}
|
|
@@ -33,14 +33,18 @@
|
|
|
33
33
|
const totalEventCount = $derived(eventsWithInfo.length);
|
|
34
34
|
|
|
35
35
|
function handleKeydown(e: KeyboardEvent) {
|
|
36
|
+
// Direction-gated like the swipes and header arrows (the DateGridScaffold
|
|
37
|
+
// pattern): an arrow key at the bound is inert — no navDirection flip, no
|
|
38
|
+
// clamped no-op onDayChange emit. Disabled calendars ignore keys entirely.
|
|
39
|
+
if (ctx.disabled) return;
|
|
36
40
|
switch (e.key) {
|
|
37
41
|
case 'ArrowLeft':
|
|
38
42
|
e.preventDefault();
|
|
39
|
-
ctx.navigate(-1);
|
|
43
|
+
if (ctx.canGoBack) ctx.navigate(-1);
|
|
40
44
|
break;
|
|
41
45
|
case 'ArrowRight':
|
|
42
46
|
e.preventDefault();
|
|
43
|
-
ctx.navigate(1);
|
|
47
|
+
if (ctx.canGoForward) ctx.navigate(1);
|
|
44
48
|
break;
|
|
45
49
|
}
|
|
46
50
|
}
|
|
@@ -296,9 +296,11 @@ export class DateGridController {
|
|
|
296
296
|
break;
|
|
297
297
|
}
|
|
298
298
|
// Week/day steps carry no month bounds of their own, so clamp the shifted
|
|
299
|
-
// reference to [minDate, maxDate] too
|
|
300
|
-
//
|
|
301
|
-
//
|
|
299
|
+
// reference to [minDate, maxDate] too. Swipes and the day/agenda arrow keys
|
|
300
|
+
// are canGoBack/canGoForward-gated like the header arrows these days, but the
|
|
301
|
+
// custom-header API (goTo/navigateWeek/navigateDay) is not — this clamp stays
|
|
302
|
+
// as the backstop so no caller can step the anchor onto an all-disabled
|
|
303
|
+
// week/day past the boundary.
|
|
302
304
|
case 'week':
|
|
303
305
|
next = clampDate(addDays(referenceDate, delta * 7), minDate, maxDate);
|
|
304
306
|
break;
|
package/dist/mint/README.md
CHANGED
|
@@ -162,6 +162,39 @@ mintRegistry.register('my-mint', (config) => ({
|
|
|
162
162
|
- **Throttling**: Verhindert Spam bei schnellen Interaktionen
|
|
163
163
|
- **Cleanup**: Automatische Bereinigung bei Component-Unmount
|
|
164
164
|
|
|
165
|
+
## Auflösung & Tree-Shaking (resolveIcon-Muster)
|
|
166
|
+
|
|
167
|
+
`mintRegistry.apply(el, mint, fallbacks?)` löst jeden Mint-Namen in dieser
|
|
168
|
+
Reihenfolge auf:
|
|
169
|
+
|
|
170
|
+
1. **Registry-Eintrag** — Consumer-`register()`-Override oder bereits geladene
|
|
171
|
+
Built-ins (gewinnt immer, wie der IconProvider bei `resolveIcon`).
|
|
172
|
+
2. **`fallbacks`** — statisch importierte Factories des Aufrufers. Button
|
|
173
|
+
importiert `scaleMint` direkt (`{ scale: scaleMint }`), damit sein Default
|
|
174
|
+
tree-shaken mitkommt, ohne das gesamte Built-in-Set zu ziehen.
|
|
175
|
+
3. **Demand-Load** — unbekannte Namen laden das Built-in-Set einmalig per
|
|
176
|
+
dynamischem `import('./presets')` nach (Chunk wird nur gefetcht, wenn
|
|
177
|
+
tatsächlich ein dynamischer Mint-Name verwendet wird) und wenden den Effekt
|
|
178
|
+
danach an. `<Button mint="ripple">` funktioniert also weiterhin ohne
|
|
179
|
+
manuelles `registerDefaultMints()`.
|
|
180
|
+
|
|
181
|
+
**Kontrakt Demand-Load (dokumentiert):** Mint-Effekte sind dekorativ.
|
|
182
|
+
Interaktionen im Fetch-Fenster werden NICHT nachgespielt — auf langsamen
|
|
183
|
+
Netzen kann der erste Klick für einen noch nicht geladenen click-getriggerten
|
|
184
|
+
Effekt (`ripple`, `shake`, …) verloren gehen; hover-getriggerte Effekte
|
|
185
|
+
greifen ab dem nächsten `mouseenter`. Consumer-Overrides überleben den
|
|
186
|
+
Demand-Load immer (die Built-ins registrieren sich nur auf freie Namen —
|
|
187
|
+
`registerBuiltin`). Wer First-Interaction-Garantien braucht, registriert den
|
|
188
|
+
Effekt statisch beim App-Start: `registerDefaultMints()` oder
|
|
189
|
+
`mintRegistry.register(name, factory)` mit direkt importierter Factory.
|
|
190
|
+
|
|
191
|
+
Modul-Layout (load-bearing für die Chunk-Zuordnung): `engine.ts` enthält die
|
|
192
|
+
Micro-Interaction-Engine + `scaleMint` (schifft statisch mit Button);
|
|
193
|
+
`micro-interactions.ts` enthält NUR die Registrierungen und ist ausschließlich
|
|
194
|
+
über den demand-geladenen `presets.ts`-Chunk erreichbar. Neue statisch
|
|
195
|
+
verschiffte Default-Effekte gehören in `engine.ts` (oder ein eigenes Modul),
|
|
196
|
+
niemals in `micro-interactions.ts`.
|
|
197
|
+
|
|
165
198
|
## Accessibility
|
|
166
199
|
|
|
167
200
|
Das Mint-System respektiert automatisch `prefers-reduced-motion`:
|
package/dist/mint/compose.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { mintRegistry } from './registry.js';
|
|
2
2
|
import type { CompositeConfig, Mint } from './types.js';
|
|
3
3
|
/**
|
|
4
|
-
* Compose multiple mints into one
|
|
4
|
+
* Compose multiple mints into one.
|
|
5
|
+
*
|
|
6
|
+
* String members resolve like `mintRegistry.apply` does: a registry entry
|
|
7
|
+
* (consumer override or loaded built-in) applies synchronously; unresolved
|
|
8
|
+
* names demand-load the built-in set once and init after the load (skipped if
|
|
9
|
+
* the composite was destroyed in the meantime). A name that is still unknown
|
|
10
|
+
* after the load warns loudly instead of silently dropping the effect.
|
|
5
11
|
*/
|
|
6
12
|
export declare function composeMints(...mints: Array<Mint | string>): Mint;
|
|
7
13
|
/**
|
package/dist/mint/compose.js
CHANGED
|
@@ -1,26 +1,53 @@
|
|
|
1
|
-
import { mintRegistry } from './registry.js';
|
|
1
|
+
import { loadBuiltinMints, mintRegistry } from './registry.js';
|
|
2
2
|
/**
|
|
3
|
-
* Compose multiple mints into one
|
|
3
|
+
* Compose multiple mints into one.
|
|
4
|
+
*
|
|
5
|
+
* String members resolve like `mintRegistry.apply` does: a registry entry
|
|
6
|
+
* (consumer override or loaded built-in) applies synchronously; unresolved
|
|
7
|
+
* names demand-load the built-in set once and init after the load (skipped if
|
|
8
|
+
* the composite was destroyed in the meantime). A name that is still unknown
|
|
9
|
+
* after the load warns loudly instead of silently dropping the effect.
|
|
4
10
|
*/
|
|
5
11
|
export function composeMints(...mints) {
|
|
6
12
|
return {
|
|
7
13
|
init(el, config) {
|
|
8
14
|
const instances = [];
|
|
15
|
+
let disposed = false;
|
|
16
|
+
const applyInstance = (instance) => {
|
|
17
|
+
instance.init(el, config);
|
|
18
|
+
instances.push(instance);
|
|
19
|
+
};
|
|
20
|
+
const unresolved = [];
|
|
9
21
|
mints.forEach((mint) => {
|
|
10
22
|
if (typeof mint === 'string') {
|
|
11
23
|
const factory = mintRegistry.get(mint);
|
|
12
24
|
if (factory) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
25
|
+
applyInstance(factory(config));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
unresolved.push(mint);
|
|
16
29
|
}
|
|
17
30
|
}
|
|
18
31
|
else {
|
|
19
|
-
mint
|
|
20
|
-
instances.push(mint);
|
|
32
|
+
applyInstance(mint);
|
|
21
33
|
}
|
|
22
34
|
});
|
|
35
|
+
if (unresolved.length > 0) {
|
|
36
|
+
void loadBuiltinMints().then(() => {
|
|
37
|
+
if (disposed)
|
|
38
|
+
return;
|
|
39
|
+
unresolved.forEach((name) => {
|
|
40
|
+
const factory = mintRegistry.get(name);
|
|
41
|
+
if (!factory) {
|
|
42
|
+
console.warn(`[composeMints] Unknown mint: ${name}`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
applyInstance(factory(config));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
23
49
|
this.destroy = () => {
|
|
50
|
+
disposed = true;
|
|
24
51
|
instances.forEach((instance) => {
|
|
25
52
|
instance.destroy?.(el);
|
|
26
53
|
});
|
|
@@ -46,5 +73,5 @@ export function createCompositeMint() {
|
|
|
46
73
|
* Called by registerDefaultMints() - not at module level.
|
|
47
74
|
*/
|
|
48
75
|
export function registerComposite(registry) {
|
|
49
|
-
registry.
|
|
76
|
+
registry.registerBuiltin('composite', createCompositeMint);
|
|
50
77
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { MicroInteractionConfig, Mint, MintFactory } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Micro-interaction engine + the statically-shipped default effect (scale).
|
|
4
|
+
*
|
|
5
|
+
* This module split is load-bearing for tree-shaking: everything in this file
|
|
6
|
+
* ships statically with components that import `scaleMint` (Button's mint
|
|
7
|
+
* default), while the per-effect registrations stay in
|
|
8
|
+
* `./micro-interactions.ts`, which is only reachable through the
|
|
9
|
+
* demand-loaded `./presets.ts` chunk. Rollup assigns a shared module's whole
|
|
10
|
+
* used-export-set to the chunk that statically owns it — if the registrations
|
|
11
|
+
* lived in this file, `registerMicroInteractions` (used by the lazy presets
|
|
12
|
+
* chunk) would be pulled into every Button consumer's initial bundle again.
|
|
13
|
+
* Add future statically-defaulted effects here (or in their own module),
|
|
14
|
+
* never in `micro-interactions.ts`.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Check if user prefers reduced motion
|
|
18
|
+
* @internal Shared with ./micro-interactions.ts; not part of the public API.
|
|
19
|
+
*/
|
|
20
|
+
export declare function prefersReducedMotion(): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Generic micro-interaction factory
|
|
23
|
+
*/
|
|
24
|
+
export declare function createMicroInteraction(className: string, defaultConfig?: MicroInteractionConfig): Mint<MicroInteractionConfig>;
|
|
25
|
+
/**
|
|
26
|
+
* The `scale` micro-interaction factory, exported standalone so components
|
|
27
|
+
* whose mint *default* is `'scale'` (Button) can import it directly and pass
|
|
28
|
+
* it as a fallback: `mintRegistry.apply(el, mint, { scale: scaleMint })`.
|
|
29
|
+
* That is the `resolveIcon` tree-shaking pattern — the default effect ships
|
|
30
|
+
* with the component; the rest of the built-in set stays demand-loaded.
|
|
31
|
+
* `registerDefaultMints()` registers this same factory, so the registry path
|
|
32
|
+
* and the fallback path are behaviour-identical.
|
|
33
|
+
*/
|
|
34
|
+
export declare const scaleMint: MintFactory<MicroInteractionConfig>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Micro-interaction engine + the statically-shipped default effect (scale).
|
|
3
|
+
*
|
|
4
|
+
* This module split is load-bearing for tree-shaking: everything in this file
|
|
5
|
+
* ships statically with components that import `scaleMint` (Button's mint
|
|
6
|
+
* default), while the per-effect registrations stay in
|
|
7
|
+
* `./micro-interactions.ts`, which is only reachable through the
|
|
8
|
+
* demand-loaded `./presets.ts` chunk. Rollup assigns a shared module's whole
|
|
9
|
+
* used-export-set to the chunk that statically owns it — if the registrations
|
|
10
|
+
* lived in this file, `registerMicroInteractions` (used by the lazy presets
|
|
11
|
+
* chunk) would be pulled into every Button consumer's initial bundle again.
|
|
12
|
+
* Add future statically-defaulted effects here (or in their own module),
|
|
13
|
+
* never in `micro-interactions.ts`.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Check if user prefers reduced motion
|
|
17
|
+
* @internal Shared with ./micro-interactions.ts; not part of the public API.
|
|
18
|
+
*/
|
|
19
|
+
export function prefersReducedMotion() {
|
|
20
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Generic micro-interaction factory
|
|
24
|
+
*/
|
|
25
|
+
export function createMicroInteraction(className, defaultConfig = {}) {
|
|
26
|
+
return {
|
|
27
|
+
init(el, config = {}) {
|
|
28
|
+
// Merge default config with provided config
|
|
29
|
+
const finalConfig = { ...defaultConfig, ...config };
|
|
30
|
+
// Skip if disabled or prefers reduced motion
|
|
31
|
+
if (finalConfig.disabled || prefersReducedMotion())
|
|
32
|
+
return;
|
|
33
|
+
const trigger = finalConfig.trigger || 'hover';
|
|
34
|
+
const duration = finalConfig.duration || 200;
|
|
35
|
+
const delay = finalConfig.delay || 0;
|
|
36
|
+
// Determine event based on trigger
|
|
37
|
+
const eventMap = {
|
|
38
|
+
hover: 'mouseenter',
|
|
39
|
+
click: 'click',
|
|
40
|
+
focus: 'focus',
|
|
41
|
+
load: 'load'
|
|
42
|
+
};
|
|
43
|
+
const event = eventMap[trigger] || 'mouseenter';
|
|
44
|
+
const handler = () => {
|
|
45
|
+
// Skip if this specific animation is already running
|
|
46
|
+
const animatingAttr = `data-animating-${className}`;
|
|
47
|
+
if (el.getAttribute(animatingAttr) === 'true')
|
|
48
|
+
return;
|
|
49
|
+
// Apply delay if specified
|
|
50
|
+
const applyAnimation = () => {
|
|
51
|
+
el.setAttribute(animatingAttr, 'true');
|
|
52
|
+
// Add dynamic styles if intensity is specified
|
|
53
|
+
if (finalConfig.intensity && className.includes('scale')) {
|
|
54
|
+
el.style.setProperty('--scale-intensity', finalConfig.intensity.toString());
|
|
55
|
+
}
|
|
56
|
+
el.classList.add(className);
|
|
57
|
+
const cleanup = () => {
|
|
58
|
+
el.classList.remove(className);
|
|
59
|
+
el.removeAttribute(animatingAttr);
|
|
60
|
+
el.style.removeProperty('--scale-intensity');
|
|
61
|
+
el.removeEventListener('animationend', cleanup);
|
|
62
|
+
el.removeEventListener('transitionend', cleanup);
|
|
63
|
+
};
|
|
64
|
+
el.addEventListener('animationend', cleanup, { once: true });
|
|
65
|
+
el.addEventListener('transitionend', cleanup, { once: true });
|
|
66
|
+
// Fallback timeout
|
|
67
|
+
setTimeout(cleanup, duration + 50);
|
|
68
|
+
};
|
|
69
|
+
if (delay > 0) {
|
|
70
|
+
setTimeout(applyAnimation, delay);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
applyAnimation();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
// Special handling for load trigger
|
|
77
|
+
if (trigger === 'load') {
|
|
78
|
+
// Execute immediately if element is already loaded
|
|
79
|
+
requestAnimationFrame(() => handler());
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
el.addEventListener(event, handler, { passive: true });
|
|
83
|
+
}
|
|
84
|
+
// Store cleanup function
|
|
85
|
+
this.destroy = () => {
|
|
86
|
+
if (event !== 'load') {
|
|
87
|
+
el.removeEventListener(event, handler);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The `scale` micro-interaction factory, exported standalone so components
|
|
95
|
+
* whose mint *default* is `'scale'` (Button) can import it directly and pass
|
|
96
|
+
* it as a fallback: `mintRegistry.apply(el, mint, { scale: scaleMint })`.
|
|
97
|
+
* That is the `resolveIcon` tree-shaking pattern — the default effect ships
|
|
98
|
+
* with the component; the rest of the built-in set stays demand-loaded.
|
|
99
|
+
* `registerDefaultMints()` registers this same factory, so the registry path
|
|
100
|
+
* and the fallback path are behaviour-identical.
|
|
101
|
+
*/
|
|
102
|
+
export const scaleMint = (config) => createMicroInteraction('blocks-mint-scale', {
|
|
103
|
+
trigger: 'hover',
|
|
104
|
+
duration: 200,
|
|
105
|
+
...config
|
|
106
|
+
});
|
package/dist/mint/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { composeMints, createCompositeMint } from './compose.js';
|
|
2
|
-
export { createMicroInteraction } from './
|
|
2
|
+
export { createMicroInteraction, scaleMint } from './engine.js';
|
|
3
3
|
export { mintPresets, registerBusinessMints, registerDefaultMints, registerPlayfulMints } from './presets.js';
|
|
4
|
-
export { mintRegistry } from './registry.js';
|
|
4
|
+
export { type MintFallbacks, mintRegistry } from './registry.js';
|
|
5
5
|
export { createRippleMint } from './ripple.js';
|
|
6
6
|
export { mint, useMint } from './svelte.js';
|
|
7
7
|
export * from './types.js';
|
package/dist/mint/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Core exports
|
|
2
2
|
export { composeMints, createCompositeMint } from './compose.js';
|
|
3
3
|
// Mint factories
|
|
4
|
-
export { createMicroInteraction } from './
|
|
4
|
+
export { createMicroInteraction, scaleMint } from './engine.js';
|
|
5
5
|
// Presets
|
|
6
6
|
export { mintPresets, registerBusinessMints, registerDefaultMints, registerPlayfulMints } from './presets.js';
|
|
7
7
|
export { mintRegistry } from './registry.js';
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type { mintRegistry } from './registry.js';
|
|
2
|
-
import type { MicroInteractionConfig, Mint } from './types.js';
|
|
3
2
|
/**
|
|
4
|
-
*
|
|
3
|
+
* Built-in micro-interaction registrations.
|
|
4
|
+
*
|
|
5
|
+
* The generic engine (`createMicroInteraction`) and the statically-shipped
|
|
6
|
+
* default (`scaleMint`) live in `./engine.ts` — this module must contain ONLY
|
|
7
|
+
* registration code, because it is reachable exclusively through the
|
|
8
|
+
* demand-loaded `./presets.ts` chunk. Anything defined here would otherwise
|
|
9
|
+
* be dragged back into every component's initial bundle once a statically
|
|
10
|
+
* imported module shared it (Rollup assigns a shared module's whole
|
|
11
|
+
* used-export-set to the chunk that statically owns it).
|
|
5
12
|
*/
|
|
6
|
-
export declare function createMicroInteraction(className: string, defaultConfig?: MicroInteractionConfig): Mint<MicroInteractionConfig>;
|
|
7
13
|
/**
|
|
8
14
|
* Register all default micro-interaction mints.
|
|
9
15
|
* Called by registerDefaultMints() - not at module level.
|
|
@@ -1,111 +1,42 @@
|
|
|
1
|
+
import { createMicroInteraction, prefersReducedMotion, scaleMint } from './engine.js';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
+
* Built-in micro-interaction registrations.
|
|
4
|
+
*
|
|
5
|
+
* The generic engine (`createMicroInteraction`) and the statically-shipped
|
|
6
|
+
* default (`scaleMint`) live in `./engine.ts` — this module must contain ONLY
|
|
7
|
+
* registration code, because it is reachable exclusively through the
|
|
8
|
+
* demand-loaded `./presets.ts` chunk. Anything defined here would otherwise
|
|
9
|
+
* be dragged back into every component's initial bundle once a statically
|
|
10
|
+
* imported module shared it (Rollup assigns a shared module's whole
|
|
11
|
+
* used-export-set to the chunk that statically owns it).
|
|
3
12
|
*/
|
|
4
|
-
function prefersReducedMotion() {
|
|
5
|
-
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Generic micro-interaction factory
|
|
9
|
-
*/
|
|
10
|
-
export function createMicroInteraction(className, defaultConfig = {}) {
|
|
11
|
-
return {
|
|
12
|
-
init(el, config = {}) {
|
|
13
|
-
// Merge default config with provided config
|
|
14
|
-
const finalConfig = { ...defaultConfig, ...config };
|
|
15
|
-
// Skip if disabled or prefers reduced motion
|
|
16
|
-
if (finalConfig.disabled || prefersReducedMotion())
|
|
17
|
-
return;
|
|
18
|
-
const trigger = finalConfig.trigger || 'hover';
|
|
19
|
-
const duration = finalConfig.duration || 200;
|
|
20
|
-
const delay = finalConfig.delay || 0;
|
|
21
|
-
// Determine event based on trigger
|
|
22
|
-
const eventMap = {
|
|
23
|
-
hover: 'mouseenter',
|
|
24
|
-
click: 'click',
|
|
25
|
-
focus: 'focus',
|
|
26
|
-
load: 'load'
|
|
27
|
-
};
|
|
28
|
-
const event = eventMap[trigger] || 'mouseenter';
|
|
29
|
-
const handler = () => {
|
|
30
|
-
// Skip if this specific animation is already running
|
|
31
|
-
const animatingAttr = `data-animating-${className}`;
|
|
32
|
-
if (el.getAttribute(animatingAttr) === 'true')
|
|
33
|
-
return;
|
|
34
|
-
// Apply delay if specified
|
|
35
|
-
const applyAnimation = () => {
|
|
36
|
-
el.setAttribute(animatingAttr, 'true');
|
|
37
|
-
// Add dynamic styles if intensity is specified
|
|
38
|
-
if (finalConfig.intensity && className.includes('scale')) {
|
|
39
|
-
el.style.setProperty('--scale-intensity', finalConfig.intensity.toString());
|
|
40
|
-
}
|
|
41
|
-
el.classList.add(className);
|
|
42
|
-
const cleanup = () => {
|
|
43
|
-
el.classList.remove(className);
|
|
44
|
-
el.removeAttribute(animatingAttr);
|
|
45
|
-
el.style.removeProperty('--scale-intensity');
|
|
46
|
-
el.removeEventListener('animationend', cleanup);
|
|
47
|
-
el.removeEventListener('transitionend', cleanup);
|
|
48
|
-
};
|
|
49
|
-
el.addEventListener('animationend', cleanup, { once: true });
|
|
50
|
-
el.addEventListener('transitionend', cleanup, { once: true });
|
|
51
|
-
// Fallback timeout
|
|
52
|
-
setTimeout(cleanup, duration + 50);
|
|
53
|
-
};
|
|
54
|
-
if (delay > 0) {
|
|
55
|
-
setTimeout(applyAnimation, delay);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
applyAnimation();
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
// Special handling for load trigger
|
|
62
|
-
if (trigger === 'load') {
|
|
63
|
-
// Execute immediately if element is already loaded
|
|
64
|
-
requestAnimationFrame(() => handler());
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
el.addEventListener(event, handler, { passive: true });
|
|
68
|
-
}
|
|
69
|
-
// Store cleanup function
|
|
70
|
-
this.destroy = () => {
|
|
71
|
-
if (event !== 'load') {
|
|
72
|
-
el.removeEventListener(event, handler);
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
13
|
/**
|
|
79
14
|
* Register all default micro-interaction mints.
|
|
80
15
|
* Called by registerDefaultMints() - not at module level.
|
|
81
16
|
*/
|
|
82
17
|
export function registerMicroInteractions(registry) {
|
|
83
|
-
registry.
|
|
84
|
-
|
|
85
|
-
duration: 200,
|
|
86
|
-
...config
|
|
87
|
-
}));
|
|
88
|
-
registry.register('translate', (config) => createMicroInteraction('blocks-mint-translate', {
|
|
18
|
+
registry.registerBuiltin('scale', scaleMint);
|
|
19
|
+
registry.registerBuiltin('translate', (config) => createMicroInteraction('blocks-mint-translate', {
|
|
89
20
|
trigger: 'hover',
|
|
90
21
|
duration: 200,
|
|
91
22
|
...config
|
|
92
23
|
}));
|
|
93
|
-
registry.
|
|
24
|
+
registry.registerBuiltin('rotate', (config) => createMicroInteraction('blocks-mint-rotate', {
|
|
94
25
|
trigger: 'hover',
|
|
95
26
|
duration: 200,
|
|
96
27
|
...config
|
|
97
28
|
}));
|
|
98
|
-
registry.
|
|
29
|
+
registry.registerBuiltin('glow', (config) => createMicroInteraction('blocks-mint-glow', {
|
|
99
30
|
trigger: 'hover',
|
|
100
31
|
duration: 300,
|
|
101
32
|
...config
|
|
102
33
|
}));
|
|
103
|
-
registry.
|
|
34
|
+
registry.registerBuiltin('bounce', (config) => createMicroInteraction('blocks-mint-bounce', {
|
|
104
35
|
trigger: 'click',
|
|
105
36
|
duration: 600,
|
|
106
37
|
...config
|
|
107
38
|
}));
|
|
108
|
-
registry.
|
|
39
|
+
registry.registerBuiltin('pulse', (config) => ({
|
|
109
40
|
init(el, inputConfig = {}) {
|
|
110
41
|
const finalConfig = { trigger: 'hover', ...config, ...inputConfig };
|
|
111
42
|
if (finalConfig.disabled || prefersReducedMotion())
|
|
@@ -143,12 +74,12 @@ export function registerMicroInteractions(registry) {
|
|
|
143
74
|
}
|
|
144
75
|
}
|
|
145
76
|
}));
|
|
146
|
-
registry.
|
|
77
|
+
registry.registerBuiltin('shake', (config) => createMicroInteraction('blocks-mint-shake', {
|
|
147
78
|
trigger: 'click',
|
|
148
79
|
duration: 500,
|
|
149
80
|
...config
|
|
150
81
|
}));
|
|
151
|
-
registry.
|
|
82
|
+
registry.registerBuiltin('wiggle', (config) => createMicroInteraction('blocks-mint-wiggle', {
|
|
152
83
|
trigger: 'hover',
|
|
153
84
|
duration: 500,
|
|
154
85
|
...config
|
package/dist/mint/presets.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Register all default mints (scale, translate, rotate, glow, bounce, pulse, shake, ripple, composite).
|
|
3
3
|
* Safe to call multiple times – subsequent calls are no-ops.
|
|
4
|
+
*
|
|
5
|
+
* All registrations go through `registerBuiltin` (register-if-absent), so a
|
|
6
|
+
* consumer `register()` override for a built-in name ALWAYS survives — no
|
|
7
|
+
* matter whether the override ran before this call or while the demand-load
|
|
8
|
+
* that triggers it was still in flight.
|
|
4
9
|
*/
|
|
5
10
|
export declare function registerDefaultMints(): void;
|
|
6
11
|
/**
|
|
@@ -12,7 +17,8 @@ export declare function registerDefaultMints(): void;
|
|
|
12
17
|
*/
|
|
13
18
|
export declare function registerPlayfulMints(): void;
|
|
14
19
|
/**
|
|
15
|
-
* Register business mints bundle
|
|
20
|
+
* Register business mints bundle.
|
|
21
|
+
* Registered via `registerBuiltin`: a consumer override for these names wins.
|
|
16
22
|
*/
|
|
17
23
|
export declare function registerBusinessMints(): void;
|
|
18
24
|
export declare const mintPresets: {
|
package/dist/mint/presets.js
CHANGED
|
@@ -6,6 +6,11 @@ let defaultMintsRegistered = false;
|
|
|
6
6
|
/**
|
|
7
7
|
* Register all default mints (scale, translate, rotate, glow, bounce, pulse, shake, ripple, composite).
|
|
8
8
|
* Safe to call multiple times – subsequent calls are no-ops.
|
|
9
|
+
*
|
|
10
|
+
* All registrations go through `registerBuiltin` (register-if-absent), so a
|
|
11
|
+
* consumer `register()` override for a built-in name ALWAYS survives — no
|
|
12
|
+
* matter whether the override ran before this call or while the demand-load
|
|
13
|
+
* that triggers it was still in flight.
|
|
9
14
|
*/
|
|
10
15
|
export function registerDefaultMints() {
|
|
11
16
|
if (defaultMintsRegistered)
|
|
@@ -26,16 +31,17 @@ export function registerPlayfulMints() {
|
|
|
26
31
|
// Reserved for future playful-only mints.
|
|
27
32
|
}
|
|
28
33
|
/**
|
|
29
|
-
* Register business mints bundle
|
|
34
|
+
* Register business mints bundle.
|
|
35
|
+
* Registered via `registerBuiltin`: a consumer override for these names wins.
|
|
30
36
|
*/
|
|
31
37
|
export function registerBusinessMints() {
|
|
32
38
|
// Subtle mints for professional UIs
|
|
33
|
-
mintRegistry.
|
|
39
|
+
mintRegistry.registerBuiltin('fade', () => ({
|
|
34
40
|
init(el) {
|
|
35
41
|
el.classList.add('blocks-mint-fade');
|
|
36
42
|
}
|
|
37
43
|
}));
|
|
38
|
-
mintRegistry.
|
|
44
|
+
mintRegistry.registerBuiltin('slide', () => ({
|
|
39
45
|
init(el) {
|
|
40
46
|
el.classList.add('blocks-mint-slide');
|
|
41
47
|
}
|
package/dist/mint/registry.d.ts
CHANGED
|
@@ -1,15 +1,54 @@
|
|
|
1
1
|
import type { MintConfig, MintFactory, MintProp } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Statically-imported fallback factories, keyed by mint name.
|
|
4
|
+
*
|
|
5
|
+
* Components pass their *default* mint's factory here — Button imports
|
|
6
|
+
* `scaleMint` directly and calls `mintRegistry.apply(el, mint, { scale:
|
|
7
|
+
* scaleMint })` — so the default effect ships tree-shaken with the component
|
|
8
|
+
* instead of dragging the whole built-in set (micro-interactions, the ripple
|
|
9
|
+
* engine, compose, presets — ~3.9 KB min) into every consumer bundle. Same
|
|
10
|
+
* precedence as `resolveIcon(name, fallback)`: a registry entry (consumer
|
|
11
|
+
* override / loaded built-in) wins, the direct import is only the fallback.
|
|
12
|
+
* See docs/ICON-DESIGN.md → "Icon resolution & tree-shaking".
|
|
13
|
+
*/
|
|
14
|
+
export type MintFallbacks = Readonly<Partial<Record<string, MintFactory>>>;
|
|
15
|
+
/**
|
|
16
|
+
* @internal Shared with ./compose.ts so composite string members resolve
|
|
17
|
+
* through the same demand-load; not part of the public API.
|
|
18
|
+
*/
|
|
19
|
+
export declare function loadBuiltinMints(): Promise<void>;
|
|
2
20
|
declare class MintRegistry {
|
|
3
21
|
private mints;
|
|
4
22
|
private instances;
|
|
5
23
|
/** Register a mint globally */
|
|
6
24
|
register<TConfig extends MintConfig = MintConfig>(name: string, factory: MintFactory<TConfig>): void;
|
|
25
|
+
/**
|
|
26
|
+
* Register a built-in mint — only if the name is still free. Used by the
|
|
27
|
+
* built-in set (`registerDefaultMints()` and the opt-in bundles) so the
|
|
28
|
+
* demand-load NEVER clobbers a consumer override: a `register()` entry
|
|
29
|
+
* survives regardless of whether it ran before or during the load. A later
|
|
30
|
+
* explicit `register()` call still overrides as before.
|
|
31
|
+
*/
|
|
32
|
+
registerBuiltin<TConfig extends MintConfig = MintConfig>(name: string, factory: MintFactory<TConfig>): void;
|
|
7
33
|
/** Get a mint factory by name */
|
|
8
34
|
get(name: string): MintFactory | undefined;
|
|
9
35
|
/** Check if a mint is registered */
|
|
10
36
|
has(name: string): boolean;
|
|
11
|
-
/**
|
|
12
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Apply mints to an element using polymorphic input.
|
|
39
|
+
*
|
|
40
|
+
* Resolution order per name: registry entry (consumer `register()` override
|
|
41
|
+
* or an already-loaded built-in) → `fallbacks` (statically imported by the
|
|
42
|
+
* caller) → lazy-loaded built-in set. Names that resolve synchronously are
|
|
43
|
+
* applied synchronously; unresolved names wait for the built-ins chunk
|
|
44
|
+
* (one-time microtask + fetch). Events firing inside that fetch window are
|
|
45
|
+
* NOT replayed — on a slow network, a click landing before a demand-loaded
|
|
46
|
+
* click-triggered mint (ripple, shake) finished loading is lost for that
|
|
47
|
+
* effect. Acceptable for decorative effects (documented contract, see
|
|
48
|
+
* mint/README.md); consumers who need first-interaction guarantees register
|
|
49
|
+
* the effect statically up front (`registerDefaultMints()`).
|
|
50
|
+
*/
|
|
51
|
+
apply(el: HTMLElement, mint: MintProp, fallbacks?: MintFallbacks): () => void;
|
|
13
52
|
/** Normalize polymorphic mint prop to consistent format */
|
|
14
53
|
private normalizeMintProp;
|
|
15
54
|
/** Update mint config for an element */
|
package/dist/mint/registry.js
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
// Lazily loads + registers the built-in mints (scale, glow, ripple, …) the
|
|
2
|
+
// first time a name resolves neither from the registry nor from the caller's
|
|
3
|
+
// fallbacks. The dynamic import() keeps the built-in set out of the static
|
|
4
|
+
// module graph of every mint-consuming component — an app that only ever uses
|
|
5
|
+
// component defaults never loads it at all; an app that passes a dynamic mint
|
|
6
|
+
// name (mint="ripple", presets, composite) fetches it once on first use.
|
|
7
|
+
//
|
|
8
|
+
// The registry ↔ presets import is cyclic but inert: presets dereferences the
|
|
9
|
+
// registry binding only at call time, and this side is a dynamic import, so
|
|
10
|
+
// module initialisation completes cleanly regardless of load order.
|
|
11
|
+
let builtinsLoading;
|
|
12
|
+
/**
|
|
13
|
+
* @internal Shared with ./compose.ts so composite string members resolve
|
|
14
|
+
* through the same demand-load; not part of the public API.
|
|
15
|
+
*/
|
|
16
|
+
export function loadBuiltinMints() {
|
|
17
|
+
builtinsLoading ??= import('./presets.js').then((presets) => {
|
|
18
|
+
presets.registerDefaultMints();
|
|
19
|
+
});
|
|
20
|
+
return builtinsLoading;
|
|
21
|
+
}
|
|
2
22
|
class MintRegistry {
|
|
3
23
|
mints = new Map();
|
|
4
24
|
instances = new WeakMap();
|
|
@@ -6,6 +26,18 @@ class MintRegistry {
|
|
|
6
26
|
register(name, factory) {
|
|
7
27
|
this.mints.set(name, factory);
|
|
8
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Register a built-in mint — only if the name is still free. Used by the
|
|
31
|
+
* built-in set (`registerDefaultMints()` and the opt-in bundles) so the
|
|
32
|
+
* demand-load NEVER clobbers a consumer override: a `register()` entry
|
|
33
|
+
* survives regardless of whether it ran before or during the load. A later
|
|
34
|
+
* explicit `register()` call still overrides as before.
|
|
35
|
+
*/
|
|
36
|
+
registerBuiltin(name, factory) {
|
|
37
|
+
if (!this.mints.has(name)) {
|
|
38
|
+
this.mints.set(name, factory);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
9
41
|
/** Get a mint factory by name */
|
|
10
42
|
get(name) {
|
|
11
43
|
return this.mints.get(name);
|
|
@@ -14,43 +46,64 @@ class MintRegistry {
|
|
|
14
46
|
has(name) {
|
|
15
47
|
return this.mints.has(name);
|
|
16
48
|
}
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Apply mints to an element using polymorphic input.
|
|
51
|
+
*
|
|
52
|
+
* Resolution order per name: registry entry (consumer `register()` override
|
|
53
|
+
* or an already-loaded built-in) → `fallbacks` (statically imported by the
|
|
54
|
+
* caller) → lazy-loaded built-in set. Names that resolve synchronously are
|
|
55
|
+
* applied synchronously; unresolved names wait for the built-ins chunk
|
|
56
|
+
* (one-time microtask + fetch). Events firing inside that fetch window are
|
|
57
|
+
* NOT replayed — on a slow network, a click landing before a demand-loaded
|
|
58
|
+
* click-triggered mint (ripple, shake) finished loading is lost for that
|
|
59
|
+
* effect. Acceptable for decorative effects (documented contract, see
|
|
60
|
+
* mint/README.md); consumers who need first-interaction guarantees register
|
|
61
|
+
* the effect statically up front (`registerDefaultMints()`).
|
|
62
|
+
*/
|
|
63
|
+
apply(el, mint, fallbacks) {
|
|
30
64
|
const mintDefinitions = this.normalizeMintProp(mint);
|
|
31
65
|
const elementMints = new Map();
|
|
32
66
|
const cleanupFunctions = [];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const config = typeof mintDef === 'object' ? mintDef.config : undefined;
|
|
36
|
-
const factory = this.get(name);
|
|
37
|
-
if (!factory) {
|
|
38
|
-
console.warn(`[MintRegistry] Unknown mint: ${name}`);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
67
|
+
let disposed = false;
|
|
68
|
+
const applyOne = (name, config, factory) => {
|
|
41
69
|
const mintInstance = factory(config);
|
|
42
70
|
mintInstance.init(el, config);
|
|
43
71
|
elementMints.set(name, mintInstance);
|
|
44
|
-
|
|
45
|
-
const cleanup = () => {
|
|
72
|
+
cleanupFunctions.push(() => {
|
|
46
73
|
mintInstance.destroy?.(el);
|
|
47
74
|
elementMints.delete(name);
|
|
48
|
-
};
|
|
49
|
-
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
const unresolved = [];
|
|
78
|
+
mintDefinitions.forEach((mintDef) => {
|
|
79
|
+
const name = typeof mintDef === 'string' ? mintDef : mintDef.name;
|
|
80
|
+
const config = typeof mintDef === 'object' ? mintDef.config : undefined;
|
|
81
|
+
const factory = this.get(name) ?? fallbacks?.[name];
|
|
82
|
+
if (factory) {
|
|
83
|
+
applyOne(name, config, factory);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
unresolved.push({ name, config });
|
|
87
|
+
}
|
|
50
88
|
});
|
|
89
|
+
if (unresolved.length > 0) {
|
|
90
|
+
void loadBuiltinMints().then(() => {
|
|
91
|
+
if (disposed)
|
|
92
|
+
return;
|
|
93
|
+
unresolved.forEach(({ name, config }) => {
|
|
94
|
+
const factory = this.get(name) ?? fallbacks?.[name];
|
|
95
|
+
if (!factory) {
|
|
96
|
+
console.warn(`[MintRegistry] Unknown mint: ${name}`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
applyOne(name, config, factory);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
51
103
|
this.instances.set(el, elementMints);
|
|
52
104
|
// Return combined cleanup function
|
|
53
105
|
return () => {
|
|
106
|
+
disposed = true;
|
|
54
107
|
cleanupFunctions.forEach((fn) => {
|
|
55
108
|
fn();
|
|
56
109
|
});
|
package/dist/mint/ripple.js
CHANGED
|
@@ -68,5 +68,5 @@ export function createRippleMint(defaultConfig = {}) {
|
|
|
68
68
|
* Called by registerDefaultMints() - not at module level.
|
|
69
69
|
*/
|
|
70
70
|
export function registerRipple(registry) {
|
|
71
|
-
registry.
|
|
71
|
+
registry.registerBuiltin('ripple', createRippleMint);
|
|
72
72
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { mintRegistry, Spinner } from '../..';
|
|
3
|
+
// Direct import (not the barrel): the resolveIcon tree-shaking pattern.
|
|
4
|
+
// Button's mint default is 'scale', so the scale factory ships statically
|
|
5
|
+
// as the apply() fallback; every other mint name stays demand-loaded.
|
|
6
|
+
import { scaleMint } from '../../mint/engine';
|
|
3
7
|
import { getBlocksConfig, resolveSlotClasses } from '../../provider';
|
|
4
8
|
import { getButtonGroupContext } from '../ButtonGroup/buttonGroup.context';
|
|
5
9
|
import { buttonVariants, type ButtonVariants } from '..';
|
|
@@ -80,7 +84,7 @@
|
|
|
80
84
|
!effectiveDisabled &&
|
|
81
85
|
!loading
|
|
82
86
|
) {
|
|
83
|
-
return mintRegistry.apply(buttonElement, effectiveMint);
|
|
87
|
+
return mintRegistry.apply(buttonElement, effectiveMint, { scale: scaleMint });
|
|
84
88
|
}
|
|
85
89
|
});
|
|
86
90
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { buttonGroupVariants, type ButtonGroupVariants } from '..';
|
|
3
3
|
import { getBlocksConfig, resolveSlotClasses } from '../../provider';
|
|
4
|
+
import { composeHandlers } from '../../utils/compose-handlers';
|
|
4
5
|
import { edgeEnabledIndex, nextEnabledIndex } from '../../utils';
|
|
5
6
|
import { getTierContext, setTierContext } from '../../utils/tier-context';
|
|
6
7
|
import type { ButtonGroupContext, ButtonGroupProps } from './index';
|
|
@@ -25,6 +26,12 @@
|
|
|
25
26
|
preset,
|
|
26
27
|
ariaLabel,
|
|
27
28
|
ariaLabelledBy,
|
|
29
|
+
// Pulled out of restProps so the `{...restProps}`-first spread (internal
|
|
30
|
+
// attributes win — see docs/COMPONENT-API-CONVENTIONS.md) can't clobber
|
|
31
|
+
// it: the roving keyboard navigation below is composed with a consumer's
|
|
32
|
+
// own handler (internal first, consumer second) instead of either side
|
|
33
|
+
// silently replacing the other.
|
|
34
|
+
onkeydown: onkeydownProp,
|
|
28
35
|
...restProps
|
|
29
36
|
}: ButtonGroupProps = $props();
|
|
30
37
|
|
|
@@ -271,18 +278,35 @@
|
|
|
271
278
|
const ariaOrientation = $derived(ariaRole === 'radiogroup' ? orientation : undefined);
|
|
272
279
|
</script>
|
|
273
280
|
|
|
281
|
+
<!--
|
|
282
|
+
restProps spreads FIRST so component-owned state wins (COMPONENT-API-CONVENTIONS
|
|
283
|
+
§restProps ordering) — a consumer role/tabindex through restProps must not
|
|
284
|
+
defeat the radiogroup semantics. The attributes after the spread are
|
|
285
|
+
conditional merges, not plain overrides, because an explicit `undefined`
|
|
286
|
+
after a spread REMOVES the attribute:
|
|
287
|
+
- role: always internally computed (radiogroup/group) — internal wins outright.
|
|
288
|
+
- aria-label / aria-labelledby: the dedicated props win, a consumer's own
|
|
289
|
+
`aria-label`/`aria-labelledby` through restProps is the fallback.
|
|
290
|
+
- aria-orientation: internal on the radiogroup arm; on the `group` arm it is
|
|
291
|
+
actively removed even against restProps — ARIA disallows aria-orientation
|
|
292
|
+
on role=group (aria-allowed-attr), mirroring Button's aria-pressed force-off.
|
|
293
|
+
- aria-disabled: internal `true` is unoverridable, idle falls back to the
|
|
294
|
+
consumer value; the default DOM output stays byte-identical (attr absent).
|
|
295
|
+
- onkeydown is destructured (never in restProps) and composed: the roving
|
|
296
|
+
keyboard nav always runs, a consumer handler runs after it.
|
|
297
|
+
-->
|
|
274
298
|
<div
|
|
275
299
|
bind:this={containerElement}
|
|
300
|
+
{...restProps}
|
|
276
301
|
role={ariaRole}
|
|
277
302
|
class={unstyled
|
|
278
303
|
? [slotClasses?.base, className].filter(Boolean).join(' ')
|
|
279
304
|
: styles.base({ class: [slotClasses?.base, className] })}
|
|
280
|
-
aria-label={ariaLabel}
|
|
281
|
-
aria-labelledby={ariaLabelledBy}
|
|
305
|
+
aria-label={ariaLabel ?? restProps['aria-label']}
|
|
306
|
+
aria-labelledby={ariaLabelledBy ?? restProps['aria-labelledby']}
|
|
282
307
|
aria-orientation={ariaOrientation}
|
|
283
|
-
aria-disabled={disabled || undefined}
|
|
284
|
-
onkeydown={handleKeyDown}
|
|
285
|
-
{...restProps}
|
|
308
|
+
aria-disabled={disabled || restProps['aria-disabled'] || undefined}
|
|
309
|
+
onkeydown={composeHandlers(handleKeyDown, onkeydownProp)}
|
|
286
310
|
>
|
|
287
311
|
{@render children?.()}
|
|
288
312
|
</div>
|
|
@@ -39,14 +39,24 @@
|
|
|
39
39
|
);
|
|
40
40
|
</script>
|
|
41
41
|
|
|
42
|
+
<!--
|
|
43
|
+
restProps spreads FIRST so component-owned state wins (COMPONENT-API-CONVENTIONS
|
|
44
|
+
§restProps ordering) — a consumer role through restProps must not defeat the
|
|
45
|
+
toolbar semantics. No conditional merges are needed here (unlike
|
|
46
|
+
Button/ButtonGroup), because every attribute after the spread is always
|
|
47
|
+
defined: role is static, `orientation` has a default, and `aria-label` is a
|
|
48
|
+
required prop destructured out of restProps. `aria-orientation` is valid on
|
|
49
|
+
role=toolbar on both arms, so there is no removal case; `aria-labelledby`
|
|
50
|
+
and other native/data-* attributes pass through the spread untouched.
|
|
51
|
+
-->
|
|
42
52
|
<div
|
|
53
|
+
{...restProps}
|
|
43
54
|
class={unstyled
|
|
44
55
|
? [slotClasses?.base, className].filter(Boolean).join(' ')
|
|
45
56
|
: styles.base({ class: [slotClasses?.base, className] })}
|
|
46
57
|
role="toolbar"
|
|
47
58
|
aria-orientation={orientation}
|
|
48
59
|
aria-label={ariaLabel}
|
|
49
|
-
{...restProps}
|
|
50
60
|
>
|
|
51
61
|
{@render children()}
|
|
52
62
|
</div>
|
package/docs/MIGRATION-v5.md
CHANGED
|
@@ -58,10 +58,12 @@ its disclosure sibling `Collapsible`.)
|
|
|
58
58
|
|
|
59
59
|
## 3. Table chrome
|
|
60
60
|
|
|
61
|
-
The Table no longer renders a frame by default. The new `
|
|
62
|
-
controls the container chrome
|
|
61
|
+
The Table no longer renders a frame by default. The new `variant` prop
|
|
62
|
+
controls the container chrome. (The prop was named `appearance` from v5.0
|
|
63
|
+
to v6.32; it now shares the single style-axis name `variant` with every
|
|
64
|
+
other component.)
|
|
63
65
|
|
|
64
|
-
| `
|
|
66
|
+
| `variant` | Effect |
|
|
65
67
|
| ------------ | ------------------------------------------------------------ |
|
|
66
68
|
| `flush` (default) | No frame; the table sits inline in the reading flow. |
|
|
67
69
|
| `surface` | Quiet tinted zone — `bg-surface-quiet`. |
|
|
@@ -73,7 +75,7 @@ The dead `tableVariants` export is removed (was unused in any consumer).
|
|
|
73
75
|
<!-- v4 — implicit frame -->
|
|
74
76
|
<Table data={rows} columns={cols} />
|
|
75
77
|
<!-- v5 — explicit equivalent -->
|
|
76
|
-
<Table data={rows} columns={cols}
|
|
78
|
+
<Table data={rows} columns={cols} variant="framed" />
|
|
77
79
|
<!-- v5 — new editorial default -->
|
|
78
80
|
<Table data={rows} columns={cols} />
|
|
79
81
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@urbicon-ui/blocks",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.34.0",
|
|
4
4
|
"description": "Svelte 5 UI component library with Tailwind CSS 4, OKLCH design tokens and zero runtime dependencies",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -95,8 +95,8 @@
|
|
|
95
95
|
"@sveltejs/package": "^2.5.8",
|
|
96
96
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
97
97
|
"@tailwindcss/vite": "^4.3.1",
|
|
98
|
-
"@urbicon-ui/i18n": "6.
|
|
99
|
-
"@urbicon-ui/shared-types": "6.
|
|
98
|
+
"@urbicon-ui/i18n": "6.34.0",
|
|
99
|
+
"@urbicon-ui/shared-types": "6.34.0",
|
|
100
100
|
"prettier": "^3.8.4",
|
|
101
101
|
"prettier-plugin-svelte": "^4.1.1",
|
|
102
102
|
"prettier-plugin-tailwindcss": "^0.8.0",
|