@signal9/era-ui 29.0.0 → 30.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [30.0.0](https://github.com/sig-nine/era-ui/compare/v29.0.0...v30.0.0) (2026-08-15)
|
|
2
|
+
|
|
3
|
+
### ⚠ BREAKING CHANGES
|
|
4
|
+
|
|
5
|
+
* **notes:** createOverlayHost(from) now requires the element the overlay
|
|
6
|
+
belongs to. It is internal to the notes app, which is not published.
|
|
7
|
+
|
|
8
|
+
Claude-Session: https://claude.ai/code/session_011FjvULDrmxd61z2xUnYs6Z
|
|
9
|
+
|
|
10
|
+
### Bug Fixes
|
|
11
|
+
|
|
12
|
+
* **apps:** an icon beside a label goes in lead, not in children ([15d0589](https://github.com/sig-nine/era-ui/commit/15d0589045929c11b4dd6a29ef4f8a570c9a0156))
|
|
13
|
+
* **notes:** the editor's overlay hosts carry the axes of the editor ([6a630c1](https://github.com/sig-nine/era-ui/commit/6a630c12d5847490305f279c73800e63094e5f8b))
|
|
14
|
+
|
|
1
15
|
## [29.0.0](https://github.com/sig-nine/era-ui/compare/v28.0.0...v29.0.0) (2026-08-15)
|
|
2
16
|
|
|
3
17
|
### ⚠ BREAKING CHANGES
|
|
@@ -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() {
|