@signal9/era-ui 21.0.0 → 23.0.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/CHANGELOG.md +45 -0
- package/dist/dev/audit/audits/off-tier-height.js +16 -2
- package/dist/dev/audit/audits/wall-is-isotropic.js +15 -10
- package/dist/era-ui.css +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/styles/typography.css +48 -9
- package/dist/ui/accordion/accordion-trigger.svelte +2 -2
- package/dist/ui/accordion/accordion-trigger.svelte.d.ts +2 -2
- package/dist/ui/alert-dialog/alert-dialog-content.svelte +6 -2
- package/dist/ui/alert-dialog/alert-dialog-content.svelte.d.ts +2 -2
- package/dist/ui/alert-dialog/alert-dialog-root.svelte +33 -0
- package/dist/ui/alert-dialog/alert-dialog-root.svelte.d.ts +8 -0
- package/dist/ui/alert-dialog/alert-dialog-trigger.svelte +8 -0
- package/dist/ui/alert-dialog/index.d.ts +1 -1
- package/dist/ui/alert-dialog/index.js +1 -1
- package/dist/ui/card/card.svelte +3 -0
- package/dist/ui/combobox/combobox-trigger.svelte +2 -2
- package/dist/ui/combobox/combobox-trigger.svelte.d.ts +2 -2
- package/dist/ui/context-menu/context-menu-content.svelte +6 -2
- package/dist/ui/context-menu/context-menu-content.svelte.d.ts +2 -2
- package/dist/ui/context-menu/context-menu-root.svelte +33 -0
- package/dist/ui/context-menu/context-menu-root.svelte.d.ts +8 -0
- package/dist/ui/context-menu/context-menu-sub-content.svelte +6 -2
- package/dist/ui/context-menu/context-menu-sub-content.svelte.d.ts +2 -2
- package/dist/ui/context-menu/context-menu-sub-trigger.svelte +2 -2
- package/dist/ui/context-menu/context-menu-sub-trigger.svelte.d.ts +2 -2
- package/dist/ui/context-menu/context-menu-trigger.svelte +8 -0
- package/dist/ui/context-menu/index.d.ts +1 -1
- package/dist/ui/context-menu/index.js +1 -1
- package/dist/ui/date-field/date-field-input.svelte +9 -0
- package/dist/ui/date-picker/date-picker-trigger.svelte +2 -2
- package/dist/ui/date-picker/date-picker-trigger.svelte.d.ts +2 -2
- package/dist/ui/date-range-field/date-range-field-input.svelte +9 -0
- package/dist/ui/date-range-picker/date-range-picker-trigger.svelte +2 -2
- package/dist/ui/date-range-picker/date-range-picker-trigger.svelte.d.ts +2 -2
- package/dist/ui/dialog/dialog-content.svelte +16 -2
- package/dist/ui/dialog/dialog-content.svelte.d.ts +2 -2
- package/dist/ui/dialog/dialog-root.svelte +43 -0
- package/dist/ui/dialog/dialog-root.svelte.d.ts +8 -0
- package/dist/ui/dialog/dialog-trigger.svelte +10 -0
- package/dist/ui/dialog/index.d.ts +1 -1
- package/dist/ui/dialog/index.js +1 -1
- package/dist/ui/dropdown-menu/dropdown-menu-content.svelte +6 -2
- package/dist/ui/dropdown-menu/dropdown-menu-content.svelte.d.ts +2 -2
- package/dist/ui/dropdown-menu/dropdown-menu-root.svelte +33 -0
- package/dist/ui/dropdown-menu/dropdown-menu-root.svelte.d.ts +8 -0
- package/dist/ui/dropdown-menu/dropdown-menu-sub-content.svelte +6 -2
- package/dist/ui/dropdown-menu/dropdown-menu-sub-content.svelte.d.ts +2 -2
- package/dist/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +2 -2
- package/dist/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte.d.ts +2 -2
- package/dist/ui/dropdown-menu/dropdown-menu-trigger.svelte +10 -2
- package/dist/ui/dropdown-menu/dropdown-menu-trigger.svelte.d.ts +2 -2
- package/dist/ui/dropdown-menu/index.d.ts +1 -1
- package/dist/ui/dropdown-menu/index.js +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/link-preview/index.d.ts +1 -1
- package/dist/ui/link-preview/index.js +1 -1
- package/dist/ui/link-preview/link-preview-content.svelte +6 -2
- package/dist/ui/link-preview/link-preview-content.svelte.d.ts +2 -2
- package/dist/ui/link-preview/link-preview-root.svelte +33 -0
- package/dist/ui/link-preview/link-preview-root.svelte.d.ts +8 -0
- package/dist/ui/link-preview/link-preview-trigger.svelte +8 -0
- package/dist/ui/menubar/menubar-content.svelte +6 -2
- package/dist/ui/menubar/menubar-content.svelte.d.ts +2 -2
- package/dist/ui/menubar/menubar-root.svelte +26 -1
- package/dist/ui/menubar/menubar-root.svelte.d.ts +1 -1
- package/dist/ui/menubar/menubar-sub-content.svelte +6 -2
- package/dist/ui/menubar/menubar-sub-content.svelte.d.ts +2 -2
- package/dist/ui/menubar/menubar-sub-trigger.svelte +2 -2
- package/dist/ui/menubar/menubar-sub-trigger.svelte.d.ts +2 -2
- package/dist/ui/meter/meter.svelte +50 -2
- package/dist/ui/meter/meter.svelte.d.ts +38 -1
- package/dist/ui/nav/index.d.ts +2 -0
- package/dist/ui/nav/index.js +2 -0
- package/dist/ui/nav/nav-bar.svelte +51 -0
- package/dist/ui/nav/nav-bar.svelte.d.ts +21 -0
- package/dist/ui/nav/nav-item.svelte +79 -0
- package/dist/ui/nav/nav-item.svelte.d.ts +29 -0
- package/dist/ui/provider/axes.d.ts +14 -1
- package/dist/ui/provider/axis-relay.svelte.d.ts +34 -0
- package/dist/ui/provider/axis-relay.svelte.js +109 -0
- package/dist/ui/select/select-content.svelte +2 -2
- package/dist/ui/select/select-content.svelte.d.ts +2 -2
- package/dist/ui/select/select-trigger.svelte +2 -2
- package/dist/ui/select/select-trigger.svelte.d.ts +2 -2
- package/dist/ui/sheet/sheet-content.svelte +8 -2
- package/dist/ui/sheet/sheet-content.svelte.d.ts +2 -2
- package/dist/ui/step/step-content.svelte +2 -2
- package/dist/ui/step/step-content.svelte.d.ts +2 -2
- package/dist/ui/step/step-summary.svelte +2 -2
- package/dist/ui/step/step-summary.svelte.d.ts +2 -2
- package/dist/ui/time-field/time-field-input.svelte +9 -0
- package/dist/ui/time-range-field/time-range-field-input.svelte +9 -0
- package/dist/ui/tooltip/index.d.ts +1 -1
- package/dist/ui/tooltip/index.js +1 -1
- package/dist/ui/tooltip/tooltip-content.svelte +6 -2
- package/dist/ui/tooltip/tooltip-content.svelte.d.ts +2 -2
- package/dist/ui/tooltip/tooltip-root.svelte +33 -0
- package/dist/ui/tooltip/tooltip-root.svelte.d.ts +8 -0
- package/dist/ui/tooltip/tooltip-trigger.svelte +8 -0
- package/dist/utils/index.d.ts +36 -3
- package/package.json +6 -4
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type $$ComponentProps = HTMLAttributes<HTMLElement> & {
|
|
4
|
+
ref?: HTMLElement | null;
|
|
5
|
+
/** Height tier, exactly as Bar's — the geometry is Bar's, not a second copy. */
|
|
6
|
+
size?: 'chip' | 'control' | 'bar';
|
|
7
|
+
/** Tier of the items inside, so the wall stays concentric to them. */
|
|
8
|
+
content?: 'tier' | 'pill';
|
|
9
|
+
/**
|
|
10
|
+
* The accessible name for this landmark. A page may hold several
|
|
11
|
+
* navigations (primary, breadcrumb, pagination, footer) and a screen
|
|
12
|
+
* reader lists them all by name — unnamed, they read as "navigation",
|
|
13
|
+
* "navigation", "navigation". Defaults to "Main" rather than nothing so
|
|
14
|
+
* the common case is named without anyone having to know this.
|
|
15
|
+
*/
|
|
16
|
+
label?: string;
|
|
17
|
+
children?: Snippet;
|
|
18
|
+
};
|
|
19
|
+
declare const NavBar: import("svelte").Component<$$ComponentProps, {}, "ref">;
|
|
20
|
+
type NavBar = ReturnType<typeof NavBar>;
|
|
21
|
+
export default NavBar;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAnchorAttributes } from 'svelte/elements';
|
|
4
|
+
import { cn } from '../../utils/index.js';
|
|
5
|
+
import { buttonVariants } from '../button/variants.js';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
ref = $bindable(null),
|
|
9
|
+
href,
|
|
10
|
+
current = false,
|
|
11
|
+
size = 'control',
|
|
12
|
+
lead,
|
|
13
|
+
trail,
|
|
14
|
+
children,
|
|
15
|
+
class: className,
|
|
16
|
+
...restProps
|
|
17
|
+
}: HTMLAnchorAttributes & {
|
|
18
|
+
ref?: HTMLAnchorElement | null;
|
|
19
|
+
href: string;
|
|
20
|
+
/**
|
|
21
|
+
* Whether this item points at the page you are on.
|
|
22
|
+
*
|
|
23
|
+
* There is deliberately no `active` prop beside this one. `active` would
|
|
24
|
+
* be a second source of truth for one fact, and the two drift the moment
|
|
25
|
+
* a route changes and someone updates the styling flag but not the
|
|
26
|
+
* announcement — leaving a tab that LOOKS current to sighted users and
|
|
27
|
+
* announces nothing to a screen reader, or the reverse. This sets
|
|
28
|
+
* `aria-current="page"`, and the styling below keys off that attribute,
|
|
29
|
+
* so the look and the announcement cannot disagree: there is one fact
|
|
30
|
+
* and it is the accessible one.
|
|
31
|
+
*/
|
|
32
|
+
current?: boolean;
|
|
33
|
+
/** Tier, matching Button's rungs so an item nests in a bar the same way. */
|
|
34
|
+
size?: 'control' | 'chip' | 'pill' | 'icon';
|
|
35
|
+
/** Icon before the label, outside it — same contract as Button's. */
|
|
36
|
+
lead?: Snippet;
|
|
37
|
+
/** Icon after the label, outside it. */
|
|
38
|
+
trail?: Snippet;
|
|
39
|
+
children?: Snippet;
|
|
40
|
+
} = $props();
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<!--
|
|
44
|
+
GEOMETRY IS BUTTON'S, borrowed rather than restated: an item in a bar is a
|
|
45
|
+
control on a rung, and a nav that invented its own heights would fall off the
|
|
46
|
+
ladder the first time the density axis moved.
|
|
47
|
+
|
|
48
|
+
The latched face is Button's too — bg-fill, the pressed shadow, held on hover
|
|
49
|
+
so it does not spring back up on bevel — but it is driven by
|
|
50
|
+
[aria-current='page'] instead of a class the caller sets. That is the whole
|
|
51
|
+
design: the attribute a screen reader reads IS the selector that paints.
|
|
52
|
+
-->
|
|
53
|
+
<!-- Nav.Item takes app routes and external URLs alike; resolution is the caller's. -->
|
|
54
|
+
<!-- eslint-disable svelte/no-navigation-without-resolve -->
|
|
55
|
+
<a
|
|
56
|
+
bind:this={ref}
|
|
57
|
+
{href}
|
|
58
|
+
aria-current={current ? 'page' : undefined}
|
|
59
|
+
class={cn(
|
|
60
|
+
buttonVariants({ size }),
|
|
61
|
+
'no-underline aria-[current=page]:bg-fill aria-[current=page]:text-bright aria-[current=page]:shadow-pressed aria-[current=page]:hover:shadow-pressed',
|
|
62
|
+
className
|
|
63
|
+
)}
|
|
64
|
+
{...restProps}
|
|
65
|
+
>
|
|
66
|
+
<span
|
|
67
|
+
data-button-content
|
|
68
|
+
class="inline-flex items-center justify-center [gap:inherit] not-has-[svg]:era-label-ink"
|
|
69
|
+
>
|
|
70
|
+
{#if lead || trail}
|
|
71
|
+
{@render lead?.()}
|
|
72
|
+
{#if children}<span class="era-text-trim">{@render children()}</span>{/if}
|
|
73
|
+
{@render trail?.()}
|
|
74
|
+
{:else}
|
|
75
|
+
{@render children?.()}
|
|
76
|
+
{/if}
|
|
77
|
+
</span>
|
|
78
|
+
</a>
|
|
79
|
+
<!-- eslint-enable svelte/no-navigation-without-resolve -->
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAnchorAttributes } from 'svelte/elements';
|
|
3
|
+
type $$ComponentProps = HTMLAnchorAttributes & {
|
|
4
|
+
ref?: HTMLAnchorElement | null;
|
|
5
|
+
href: string;
|
|
6
|
+
/**
|
|
7
|
+
* Whether this item points at the page you are on.
|
|
8
|
+
*
|
|
9
|
+
* There is deliberately no `active` prop beside this one. `active` would
|
|
10
|
+
* be a second source of truth for one fact, and the two drift the moment
|
|
11
|
+
* a route changes and someone updates the styling flag but not the
|
|
12
|
+
* announcement — leaving a tab that LOOKS current to sighted users and
|
|
13
|
+
* announces nothing to a screen reader, or the reverse. This sets
|
|
14
|
+
* `aria-current="page"`, and the styling below keys off that attribute,
|
|
15
|
+
* so the look and the announcement cannot disagree: there is one fact
|
|
16
|
+
* and it is the accessible one.
|
|
17
|
+
*/
|
|
18
|
+
current?: boolean;
|
|
19
|
+
/** Tier, matching Button's rungs so an item nests in a bar the same way. */
|
|
20
|
+
size?: 'control' | 'chip' | 'pill' | 'icon';
|
|
21
|
+
/** Icon before the label, outside it — same contract as Button's. */
|
|
22
|
+
lead?: Snippet;
|
|
23
|
+
/** Icon after the label, outside it. */
|
|
24
|
+
trail?: Snippet;
|
|
25
|
+
children?: Snippet;
|
|
26
|
+
};
|
|
27
|
+
declare const NavItem: import("svelte").Component<$$ComponentProps, {}, "ref">;
|
|
28
|
+
type NavItem = ReturnType<typeof NavItem>;
|
|
29
|
+
export default NavItem;
|
|
@@ -15,7 +15,20 @@ export interface AxisMeta {
|
|
|
15
15
|
key: string;
|
|
16
16
|
/** Default level value. */
|
|
17
17
|
default: string;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Set this axis on <html> for a whole-document default.
|
|
20
|
+
*
|
|
21
|
+
* This used to read "MUST be set on <html> so portalled content inherits
|
|
22
|
+
* it", which was not a description of the axis but a surrender: portalled
|
|
23
|
+
* content is reparented to <body>, so it stopped inheriting from its subtree,
|
|
24
|
+
* and hoisting to the root was the only way to make it look right. That cost
|
|
25
|
+
* subtree scoping for the eight portalling components, and it was not even
|
|
26
|
+
* always available — an app that scopes a surface to part of itself cannot
|
|
27
|
+
* hoist without restyling the rest.
|
|
28
|
+
*
|
|
29
|
+
* The axes now cross the portal on their own (provider/axis-relay.svelte.ts),
|
|
30
|
+
* so this is a convenience, not a requirement.
|
|
31
|
+
*/
|
|
19
32
|
onHtml: boolean;
|
|
20
33
|
/** Selectable levels, in scale order. */
|
|
21
34
|
levels: readonly AxisLevel[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare class AxisRelay {
|
|
2
|
+
#private;
|
|
3
|
+
/** Attributes to stamp on portalled content. Empty until a capture runs. */
|
|
4
|
+
attrs: Record<string, string>;
|
|
5
|
+
/** `dark` / `light` when a scoped one is in force, else undefined. */
|
|
6
|
+
scheme: "dark" | "light" | undefined;
|
|
7
|
+
/** Called by the Trigger once it has an element. */
|
|
8
|
+
register(el: HTMLElement | null | undefined): void;
|
|
9
|
+
/**
|
|
10
|
+
* Read the axes in force at the anchor and remember them.
|
|
11
|
+
*
|
|
12
|
+
* CALLED WHEN THE CONTENT OPENS, not when the trigger mounts. Capturing at
|
|
13
|
+
* mount looks equivalent and is not: an axis can change while a trigger sits
|
|
14
|
+
* there — the docs' own pickers do it on every click, and a consumer toggling
|
|
15
|
+
* a theme or pinning a panel to dense does it too — and a value read once at
|
|
16
|
+
* mount is then wrong for every subsequent open. Content is created fresh on
|
|
17
|
+
* each open, so refreshing there is both correct and self-scheduling.
|
|
18
|
+
*
|
|
19
|
+
* Nearest-ancestor per axis, INDEPENDENTLY: a subtree can override density
|
|
20
|
+
* without touching surface, and closest() answers each question separately,
|
|
21
|
+
* which is what makes the axes orthogonal in the first place.
|
|
22
|
+
*/
|
|
23
|
+
refresh(): void;
|
|
24
|
+
/** The class string portalled content should carry. */
|
|
25
|
+
get schemeClass(): string;
|
|
26
|
+
}
|
|
27
|
+
/** Called by era's Root for a portalling component. */
|
|
28
|
+
export declare function setAxisRelay(): AxisRelay;
|
|
29
|
+
/**
|
|
30
|
+
* Called by a Trigger or Content. Returns null outside an era Root — a consumer
|
|
31
|
+
* using bits-ui's own Root directly still gets working components, just without
|
|
32
|
+
* the relay, so this must never throw.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getAxisRelay(): AxisRelay | null;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { getContext, hasContext, setContext } from 'svelte';
|
|
2
|
+
import { axisKeys, axisRegistry } from './axes.js';
|
|
3
|
+
/*
|
|
4
|
+
* CARRYING THE AXES ACROSS A PORTAL.
|
|
5
|
+
*
|
|
6
|
+
* Eight components render their content through a Portal, which moves it to
|
|
7
|
+
* document.body. Everything era styles with is inherited — the density ladder
|
|
8
|
+
* and the roundness scale are custom properties on `*`, surfaces re-baseline a
|
|
9
|
+
* token contract, the font axis publishes an inherited stack — so content that
|
|
10
|
+
* leaves its subtree stops inheriting from it and starts inheriting from :root.
|
|
11
|
+
*
|
|
12
|
+
* The consequence was not subtle and it was not theoretical. A dialog opened
|
|
13
|
+
* from inside a `data-surface="bevel"` panel rendered flat. A dropdown inside a
|
|
14
|
+
* pinned-dense sidebar rendered at the page's density, so its rows were the
|
|
15
|
+
* wrong tier and every concentric gap inside it was computed against heights it
|
|
16
|
+
* did not have. A menu inside a scoped theme rendered the page's colours. The
|
|
17
|
+
* library's answer was the `onHtml: true` flag in the axis registry — "must be
|
|
18
|
+
* set on <html> so portalled content inherits it" — which is not a fix, it is
|
|
19
|
+
* the instruction to give up subtree scoping for these eight components. And it
|
|
20
|
+
* is not always available: signalnine.org scopes `data-surface` to five app
|
|
21
|
+
* surfaces precisely so its marketing pages are NOT bevelled, so hoisting to
|
|
22
|
+
* <html> would have been a worse bug than the one it fixed.
|
|
23
|
+
*
|
|
24
|
+
* WHY A RELAY RATHER THAN READING AT THE CONTENT. The content cannot answer the
|
|
25
|
+
* question itself: by the time it exists it is already a child of <body>, and
|
|
26
|
+
* every ancestor it can see is the wrong one. The value has to be captured from
|
|
27
|
+
* a node that is still in the subtree, and handed across.
|
|
28
|
+
*
|
|
29
|
+
* The trigger is that node. It is a real element, it lives exactly where the
|
|
30
|
+
* content logically belongs, and it is present for every component here. The
|
|
31
|
+
* root sets up the channel (era owns Root for all eight now — it did not, which
|
|
32
|
+
* is why there was nowhere to put this), the trigger fills it from its own DOM
|
|
33
|
+
* position, and the content spreads it onto itself.
|
|
34
|
+
*
|
|
35
|
+
* WHAT IT DOES NOT DO. A trigger-less flow — a dialog driven purely by
|
|
36
|
+
* `bind:open` with no Trigger rendered — has nothing to capture from, and the
|
|
37
|
+
* content falls back to inheriting from :root exactly as it does today. That is
|
|
38
|
+
* unchanged behaviour rather than a regression, and the relay is written so the
|
|
39
|
+
* absence is a null, not a crash.
|
|
40
|
+
*/
|
|
41
|
+
const KEY = Symbol('era.axis-relay');
|
|
42
|
+
/** The axis attributes, plus the theme pair, which is not in the axis registry. */
|
|
43
|
+
const THEME_ATTR = 'data-theme';
|
|
44
|
+
const SCHEME_CLASSES = ['dark', 'light'];
|
|
45
|
+
export class AxisRelay {
|
|
46
|
+
/** Attributes to stamp on portalled content. Empty until a capture runs. */
|
|
47
|
+
attrs = $state({});
|
|
48
|
+
/** `dark` / `light` when a scoped one is in force, else undefined. */
|
|
49
|
+
scheme = $state(undefined);
|
|
50
|
+
/** The trigger's element — the last node still inside the subtree. */
|
|
51
|
+
#anchor = null;
|
|
52
|
+
/** Called by the Trigger once it has an element. */
|
|
53
|
+
register(el) {
|
|
54
|
+
this.#anchor = el ?? null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Read the axes in force at the anchor and remember them.
|
|
58
|
+
*
|
|
59
|
+
* CALLED WHEN THE CONTENT OPENS, not when the trigger mounts. Capturing at
|
|
60
|
+
* mount looks equivalent and is not: an axis can change while a trigger sits
|
|
61
|
+
* there — the docs' own pickers do it on every click, and a consumer toggling
|
|
62
|
+
* a theme or pinning a panel to dense does it too — and a value read once at
|
|
63
|
+
* mount is then wrong for every subsequent open. Content is created fresh on
|
|
64
|
+
* each open, so refreshing there is both correct and self-scheduling.
|
|
65
|
+
*
|
|
66
|
+
* Nearest-ancestor per axis, INDEPENDENTLY: a subtree can override density
|
|
67
|
+
* without touching surface, and closest() answers each question separately,
|
|
68
|
+
* which is what makes the axes orthogonal in the first place.
|
|
69
|
+
*/
|
|
70
|
+
refresh() {
|
|
71
|
+
const el = this.#anchor;
|
|
72
|
+
if (!el?.closest)
|
|
73
|
+
return;
|
|
74
|
+
const next = {};
|
|
75
|
+
for (const key of axisKeys) {
|
|
76
|
+
const attr = axisRegistry[key].attr;
|
|
77
|
+
const value = el.closest(`[${attr}]`)?.getAttribute(attr);
|
|
78
|
+
if (value)
|
|
79
|
+
next[attr] = value;
|
|
80
|
+
}
|
|
81
|
+
// Theme is not an axis in the registry (it has no picker contract), but it
|
|
82
|
+
// is inherited the same way and breaks across a portal the same way.
|
|
83
|
+
const theme = el.closest(`[${THEME_ATTR}]`)?.getAttribute(THEME_ATTR);
|
|
84
|
+
if (theme)
|
|
85
|
+
next[THEME_ATTR] = theme;
|
|
86
|
+
this.attrs = next;
|
|
87
|
+
// The colour-scheme pair is a CLASS, not an attribute, so it is separate.
|
|
88
|
+
const schemeHost = el.closest('.dark, .light');
|
|
89
|
+
this.scheme = SCHEME_CLASSES.find((c) => schemeHost?.classList.contains(c)) ?? undefined;
|
|
90
|
+
}
|
|
91
|
+
/** The class string portalled content should carry. */
|
|
92
|
+
get schemeClass() {
|
|
93
|
+
return this.scheme ?? '';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** Called by era's Root for a portalling component. */
|
|
97
|
+
export function setAxisRelay() {
|
|
98
|
+
const relay = new AxisRelay();
|
|
99
|
+
setContext(KEY, relay);
|
|
100
|
+
return relay;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Called by a Trigger or Content. Returns null outside an era Root — a consumer
|
|
104
|
+
* using bits-ui's own Root directly still gets working components, just without
|
|
105
|
+
* the relay, so this must never throw.
|
|
106
|
+
*/
|
|
107
|
+
export function getAxisRelay() {
|
|
108
|
+
return hasContext(KEY) ? getContext(KEY) : null;
|
|
109
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Select } from 'bits-ui';
|
|
3
|
-
import { cn, type
|
|
3
|
+
import { cn, type OwnedPartProps } from '../../utils/index.js';
|
|
4
4
|
|
|
5
5
|
let {
|
|
6
6
|
ref = $bindable(null),
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
class: className,
|
|
9
9
|
sideOffset = 0,
|
|
10
10
|
...restProps
|
|
11
|
-
}:
|
|
11
|
+
}: OwnedPartProps<Select.ContentProps> = $props();
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
14
|
<Select.Content
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Select } from 'bits-ui';
|
|
2
|
-
import { type
|
|
3
|
-
declare const SelectContent: import("svelte").Component<
|
|
2
|
+
import { type OwnedPartProps } from '../../utils/index.js';
|
|
3
|
+
declare const SelectContent: import("svelte").Component<OwnedPartProps<Select.ContentProps>, {}, "ref">;
|
|
4
4
|
type SelectContent = ReturnType<typeof SelectContent>;
|
|
5
5
|
export default SelectContent;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Select } from 'bits-ui';
|
|
3
3
|
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
|
4
|
-
import { cn, type
|
|
4
|
+
import { cn, type OwnedPartProps } from '../../utils/index.js';
|
|
5
5
|
|
|
6
6
|
let {
|
|
7
7
|
ref = $bindable(null),
|
|
8
8
|
children,
|
|
9
9
|
class: className,
|
|
10
10
|
...restProps
|
|
11
|
-
}:
|
|
11
|
+
}: OwnedPartProps<Select.TriggerProps> = $props();
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
14
|
<Select.Trigger
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Select } from 'bits-ui';
|
|
2
|
-
import { type
|
|
3
|
-
declare const SelectTrigger: import("svelte").Component<
|
|
2
|
+
import { type OwnedPartProps } from '../../utils/index.js';
|
|
3
|
+
declare const SelectTrigger: import("svelte").Component<OwnedPartProps<Select.TriggerProps>, {}, "ref">;
|
|
4
4
|
type SelectTrigger = ReturnType<typeof SelectTrigger>;
|
|
5
5
|
export default SelectTrigger;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
-
import { cn, tv, type
|
|
2
|
+
import { cn, tv, type OwnedPartProps } from '../../utils/index.js';
|
|
3
3
|
|
|
4
4
|
// A Sheet is a Dialog pinned to a screen edge — the idiomatic mobile drawer
|
|
5
5
|
// (bottom for controls, left/right for navigation). Positioning + which
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
</script>
|
|
37
37
|
|
|
38
38
|
<script lang="ts">
|
|
39
|
+
import { getAxisRelay } from '../provider/axis-relay.svelte.js';
|
|
39
40
|
import type { Snippet } from 'svelte';
|
|
40
41
|
import { Dialog } from 'bits-ui';
|
|
41
42
|
import Overlay from '../dialog/dialog-overlay.svelte';
|
|
@@ -47,18 +48,23 @@
|
|
|
47
48
|
header,
|
|
48
49
|
class: className,
|
|
49
50
|
...restProps
|
|
50
|
-
}:
|
|
51
|
+
}: OwnedPartProps<Dialog.ContentProps> & {
|
|
51
52
|
/** Screen edge the sheet is anchored to. */
|
|
52
53
|
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
53
54
|
/** A top bar in the pane's grammar — pass `Sheet.Header` holding `Sheet.Title`. */
|
|
54
55
|
header?: Snippet;
|
|
55
56
|
} = $props();
|
|
57
|
+
|
|
58
|
+
// Sheet is a Dialog pinned to an edge — it reuses Dialog's Root and Trigger,
|
|
59
|
+
// so the relay is already open by the time this renders.
|
|
60
|
+
const relay = getAxisRelay();
|
|
56
61
|
</script>
|
|
57
62
|
|
|
58
63
|
<Dialog.Portal>
|
|
59
64
|
<Overlay />
|
|
60
65
|
<Dialog.Content
|
|
61
66
|
bind:ref
|
|
67
|
+
{...relay?.attrs}
|
|
62
68
|
class={cn(sheetVariants({ side, header: header != null }), className)}
|
|
63
69
|
{...restProps}
|
|
64
70
|
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type OwnedPartProps } from '../../utils/index.js';
|
|
2
2
|
export declare const sheetVariants: import("tailwind-variants").TVReturnType<{
|
|
3
3
|
side: {
|
|
4
4
|
top: "inset-x-0 top-0 max-h-[85dvh] rounded-b-(--sheet-rd)";
|
|
@@ -56,7 +56,7 @@ export declare const sheetVariants: import("tailwind-variants").TVReturnType<{
|
|
|
56
56
|
}, undefined>>;
|
|
57
57
|
import type { Snippet } from 'svelte';
|
|
58
58
|
import { Dialog } from 'bits-ui';
|
|
59
|
-
type $$ComponentProps =
|
|
59
|
+
type $$ComponentProps = OwnedPartProps<Dialog.ContentProps> & {
|
|
60
60
|
/** Screen edge the sheet is anchored to. */
|
|
61
61
|
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
62
62
|
/** A top bar in the pane's grammar — pass `Sheet.Header` holding `Sheet.Title`. */
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Collapsible } from 'bits-ui';
|
|
3
|
-
import { cn, type
|
|
3
|
+
import { cn, type OwnedPartProps } from '../../utils/index.js';
|
|
4
4
|
|
|
5
5
|
let {
|
|
6
6
|
ref = $bindable(null),
|
|
7
7
|
children,
|
|
8
8
|
class: className,
|
|
9
9
|
...restProps
|
|
10
|
-
}:
|
|
10
|
+
}: OwnedPartProps<Collapsible.ContentProps> = $props();
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
<!-- Height animates on the outer (padding-free) element via era-collapse, which
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Collapsible } from 'bits-ui';
|
|
2
|
-
import { type
|
|
3
|
-
declare const StepContent: import("svelte").Component<
|
|
2
|
+
import { type OwnedPartProps } from '../../utils/index.js';
|
|
3
|
+
declare const StepContent: import("svelte").Component<OwnedPartProps<Collapsible.ContentProps>, {}, "ref">;
|
|
4
4
|
type StepContent = ReturnType<typeof StepContent>;
|
|
5
5
|
export default StepContent;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import CircleX from '@lucide/svelte/icons/circle-x';
|
|
6
6
|
import CircleDashed from '@lucide/svelte/icons/circle-dashed';
|
|
7
7
|
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
|
8
|
-
import { cn, type
|
|
8
|
+
import { cn, type OwnedPartProps } from '../../utils/index.js';
|
|
9
9
|
import { getStepContext } from './context.js';
|
|
10
10
|
|
|
11
11
|
let {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
children,
|
|
14
14
|
class: className,
|
|
15
15
|
...restProps
|
|
16
|
-
}:
|
|
16
|
+
}: OwnedPartProps<Collapsible.TriggerProps> = $props();
|
|
17
17
|
|
|
18
18
|
const step = getStepContext();
|
|
19
19
|
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Collapsible } from 'bits-ui';
|
|
2
|
-
import { type
|
|
3
|
-
declare const StepSummary: import("svelte").Component<
|
|
2
|
+
import { type OwnedPartProps } from '../../utils/index.js';
|
|
3
|
+
declare const StepSummary: import("svelte").Component<OwnedPartProps<Collapsible.TriggerProps>, {}, "ref">;
|
|
4
4
|
type StepSummary = ReturnType<typeof StepSummary>;
|
|
5
5
|
export default StepSummary;
|
|
@@ -6,8 +6,17 @@
|
|
|
6
6
|
let { ref = $bindable(null), class: className, ...restProps }: TimeField.InputProps = $props();
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
|
+
<!-- A TEXT FIELD'S SIDE PADDING IS DELIBERATELY NOT ITS CONCENTRIC INSET.
|
|
10
|
+
--era-field-px is the room where typed text STARTS — purpose-named in
|
|
11
|
+
density.css and floored at 0.5rem because the derived 6.8px read cramped at
|
|
12
|
+
dense — while the segment boxes inside are chip-tier and centre 1px from the
|
|
13
|
+
top. So this row is 8px at the sides against 1px above and below, on
|
|
14
|
+
purpose, and it says so rather than being quietly skipped by
|
|
15
|
+
layout/wall-is-isotropic's heuristics: a claim a reviewer can check beats a
|
|
16
|
+
silence. -->
|
|
9
17
|
<TimeField.Input
|
|
10
18
|
bind:ref
|
|
19
|
+
data-era-anisotropic
|
|
11
20
|
data-input-field
|
|
12
21
|
class={cn(
|
|
13
22
|
inputVariants(),
|
|
@@ -10,8 +10,17 @@
|
|
|
10
10
|
}: TimeRangeField.InputProps = $props();
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
|
+
<!-- A TEXT FIELD'S SIDE PADDING IS DELIBERATELY NOT ITS CONCENTRIC INSET.
|
|
14
|
+
--era-field-px is the room where typed text STARTS — purpose-named in
|
|
15
|
+
density.css and floored at 0.5rem because the derived 6.8px read cramped at
|
|
16
|
+
dense — while the segment boxes inside are chip-tier and centre 1px from the
|
|
17
|
+
top. So this row is 8px at the sides against 1px above and below, on
|
|
18
|
+
purpose, and it says so rather than being quietly skipped by
|
|
19
|
+
layout/wall-is-isotropic's heuristics: a claim a reviewer can check beats a
|
|
20
|
+
silence. -->
|
|
13
21
|
<TimeRangeField.Input
|
|
14
22
|
bind:ref
|
|
23
|
+
data-era-anisotropic
|
|
15
24
|
data-input-field
|
|
16
25
|
class={cn(
|
|
17
26
|
inputVariants(),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Tooltip } from 'bits-ui';
|
|
2
2
|
export declare const Provider: typeof Tooltip.Provider;
|
|
3
|
-
export
|
|
3
|
+
export { default as Root } from './tooltip-root.svelte';
|
|
4
4
|
export declare const Portal: typeof Tooltip.Portal;
|
|
5
5
|
export declare const Arrow: typeof Tooltip.Arrow;
|
|
6
6
|
export { default as Trigger } from './tooltip-trigger.svelte';
|
package/dist/ui/tooltip/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Tooltip } from 'bits-ui';
|
|
2
2
|
export const Provider = Tooltip.Provider;
|
|
3
|
-
export
|
|
3
|
+
export { default as Root } from './tooltip-root.svelte';
|
|
4
4
|
export const Portal = Tooltip.Portal;
|
|
5
5
|
export const Arrow = Tooltip.Arrow;
|
|
6
6
|
export { default as Trigger } from './tooltip-trigger.svelte';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { getAxisRelay } from '../provider/axis-relay.svelte.js';
|
|
2
3
|
import { Tooltip } from 'bits-ui';
|
|
3
|
-
import { cn, type
|
|
4
|
+
import { cn, type OwnedPartProps } from '../../utils/index.js';
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
ref = $bindable(null),
|
|
@@ -8,12 +9,15 @@
|
|
|
8
9
|
class: className,
|
|
9
10
|
sideOffset = 4,
|
|
10
11
|
...restProps
|
|
11
|
-
}:
|
|
12
|
+
}: OwnedPartProps<Tooltip.ContentProps> = $props();
|
|
13
|
+
|
|
14
|
+
const relay = getAxisRelay();
|
|
12
15
|
</script>
|
|
13
16
|
|
|
14
17
|
<Tooltip.Portal>
|
|
15
18
|
<Tooltip.Content
|
|
16
19
|
bind:ref
|
|
20
|
+
{...relay?.attrs}
|
|
17
21
|
class={cn(
|
|
18
22
|
'z-overlay inline-flex h-pill items-center gap-gutter rounded-pill bg-elevated px-inset-pill text-body leading-none whitespace-nowrap text-fg shadow-lg glass-blur has-[svg]:gap-inset-pill',
|
|
19
23
|
className
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tooltip } from 'bits-ui';
|
|
2
|
-
import { type
|
|
3
|
-
declare const TooltipContent: import("svelte").Component<
|
|
2
|
+
import { type OwnedPartProps } from '../../utils/index.js';
|
|
3
|
+
declare const TooltipContent: import("svelte").Component<OwnedPartProps<Tooltip.ContentProps>, {}, "ref">;
|
|
4
4
|
type TooltipContent = ReturnType<typeof TooltipContent>;
|
|
5
5
|
export default TooltipContent;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { Tooltip } from 'bits-ui';
|
|
4
|
+
import { setAxisRelay } from '../provider/axis-relay.svelte.js';
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* era owns Root ONLY to open the axis channel — it renders no DOM, adds no
|
|
8
|
+
* behaviour and forwards everything. There has to BE a component here: a
|
|
9
|
+
* re-export (`export const Root = Tooltip.Root`) has nowhere to put a context,
|
|
10
|
+
* which is exactly why the axes could not cross the portal before.
|
|
11
|
+
*
|
|
12
|
+
* `open` is re-declared bindable because a spread cannot carry a two-way
|
|
13
|
+
* binding, so wrapping a primitive breaks `bind:open` at every call site
|
|
14
|
+
* unless the wrapper states it.
|
|
15
|
+
*/
|
|
16
|
+
let {
|
|
17
|
+
open = $bindable(false),
|
|
18
|
+
children,
|
|
19
|
+
...restProps
|
|
20
|
+
}: Tooltip.RootProps & { children?: Snippet } = $props();
|
|
21
|
+
|
|
22
|
+
const relay = setAxisRelay();
|
|
23
|
+
|
|
24
|
+
// Capture on OPEN, not on mount: bits-ui mounts content eagerly and toggles
|
|
25
|
+
// it, so a mount-time read runs once at page load and never again.
|
|
26
|
+
$effect(() => {
|
|
27
|
+
if (open) relay.refresh();
|
|
28
|
+
});
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<Tooltip.Root bind:open {...restProps}>
|
|
32
|
+
{@render children?.()}
|
|
33
|
+
</Tooltip.Root>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { Tooltip } from 'bits-ui';
|
|
3
|
+
type $$ComponentProps = Tooltip.RootProps & {
|
|
4
|
+
children?: Snippet;
|
|
5
|
+
};
|
|
6
|
+
declare const TooltipRoot: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
7
|
+
type TooltipRoot = ReturnType<typeof TooltipRoot>;
|
|
8
|
+
export default TooltipRoot;
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { getAxisRelay } from '../provider/axis-relay.svelte.js';
|
|
2
3
|
import { Tooltip } from 'bits-ui';
|
|
3
4
|
|
|
4
5
|
let { ref = $bindable(null), class: className, ...restProps }: Tooltip.TriggerProps = $props();
|
|
6
|
+
|
|
7
|
+
// The trigger is the last node still inside the subtree, so it is the
|
|
8
|
+
// anchor the axes are read FROM when the content opens.
|
|
9
|
+
const relay = getAxisRelay();
|
|
10
|
+
$effect(() => {
|
|
11
|
+
relay?.register(ref);
|
|
12
|
+
});
|
|
5
13
|
</script>
|
|
6
14
|
|
|
7
15
|
<Tooltip.Trigger bind:ref class={className} {...restProps} />
|