@signal9/era-ui 29.0.0 → 30.0.1

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 CHANGED
@@ -1,3 +1,23 @@
1
+ ## [30.0.1](https://github.com/sig-nine/era-ui/compare/v30.0.0...v30.0.1) (2026-08-15)
2
+
3
+ ### Bug Fixes
4
+
5
+ * **provider:** export axesAt/carryAxes, and gate the class of bug that hid them ([59ac508](https://github.com/sig-nine/era-ui/commit/59ac50883fcf198a14bfcbfebf38bab3c06073ed))
6
+
7
+ ## [30.0.0](https://github.com/sig-nine/era-ui/compare/v29.0.0...v30.0.0) (2026-08-15)
8
+
9
+ ### ⚠ BREAKING CHANGES
10
+
11
+ * **notes:** createOverlayHost(from) now requires the element the overlay
12
+ belongs to. It is internal to the notes app, which is not published.
13
+
14
+ Claude-Session: https://claude.ai/code/session_011FjvULDrmxd61z2xUnYs6Z
15
+
16
+ ### Bug Fixes
17
+
18
+ * **apps:** an icon beside a label goes in lead, not in children ([15d0589](https://github.com/sig-nine/era-ui/commit/15d0589045929c11b4dd6a29ef4f8a570c9a0156))
19
+ * **notes:** the editor's overlay hosts carry the axes of the editor ([6a630c1](https://github.com/sig-nine/era-ui/commit/6a630c12d5847490305f279c73800e63094e5f8b))
20
+
1
21
  ## [29.0.0](https://github.com/sig-nine/era-ui/compare/v28.0.0...v29.0.0) (2026-08-15)
2
22
 
3
23
  ### ⚠ BREAKING CHANGES
@@ -453,6 +453,20 @@ padding, the chrome doesn't move, and the caret and selection follow the
453
453
  text. In em on purpose: the correction is a fraction of the type, so it
454
454
  holds across every density and text size with one value.
455
455
 
456
+ WHEN SOMEONE REPORTS A CORRECTLY-CENTRED FIELD AS "TOO HIGH", check the
457
+ STRING before the geometry. A placeholder like "Filter…" has no descender and
458
+ no cap, so its ink occupies only the x-height band — and a box that centres
459
+ that band exactly still reads high, because the eye centres on the mass it
460
+ can see and expects the space a descender would have filled. Measured at the
461
+ pill tier on the shipped mono stack, "Filter…" sits within half a pixel of
462
+ centre while looking a little high; the pill suite documents the same effect
463
+ from the other side (see OFF_BAND_LOW in ink-centering.spec.ts, where "soon"
464
+ cannot be symmetric and is bounded by sign instead of magnitude).
465
+
466
+ That is not a licence to ignore the report — era-ink-center IS wrong by
467
+ 1.5–2px on the sans and serif stacks (pinned in FIELD_KNOWN_SKEW) — only a
468
+ reason to measure the ink before touching the constant.
469
+
456
470
  Declared in `index.css`.
457
471
 
458
472
  ```css
@@ -52,7 +52,7 @@
52
52
  "slug": "utilities",
53
53
  "title": "Utilities",
54
54
  "summary": "The CSS classes era ships alongside the components — links, ink-centred text, shimmer, hidden scrollbars.",
55
- "tokenEstimate": 12389,
55
+ "tokenEstimate": 12608,
56
56
  "sections": [
57
57
  "Overview",
58
58
  "era-interactive",
@@ -1095,7 +1095,7 @@
1095
1095
  ],
1096
1096
  "file": "index.css",
1097
1097
  "useCase": "on a native single-line <input> (or the text layer that must mirror one) inside a fixed-height field — never alongside era-text-trim.",
1098
- "description": "The EDITABLE counterpart of era-text-trim — for native <input>s, which the\ntrim is forbidden on (it collapses the UA's inner editor box and slices the\nglyphs; see typography/collapsed-text-trim).\n\nWithout help, a tier-height input centres its text by FONT BOX, and the ui\nstacks' font box is deeply asymmetric: ascent ≈1.07em against descent\n≈0.29em. Centring that box parks the visible band (caps, x-height) ~0.115em\nLOW — so a field's text sat visibly below the trimmed label beside it.\n\nThe value is OPTICAL, not the derivation's: pure x-band centring computes\nto ~0.23em of padding, but pixel-measured against real Title-Case text it\nover-lifts — the eye weighs the cap-to-baseline mass, which x-band\ncentring parks ~2px high (caps 2.75px off the top against 5.7px of air\nunder the baseline, measured at dense). 0.08em splits the difference\nbetween the x-band ideal and the cap-band ideal, landing caps ~3.7px in an\n18px field with ~4.7px under the baseline — the reading where neither\nband's partisans win and neither complains.\n\nThe lift is padding, not a translateY: an icon-less Input paints the field\nchrome itself, and a transform would drag the field along with the ink.\npadding-bottom shrinks the content box from below, and a single-line input\nre-centres its line box in what remains — the ink rises by exactly half the\npadding, the chrome doesn't move, and the caret and selection follow the\ntext. In em on purpose: the correction is a fraction of the type, so it\nholds across every density and text size with one value.",
1098
+ "description": "The EDITABLE counterpart of era-text-trim — for native <input>s, which the\ntrim is forbidden on (it collapses the UA's inner editor box and slices the\nglyphs; see typography/collapsed-text-trim).\n\nWithout help, a tier-height input centres its text by FONT BOX, and the ui\nstacks' font box is deeply asymmetric: ascent ≈1.07em against descent\n≈0.29em. Centring that box parks the visible band (caps, x-height) ~0.115em\nLOW — so a field's text sat visibly below the trimmed label beside it.\n\nThe value is OPTICAL, not the derivation's: pure x-band centring computes\nto ~0.23em of padding, but pixel-measured against real Title-Case text it\nover-lifts — the eye weighs the cap-to-baseline mass, which x-band\ncentring parks ~2px high (caps 2.75px off the top against 5.7px of air\nunder the baseline, measured at dense). 0.08em splits the difference\nbetween the x-band ideal and the cap-band ideal, landing caps ~3.7px in an\n18px field with ~4.7px under the baseline — the reading where neither\nband's partisans win and neither complains.\n\nThe lift is padding, not a translateY: an icon-less Input paints the field\nchrome itself, and a transform would drag the field along with the ink.\npadding-bottom shrinks the content box from below, and a single-line input\nre-centres its line box in what remains — the ink rises by exactly half the\npadding, the chrome doesn't move, and the caret and selection follow the\ntext. In em on purpose: the correction is a fraction of the type, so it\nholds across every density and text size with one value.\n\nWHEN SOMEONE REPORTS A CORRECTLY-CENTRED FIELD AS \"TOO HIGH\", check the\nSTRING before the geometry. A placeholder like \"Filter…\" has no descender and\nno cap, so its ink occupies only the x-height band — and a box that centres\nthat band exactly still reads high, because the eye centres on the mass it\ncan see and expects the space a descender would have filled. Measured at the\npill tier on the shipped mono stack, \"Filter…\" sits within half a pixel of\ncentre while looking a little high; the pill suite documents the same effect\nfrom the other side (see OFF_BAND_LOW in ink-centering.spec.ts, where \"soon\"\ncannot be symmetric and is bounded by sign instead of magnitude).\n\nThat is not a licence to ignore the report — era-ink-center IS wrong by\n1.5–2px on the sans and serif stacks (pinned in FIELD_KNOWN_SKEW) — only a\nreason to measure the ink before touching the constant.",
1099
1099
  "css": "@utility era-ink-center {\n\tpadding-bottom: 0.08em;\n}"
1100
1100
  },
1101
1101
  {
@@ -67,7 +67,7 @@
67
67
  ],
68
68
  "file": "index.css",
69
69
  "useCase": "on a native single-line <input> (or the text layer that must mirror one) inside a fixed-height field — never alongside era-text-trim.",
70
- "description": "The EDITABLE counterpart of era-text-trim — for native <input>s, which the\ntrim is forbidden on (it collapses the UA's inner editor box and slices the\nglyphs; see typography/collapsed-text-trim).\n\nWithout help, a tier-height input centres its text by FONT BOX, and the ui\nstacks' font box is deeply asymmetric: ascent ≈1.07em against descent\n≈0.29em. Centring that box parks the visible band (caps, x-height) ~0.115em\nLOW — so a field's text sat visibly below the trimmed label beside it.\n\nThe value is OPTICAL, not the derivation's: pure x-band centring computes\nto ~0.23em of padding, but pixel-measured against real Title-Case text it\nover-lifts — the eye weighs the cap-to-baseline mass, which x-band\ncentring parks ~2px high (caps 2.75px off the top against 5.7px of air\nunder the baseline, measured at dense). 0.08em splits the difference\nbetween the x-band ideal and the cap-band ideal, landing caps ~3.7px in an\n18px field with ~4.7px under the baseline — the reading where neither\nband's partisans win and neither complains.\n\nThe lift is padding, not a translateY: an icon-less Input paints the field\nchrome itself, and a transform would drag the field along with the ink.\npadding-bottom shrinks the content box from below, and a single-line input\nre-centres its line box in what remains — the ink rises by exactly half the\npadding, the chrome doesn't move, and the caret and selection follow the\ntext. In em on purpose: the correction is a fraction of the type, so it\nholds across every density and text size with one value.",
70
+ "description": "The EDITABLE counterpart of era-text-trim — for native <input>s, which the\ntrim is forbidden on (it collapses the UA's inner editor box and slices the\nglyphs; see typography/collapsed-text-trim).\n\nWithout help, a tier-height input centres its text by FONT BOX, and the ui\nstacks' font box is deeply asymmetric: ascent ≈1.07em against descent\n≈0.29em. Centring that box parks the visible band (caps, x-height) ~0.115em\nLOW — so a field's text sat visibly below the trimmed label beside it.\n\nThe value is OPTICAL, not the derivation's: pure x-band centring computes\nto ~0.23em of padding, but pixel-measured against real Title-Case text it\nover-lifts — the eye weighs the cap-to-baseline mass, which x-band\ncentring parks ~2px high (caps 2.75px off the top against 5.7px of air\nunder the baseline, measured at dense). 0.08em splits the difference\nbetween the x-band ideal and the cap-band ideal, landing caps ~3.7px in an\n18px field with ~4.7px under the baseline — the reading where neither\nband's partisans win and neither complains.\n\nThe lift is padding, not a translateY: an icon-less Input paints the field\nchrome itself, and a transform would drag the field along with the ink.\npadding-bottom shrinks the content box from below, and a single-line input\nre-centres its line box in what remains — the ink rises by exactly half the\npadding, the chrome doesn't move, and the caret and selection follow the\ntext. In em on purpose: the correction is a fraction of the type, so it\nholds across every density and text size with one value.\n\nWHEN SOMEONE REPORTS A CORRECTLY-CENTRED FIELD AS \"TOO HIGH\", check the\nSTRING before the geometry. A placeholder like \"Filter…\" has no descender and\nno cap, so its ink occupies only the x-height band — and a box that centres\nthat band exactly still reads high, because the eye centres on the mass it\ncan see and expects the space a descender would have filled. Measured at the\npill tier on the shipped mono stack, \"Filter…\" sits within half a pixel of\ncentre while looking a little high; the pill suite documents the same effect\nfrom the other side (see OFF_BAND_LOW in ink-centering.spec.ts, where \"soon\"\ncannot be symmetric and is bounded by sign instead of magnitude).\n\nThat is not a licence to ignore the report — era-ink-center IS wrong by\n1.5–2px on the sans and serif stacks (pinned in FIELD_KNOWN_SKEW) — only a\nreason to measure the ink before touching the constant.",
71
71
  "css": "@utility era-ink-center {\n\tpadding-bottom: 0.08em;\n}"
72
72
  },
73
73
  {
@@ -304,6 +304,20 @@ padding, the chrome doesn't move, and the caret and selection follow the
304
304
  text. In em on purpose: the correction is a fraction of the type, so it
305
305
  holds across every density and text size with one value.
306
306
 
307
+ WHEN SOMEONE REPORTS A CORRECTLY-CENTRED FIELD AS "TOO HIGH", check the
308
+ STRING before the geometry. A placeholder like "Filter…" has no descender and
309
+ no cap, so its ink occupies only the x-height band — and a box that centres
310
+ that band exactly still reads high, because the eye centres on the mass it
311
+ can see and expects the space a descender would have filled. Measured at the
312
+ pill tier on the shipped mono stack, "Filter…" sits within half a pixel of
313
+ centre while looking a little high; the pill suite documents the same effect
314
+ from the other side (see OFF_BAND_LOW in ink-centering.spec.ts, where "soon"
315
+ cannot be symmetric and is bounded by sign instead of magnitude).
316
+
317
+ That is not a licence to ignore the report — era-ink-center IS wrong by
318
+ 1.5–2px on the sans and serif stacks (pinned in FIELD_KNOWN_SKEW) — only a
319
+ reason to measure the ink before touching the constant.
320
+
307
321
  Declared in `index.css`.
308
322
 
309
323
  ```css
@@ -308,6 +308,20 @@
308
308
  * text. In em on purpose: the correction is a fraction of the type, so it
309
309
  * holds across every density and text size with one value.
310
310
  *
311
+ * WHEN SOMEONE REPORTS A CORRECTLY-CENTRED FIELD AS "TOO HIGH", check the
312
+ * STRING before the geometry. A placeholder like "Filter…" has no descender and
313
+ * no cap, so its ink occupies only the x-height band — and a box that centres
314
+ * that band exactly still reads high, because the eye centres on the mass it
315
+ * can see and expects the space a descender would have filled. Measured at the
316
+ * pill tier on the shipped mono stack, "Filter…" sits within half a pixel of
317
+ * centre while looking a little high; the pill suite documents the same effect
318
+ * from the other side (see OFF_BAND_LOW in ink-centering.spec.ts, where "soon"
319
+ * cannot be symmetric and is bounded by sign instead of magnitude).
320
+ *
321
+ * That is not a licence to ignore the report — era-ink-center IS wrong by
322
+ * 1.5–2px on the sans and serif stacks (pinned in FIELD_KNOWN_SKEW) — only a
323
+ * reason to measure the ink before touching the constant.
324
+ *
311
325
  * @use on a native single-line <input> (or the text layer that must mirror
312
326
  * one) inside a fixed-height field — never alongside era-text-trim. */
313
327
  @utility era-ink-center {
@@ -1,4 +1,4 @@
1
1
  import { Calendar } from 'bits-ui';
2
- declare const CalendarRoot: import("svelte").Component<Calendar.RootProps, {}, "placeholder" | "value" | "ref">;
2
+ declare const CalendarRoot: import("svelte").Component<Calendar.RootProps, {}, "value" | "placeholder" | "ref">;
3
3
  type CalendarRoot = ReturnType<typeof CalendarRoot>;
4
4
  export default CalendarRoot;
@@ -3,7 +3,7 @@ import type { Snippet } from 'svelte';
3
3
  import { type ButtonVariants } from '../button/index.js';
4
4
  import type { CycleOption } from './types.js';
5
5
  declare function $$render<T extends string>(): {
6
- props: Omit<HTMLButtonAttributes, "children" | "value"> & {
6
+ props: Omit<HTMLButtonAttributes, "value" | "children"> & {
7
7
  ref?: HTMLElement | null;
8
8
  } & Omit<ButtonVariants, "active"> & {
9
9
  /** The stops, in cycle order. Two or more. */
@@ -1,3 +1,3 @@
1
- declare const DateFieldRoot: import("svelte").Component<import("bits-ui").DateFieldRootPropsWithoutHTML, {}, "placeholder" | "value">;
1
+ declare const DateFieldRoot: import("svelte").Component<import("bits-ui").DateFieldRootPropsWithoutHTML, {}, "value" | "placeholder">;
2
2
  type DateFieldRoot = ReturnType<typeof DateFieldRoot>;
3
3
  export default DateFieldRoot;
@@ -1,3 +1,3 @@
1
- declare const DatePickerRoot: import("svelte").Component<import("bits-ui").DatePickerRootPropsWithoutHTML, {}, "placeholder" | "value" | "open">;
1
+ declare const DatePickerRoot: import("svelte").Component<import("bits-ui").DatePickerRootPropsWithoutHTML, {}, "value" | "placeholder" | "open">;
2
2
  type DatePickerRoot = ReturnType<typeof DatePickerRoot>;
3
3
  export default DatePickerRoot;
@@ -1,4 +1,4 @@
1
1
  import { DateRangeField } from 'bits-ui';
2
- declare const DateRangeFieldRoot: import("svelte").Component<DateRangeField.RootProps, {}, "placeholder" | "value" | "ref">;
2
+ declare const DateRangeFieldRoot: import("svelte").Component<DateRangeField.RootProps, {}, "value" | "placeholder" | "ref">;
3
3
  type DateRangeFieldRoot = ReturnType<typeof DateRangeFieldRoot>;
4
4
  export default DateRangeFieldRoot;
@@ -1,4 +1,4 @@
1
1
  import { DateRangePicker } from 'bits-ui';
2
- declare const DateRangePickerRoot: import("svelte").Component<DateRangePicker.RootProps, {}, "placeholder" | "value" | "open">;
2
+ declare const DateRangePickerRoot: import("svelte").Component<DateRangePicker.RootProps, {}, "value" | "placeholder" | "open">;
3
3
  type DateRangePickerRoot = ReturnType<typeof DateRangePickerRoot>;
4
4
  export default DateRangePickerRoot;
@@ -0,0 +1,16 @@
1
+ export type Axes = {
2
+ /** Attributes to stamp on the receiving element. */
3
+ attrs: Record<string, string>;
4
+ /** `dark` / `light` when a scoped one is in force, else undefined. */
5
+ scheme?: 'dark' | 'light';
6
+ };
7
+ /** Read the axes governing `el`. Safe on null and on a detached node. */
8
+ export declare function axesAt(el: Element | null | undefined): Axes;
9
+ /**
10
+ * Stamp the axes governing `from` onto `to`.
11
+ *
12
+ * For hosts built by hand rather than rendered by a component, where there is
13
+ * no context to relay through and no markup to spread onto — the host has no
14
+ * other way to know where it logically belongs.
15
+ */
16
+ export declare function carryAxes(to: HTMLElement, from: Element | null | undefined): void;
@@ -0,0 +1,65 @@
1
+ import { axisKeys, axisRegistry } from './axes.js';
2
+ /**
3
+ * THE AXES IN FORCE AT AN ELEMENT — the one implementation of that question.
4
+ *
5
+ * Anything that moves content to `document.body` has to answer it, because
6
+ * everything era styles with is inherited: the density ladder and roundness
7
+ * scale are custom properties on `*`, a surface re-baselines a token contract,
8
+ * the font axis publishes an inherited stack. Content that leaves its subtree
9
+ * stops inheriting from it and starts inheriting from `:root`.
10
+ *
11
+ * AxisRelay had this logic and could not share it. It lives in a `.svelte.ts`
12
+ * module because it holds `$state`, so a plain `.ts` consumer — the notes
13
+ * editor's overlay hosts, which are created by hand rather than by a component
14
+ * — could not import it without pulling runes into a non-rune module. So the
15
+ * notes app grew its own copy, and the copy was never written: the hosts simply
16
+ * did not carry the axes, and an editor inside `data-surface="flat"` opened a
17
+ * bevelled slash menu and bubble toolbar. Reported by signalnine.org, running
18
+ * flat demos inside a bevel site.
19
+ *
20
+ * Runes-free on purpose, for exactly the reason ancestor-path.ts is: the moment
21
+ * a shared truth can only be imported by components, the non-component callers
22
+ * reimplement it or skip it.
23
+ *
24
+ * NEAREST ANCESTOR PER AXIS, INDEPENDENTLY. A subtree can override density
25
+ * without touching surface, and `closest()` answers each question separately —
26
+ * which is what makes the axes orthogonal rather than a bundle.
27
+ */
28
+ /** Theme is not an axis in the registry (no picker contract), but it inherits
29
+ * the same way and breaks across a portal the same way. */
30
+ const THEME_ATTR = 'data-theme';
31
+ /** The colour-scheme pair is a CLASS, not an attribute, so it travels apart. */
32
+ const SCHEME_CLASSES = ['dark', 'light'];
33
+ /** Read the axes governing `el`. Safe on null and on a detached node. */
34
+ export function axesAt(el) {
35
+ if (!el?.closest)
36
+ return { attrs: {} };
37
+ const attrs = {};
38
+ for (const key of axisKeys) {
39
+ const attr = axisRegistry[key].attr;
40
+ const value = el.closest(`[${attr}]`)?.getAttribute(attr);
41
+ if (value)
42
+ attrs[attr] = value;
43
+ }
44
+ const theme = el.closest(`[${THEME_ATTR}]`)?.getAttribute(THEME_ATTR);
45
+ if (theme)
46
+ attrs[THEME_ATTR] = theme;
47
+ const schemeHost = el.closest('.dark, .light');
48
+ const scheme = SCHEME_CLASSES.find((c) => schemeHost?.classList.contains(c));
49
+ return { attrs, scheme };
50
+ }
51
+ /**
52
+ * Stamp the axes governing `from` onto `to`.
53
+ *
54
+ * For hosts built by hand rather than rendered by a component, where there is
55
+ * no context to relay through and no markup to spread onto — the host has no
56
+ * other way to know where it logically belongs.
57
+ */
58
+ export function carryAxes(to, from) {
59
+ const { attrs, scheme } = axesAt(from);
60
+ for (const [attr, value] of Object.entries(attrs))
61
+ to.setAttribute(attr, value);
62
+ to.classList.remove(...SCHEME_CLASSES);
63
+ if (scheme)
64
+ to.classList.add(scheme);
65
+ }
@@ -1,5 +1,5 @@
1
1
  import { getContext, hasContext, setContext } from 'svelte';
2
- import { axisKeys, axisRegistry } from './axes.js';
2
+ import { axesAt } from './axes-at.js';
3
3
  /*
4
4
  * CARRYING THE AXES ACROSS A PORTAL.
5
5
  *
@@ -39,9 +39,6 @@ import { axisKeys, axisRegistry } from './axes.js';
39
39
  * absence is a null, not a crash.
40
40
  */
41
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
42
  export class AxisRelay {
46
43
  /** Attributes to stamp on portalled content. Empty until a capture runs. */
47
44
  attrs = $state({});
@@ -71,22 +68,12 @@ export class AxisRelay {
71
68
  const el = this.#anchor;
72
69
  if (!el?.closest)
73
70
  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;
71
+ // The reading itself lives in axes-at.ts, runes-free, because the notes
72
+ // editor's hand-built overlay hosts need the same answer from a plain .ts
73
+ // module and cannot import this one. They had gone without it.
74
+ const { attrs, scheme } = axesAt(el);
75
+ this.attrs = attrs;
76
+ this.scheme = scheme;
90
77
  }
91
78
  /** The class string portalled content should carry. */
92
79
  get schemeClass() {
@@ -4,3 +4,17 @@ export { corners, defaultCorners, type Corners } from './modes.js';
4
4
  export { fonts, defaultFont, type Font } from './modes.js';
5
5
  export { motions, defaultMotion, type Motion } from './modes.js';
6
6
  export { axisRegistry, axisKeys, type AxisKey, type AxisMeta, type AxisLevel } from './axes.js';
7
+ /**
8
+ * Reading the axes at a node, for anything that moves content to document.body
9
+ * outside era's own components — a hand-built overlay host, a canvas layer, an
10
+ * editor's floating chrome. era's portalling components go through AxisRelay;
11
+ * these have no context to relay through and no markup to spread onto.
12
+ *
13
+ * Exported because it shipped in the tarball reachable from nothing. `./*`
14
+ * resolves to a directory's index.js, so a module the index does not re-export
15
+ * is published and unimportable — a consumer measured it at runtime, got 13
16
+ * names, and correctly refused to deep-import past the exports map to reach the
17
+ * fourteenth. They kept a thirty-line copy instead, which is the right call and
18
+ * exactly the duplication axes-at.ts was extracted to prevent.
19
+ */
20
+ export { axesAt, carryAxes, type Axes } from './axes-at.js';
@@ -4,3 +4,17 @@ export { corners, defaultCorners } from './modes.js';
4
4
  export { fonts, defaultFont } from './modes.js';
5
5
  export { motions, defaultMotion } from './modes.js';
6
6
  export { axisRegistry, axisKeys } from './axes.js';
7
+ /**
8
+ * Reading the axes at a node, for anything that moves content to document.body
9
+ * outside era's own components — a hand-built overlay host, a canvas layer, an
10
+ * editor's floating chrome. era's portalling components go through AxisRelay;
11
+ * these have no context to relay through and no markup to spread onto.
12
+ *
13
+ * Exported because it shipped in the tarball reachable from nothing. `./*`
14
+ * resolves to a directory's index.js, so a module the index does not re-export
15
+ * is published and unimportable — a consumer measured it at runtime, got 13
16
+ * names, and correctly refused to deep-import past the exports map to reach the
17
+ * fourteenth. They kept a thirty-line copy instead, which is the right call and
18
+ * exactly the duplication axes-at.ts was extracted to prevent.
19
+ */
20
+ export { axesAt, carryAxes } from './axes-at.js';
@@ -1,4 +1,4 @@
1
1
  import { TimeField } from 'bits-ui';
2
- declare const TimeFieldRoot: import("svelte").Component<TimeField.RootProps, {}, "placeholder" | "value">;
2
+ declare const TimeFieldRoot: import("svelte").Component<TimeField.RootProps, {}, "value" | "placeholder">;
3
3
  type TimeFieldRoot = ReturnType<typeof TimeFieldRoot>;
4
4
  export default TimeFieldRoot;
@@ -1,4 +1,4 @@
1
1
  import { TimeRangeField } from 'bits-ui';
2
- declare const TimeRangeFieldRoot: import("svelte").Component<TimeRangeField.RootProps, {}, "placeholder" | "value" | "ref">;
2
+ declare const TimeRangeFieldRoot: import("svelte").Component<TimeRangeField.RootProps, {}, "value" | "placeholder" | "ref">;
3
3
  type TimeRangeFieldRoot = ReturnType<typeof TimeRangeFieldRoot>;
4
4
  export default TimeRangeFieldRoot;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "29.0.0",
3
+ "version": "30.0.1",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",
@@ -25,14 +25,15 @@
25
25
  "check:classes": "node --experimental-strip-types scripts/check-classes.ts",
26
26
  "check:twmerge": "node --experimental-strip-types scripts/check-twmerge.ts",
27
27
  "build:llm-docs": "node --experimental-strip-types scripts/build-llm-docs.ts",
28
- "prebuild": "npm run check:surfaces && npm run check:tokens && npm run check:routes && npm run check:canonical && npm run check:layers && npm run check:themes && npm run check:classes && npm run check:twmerge && npm run check:doc-tokens && npm run check:audit-controls && npm run check:specimen-coverage && npm run check:eslint-rules && npm run check:install && npm run check:double-edge && npm run build:llm-docs",
29
- "validate": "npm run check && npm run lint && npm run format:check && npm run check:surfaces && npm run check:tokens && npm run check:routes && npm run check:canonical && npm run check:layers && npm run check:themes && npm run check:classes && npm run check:twmerge && npm run check:doc-tokens && npm run check:audit-controls && npm run check:specimen-coverage && npm run check:eslint-rules && npm run check:install && npm run check:double-edge",
28
+ "prebuild": "npm run check:surfaces && npm run check:tokens && npm run check:routes && npm run check:canonical && npm run check:layers && npm run check:themes && npm run check:classes && npm run check:twmerge && npm run check:doc-tokens && npm run check:audit-controls && npm run check:specimen-coverage && npm run check:eslint-rules && npm run check:install && npm run check:reachable && npm run check:double-edge && npm run build:llm-docs",
29
+ "validate": "npm run check && npm run lint && npm run format:check && npm run check:surfaces && npm run check:tokens && npm run check:routes && npm run check:canonical && npm run check:layers && npm run check:themes && npm run check:classes && npm run check:twmerge && npm run check:doc-tokens && npm run check:audit-controls && npm run check:specimen-coverage && npm run check:eslint-rules && npm run check:install && npm run check:reachable && npm run check:double-edge",
30
30
  "test": "npm run test:visual",
31
31
  "check:doc-tokens": "node --experimental-strip-types scripts/check-doc-tokens.ts",
32
32
  "check:audit-controls": "node --experimental-strip-types scripts/check-audit-controls.ts",
33
33
  "check:specimen-coverage": "node --experimental-strip-types scripts/check-specimen-coverage.ts",
34
34
  "check:eslint-rules": "node --experimental-strip-types scripts/check-eslint-rules.ts",
35
35
  "check:install": "node --experimental-strip-types scripts/check-install.ts",
36
+ "check:reachable": "node --experimental-strip-types scripts/check-reachable.ts",
36
37
  "check:double-edge": "node --experimental-strip-types scripts/check-double-edge.ts",
37
38
  "verify:published": "node scripts/verify-published.mjs"
38
39
  },