@signal9/era-ui 22.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.
Files changed (93) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/era-ui.css +1 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/ui/accordion/accordion-trigger.svelte +2 -2
  5. package/dist/ui/accordion/accordion-trigger.svelte.d.ts +2 -2
  6. package/dist/ui/alert-dialog/alert-dialog-content.svelte +6 -2
  7. package/dist/ui/alert-dialog/alert-dialog-content.svelte.d.ts +2 -2
  8. package/dist/ui/alert-dialog/alert-dialog-root.svelte +33 -0
  9. package/dist/ui/alert-dialog/alert-dialog-root.svelte.d.ts +8 -0
  10. package/dist/ui/alert-dialog/alert-dialog-trigger.svelte +8 -0
  11. package/dist/ui/alert-dialog/index.d.ts +1 -1
  12. package/dist/ui/alert-dialog/index.js +1 -1
  13. package/dist/ui/combobox/combobox-trigger.svelte +2 -2
  14. package/dist/ui/combobox/combobox-trigger.svelte.d.ts +2 -2
  15. package/dist/ui/context-menu/context-menu-content.svelte +6 -2
  16. package/dist/ui/context-menu/context-menu-content.svelte.d.ts +2 -2
  17. package/dist/ui/context-menu/context-menu-root.svelte +33 -0
  18. package/dist/ui/context-menu/context-menu-root.svelte.d.ts +8 -0
  19. package/dist/ui/context-menu/context-menu-sub-content.svelte +6 -2
  20. package/dist/ui/context-menu/context-menu-sub-content.svelte.d.ts +2 -2
  21. package/dist/ui/context-menu/context-menu-sub-trigger.svelte +2 -2
  22. package/dist/ui/context-menu/context-menu-sub-trigger.svelte.d.ts +2 -2
  23. package/dist/ui/context-menu/context-menu-trigger.svelte +8 -0
  24. package/dist/ui/context-menu/index.d.ts +1 -1
  25. package/dist/ui/context-menu/index.js +1 -1
  26. package/dist/ui/date-picker/date-picker-trigger.svelte +2 -2
  27. package/dist/ui/date-picker/date-picker-trigger.svelte.d.ts +2 -2
  28. package/dist/ui/date-range-picker/date-range-picker-trigger.svelte +2 -2
  29. package/dist/ui/date-range-picker/date-range-picker-trigger.svelte.d.ts +2 -2
  30. package/dist/ui/dialog/dialog-content.svelte +16 -2
  31. package/dist/ui/dialog/dialog-content.svelte.d.ts +2 -2
  32. package/dist/ui/dialog/dialog-root.svelte +43 -0
  33. package/dist/ui/dialog/dialog-root.svelte.d.ts +8 -0
  34. package/dist/ui/dialog/dialog-trigger.svelte +10 -0
  35. package/dist/ui/dialog/index.d.ts +1 -1
  36. package/dist/ui/dialog/index.js +1 -1
  37. package/dist/ui/dropdown-menu/dropdown-menu-content.svelte +6 -2
  38. package/dist/ui/dropdown-menu/dropdown-menu-content.svelte.d.ts +2 -2
  39. package/dist/ui/dropdown-menu/dropdown-menu-root.svelte +33 -0
  40. package/dist/ui/dropdown-menu/dropdown-menu-root.svelte.d.ts +8 -0
  41. package/dist/ui/dropdown-menu/dropdown-menu-sub-content.svelte +6 -2
  42. package/dist/ui/dropdown-menu/dropdown-menu-sub-content.svelte.d.ts +2 -2
  43. package/dist/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +2 -2
  44. package/dist/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte.d.ts +2 -2
  45. package/dist/ui/dropdown-menu/dropdown-menu-trigger.svelte +10 -2
  46. package/dist/ui/dropdown-menu/dropdown-menu-trigger.svelte.d.ts +2 -2
  47. package/dist/ui/dropdown-menu/index.d.ts +1 -1
  48. package/dist/ui/dropdown-menu/index.js +1 -1
  49. package/dist/ui/index.d.ts +1 -0
  50. package/dist/ui/index.js +1 -0
  51. package/dist/ui/link-preview/index.d.ts +1 -1
  52. package/dist/ui/link-preview/index.js +1 -1
  53. package/dist/ui/link-preview/link-preview-content.svelte +6 -2
  54. package/dist/ui/link-preview/link-preview-content.svelte.d.ts +2 -2
  55. package/dist/ui/link-preview/link-preview-root.svelte +33 -0
  56. package/dist/ui/link-preview/link-preview-root.svelte.d.ts +8 -0
  57. package/dist/ui/link-preview/link-preview-trigger.svelte +8 -0
  58. package/dist/ui/menubar/menubar-content.svelte +6 -2
  59. package/dist/ui/menubar/menubar-content.svelte.d.ts +2 -2
  60. package/dist/ui/menubar/menubar-root.svelte +26 -1
  61. package/dist/ui/menubar/menubar-root.svelte.d.ts +1 -1
  62. package/dist/ui/menubar/menubar-sub-content.svelte +6 -2
  63. package/dist/ui/menubar/menubar-sub-content.svelte.d.ts +2 -2
  64. package/dist/ui/menubar/menubar-sub-trigger.svelte +2 -2
  65. package/dist/ui/menubar/menubar-sub-trigger.svelte.d.ts +2 -2
  66. package/dist/ui/nav/index.d.ts +2 -0
  67. package/dist/ui/nav/index.js +2 -0
  68. package/dist/ui/nav/nav-bar.svelte +51 -0
  69. package/dist/ui/nav/nav-bar.svelte.d.ts +21 -0
  70. package/dist/ui/nav/nav-item.svelte +79 -0
  71. package/dist/ui/nav/nav-item.svelte.d.ts +29 -0
  72. package/dist/ui/provider/axes.d.ts +14 -1
  73. package/dist/ui/provider/axis-relay.svelte.d.ts +34 -0
  74. package/dist/ui/provider/axis-relay.svelte.js +109 -0
  75. package/dist/ui/select/select-content.svelte +2 -2
  76. package/dist/ui/select/select-content.svelte.d.ts +2 -2
  77. package/dist/ui/select/select-trigger.svelte +2 -2
  78. package/dist/ui/select/select-trigger.svelte.d.ts +2 -2
  79. package/dist/ui/sheet/sheet-content.svelte +8 -2
  80. package/dist/ui/sheet/sheet-content.svelte.d.ts +2 -2
  81. package/dist/ui/step/step-content.svelte +2 -2
  82. package/dist/ui/step/step-content.svelte.d.ts +2 -2
  83. package/dist/ui/step/step-summary.svelte +2 -2
  84. package/dist/ui/step/step-summary.svelte.d.ts +2 -2
  85. package/dist/ui/tooltip/index.d.ts +1 -1
  86. package/dist/ui/tooltip/index.js +1 -1
  87. package/dist/ui/tooltip/tooltip-content.svelte +6 -2
  88. package/dist/ui/tooltip/tooltip-content.svelte.d.ts +2 -2
  89. package/dist/ui/tooltip/tooltip-root.svelte +33 -0
  90. package/dist/ui/tooltip/tooltip-root.svelte.d.ts +8 -0
  91. package/dist/ui/tooltip/tooltip-trigger.svelte +8 -0
  92. package/dist/utils/index.d.ts +36 -3
  93. package/package.json +1 -1
