@glissade/backend-dom 0.23.0-pre.0 → 0.23.0-pre.2
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/README.md +35 -8
- package/dist/index.d.ts +15 -0
- package/dist/index.js +24 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -136,11 +136,38 @@ base bundle is absent or a different version (never a cryptic `undefined`).
|
|
|
136
136
|
The backend only ever manages its **own root** subtree — your overlay/foreign DOM
|
|
137
137
|
in the host element is left untouched.
|
|
138
138
|
|
|
139
|
-
##
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
`
|
|
139
|
+
## Accessibility & theming (S4)
|
|
140
|
+
|
|
141
|
+
The DOM tier exists for editing + a11y, so it keeps the **real text** readable by
|
|
142
|
+
assistive tech and hides the decorative geometry:
|
|
143
|
+
|
|
144
|
+
- **Shape `<svg>` islands and `<img>` elements are `aria-hidden`** — a screen
|
|
145
|
+
reader reads the Text divs (the meaningful content), not the paths.
|
|
146
|
+
- **`ariaLabel`** names the whole graphic: the root gets `role="figure"` +
|
|
147
|
+
`aria-label` (a labeled region whose readable text stays exposed — unlike
|
|
148
|
+
`role="img"`, which would hide the text). Unset ⇒ a generic container.
|
|
149
|
+
- **Focus order** is the host/editor's to manage — every node carries
|
|
150
|
+
`data-node-id`, so an editor decides which nodes are focusable (`tabindex`)
|
|
151
|
+
rather than the backend imposing a tab order on every shape.
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
const backend = new DomBackend(stage, {
|
|
155
|
+
ariaLabel: 'Episode 1 cold open',
|
|
156
|
+
cssColorVars: true, // emit colors as var(--gs-c-…, color) for re-theming
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**CSS-variable theming** (`cssColorVars`): solid fills/text colors emit as
|
|
161
|
+
`var(--gs-c-<ident>, <color>)`, so a host can re-theme (light/dark, brand) by
|
|
162
|
+
overriding the `--gs-c-*` variables in CSS — the browser re-paints with **no
|
|
163
|
+
re-render**. Off by default (literal colors; byte-stable for existing consumers).
|
|
164
|
+
Gradient stops are not yet varied (a follow-up).
|
|
165
|
+
|
|
166
|
+
## Stages
|
|
167
|
+
|
|
168
|
+
S2 (forward render) + **S3** (the retained-DOM reconciler — reuse + patch per
|
|
169
|
+
`data-node-id` so inline-edit caret / selection / focus / listeners survive a
|
|
170
|
+
re-render) + the 0.22 structural hardening (shape-sized SVG islands, rounded-rect
|
|
171
|
+
fill, movement-boundary reconcile, `onReflow` font re-wrap) + **S4** (a11y +
|
|
172
|
+
CSS-native theming, above) have all shipped. The real-browser visual-smoke gate
|
|
173
|
+
runs in the consumer canaries' standing harnesses. See `docs/design/dom-backend.md`.
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,21 @@ interface DomBackendOptions {
|
|
|
13
13
|
* draw loop. No-op where `document.fonts` is absent (e.g. jsdom).
|
|
14
14
|
*/
|
|
15
15
|
onReflow?: () => void;
|
|
16
|
+
/**
|
|
17
|
+
* S4 a11y: an accessible name for the whole rendered graphic. When set, the
|
|
18
|
+
* root gets `role="figure"` + `aria-label` (a labeled region whose readable
|
|
19
|
+
* text stays in the a11y tree). Decorative geometry (the SVG shape islands +
|
|
20
|
+
* images) is always `aria-hidden`, so a screen reader reads the Text, not the
|
|
21
|
+
* paths. Unset ⇒ the root carries no role (a generic container).
|
|
22
|
+
*/
|
|
23
|
+
ariaLabel?: string;
|
|
24
|
+
/**
|
|
25
|
+
* S4 theming: emit solid colors as CSS custom properties — `var(--gs-c-<ident>,
|
|
26
|
+
* <color>)` — so a host can re-theme (light/dark, brand) by overriding the
|
|
27
|
+
* `--gs-c-*` variables in CSS, **without a re-render** (the browser re-paints).
|
|
28
|
+
* Default off ⇒ literal colors (byte-stable for existing consumers).
|
|
29
|
+
*/
|
|
30
|
+
cssColorVars?: boolean;
|
|
16
31
|
}
|
|
17
32
|
/**
|
|
18
33
|
* A DOM/SVG `RenderBackend`. Construct with a host element (renders into it) or a
|
package/dist/index.js
CHANGED
|
@@ -158,6 +158,8 @@ var DomBackend = class {
|
|
|
158
158
|
#owned = /* @__PURE__ */ new WeakMap();
|
|
159
159
|
#ids = [];
|
|
160
160
|
#onReflow;
|
|
161
|
+
/** S4: wrap solid colors in `var(--gs-c-…, color)` so a host re-themes via CSS. */
|
|
162
|
+
#cssColorVars;
|
|
161
163
|
#measureSpan = null;
|
|
162
164
|
#warnedMeasure = false;
|
|
163
165
|
#warnedMesh = false;
|
|
@@ -172,8 +174,13 @@ var DomBackend = class {
|
|
|
172
174
|
this.root.setAttribute("data-gs-dom", "");
|
|
173
175
|
this.root.style.position = "relative";
|
|
174
176
|
this.root.style.overflow = "hidden";
|
|
177
|
+
if (opts.ariaLabel !== void 0) {
|
|
178
|
+
this.root.setAttribute("role", "figure");
|
|
179
|
+
this.root.setAttribute("aria-label", opts.ariaLabel);
|
|
180
|
+
}
|
|
175
181
|
if (this.#host) this.#host.appendChild(this.root);
|
|
176
182
|
this.#onReflow = opts.onReflow;
|
|
183
|
+
this.#cssColorVars = opts.cssColorVars ?? false;
|
|
177
184
|
this.#wireFontReflow();
|
|
178
185
|
}
|
|
179
186
|
/**
|
|
@@ -236,6 +243,7 @@ var DomBackend = class {
|
|
|
236
243
|
const svg = doc.createElementNS(SVG_NS, "svg");
|
|
237
244
|
svg.setAttribute("width", "0");
|
|
238
245
|
svg.setAttribute("height", "0");
|
|
246
|
+
svg.setAttribute("aria-hidden", "true");
|
|
239
247
|
svg.style.position = "absolute";
|
|
240
248
|
svg.style.left = "0";
|
|
241
249
|
svg.style.top = "0";
|
|
@@ -397,6 +405,8 @@ var DomBackend = class {
|
|
|
397
405
|
const img = doc.createElement("img");
|
|
398
406
|
img.style.position = "absolute";
|
|
399
407
|
img.style.objectFit = "fill";
|
|
408
|
+
img.setAttribute("alt", "");
|
|
409
|
+
img.setAttribute("aria-hidden", "true");
|
|
400
410
|
return {
|
|
401
411
|
op: "drawImage",
|
|
402
412
|
el: img,
|
|
@@ -716,9 +726,19 @@ var DomBackend = class {
|
|
|
716
726
|
if (a && typeof a === "object" && "src" in a && typeof a.src === "string") return a.src;
|
|
717
727
|
}
|
|
718
728
|
#solid(paint) {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
729
|
+
const color = paint.kind === "color" ? paint.color : paint.kind === "mesh" ? paint.bg ?? paint.points[0]?.color ?? "#000" : paint.stops[0]?.color ?? "#000";
|
|
730
|
+
return this.#themeColor(color);
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* S4 theming: when `cssColorVars` is on, wrap a literal color in a CSS custom
|
|
734
|
+
* property `var(--gs-c-<ident>, <color>)` so a host re-themes by overriding the
|
|
735
|
+
* `--gs-c-*` vars in CSS — no re-render (the browser re-paints). Off ⇒ the
|
|
736
|
+
* literal color (byte-stable). The ident is the color with non-ident runs
|
|
737
|
+
* collapsed to '-' (`'#89b4fa'` → `89b4fa`, `'rgb(1,2,3)'` → `rgb-1-2-3`).
|
|
738
|
+
*/
|
|
739
|
+
#themeColor(color) {
|
|
740
|
+
if (!this.#cssColorVars) return color;
|
|
741
|
+
return `var(--gs-c-${color.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "")}, ${color})`;
|
|
722
742
|
}
|
|
723
743
|
/** A signature of the gradient paint — a `<defs>` subtree is rebuilt only when
|
|
724
744
|
* this changes (kind / coords / stops), avoiding per-frame churn. */
|
|
@@ -731,7 +751,7 @@ var DomBackend = class {
|
|
|
731
751
|
#resolvePaint(paint, o, scope, key) {
|
|
732
752
|
if (paint.kind === "color") {
|
|
733
753
|
this.#setAttr(o.path, o, "dataApprox", "data-approx", void 0);
|
|
734
|
-
return paint.color;
|
|
754
|
+
return this.#themeColor(paint.color);
|
|
735
755
|
}
|
|
736
756
|
if (paint.kind === "mesh") {
|
|
737
757
|
this.#setAttr(o.path, o, "dataApprox", "data-approx", "true");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/backend-dom",
|
|
3
|
-
"version": "0.23.0-pre.
|
|
3
|
+
"version": "0.23.0-pre.2",
|
|
4
4
|
"description": "glissade DOM render backend: DisplayList -> HTML/SVG elements. A preview / non-parity realtime tier (accessibility, selectable text, CSS-native embedding) — NOT a Skia-export twin.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"engines": {
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@glissade/core": "0.23.0-pre.
|
|
22
|
-
"@glissade/scene": "0.23.0-pre.
|
|
21
|
+
"@glissade/core": "0.23.0-pre.2",
|
|
22
|
+
"@glissade/scene": "0.23.0-pre.2"
|
|
23
23
|
},
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|