@signal9/era-ui 30.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,9 @@
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
+
1
7
  ## [30.0.0](https://github.com/sig-nine/era-ui/compare/v29.0.0...v30.0.0) (2026-08-15)
2
8
 
3
9
  ### ⚠ 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;
@@ -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": "30.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
  },