@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 +20 -0
- package/dist/generated-docs/llms-full.txt +14 -0
- package/dist/generated-docs/manifest.json +2 -2
- package/dist/generated-docs/utilities.json +1 -1
- package/dist/generated-docs/utilities.md +14 -0
- package/dist/styles/index.css +14 -0
- package/dist/ui/calendar/calendar-root.svelte.d.ts +1 -1
- package/dist/ui/cycle/cycle.svelte.d.ts +1 -1
- package/dist/ui/date-field/date-field-root.svelte.d.ts +1 -1
- package/dist/ui/date-picker/date-picker-root.svelte.d.ts +1 -1
- package/dist/ui/date-range-field/date-range-field-root.svelte.d.ts +1 -1
- package/dist/ui/date-range-picker/date-range-picker-root.svelte.d.ts +1 -1
- package/dist/ui/provider/axes-at.d.ts +16 -0
- package/dist/ui/provider/axes-at.js +65 -0
- package/dist/ui/provider/axis-relay.svelte.js +7 -20
- package/dist/ui/provider/index.d.ts +14 -0
- package/dist/ui/provider/index.js +14 -0
- package/dist/ui/time-field/time-field-root.svelte.d.ts +1 -1
- package/dist/ui/time-range-field/time-range-field-root.svelte.d.ts +1 -1
- package/package.json +4 -3
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":
|
|
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
|
package/dist/styles/index.css
CHANGED
|
@@ -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, {}, "
|
|
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, "
|
|
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, {}, "
|
|
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, {}, "
|
|
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, {}, "
|
|
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, {}, "
|
|
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 {
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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, {}, "
|
|
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, {}, "
|
|
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": "
|
|
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
|
},
|