@@ -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 PartProps } from '../../utils/index.js';
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
- }: PartProps<Select.ContentProps> = $props();
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 PartProps } from '../../utils/index.js';
3
- declare const SelectContent: import("svelte").Component<PartProps<Select.ContentProps>, {}, "ref">;
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 PartProps } from '../../utils/index.js';
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
- }: PartProps<Select.TriggerProps> = $props();
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 PartProps } from '../../utils/index.js';
3
- declare const SelectTrigger: import("svelte").Component<PartProps<Select.TriggerProps>, {}, "ref">;
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 PartProps } from '../../utils/index.js';
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
- }: PartProps<Dialog.ContentProps> & {
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 PartProps } from '../../utils/index.js';
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 = PartProps<Dialog.ContentProps> & {
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 PartProps } from '../../utils/index.js';
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
- }: PartProps<Collapsible.ContentProps> = $props();
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 PartProps } from '../../utils/index.js';
3
- declare const StepContent: import("svelte").Component<PartProps<Collapsible.ContentProps>, {}, "ref">;
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 PartProps } from '../../utils/index.js';
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
- }: PartProps<Collapsible.TriggerProps> = $props();
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 PartProps } from '../../utils/index.js';
3
- declare const StepSummary: import("svelte").Component<PartProps<Collapsible.TriggerProps>, {}, "ref">;
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;
@@ -1,6 +1,6 @@
1
1
  import { Tooltip } from 'bits-ui';
