@urbicon-ui/blocks 6.33.0 → 6.35.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/README.md +11 -1
- package/dist/components/Calendar/CalendarHeader.svelte +32 -26
- package/dist/components/Calendar/CalendarMiniMonth.svelte +10 -10
- package/dist/components/Calendar/calendar.variants.d.ts +63 -63
- package/dist/components/Calendar/calendar.variants.js +19 -5
- package/dist/components/FileUpload/FileUpload.svelte +17 -2
- package/dist/components/Planner/PlannerHeader.svelte +16 -13
- package/dist/components/Planner/planner.variants.js +14 -3
- package/dist/i18n/index.d.ts +467 -205
- package/dist/i18n/index.js +83 -9
- package/dist/internal/core/CoreIconButton.svelte +66 -0
- package/dist/internal/core/CoreIconButton.svelte.d.ts +13 -0
- package/dist/internal/core/CoreSpinner.svelte +49 -0
- package/dist/internal/core/CoreSpinner.svelte.d.ts +8 -0
- package/dist/internal/core/spinner-geometry.d.ts +6 -0
- package/dist/internal/core/spinner-geometry.js +6 -0
- 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 +61 -4
- package/dist/mint/registry.js +99 -28
- package/dist/mint/ripple.js +1 -1
- package/dist/primitives/Alert/alert.variants.d.ts +8 -8
- package/dist/primitives/Badge/Badge.svelte +5 -5
- package/dist/primitives/Badge/badge.variants.js +17 -1
- package/dist/primitives/Button/Button.svelte +9 -3
- package/dist/primitives/Dialog/Dialog.svelte +7 -5
- package/dist/primitives/Dialog/dialog.variants.d.ts +7 -0
- package/dist/primitives/Dialog/dialog.variants.js +17 -0
- package/dist/primitives/Drawer/Drawer.svelte +7 -5
- package/dist/primitives/Drawer/drawer.variants.d.ts +8 -0
- package/dist/primitives/Drawer/drawer.variants.js +17 -0
- package/dist/primitives/JourneyTimeline/journey-timeline.variants.d.ts +22 -22
- package/dist/primitives/Spinner/Spinner.svelte +3 -1
- package/dist/primitives/Spinner/spinner.variants.d.ts +16 -16
- package/dist/primitives/Stepper/stepper.variants.d.ts +11 -11
- package/dist/primitives/Toast/Toaster.svelte +17 -2
- package/dist/primitives/Toast/toast.variants.d.ts +12 -12
- package/dist/primitives/Tooltip/tooltip.variants.d.ts +3 -3
- package/dist/utils/variants.js +44 -10
- package/package.json +4 -6
package/dist/i18n/index.js
CHANGED
|
@@ -1,13 +1,62 @@
|
|
|
1
1
|
import { createPackageI18n } from '@urbicon-ui/i18n';
|
|
2
|
-
import deTranslations from '../translations/de.js';
|
|
3
2
|
import enTranslations from '../translations/en.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
/**
|
|
4
|
+
* blocks package i18n.
|
|
5
|
+
*
|
|
6
|
+
* **Only the English base bundle is eager.** German (`de`) is registered as a
|
|
7
|
+
* lazy dynamic-import loader, so an English-only app never statically bundles the
|
|
8
|
+
* `de` catalog (~4 KB min) — Vite/Rollup splits it into its own chunk that loads
|
|
9
|
+
* only when the `de` locale is activated. `en` is the base/fallback locale.
|
|
10
|
+
*
|
|
11
|
+
* The eager base is `{ en }` (not `blocksTranslations`, which is retained below as
|
|
12
|
+
* an `{ en }` back-compat export). Registration is lazy on first
|
|
13
|
+
* `useBlocksI18n()`/`t()` — see `createPackageI18n`.
|
|
14
|
+
*
|
|
15
|
+
* ## Before the `de` chunk loads: resolution falls back to `en`
|
|
16
|
+
*
|
|
17
|
+
* A package-scoped lookup for a `de` key that isn't loaded yet resolves through
|
|
18
|
+
* the package fallback locale, i.e. it returns the **English** string (verified:
|
|
19
|
+
* `@urbicon-ui/i18n` `registry.translate` package path + `lazy-load.test.ts`). It
|
|
20
|
+
* never renders the raw key. The provider (`<I18nProvider>`) triggers the `de`
|
|
21
|
+
* load in a **client-only** `$effect` on mount, so a `de` app renders English on
|
|
22
|
+
* the server and the first client paint, then re-resolves to German once the
|
|
23
|
+
* chunk lands — a brief text flash and a possible hydration text mismatch.
|
|
24
|
+
* Two consequences of that window worth knowing:
|
|
25
|
+
*
|
|
26
|
+
* - The "falls back to English" guarantee assumes the provider's `fallbackLocale`
|
|
27
|
+
* stays at its default (`en`). Pointing `fallbackLocale` at a lazy locale that
|
|
28
|
+
* has not loaded yet leaves both lookup paths empty — such a key then renders
|
|
29
|
+
* raw and fires `onMissingKey`. Eager-register any locale you use as fallback.
|
|
30
|
+
* - `LocaleSwitcher` disables its trigger while a locale chunk load is in flight
|
|
31
|
+
* (`registry.isLoading`), so without the eager register a `de` app briefly
|
|
32
|
+
* shows the switcher disabled on first mount.
|
|
33
|
+
*
|
|
34
|
+
* The SSR recipe below removes both, along with the flash itself.
|
|
35
|
+
*
|
|
36
|
+
* ## SSR recipe for German (and other non-base) apps
|
|
37
|
+
*
|
|
38
|
+
* German SvelteKit SSR apps should register the `de` bundle eagerly **once at
|
|
39
|
+
* app start** so the very first server render is already German (no flash, no
|
|
40
|
+
* hydration mismatch). The registry is module-global and holds only static,
|
|
41
|
+
* request-identical translation data, so a single startup registration is
|
|
42
|
+
* SSR-safe — it carries no per-request state:
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* // src/hooks.server.ts (or any module evaluated once at server start)
|
|
46
|
+
* import { registerBlocksLocale } from '@urbicon-ui/blocks';
|
|
47
|
+
* import de from '@urbicon-ui/blocks/i18n/de';
|
|
48
|
+
*
|
|
49
|
+
* registerBlocksLocale('de', de);
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* This keeps the `de` catalog out of English-only client bundles (the whole point
|
|
53
|
+
* of the split) while making it eagerly present wherever it is actually rendered.
|
|
54
|
+
*/
|
|
55
|
+
export const blocksI18n = createPackageI18n('blocks', { en: enTranslations }, {
|
|
56
|
+
loaders: {
|
|
57
|
+
de: () => import('../translations/de.js').then((m) => m.default)
|
|
58
|
+
}
|
|
59
|
+
});
|
|
11
60
|
/**
|
|
12
61
|
* Context-scoped translation hook for the blocks package. Call during component
|
|
13
62
|
* initialisation, then use the returned `t` (conventionally aliased `bt`):
|
|
@@ -18,10 +67,35 @@ export const blocksI18n = createPackageI18n('blocks', blocksTranslations);
|
|
|
18
67
|
* ```
|
|
19
68
|
*
|
|
20
69
|
* Resolves against the nearest `<I18nProvider>`'s locale (or the base locale
|
|
21
|
-
* when none is mounted) and re-renders reactively on locale change.
|
|
70
|
+
* when none is mounted) and re-renders reactively on locale change. For a lazy
|
|
71
|
+
* locale (`de`) that isn't loaded yet, resolution falls back to the base locale
|
|
72
|
+
* (`en`); see the module docblock and `registerBlocksLocale` for the SSR path.
|
|
22
73
|
*/
|
|
23
74
|
export const useBlocksI18n = blocksI18n.useTranslate;
|
|
75
|
+
/**
|
|
76
|
+
* Eagerly register the blocks bundle for a lazy locale (currently `de`) so it is
|
|
77
|
+
* present for SSR / the first render, instead of loading client-side on mount.
|
|
78
|
+
* Call once at app/server start, paired with the public locale subpath import:
|
|
79
|
+
*
|
|
80
|
+
* ```ts
|
|
81
|
+
* import { registerBlocksLocale } from '@urbicon-ui/blocks';
|
|
82
|
+
* import de from '@urbicon-ui/blocks/i18n/de';
|
|
83
|
+
* registerBlocksLocale('de', de);
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* Additive (does not drop the eager `en` base) and strict (throws on an
|
|
87
|
+
* unsupported locale or a non-object bundle).
|
|
88
|
+
*/
|
|
89
|
+
export const registerBlocksLocale = (locale, bundle) => blocksI18n.registerLocale(locale, bundle);
|
|
24
90
|
// Introspection helpers (locale list / key existence).
|
|
25
91
|
export const { exists: hasBlocksTranslation, getLocales: getBlocksLocales } = blocksI18n;
|
|
92
|
+
/**
|
|
93
|
+
* The eager base bundle only (`{ en }`). `de` is lazy and no longer part of this
|
|
94
|
+
* object; import it explicitly via `@urbicon-ui/blocks/i18n/de` (e.g. for a parity
|
|
95
|
+
* test or the SSR eager-register recipe above).
|
|
96
|
+
*/
|
|
97
|
+
const blocksTranslations = {
|
|
98
|
+
en: enTranslations
|
|
99
|
+
};
|
|
26
100
|
// Re-export for external use
|
|
27
101
|
export { blocksTranslations };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CoreIconButton — behaviour-only internal core for the small icon-only controls
|
|
3
|
+
embedded inside other public components (Badge's remove ×, Dialog/Drawer close
|
|
4
|
+
×). It renders a native `<button type="button">` with just the interaction
|
|
5
|
+
baseline (flex-centred content, pointer/select affordance, focus-visible reset,
|
|
6
|
+
disabled inertness). Visual identity — size, radius, colour, ring, shadow — comes
|
|
7
|
+
entirely from the call-site's `class` (a `*.variants.ts` slot), NOT from a variant
|
|
8
|
+
engine pass here: this core deliberately does not touch tv()/mint/tokens, which is
|
|
9
|
+
what lets a public component embed a control without importing another public
|
|
10
|
+
component (keeps the public-to-public import graph clean, see internal/core/).
|
|
11
|
+
|
|
12
|
+
The base classes are structural plumbing, deliberately NOT tw-merged against the
|
|
13
|
+
incoming `class` (no engine here): a consumer slot class targeting one of these
|
|
14
|
+
buckets (display/cursor/select/disabled-opacity) is resolved by stylesheet order,
|
|
15
|
+
not by the override ladder. That is accepted: overrides belong on the call-site's
|
|
16
|
+
variants slot (which merges correctly bucket-by-bucket); the plumbing is not an
|
|
17
|
+
override surface. Under `unstyled` the plumbing also stays — stripping
|
|
18
|
+
`disabled:pointer-events-none` & co. would break behaviour, not styling.
|
|
19
|
+
|
|
20
|
+
INTERNAL — never exported from the package barrel, no docs/MCP entry. For a full
|
|
21
|
+
themed button use the public `Button`.
|
|
22
|
+
|
|
23
|
+
restProps-first spread (COMPONENT-API-CONVENTIONS §restProps ordering): the
|
|
24
|
+
explicit attributes come AFTER `{...restProps}` so component-owned wiring wins —
|
|
25
|
+
`aria-label` is required (an icon-only button has no text accessible name) and
|
|
26
|
+
can never be clobbered, and `type="button"` stays a submit-safe default.
|
|
27
|
+
-->
|
|
28
|
+
<script lang="ts">
|
|
29
|
+
import type { Snippet } from 'svelte';
|
|
30
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
'aria-label': ariaLabel,
|
|
34
|
+
class: className = '',
|
|
35
|
+
disabled = false,
|
|
36
|
+
onclick,
|
|
37
|
+
children,
|
|
38
|
+
...restProps
|
|
39
|
+
}: {
|
|
40
|
+
/** Required — an icon-only control has no text to name it. */
|
|
41
|
+
'aria-label': string;
|
|
42
|
+
class?: string;
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
onclick?: (event: MouseEvent) => void;
|
|
45
|
+
children: Snippet;
|
|
46
|
+
} & Omit<
|
|
47
|
+
HTMLButtonAttributes,
|
|
48
|
+
'aria-label' | 'class' | 'disabled' | 'onclick' | 'children'
|
|
49
|
+
> = $props();
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<button
|
|
53
|
+
{...restProps}
|
|
54
|
+
type="button"
|
|
55
|
+
{disabled}
|
|
56
|
+
{onclick}
|
|
57
|
+
aria-label={ariaLabel}
|
|
58
|
+
class={[
|
|
59
|
+
'inline-flex items-center justify-center cursor-pointer select-none',
|
|
60
|
+
'focus-visible:outline-none',
|
|
61
|
+
'disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none',
|
|
62
|
+
className
|
|
63
|
+
]}
|
|
64
|
+
>
|
|
65
|
+
{@render children()}
|
|
66
|
+
</button>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
/** Required — an icon-only control has no text to name it. */
|
|
5
|
+
'aria-label': string;
|
|
6
|
+
class?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
onclick?: (event: MouseEvent) => void;
|
|
9
|
+
children: Snippet;
|
|
10
|
+
} & Omit<HTMLButtonAttributes, 'aria-label' | 'class' | 'disabled' | 'onclick' | 'children'>;
|
|
11
|
+
declare const CoreIconButton: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
|
+
type CoreIconButton = ReturnType<typeof CoreIconButton>;
|
|
13
|
+
export default CoreIconButton;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CoreSpinner — the bare rotating-arc SVG shared by embedding contexts (loading
|
|
3
|
+
Button, and any internal surface that needs an inline busy indicator) WITHOUT
|
|
4
|
+
pulling in the public Spinner.
|
|
5
|
+
|
|
6
|
+
INTERNAL — never exported from the package barrel, no docs/MCP entry. For a
|
|
7
|
+
configurable spinner (variants, speed, intent palette, sr-only label, unstyled
|
|
8
|
+
/ slotClasses) use the public `Spinner`; this core is deliberately fixed to the
|
|
9
|
+
default variant at 1s.
|
|
10
|
+
|
|
11
|
+
No `role` / `aria-*` / sr-only label here on purpose: the embedding context
|
|
12
|
+
owns the semantics. Button wraps this in an `aria-hidden` span and announces
|
|
13
|
+
loading via its own `aria-busy`; a `role="status"` here would nest a live
|
|
14
|
+
region inside contexts that already declare one (e.g. Toast). The geometry is
|
|
15
|
+
imported from `spinner-geometry.ts` so it can never drift from the public
|
|
16
|
+
Spinner.
|
|
17
|
+
-->
|
|
18
|
+
<script lang="ts">
|
|
19
|
+
import { SPINNER_ARC_PATH } from './spinner-geometry';
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
size = 'md',
|
|
23
|
+
class: className = ''
|
|
24
|
+
}: {
|
|
25
|
+
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
26
|
+
/** Extra classes on the wrapper span (the call site supplies visual identity). */
|
|
27
|
+
class?: string;
|
|
28
|
+
} = $props();
|
|
29
|
+
|
|
30
|
+
// Matches the public Spinner's per-size box (spinner.variants → base sizes).
|
|
31
|
+
const sizeClass = {
|
|
32
|
+
xs: 'w-4 h-4',
|
|
33
|
+
sm: 'w-5 h-5',
|
|
34
|
+
md: 'w-6 h-6',
|
|
35
|
+
lg: 'w-8 h-8'
|
|
36
|
+
};
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<span class={['inline-flex items-center justify-center text-current', sizeClass[size], className]}>
|
|
40
|
+
<svg
|
|
41
|
+
class="w-full h-full animate-spin [animation-duration:1s] motion-reduce:animate-none"
|
|
42
|
+
viewBox="0 0 24 24"
|
|
43
|
+
fill="none"
|
|
44
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
45
|
+
>
|
|
46
|
+
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" class="opacity-25" />
|
|
47
|
+
<path class="fill-current" d={SPINNER_ARC_PATH} />
|
|
48
|
+
</svg>
|
|
49
|
+
</span>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
3
|
+
/** Extra classes on the wrapper span (the call site supplies visual identity). */
|
|
4
|
+
class?: string;
|
|
5
|
+
};
|
|
6
|
+
declare const CoreSpinner: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type CoreSpinner = ReturnType<typeof CoreSpinner>;
|
|
8
|
+
export default CoreSpinner;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* single source of the spinner geometry — imported by CoreSpinner and the
|
|
3
|
+
* public Spinner. The `d` attribute of the rotating arc `<path>` in the
|
|
4
|
+
* default (spinner) variant. Kept in one place so the two SVGs never drift.
|
|
5
|
+
*/
|
|
6
|
+
export declare const SPINNER_ARC_PATH = "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* single source of the spinner geometry — imported by CoreSpinner and the
|
|
3
|
+
* public Spinner. The `d` attribute of the rotating arc `<path>` in the
|
|
4
|
+
* default (spinner) variant. Kept in one place so the two SVGs never drift.
|
|
5
|
+
*/
|
|
6
|
+
export const SPINNER_ARC_PATH = 'M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z';
|
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.
|