@urbicon-ui/blocks 6.21.0 → 6.21.2
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.variants.d.ts +132 -419
- package/dist/components/CommandPalette/commandPalette.variants.d.ts +24 -51
- package/dist/components/CompositionBar/CompositionBar.svelte +6 -1
- package/dist/components/CompositionBar/composition-bar.variants.d.ts +23 -83
- package/dist/components/CompositionBar/composition-bar.variants.js +9 -3
- package/dist/components/EmptyState/emptyState.variants.d.ts +11 -27
- package/dist/components/FileUpload/fileUpload.variants.d.ts +24 -93
- package/dist/components/Guide/GuidePanel.svelte +38 -0
- package/dist/components/Guide/guide.variants.d.ts +69 -124
- package/dist/components/Guide/guide.variants.js +6 -1
- package/dist/components/NumberInput/NumberInput.svelte +197 -0
- package/dist/components/NumberInput/NumberInput.svelte.d.ts +4 -0
- package/dist/components/NumberInput/index.d.ts +55 -0
- package/dist/components/NumberInput/index.js +1 -0
- package/dist/components/Planner/planner.variants.d.ts +23 -94
- package/dist/components/Sankey/sankey.variants.d.ts +15 -48
- package/dist/components/SidebarLayout/sidebar-layout.variants.d.ts +10 -38
- package/dist/components/SidebarLayout/sidebar-layout.variants.js +12 -2
- package/dist/components/ThemeSwitcher/themeSwitcher.variants.d.ts +9 -36
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +1 -0
- package/dist/i18n/index.d.ts +8 -372
- package/dist/internal/charts/variants.d.ts +18 -31
- package/dist/primitives/Accordion/Accordion.svelte +11 -0
- package/dist/primitives/Accordion/AccordionItem.svelte +9 -3
- package/dist/primitives/Accordion/accordion.variants.d.ts +12 -43
- package/dist/primitives/Accordion/accordion.variants.js +6 -2
- package/dist/primitives/Accordion/index.d.ts +12 -0
- package/dist/primitives/Alert/alert.variants.d.ts +21 -62
- package/dist/primitives/Avatar/avatar.variants.d.ts +18 -138
- package/dist/primitives/Badge/Badge.svelte +12 -4
- package/dist/primitives/Badge/badge.variants.d.ts +19 -92
- package/dist/primitives/Badge/badge.variants.js +11 -4
- package/dist/primitives/Badge/index.d.ts +26 -6
- package/dist/primitives/Breadcrumb/Breadcrumb.svelte +11 -6
- package/dist/primitives/Breadcrumb/breadcrumb.variants.d.ts +13 -40
- package/dist/primitives/Breadcrumb/index.d.ts +2 -2
- package/dist/primitives/Button/button.variants.d.ts +19 -98
- package/dist/primitives/Button/button.variants.js +13 -9
- package/dist/primitives/ButtonGroup/buttongroup.variants.d.ts +9 -29
- package/dist/primitives/ButtonGroup/buttongroup.variants.js +3 -1
- package/dist/primitives/Card/card.variants.d.ts +14 -81
- package/dist/primitives/Checkbox/Checkbox.svelte +3 -3
- package/dist/primitives/Checkbox/checkbox.variants.d.ts +18 -77
- package/dist/primitives/Collapsible/Collapsible.svelte +10 -0
- package/dist/primitives/Collapsible/collapsible.variants.d.ts +11 -40
- package/dist/primitives/Collapsible/collapsible.variants.js +7 -2
- package/dist/primitives/Collapsible/index.d.ts +16 -0
- package/dist/primitives/Combobox/Combobox.svelte +478 -104
- package/dist/primitives/Combobox/combobox.variants.d.ts +112 -94
- package/dist/primitives/Combobox/combobox.variants.js +94 -13
- package/dist/primitives/Combobox/index.d.ts +166 -51
- package/dist/primitives/ConfirmDialog/index.d.ts +19 -0
- package/dist/primitives/Dialog/Dialog.svelte +95 -10
- package/dist/primitives/Dialog/dialog.variants.d.ts +15 -54
- package/dist/primitives/Dialog/index.d.ts +12 -1
- package/dist/primitives/Drawer/Drawer.svelte +23 -11
- package/dist/primitives/Drawer/drawer.variants.d.ts +22 -49
- package/dist/primitives/Drawer/drawer.variants.js +45 -10
- package/dist/primitives/Drawer/index.d.ts +13 -4
- package/dist/primitives/FormField/FormField.svelte +6 -6
- package/dist/primitives/FormField/index.d.ts +13 -4
- package/dist/primitives/Input/Input.svelte +8 -4
- package/dist/primitives/Input/input.variants.d.ts +32 -139
- package/dist/primitives/Input/input.variants.js +12 -10
- package/dist/primitives/JourneyTimeline/journey-timeline.variants.d.ts +55 -197
- package/dist/primitives/JourneyTimeline/journey-timeline.variants.js +4 -1
- package/dist/primitives/Menu/Menu.svelte +66 -4
- package/dist/primitives/Menu/index.d.ts +25 -0
- package/dist/primitives/Menu/menu.variants.d.ts +29 -93
- package/dist/primitives/Pagination/Pagination.svelte +28 -13
- package/dist/primitives/Pagination/index.d.ts +44 -0
- package/dist/primitives/Pagination/pagination.variants.d.ts +16 -54
- package/dist/primitives/Pagination/pagination.variants.js +16 -14
- package/dist/primitives/Popover/Popover.svelte +11 -1
- package/dist/primitives/Popover/index.d.ts +3 -3
- package/dist/primitives/Popover/popover.variants.d.ts +4 -2
- package/dist/primitives/Progress/progress.variants.d.ts +19 -83
- package/dist/primitives/Progress/progress.variants.js +2 -1
- package/dist/primitives/RadioGroup/RadioGroup.svelte +3 -3
- package/dist/primitives/RadioGroup/radioGroup.variants.d.ts +28 -109
- package/dist/primitives/SegmentGroup/SegmentGroup.svelte +14 -6
- package/dist/primitives/SegmentGroup/segmentgroup.variants.d.ts +12 -53
- package/dist/primitives/SegmentGroup/segmentgroup.variants.js +10 -10
- package/dist/primitives/Select/Select.svelte +21 -11
- package/dist/primitives/Select/index.d.ts +7 -0
- package/dist/primitives/Select/select.variants.d.ts +61 -150
- package/dist/primitives/Select/select.variants.js +24 -4
- package/dist/primitives/Separator/separator.variants.d.ts +4 -2
- package/dist/primitives/Sidebar/Sidebar.svelte +10 -15
- package/dist/primitives/Sidebar/sidebar.variants.d.ts +11 -29
- package/dist/primitives/Skeleton/skeleton.variants.d.ts +9 -36
- package/dist/primitives/Slider/Slider.svelte +4 -8
- package/dist/primitives/Slider/slider.variants.d.ts +37 -136
- package/dist/primitives/Slider/slider.variants.js +8 -5
- package/dist/primitives/Spinner/spinner.variants.d.ts +38 -135
- package/dist/primitives/Stepper/stepper.variants.d.ts +32 -130
- package/dist/primitives/Tab/Tab.svelte +14 -6
- package/dist/primitives/Tab/tab.variants.d.ts +17 -78
- package/dist/primitives/Textarea/Textarea.svelte +4 -5
- package/dist/primitives/Textarea/textarea.variants.d.ts +33 -118
- package/dist/primitives/Textarea/textarea.variants.js +17 -12
- package/dist/primitives/Toast/Toaster.svelte +58 -5
- package/dist/primitives/Toast/index.d.ts +39 -0
- package/dist/primitives/Toast/toast.store.svelte.d.ts +25 -1
- package/dist/primitives/Toast/toast.store.svelte.js +91 -1
- package/dist/primitives/Toast/toast.variants.d.ts +40 -78
- package/dist/primitives/Toast/toast.variants.js +15 -0
- package/dist/primitives/Toggle/Toggle.svelte +17 -3
- package/dist/primitives/Toggle/index.d.ts +8 -2
- package/dist/primitives/Toggle/toggle.variants.d.ts +24 -71
- package/dist/primitives/Toggle/toggle.variants.js +26 -0
- package/dist/primitives/Toolbar/toolbar.variants.d.ts +9 -60
- package/dist/primitives/Tooltip/Tooltip.svelte +58 -21
- package/dist/primitives/Tooltip/Tooltip.svelte.d.ts +1 -1
- package/dist/primitives/Tooltip/index.d.ts +45 -3
- package/dist/primitives/Tooltip/tooltip.variants.d.ts +14 -53
- package/dist/primitives/Tooltip/tooltip.variants.js +9 -3
- package/dist/primitives/index.d.ts +3 -3
- package/dist/style/foundation.css +4 -1
- package/dist/style/interaction.css +39 -11
- package/dist/style/semantic.css +2 -2
- package/dist/style/themes/index.css +1 -1
- package/dist/translations/de.d.ts +3 -0
- package/dist/translations/de.js +4 -1
- package/dist/translations/en.d.ts +3 -0
- package/dist/translations/en.js +4 -1
- package/dist/utils/guide.svelte.d.ts +8 -2
- package/dist/utils/guide.svelte.js +86 -5
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +5 -0
- package/dist/utils/overlay.d.ts +33 -4
- package/dist/utils/overlay.js +61 -18
- package/dist/utils/roving.d.ts +15 -0
- package/dist/utils/roving.js +44 -0
- package/dist/utils/use-floating-panel.svelte.d.ts +4 -1
- package/dist/utils/use-floating-panel.svelte.js +23 -1
- package/dist/utils/use-form-field.svelte.d.ts +9 -9
- package/dist/utils/use-form-field.svelte.js +12 -12
- package/dist/utils/variants.d.ts +60 -16
- package/dist/utils/variants.js +424 -123
- package/package.json +9 -6
|
@@ -156,6 +156,22 @@ export class GuideController {
|
|
|
156
156
|
#targets = new SvelteMap();
|
|
157
157
|
/** Persisted "seen" ids. */
|
|
158
158
|
#seen;
|
|
159
|
+
/** False only while an async `storage.load()` is still pending. */
|
|
160
|
+
#seenLoaded = true;
|
|
161
|
+
/**
|
|
162
|
+
* Set when the user writes (markSeen/resetSeen) while an async load is still
|
|
163
|
+
* pending, so the load's `.then` persists the merged union once — writing
|
|
164
|
+
* mid-load directly would clobber the remote store before its ids are merged in.
|
|
165
|
+
*/
|
|
166
|
+
#pendingSeenWrite = false;
|
|
167
|
+
/**
|
|
168
|
+
* Ids the user forgot during the load window, so the merge doesn't resurrect
|
|
169
|
+
* them from the freshly-loaded remote set. A plain `Set` — pure bookkeeping,
|
|
170
|
+
* never rendered, so the `SvelteSet` rule doesn't apply.
|
|
171
|
+
*/
|
|
172
|
+
#pendingRemovals = new Set();
|
|
173
|
+
/** `resetSeen()` (clear-all) during the load window: drop the remote set entirely. */
|
|
174
|
+
#pendingClearAll = false;
|
|
159
175
|
#storage;
|
|
160
176
|
#overlayStack;
|
|
161
177
|
#dev;
|
|
@@ -209,7 +225,58 @@ export class GuideController {
|
|
|
209
225
|
// `?? false` keeps `#dev` a strict boolean and means non-Vite consumers
|
|
210
226
|
// simply get no dev warnings (rather than a crash).
|
|
211
227
|
this.#dev = options.dev ?? import.meta.env?.DEV ?? false;
|
|
212
|
-
|
|
228
|
+
// Sync adapters (localStorage) seed the set immediately. Async adapters
|
|
229
|
+
// (DB/remote) start empty and get their ids merged in when the promise
|
|
230
|
+
// settles — union, not replace, so anything marked seen in the meantime
|
|
231
|
+
// survives. A rejected load is swallowed (dev-warned): the tour just stays
|
|
232
|
+
// "unseen", which fails open rather than hiding help behind a broken store.
|
|
233
|
+
const loaded = this.#storage.load();
|
|
234
|
+
if (Array.isArray(loaded)) {
|
|
235
|
+
this.#seen = new SvelteSet(loaded);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
this.#seen = new SvelteSet();
|
|
239
|
+
this.#seenLoaded = false;
|
|
240
|
+
loaded
|
|
241
|
+
.then((ids) => {
|
|
242
|
+
// Merge the loaded set in, but honour writes made during the window:
|
|
243
|
+
// skip everything if the user cleared all, and don't resurrect ids the
|
|
244
|
+
// user explicitly forgot. Adds are safe (union is idempotent).
|
|
245
|
+
if (!this.#pendingClearAll) {
|
|
246
|
+
for (const id of ids) {
|
|
247
|
+
if (!this.#pendingRemovals.has(id))
|
|
248
|
+
this.#seen.add(id);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
this.#seenLoaded = true;
|
|
252
|
+
// Reconcile persistence: if the user wrote during the window, the store
|
|
253
|
+
// still holds the pre-merge state (we deliberately didn't save then), so
|
|
254
|
+
// persist the union now. Nothing wrote → leave the store untouched (no
|
|
255
|
+
// write-amplification of a remote we just read).
|
|
256
|
+
if (this.#pendingSeenWrite)
|
|
257
|
+
this.#storage.save([...this.#seen]);
|
|
258
|
+
})
|
|
259
|
+
.catch((err) => {
|
|
260
|
+
// Read failed: fail open (tour stays "unseen"). We don't persist the
|
|
261
|
+
// window's in-memory writes here — the store is in an unknown state, and
|
|
262
|
+
// blindly saving could destroy data we simply couldn't read ("read
|
|
263
|
+
// tolerant, write strict"). Later writes persist normally.
|
|
264
|
+
this.#seenLoaded = true;
|
|
265
|
+
if (this.#dev)
|
|
266
|
+
console.warn('[Guide] storage.load() rejected:', err);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Persist the seen-set — unless an async load is still pending, in which case a
|
|
272
|
+
* direct save would clobber the not-yet-loaded remote store. Defer to the load's
|
|
273
|
+
* `.then` reconciliation instead (see the constructor).
|
|
274
|
+
*/
|
|
275
|
+
#persistSeen() {
|
|
276
|
+
if (this.#seenLoaded)
|
|
277
|
+
this.#storage.save([...this.#seen]);
|
|
278
|
+
else
|
|
279
|
+
this.#pendingSeenWrite = true;
|
|
213
280
|
}
|
|
214
281
|
// ─── Target registry ──────────────────────────────────────────────────────
|
|
215
282
|
/**
|
|
@@ -738,15 +805,29 @@ export class GuideController {
|
|
|
738
805
|
if (this.#seen.has(id))
|
|
739
806
|
return;
|
|
740
807
|
this.#seen.add(id);
|
|
741
|
-
|
|
808
|
+
// Re-seeing an id undoes a forget that's still pending reconciliation.
|
|
809
|
+
if (!this.#seenLoaded)
|
|
810
|
+
this.#pendingRemovals.delete(id);
|
|
811
|
+
this.#persistSeen();
|
|
742
812
|
}
|
|
743
813
|
/** Forget one id, or all of them when called without an argument. */
|
|
744
814
|
resetSeen(id) {
|
|
745
|
-
if (id === undefined)
|
|
815
|
+
if (id === undefined) {
|
|
746
816
|
this.#seen.clear();
|
|
747
|
-
|
|
817
|
+
// Clear-all during a pending load must survive the merge: drop the whole
|
|
818
|
+
// remote set, superseding any earlier pending removals.
|
|
819
|
+
if (!this.#seenLoaded) {
|
|
820
|
+
this.#pendingClearAll = true;
|
|
821
|
+
this.#pendingRemovals.clear();
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
748
825
|
this.#seen.delete(id);
|
|
749
|
-
|
|
826
|
+
// Record the forget so the pending-load merge doesn't resurrect it.
|
|
827
|
+
if (!this.#seenLoaded)
|
|
828
|
+
this.#pendingRemovals.add(id);
|
|
829
|
+
}
|
|
830
|
+
this.#persistSeen();
|
|
750
831
|
}
|
|
751
832
|
/** Snapshot of all seen ids. */
|
|
752
833
|
get seenIds() {
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -9,8 +9,9 @@ export { createOptionalContext } from './optional-context.js';
|
|
|
9
9
|
export { overlayStack } from './overlay-stack.svelte.js';
|
|
10
10
|
export { type EasingFn, getOverlayMotion, OVERLAY_MOTION_DEFAULTS, type OverlayMotion, type OverlayMotionOverride } from './overlay-tokens.js';
|
|
11
11
|
export * from './persistent-state.svelte.js';
|
|
12
|
+
export { edgeEnabledIndex, nextEnabledIndex } from './roving.js';
|
|
12
13
|
export { getTierContext, type InteractiveTier, setTierContext, type TierContext } from './tier-context.js';
|
|
13
14
|
export * from './types.js';
|
|
14
15
|
export { type FloatingPanelOptions, type FloatingPanelState, floatingPanelHidden, useFloatingPanel } from './use-floating-panel.svelte.js';
|
|
15
16
|
export { computeFormFieldAria, type UseFormFieldInputs, type UseFormFieldReturn, useFormField } from './use-form-field.svelte.js';
|
|
16
|
-
export { cx, type TVProps, tv, type VariantProps } from './variants.js';
|
|
17
|
+
export { cx, type SlotNames, type TVConfig, type TVProps, tv, type VariantProps } from './variants.js';
|
package/dist/utils/index.js
CHANGED
|
@@ -9,8 +9,13 @@ export { createOptionalContext } from './optional-context.js';
|
|
|
9
9
|
export { overlayStack } from './overlay-stack.svelte.js';
|
|
10
10
|
export { getOverlayMotion, OVERLAY_MOTION_DEFAULTS } from './overlay-tokens.js';
|
|
11
11
|
export * from './persistent-state.svelte.js';
|
|
12
|
+
export { edgeEnabledIndex, nextEnabledIndex } from './roving.js';
|
|
12
13
|
export { getTierContext, setTierContext } from './tier-context.js';
|
|
13
14
|
export * from './types.js';
|
|
14
15
|
export { floatingPanelHidden, useFloatingPanel } from './use-floating-panel.svelte.js';
|
|
15
16
|
export { computeFormFieldAria, useFormField } from './use-form-field.svelte.js';
|
|
17
|
+
// TVConfig/SlotNames must be reachable from the package root: without them a
|
|
18
|
+
// consuming package's `tv()` result is not *nameable* during its declaration
|
|
19
|
+
// emit (TS2883) and svelte-package silently drops that file's .d.ts — every
|
|
20
|
+
// `*Props extends …VariantProps` consumer then loses all variant props.
|
|
16
21
|
export { cx, tv } from './variants.js';
|
package/dist/utils/overlay.d.ts
CHANGED
|
@@ -1,11 +1,40 @@
|
|
|
1
1
|
export declare const FOCUSABLE_SELECTOR: string;
|
|
2
2
|
export declare function getFocusableElements(container: HTMLElement | undefined): HTMLElement[];
|
|
3
3
|
export declare function trapFocus(event: KeyboardEvent, container: HTMLElement | undefined): void;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Acquire a shared body-scroll lock; returns the matching release function.
|
|
6
|
+
*
|
|
7
|
+
* The count is module-global — the body stays `overflow: hidden` while at
|
|
8
|
+
* least one overlay (Dialog, Drawer, mobile Sidebar) holds a lock. Ownership
|
|
9
|
+
* is per-acquisition: only the returned release can decrement this holder's
|
|
10
|
+
* share, and it is idempotent, so calling it from multiple teardown paths
|
|
11
|
+
* (outro end + onDestroy safety net) releases exactly once and can never free
|
|
12
|
+
* a lock held by another overlay instance. There is deliberately no standalone
|
|
13
|
+
* `unlock` export — an unpaired decrement is exactly the bug this design
|
|
14
|
+
* removes.
|
|
15
|
+
*/
|
|
16
|
+
export declare function lockBodyScroll(): () => void;
|
|
6
17
|
export declare function focusFirstElement(container: HTMLElement | undefined): void;
|
|
7
|
-
|
|
8
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Promote a `<dialog>` to the top layer and move focus into its panel.
|
|
20
|
+
*
|
|
21
|
+
* **Precondition: `dialogEl` must already be bound.** Call this only once the
|
|
22
|
+
* `bind:this` target exists — e.g. from `tick().then(...)` in the opener effect,
|
|
23
|
+
* after the `{#if isVisible}` block has rendered. An earlier version deferred the
|
|
24
|
+
* `showModal()` call with its own `tick()` in the hope of waiting for the bind,
|
|
25
|
+
* but the ref is captured by value: if the caller passes an as-yet-unbound
|
|
26
|
+
* `dialogEl`, the `tick()` still runs `undefined?.showModal()` — a silent no-op
|
|
27
|
+
* that left Dialog/Drawer never actually modal. Sequencing is the caller's job;
|
|
28
|
+
* the top-layer promotion here is synchronous (only the focus move inside
|
|
29
|
+
* `focusFirstElement` defers by a tick).
|
|
30
|
+
*
|
|
31
|
+
* Acquires a body-scroll lock and returns its release function — the caller
|
|
32
|
+
* owns the lock and must call the release on every teardown path (outro end
|
|
33
|
+
* and destroy; it is idempotent, so overlapping paths are safe). Pass it back
|
|
34
|
+
* through {@link closeDialogModal} on the regular close path.
|
|
35
|
+
*/
|
|
36
|
+
export declare function showDialogModal(dialogEl: HTMLDialogElement | undefined, panelEl: HTMLElement | undefined): () => void;
|
|
37
|
+
export declare function closeDialogModal(dialogEl: HTMLDialogElement | undefined, previouslyFocused: HTMLElement | null, releaseScrollLock?: () => void): void;
|
|
9
38
|
/**
|
|
10
39
|
* True when `el` is a descendant of an OPEN *modal* `<dialog>` — one opened via
|
|
11
40
|
* `showModal()`, which matches `:modal` and occupies the browser top layer.
|
package/dist/utils/overlay.js
CHANGED
|
@@ -37,23 +37,37 @@ export function trapFocus(event, container) {
|
|
|
37
37
|
}
|
|
38
38
|
let bodyScrollLockCount = 0;
|
|
39
39
|
let savedBodyOverflow;
|
|
40
|
+
/**
|
|
41
|
+
* Acquire a shared body-scroll lock; returns the matching release function.
|
|
42
|
+
*
|
|
43
|
+
* The count is module-global — the body stays `overflow: hidden` while at
|
|
44
|
+
* least one overlay (Dialog, Drawer, mobile Sidebar) holds a lock. Ownership
|
|
45
|
+
* is per-acquisition: only the returned release can decrement this holder's
|
|
46
|
+
* share, and it is idempotent, so calling it from multiple teardown paths
|
|
47
|
+
* (outro end + onDestroy safety net) releases exactly once and can never free
|
|
48
|
+
* a lock held by another overlay instance. There is deliberately no standalone
|
|
49
|
+
* `unlock` export — an unpaired decrement is exactly the bug this design
|
|
50
|
+
* removes.
|
|
51
|
+
*/
|
|
40
52
|
export function lockBodyScroll() {
|
|
41
53
|
if (!isBrowser)
|
|
42
|
-
return;
|
|
54
|
+
return () => { };
|
|
43
55
|
if (bodyScrollLockCount === 0) {
|
|
44
56
|
savedBodyOverflow = document.body.style.overflow;
|
|
45
57
|
document.body.style.overflow = 'hidden';
|
|
46
58
|
}
|
|
47
59
|
bodyScrollLockCount += 1;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
let released = false;
|
|
61
|
+
return () => {
|
|
62
|
+
if (released)
|
|
63
|
+
return;
|
|
64
|
+
released = true;
|
|
65
|
+
bodyScrollLockCount -= 1;
|
|
66
|
+
if (bodyScrollLockCount === 0) {
|
|
67
|
+
document.body.style.overflow = savedBodyOverflow ?? '';
|
|
68
|
+
savedBodyOverflow = undefined;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
57
71
|
}
|
|
58
72
|
export function focusFirstElement(container) {
|
|
59
73
|
if (!isBrowser)
|
|
@@ -64,21 +78,50 @@ export function focusFirstElement(container) {
|
|
|
64
78
|
focusable[0].focus();
|
|
65
79
|
});
|
|
66
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Promote a `<dialog>` to the top layer and move focus into its panel.
|
|
83
|
+
*
|
|
84
|
+
* **Precondition: `dialogEl` must already be bound.** Call this only once the
|
|
85
|
+
* `bind:this` target exists — e.g. from `tick().then(...)` in the opener effect,
|
|
86
|
+
* after the `{#if isVisible}` block has rendered. An earlier version deferred the
|
|
87
|
+
* `showModal()` call with its own `tick()` in the hope of waiting for the bind,
|
|
88
|
+
* but the ref is captured by value: if the caller passes an as-yet-unbound
|
|
89
|
+
* `dialogEl`, the `tick()` still runs `undefined?.showModal()` — a silent no-op
|
|
90
|
+
* that left Dialog/Drawer never actually modal. Sequencing is the caller's job;
|
|
91
|
+
* the top-layer promotion here is synchronous (only the focus move inside
|
|
92
|
+
* `focusFirstElement` defers by a tick).
|
|
93
|
+
*
|
|
94
|
+
* Acquires a body-scroll lock and returns its release function — the caller
|
|
95
|
+
* owns the lock and must call the release on every teardown path (outro end
|
|
96
|
+
* and destroy; it is idempotent, so overlapping paths are safe). Pass it back
|
|
97
|
+
* through {@link closeDialogModal} on the regular close path.
|
|
98
|
+
*/
|
|
67
99
|
export function showDialogModal(dialogEl, panelEl) {
|
|
68
100
|
if (!isBrowser)
|
|
69
|
-
return;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
101
|
+
return () => { };
|
|
102
|
+
if (!dialogEl) {
|
|
103
|
+
// Precondition violation: nothing to promote, so also take no lock — a
|
|
104
|
+
// lock acquired here would scroll-freeze the page under a non-modal
|
|
105
|
+
// overlay even if the caller dutifully releases the returned handle.
|
|
106
|
+
if (import.meta.env?.DEV) {
|
|
107
|
+
console.warn('[blocks] showDialogModal called before the <dialog> ref was bound — ' +
|
|
108
|
+
'the overlay will not enter the top layer (no :modal, no initial focus, ' +
|
|
109
|
+
'no scroll lock). Defer the call until after the render that binds it ' +
|
|
110
|
+
'(e.g. tick().then(...)).');
|
|
111
|
+
}
|
|
112
|
+
return () => { };
|
|
113
|
+
}
|
|
114
|
+
const releaseScrollLock = lockBodyScroll();
|
|
115
|
+
dialogEl.showModal();
|
|
116
|
+
focusFirstElement(panelEl);
|
|
117
|
+
return releaseScrollLock;
|
|
75
118
|
}
|
|
76
|
-
export function closeDialogModal(dialogEl, previouslyFocused) {
|
|
119
|
+
export function closeDialogModal(dialogEl, previouslyFocused, releaseScrollLock) {
|
|
77
120
|
if (!isBrowser)
|
|
78
121
|
return;
|
|
79
122
|
if (dialogEl?.open)
|
|
80
123
|
dialogEl.close();
|
|
81
|
-
|
|
124
|
+
releaseScrollLock?.();
|
|
82
125
|
previouslyFocused?.focus();
|
|
83
126
|
}
|
|
84
127
|
/**
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The next enabled index walking `dir` (+1 / -1) from `from`, wrapping around.
|
|
3
|
+
*
|
|
4
|
+
* - `from` may be -1 (nothing active yet): the walk anchors just off the leading
|
|
5
|
+
* edge, so the first probe is index 0 for `dir` +1 and the last index for -1.
|
|
6
|
+
* - Returns `from` when it is a valid index but every *other* item is disabled
|
|
7
|
+
* (nowhere to move — the caller's `next !== from` guard makes it a no-op).
|
|
8
|
+
* - Returns -1 when there is no enabled item at all (or `length` is 0).
|
|
9
|
+
*/
|
|
10
|
+
export declare function nextEnabledIndex(length: number, from: number, dir: 1 | -1, isDisabled: (index: number) => boolean): number;
|
|
11
|
+
/**
|
|
12
|
+
* The first enabled index scanning from an edge: `dir` +1 from the start (Home),
|
|
13
|
+
* -1 from the end (End). Returns -1 when every item is disabled.
|
|
14
|
+
*/
|
|
15
|
+
export declare function edgeEnabledIndex(length: number, dir: 1 | -1, isDisabled: (index: number) => boolean): number;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Roving-focus index helpers for keyboard navigation across a set of items where
|
|
2
|
+
// some may be disabled. Shared by the button-based composite widgets (Tab,
|
|
3
|
+
// SegmentGroup): a disabled item there renders a native `<button disabled>`,
|
|
4
|
+
// which can't hold focus, so navigation must step over it — otherwise selection
|
|
5
|
+
// strands on an unfocusable item (aria state set, focus stuck on the previous
|
|
6
|
+
// one). RadioGroup doesn't use these: its items are native radios, so it filters
|
|
7
|
+
// with a `:not(:disabled)` DOM query the browser understands directly.
|
|
8
|
+
//
|
|
9
|
+
// Pure index math — the caller supplies `isDisabled(index)`; nothing here reads
|
|
10
|
+
// the DOM, so the edge cases (wrap-around, all-disabled, nothing-active) are
|
|
11
|
+
// unit-tested in isolation.
|
|
12
|
+
/**
|
|
13
|
+
* The next enabled index walking `dir` (+1 / -1) from `from`, wrapping around.
|
|
14
|
+
*
|
|
15
|
+
* - `from` may be -1 (nothing active yet): the walk anchors just off the leading
|
|
16
|
+
* edge, so the first probe is index 0 for `dir` +1 and the last index for -1.
|
|
17
|
+
* - Returns `from` when it is a valid index but every *other* item is disabled
|
|
18
|
+
* (nowhere to move — the caller's `next !== from` guard makes it a no-op).
|
|
19
|
+
* - Returns -1 when there is no enabled item at all (or `length` is 0).
|
|
20
|
+
*/
|
|
21
|
+
export function nextEnabledIndex(length, from, dir, isDisabled) {
|
|
22
|
+
if (length <= 0)
|
|
23
|
+
return -1;
|
|
24
|
+
const inRange = from >= 0 && from < length;
|
|
25
|
+
const anchored = inRange ? from : dir === 1 ? -1 : length;
|
|
26
|
+
for (let i = 1; i <= length; i++) {
|
|
27
|
+
const idx = (((anchored + dir * i) % length) + length) % length;
|
|
28
|
+
if (!isDisabled(idx))
|
|
29
|
+
return idx;
|
|
30
|
+
}
|
|
31
|
+
return inRange ? from : -1;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The first enabled index scanning from an edge: `dir` +1 from the start (Home),
|
|
35
|
+
* -1 from the end (End). Returns -1 when every item is disabled.
|
|
36
|
+
*/
|
|
37
|
+
export function edgeEnabledIndex(length, dir, isDisabled) {
|
|
38
|
+
for (let i = 0; i < length; i++) {
|
|
39
|
+
const idx = dir === 1 ? i : length - 1 - i;
|
|
40
|
+
if (!isDisabled(idx))
|
|
41
|
+
return idx;
|
|
42
|
+
}
|
|
43
|
+
return -1;
|
|
44
|
+
}
|
|
@@ -65,7 +65,10 @@ export interface FloatingPanelState {
|
|
|
65
65
|
* `<dialog>`, where a second top-layer element is invisible on iOS/WebKit
|
|
66
66
|
* (Codeberg #23) — the panel then renders `position: fixed` in place. Callers
|
|
67
67
|
* mirror the returned `topLayer`/`strategy` in their markup via per-property
|
|
68
|
-
* `style:` directives and {@link floatingPanelHidden}.
|
|
68
|
+
* `style:` directives and {@link floatingPanelHidden}. Stacking needs no
|
|
69
|
+
* caller wiring: in the two in-place modes the helper itself stamps the
|
|
70
|
+
* panel's `z-index` imperatively (see the `zIndex` derivation below), so an
|
|
71
|
+
* in-place panel always paints above later positioned siblings.
|
|
69
72
|
*
|
|
70
73
|
* The Floating-UI `size` middleware feeds the room actually left between the
|
|
71
74
|
* anchor and the (visual) viewport edge into `--blocks-overlay-available-height`.
|
|
@@ -17,7 +17,10 @@ import { isAnchoredInModalDialog } from './overlay.js';
|
|
|
17
17
|
* `<dialog>`, where a second top-layer element is invisible on iOS/WebKit
|
|
18
18
|
* (Codeberg #23) — the panel then renders `position: fixed` in place. Callers
|
|
19
19
|
* mirror the returned `topLayer`/`strategy` in their markup via per-property
|
|
20
|
-
* `style:` directives and {@link floatingPanelHidden}.
|
|
20
|
+
* `style:` directives and {@link floatingPanelHidden}. Stacking needs no
|
|
21
|
+
* caller wiring: in the two in-place modes the helper itself stamps the
|
|
22
|
+
* panel's `z-index` imperatively (see the `zIndex` derivation below), so an
|
|
23
|
+
* in-place panel always paints above later positioned siblings.
|
|
21
24
|
*
|
|
22
25
|
* The Floating-UI `size` middleware feeds the room actually left between the
|
|
23
26
|
* anchor and the (visual) viewport edge into `--blocks-overlay-available-height`.
|
|
@@ -39,12 +42,29 @@ export function useFloatingPanel(opts) {
|
|
|
39
42
|
// inline mode (`portal=false`) falls back to `absolute`.
|
|
40
43
|
const topLayer = $derived(portalHint && !inModalDialog);
|
|
41
44
|
const strategy = $derived(portalHint ? 'fixed' : 'absolute');
|
|
45
|
+
// z-index for the two in-place modes; the top layer needs none (it stacks by
|
|
46
|
+
// insertion order and ignores z-index). An in-place panel stacks at `auto`,
|
|
47
|
+
// so any positioned element AFTER it in the DOM (Button/Input/Select roots
|
|
48
|
+
// are all `position: relative`) would paint over the open panel in document
|
|
49
|
+
// order — the table FilterMenu overdraw. Stamped imperatively on the show
|
|
50
|
+
// path alongside Floating UI's `left`/`top` writes, never via a `style:`
|
|
51
|
+
// directive: Svelte reserves a directive's property even while its value is
|
|
52
|
+
// null, which would strip a consumer-supplied `style="z-index: …"` in
|
|
53
|
+
// top-layer mode. Set-only (no unset on hide/mode-flip — a leftover value is
|
|
54
|
+
// inert in the top layer and wanted in place). The literal fallback keeps
|
|
55
|
+
// the panel stacked when a consumer's Tailwind build prunes the unused
|
|
56
|
+
// `@theme` token (an unresolvable var() computes to `auto` and would
|
|
57
|
+
// silently revert the fix). Consumers override via an important utility on
|
|
58
|
+
// the panel slot (e.g. `slotClasses={{ listbox: 'z-20!' }}`) — plain
|
|
59
|
+
// utilities lose to this inline style by design, like `position`/`display`.
|
|
60
|
+
const zIndex = $derived(topLayer ? null : 'var(--z-dropdown, 1150)');
|
|
42
61
|
$effect(() => {
|
|
43
62
|
const ref = opts.reference();
|
|
44
63
|
const floating = opts.floating();
|
|
45
64
|
const isOpen = opts.open();
|
|
46
65
|
const useTopLayer = topLayer;
|
|
47
66
|
const positionStrategy = strategy;
|
|
67
|
+
const zIndexValue = zIndex;
|
|
48
68
|
const placement = opts.placement?.() ?? 'bottom-start';
|
|
49
69
|
const offsetDistance = opts.offsetDistance?.() ?? 4;
|
|
50
70
|
const shiftPadding = opts.shiftPadding?.() ?? 8;
|
|
@@ -80,6 +100,8 @@ export function useFloatingPanel(opts) {
|
|
|
80
100
|
}
|
|
81
101
|
return;
|
|
82
102
|
}
|
|
103
|
+
if (zIndexValue)
|
|
104
|
+
floating.style.zIndex = zIndexValue;
|
|
83
105
|
if (useTopLayer && !floating.matches(':popover-open')) {
|
|
84
106
|
try {
|
|
85
107
|
floating.showPopover();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `useFormField` — Single source of truth for the ARIA wiring that every
|
|
3
|
-
* Form primitive duplicates: error/
|
|
4
|
-
* `aria-invalid`, and the mutually-exclusive error-over-
|
|
3
|
+
* Form primitive duplicates: error/helper id derivation, `aria-describedby`,
|
|
4
|
+
* `aria-invalid`, and the mutually-exclusive error-over-helper convention.
|
|
5
5
|
*
|
|
6
|
-
* Layout (label position, error/
|
|
6
|
+
* Layout (label position, error/helper placement, required marker) stays
|
|
7
7
|
* component-specific because real Form primitives differ in shape — Input
|
|
8
8
|
* stacks the label on top, Checkbox/Toggle put it inline, RadioGroup and
|
|
9
9
|
* SegmentGroup have a group caption + per-item labels. The hook does not
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
* ```svelte
|
|
24
24
|
* <script lang="ts">
|
|
25
25
|
* import { useFormField } from './index.js';
|
|
26
|
-
* let { id: idProp,
|
|
26
|
+
* let { id: idProp, helper, error, required, disabled, label } = $props();
|
|
27
27
|
* const propsId = $props.id();
|
|
28
28
|
* const ff = useFormField(() => ({
|
|
29
29
|
* fieldId: idProp ?? `field-${propsId}`,
|
|
30
|
-
*
|
|
30
|
+
* helper,
|
|
31
31
|
* error,
|
|
32
32
|
* required,
|
|
33
33
|
* disabled
|
|
@@ -47,8 +47,8 @@ export interface UseFormFieldInputs {
|
|
|
47
47
|
/** The DOM id to apply to the field element. Compute once in the caller via `$props.id()`. */
|
|
48
48
|
fieldId: string;
|
|
49
49
|
/** Helper text shown below the field when no error is set. */
|
|
50
|
-
|
|
51
|
-
/** Error message — when truthy, replaces `
|
|
50
|
+
helper?: string;
|
|
51
|
+
/** Error message — when truthy, replaces `helper` and flags the field invalid. */
|
|
52
52
|
error?: string;
|
|
53
53
|
/** Required flag — exposed verbatim for the caller's label markup. */
|
|
54
54
|
required?: boolean;
|
|
@@ -60,8 +60,8 @@ export interface UseFormFieldReturn {
|
|
|
60
60
|
readonly fieldId: string;
|
|
61
61
|
/** Id of the error description, or undefined when no error is set. */
|
|
62
62
|
readonly errorId: string | undefined;
|
|
63
|
-
/** Id of the
|
|
64
|
-
readonly
|
|
63
|
+
/** Id of the helper description, or undefined when error is set or no helper is given. */
|
|
64
|
+
readonly helperId: string | undefined;
|
|
65
65
|
/** Concatenation suitable for `aria-describedby` (`undefined` when both are unset). */
|
|
66
66
|
readonly describedBy: string | undefined;
|
|
67
67
|
/** `true` iff `error` is truthy. Use for `aria-invalid={ff.invalid || undefined}`. */
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `useFormField` — Single source of truth for the ARIA wiring that every
|
|
3
|
-
* Form primitive duplicates: error/
|
|
4
|
-
* `aria-invalid`, and the mutually-exclusive error-over-
|
|
3
|
+
* Form primitive duplicates: error/helper id derivation, `aria-describedby`,
|
|
4
|
+
* `aria-invalid`, and the mutually-exclusive error-over-helper convention.
|
|
5
5
|
*
|
|
6
|
-
* Layout (label position, error/
|
|
6
|
+
* Layout (label position, error/helper placement, required marker) stays
|
|
7
7
|
* component-specific because real Form primitives differ in shape — Input
|
|
8
8
|
* stacks the label on top, Checkbox/Toggle put it inline, RadioGroup and
|
|
9
9
|
* SegmentGroup have a group caption + per-item labels. The hook does not
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
* ```svelte
|
|
24
24
|
* <script lang="ts">
|
|
25
25
|
* import { useFormField } from './index.js';
|
|
26
|
-
* let { id: idProp,
|
|
26
|
+
* let { id: idProp, helper, error, required, disabled, label } = $props();
|
|
27
27
|
* const propsId = $props.id();
|
|
28
28
|
* const ff = useFormField(() => ({
|
|
29
29
|
* fieldId: idProp ?? `field-${propsId}`,
|
|
30
|
-
*
|
|
30
|
+
* helper,
|
|
31
31
|
* error,
|
|
32
32
|
* required,
|
|
33
33
|
* disabled
|
|
@@ -51,20 +51,20 @@
|
|
|
51
51
|
export function computeFormFieldAria(input) {
|
|
52
52
|
const fieldId = input.fieldId;
|
|
53
53
|
const errorId = input.error ? `${fieldId}-error` : undefined;
|
|
54
|
-
//
|
|
54
|
+
// Helper is suppressed when an error is present — the error message is
|
|
55
55
|
// the more important description at that point. This is the same
|
|
56
56
|
// exclusive convention as Material / Carbon / Polaris.
|
|
57
|
-
const
|
|
58
|
-
// Error first,
|
|
57
|
+
const helperId = !input.error && input.helper ? `${fieldId}-helper` : undefined;
|
|
58
|
+
// Error first, helper second — error is the more semantically urgent
|
|
59
59
|
// description and should reach assistive tech first.
|
|
60
|
-
const describedBy = [errorId,
|
|
60
|
+
const describedBy = [errorId, helperId].filter(Boolean).join(' ') || undefined;
|
|
61
61
|
const invalid = !!input.error;
|
|
62
62
|
const required = !!input.required;
|
|
63
63
|
const disabled = !!input.disabled;
|
|
64
64
|
return {
|
|
65
65
|
fieldId,
|
|
66
66
|
errorId,
|
|
67
|
-
|
|
67
|
+
helperId,
|
|
68
68
|
describedBy,
|
|
69
69
|
invalid,
|
|
70
70
|
required,
|
|
@@ -80,8 +80,8 @@ export function useFormField(inputs) {
|
|
|
80
80
|
get errorId() {
|
|
81
81
|
return result.errorId;
|
|
82
82
|
},
|
|
83
|
-
get
|
|
84
|
-
return result.
|
|
83
|
+
get helperId() {
|
|
84
|
+
return result.helperId;
|
|
85
85
|
},
|
|
86
86
|
get describedBy() {
|
|
87
87
|
return result.describedBy;
|
package/dist/utils/variants.d.ts
CHANGED
|
@@ -1,21 +1,49 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Call-site class value — mirrors Svelte 5's `ClassValue` (clsx shape):
|
|
3
|
+
* strings, nested arrays, `{ class: condition }` records, falsy values
|
|
4
|
+
* (dropped). Accepted by `cx()` and every `class` input (component props,
|
|
5
|
+
* slot-function overrides). Config-side values (variants, compound classes)
|
|
6
|
+
* deliberately do NOT take the record form — there an object is a slot map.
|
|
7
|
+
*/
|
|
8
|
+
type ClassInput = string | number | ClassInput[] | Record<string, boolean | undefined | null> | undefined | null | false;
|
|
2
9
|
type VariantPropType<V> = V extends Record<string, unknown> ? 'true' extends Extract<keyof V, string> ? boolean | Exclude<Extract<keyof V, string>, 'true' | 'false'> : Extract<keyof V, string> : never;
|
|
3
10
|
type VariantPropsMap<V extends Record<string, Record<string, unknown>>> = {
|
|
4
11
|
[K in keyof V]?: VariantPropType<V[K]>;
|
|
5
12
|
};
|
|
6
|
-
type
|
|
13
|
+
type SlotFn<V extends Record<string, Record<string, unknown>>> = (props?: VariantPropsMap<V> & {
|
|
7
14
|
class?: ClassInput;
|
|
8
|
-
|
|
15
|
+
}) => string;
|
|
16
|
+
/** Per-slot class map used config-side (variant values, compound classes). */
|
|
17
|
+
type SlotClassMap<S> = {
|
|
18
|
+
[K in keyof S]?: string | string[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Validation companion for slot-mode `variants`: intersecting the inferred
|
|
22
|
+
* `V` with this mapped type turns every slot-map key that is not a declared
|
|
23
|
+
* slot into an assignability error AT that key — the compiler catches
|
|
24
|
+
* `wrapeer` typos that structural checking alone would let through.
|
|
25
|
+
*/
|
|
26
|
+
type ValidSlotVariants<V, S> = {
|
|
27
|
+
[A in keyof V]: {
|
|
28
|
+
[Q in keyof V[A]]: V[A][Q] extends string | readonly string[] ? unknown : {
|
|
29
|
+
[K in keyof V[A][Q]]: K extends keyof S ? unknown : ['unknown slot', K];
|
|
30
|
+
};
|
|
31
|
+
};
|
|
9
32
|
};
|
|
10
|
-
type SlotFn<V extends Record<string, Record<string, unknown>>> = (props?: VariantPropsMap<V> & TVClassOverrides) => string;
|
|
11
33
|
type CompoundVariant<V extends Record<string, Record<string, unknown>>> = {
|
|
12
34
|
[K in keyof V]?: VariantPropType<V[K]> | VariantPropType<V[K]>[];
|
|
13
35
|
} & {
|
|
14
|
-
class?: string | string[]
|
|
15
|
-
className?: string | string[] | Record<string, string | string[]>;
|
|
36
|
+
class?: string | string[];
|
|
16
37
|
};
|
|
17
|
-
|
|
18
|
-
|
|
38
|
+
type SlotCompoundVariant<V extends Record<string, Record<string, unknown>>, S> = {
|
|
39
|
+
[K in keyof V]?: VariantPropType<V[K]> | VariantPropType<V[K]>[];
|
|
40
|
+
} & {
|
|
41
|
+
class?: string | string[] | SlotClassMap<S>;
|
|
42
|
+
};
|
|
43
|
+
export type TVProps<V extends Record<string, Record<string, unknown>>> = VariantPropsMap<V> & {
|
|
44
|
+
class?: ClassInput;
|
|
45
|
+
};
|
|
46
|
+
export type VariantProps<T extends (...args: never[]) => unknown> = Omit<Exclude<Parameters<T>[0], undefined>, 'class'>;
|
|
19
47
|
/**
|
|
20
48
|
* Extracts the slot-name union from a slotted `tv()` config function — the
|
|
21
49
|
* companion to {@link VariantProps}. The slot-mode overload returns
|
|
@@ -35,8 +63,9 @@ export type VariantProps<T extends (...args: never[]) => unknown> = Omit<Exclude
|
|
|
35
63
|
export type SlotNames<T extends (...args: never[]) => unknown> = keyof ReturnType<T> & string;
|
|
36
64
|
/**
|
|
37
65
|
* Concatenate class inputs into a single string. Accepts strings, nested
|
|
38
|
-
* arrays,
|
|
39
|
-
*
|
|
66
|
+
* arrays, `{ class: condition }` records (Svelte 5 `ClassValue` / clsx
|
|
67
|
+
* shape — keys with truthy values are included) and falsy values (filtered
|
|
68
|
+
* out). Trims and joins with single spaces.
|
|
40
69
|
*
|
|
41
70
|
* Note: `cx()` does **not** deduplicate — `cx('foo', 'foo bar')` returns
|
|
42
71
|
* `'foo foo bar'`. Duplicate Tailwind classes are harmless at runtime
|
|
@@ -59,19 +88,34 @@ export declare function cx(...inputs: ClassInput[]): string;
|
|
|
59
88
|
*/
|
|
60
89
|
export declare function resolveClassChain(...sources: (string | null | undefined)[]): string;
|
|
61
90
|
export declare function matchesCompound(compound: Record<string, unknown>, effectiveProps: Record<string, unknown>): boolean;
|
|
62
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Config record exposed on every resolver as `.config` (tooling/linting).
|
|
93
|
+
* Read-only by convention: it is the live config object — mutating it after
|
|
94
|
+
* init bypasses validateTvConfig. Tools must treat it as immutable.
|
|
95
|
+
*/
|
|
96
|
+
export type TVConfig = {
|
|
97
|
+
readonly base?: string | string[];
|
|
98
|
+
readonly slots?: Record<string, string | string[]>;
|
|
99
|
+
readonly variants?: Record<string, Record<string, unknown>>;
|
|
100
|
+
readonly compoundVariants?: Record<string, unknown>[];
|
|
101
|
+
readonly defaultVariants?: Record<string, unknown>;
|
|
102
|
+
};
|
|
103
|
+
export declare function tv<V extends Record<string, Record<string, string | string[]>> = {}>(config: {
|
|
63
104
|
base?: string | string[];
|
|
64
105
|
variants?: V;
|
|
65
106
|
compoundVariants?: CompoundVariant<V>[];
|
|
66
107
|
defaultVariants?: VariantPropsMap<V>;
|
|
67
|
-
}): (props?: TVProps<V>) => string
|
|
108
|
+
}): ((props?: TVProps<V>) => string) & {
|
|
109
|
+
readonly config: TVConfig;
|
|
110
|
+
};
|
|
68
111
|
export declare function tv<V extends Record<string, Record<string, unknown>> = {}, S extends Record<string, string | string[]> = Record<string, string>>(config: {
|
|
69
|
-
base?: string | string[];
|
|
70
112
|
slots: S;
|
|
71
|
-
variants?: V
|
|
72
|
-
compoundVariants?:
|
|
113
|
+
variants?: V & ValidSlotVariants<V, S>;
|
|
114
|
+
compoundVariants?: SlotCompoundVariant<V, S>[];
|
|
73
115
|
defaultVariants?: VariantPropsMap<V>;
|
|
74
|
-
}): (props?:
|
|
116
|
+
}): ((props?: VariantPropsMap<V>) => {
|
|
75
117
|
[K in keyof S]: SlotFn<V>;
|
|
118
|
+
}) & {
|
|
119
|
+
readonly config: TVConfig;
|
|
76
120
|
};
|
|
77
121
|
export {};
|