2
2
  export declare const Provider: typeof Tooltip.Provider;
3
- export declare const Root: typeof Tooltip.Root;
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';
@@ -1,6 +1,6 @@
1
1
  import { Tooltip } from 'bits-ui';
2
2
  export const Provider = Tooltip.Provider;
3
- export const Root = Tooltip.Root;
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 PartProps } from '../../utils/index.js';
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
- }: PartProps<Tooltip.ContentProps> = $props();
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 PartProps } from '../../utils/index.js';
3
- declare const TooltipContent: import("svelte").Component<PartProps<Tooltip.ContentProps>, {}, "ref">;
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} />
@@ -1,6 +1,6 @@
1
1
  import { type ClassValue } from 'clsx';
2
2
  import type { Snippet } from 'svelte';
3
- import type { WithoutChildrenOrChild } from 'bits-ui';
3
+ import type { WithoutChildren, WithoutChildrenOrChild } from 'bits-ui';
4
4
  /**
5
5
  * Merges class lists with Tailwind conflict resolution (last wins).
6
6
  * Accepts Svelte's full `ClassValue` shape (strings, arrays, objects).
@@ -9,8 +9,41 @@ export declare function cn(...inputs: ClassValue[]): string;
9
9
  /** `tv` preconfigured with this library's tailwind-merge settings. */
10
10
  export declare const tv: import("tailwind-variants").TV;
11
11
  export type { VariantProps } from 'tailwind-variants';
12
- /** Props for a bits-ui part wrapper that renders its own `children`. */
13
- export type PartProps<T> = WithoutChildrenOrChild<T> & {
12
+ /**
13
+ * Props for a bits-ui part that era only RESTYLES — it forwards `children` and
14
+ * adds classes, and renders no markup of its own.
15
+ *
16
+ * `child` is kept. It is bits-ui's escape hatch: the snippet receives the props
17
+ * the part would have rendered, and the caller renders their own element with
18
+ * them — a Trigger as an `<a>`, a Content as a custom component. era used to
19
+ * strip it from every part, which made the whole library strictly less capable
20
+ * than the primitives it wraps, for no reason on these 18: era contributes a
21
+ * class string, and bits-ui hands that class straight to the `child` snippet's
22
+ * props, so the escape hatch keeps era's styling on the way out. It already
23
+ * worked at runtime — `child` rode in on restProps and bits-ui checks it before
24
+ * `children` — so the only thing stopping anyone was this type.
25
+ *
26
+ * A part where era renders chrome of its own takes {@link OwnedPartProps}.
27
+ */
28
+ export type PartProps<T> = WithoutChildren<T> & {
29
+ children?: Snippet;
30
+ };
31
+ /**
32
+ * Props for a bits-ui part whose markup era OWNS — a trigger that renders its
33
+ * own chevron, a content that renders its own overlay, a summary with a status
34
+ * spinner.
35
+ *
36
+ * `child` is withheld here, and the reason is that it cannot be honoured. The
37
+ * snippet replaces the element era was going to render, and era's extra markup
38
+ * lives INSIDE that element, so taking over rendering drops the chevron, the
39
+ * overlay or the indicator with no error and no visual clue — era's signature
40
+ * failure mode. Better to refuse it in the type than to accept it and quietly
41
+ * render something else.
42
+ *
43
+ * If you need the underlying element, reach for the primitive directly:
44
+ * `import { Select } from 'bits-ui'`. era wraps, it does not replace.
45
+ */
46
+ export type OwnedPartProps<T> = WithoutChildrenOrChild<T> & {
14
47
  children?: Snippet;
15
48
  };
16
49
  export { hotkeys, keys, parseKeybinding, matchKeyBindingPress, createKeybindingsHandler, formatKeybinding, isApplePlatform, modKey, type KeyBindingMap, type KeyBindingPress, type KeyBindingOptions } from './hotkeys.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "22.0.0",
3
+ "version": "23.0.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",