@xenosystem/generate 0.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/LICENSE +18 -0
- package/README.md +33 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/manifest.d.ts +27 -0
- package/dist/manifest.js +20 -0
- package/dist/scene.d.ts +69 -0
- package/dist/scene.js +99 -0
- package/dist/svg.d.ts +19 -0
- package/dist/svg.js +50 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Copyright (c) 2026 XENO Corporation. All rights reserved.
|
|
2
|
+
|
|
3
|
+
PROPRIETARY AND CONFIDENTIAL.
|
|
4
|
+
|
|
5
|
+
This software and its source code are the confidential and proprietary property
|
|
6
|
+
of XENO Corporation. No licence, express or implied, is granted to any person to
|
|
7
|
+
use, copy, modify, merge, publish, distribute, sublicense, or sell copies of this
|
|
8
|
+
software, in whole or in part, without the prior written permission of XENO
|
|
9
|
+
Corporation.
|
|
10
|
+
|
|
11
|
+
Unauthorised copying of this file, via any medium, is strictly prohibited.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
17
|
+
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
18
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @xenosystem/generate
|
|
2
|
+
|
|
3
|
+
**Declarations β per-platform artifacts**
|
|
4
|
+
|
|
5
|
+
> Status: π΅ **planned** β see [`../../SPEC.md`](../../SPEC.md) Β§13 for the build order.
|
|
6
|
+
|
|
7
|
+
Turns `@xenosystem/elements` declarations into artifacts for each runtime β the Style
|
|
8
|
+
Dictionary model, generalised past tokens to geometry and state machines.
|
|
9
|
+
|
|
10
|
+
**Not built yet** β Phase 2.
|
|
11
|
+
|
|
12
|
+
## Pipeline
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Figma / XENO Canvas β SVGO (30β70% reduction) β declaration generator β @xenosystem/elements β CI
|
|
16
|
+
β
|
|
17
|
+
βββββββββββββββββββββββββββββββββββββββββββ€
|
|
18
|
+
βΌ βΌ βΌ
|
|
19
|
+
elements-react XenoElementsKit xenosystem-elements
|
|
20
|
+
(npm) (SPM) (Maven)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Swift cannot be an npm package, which is why renderers ship on each runtime's native channel
|
|
24
|
+
rather than pretending to be siblings in one scope.
|
|
25
|
+
|
|
26
|
+
## Rules
|
|
27
|
+
|
|
28
|
+
- **Never hand-author a renderer component.** 300 hand-maintained animated components will rot.
|
|
29
|
+
- The generator is the only writer of generated output; generated files are marked as such and
|
|
30
|
+
are not edited by hand or by an agent.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
Part of [`xeno-elements`](../../README.md). Visual authority: [`DESIGN_SYSTEM.md`](../../DESIGN_SYSTEM.md) β LOCKED.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@xenosystem/generate` β the pipeline that turns declarations into per-platform artifacts.
|
|
3
|
+
*
|
|
4
|
+
* It is the ONLY writer of generated output (SPEC Β§6, README): renderers are generated, never
|
|
5
|
+
* hand-authored, so this package owns the geometryβmarkup interpretation and the pack manifest.
|
|
6
|
+
* Framework-free and platform-free; a native target adds its own emitter alongside `renderSvg`.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
export { interpret, type Scene, type SceneOptions, type ShapeNode, type RectNode, type PathNode, } from './scene.js';
|
|
11
|
+
export { renderSvg, sceneToSvg, type RenderOptions } from './svg.js';
|
|
12
|
+
export { buildManifest, toEntry, type Manifest, type ManifestEntry } from './manifest.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@xenosystem/generate` β the pipeline that turns declarations into per-platform artifacts.
|
|
3
|
+
*
|
|
4
|
+
* It is the ONLY writer of generated output (SPEC Β§6, README): renderers are generated, never
|
|
5
|
+
* hand-authored, so this package owns the geometryβmarkup interpretation and the pack manifest.
|
|
6
|
+
* Framework-free and platform-free; a native target adds its own emitter alongside `renderSvg`.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
export { interpret, } from './scene.js';
|
|
11
|
+
export { renderSvg, sceneToSvg } from './svg.js';
|
|
12
|
+
export { buildManifest, toEntry } from './manifest.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MANIFEST: a flat, sorted index of every element in a pack β the catalogue a foundry, a docs
|
|
3
|
+
* site, or a consumer's build reads to know what exists WITHOUT importing 300 declaration modules.
|
|
4
|
+
* Pure projection of the declarations; carries no geometry (that lives in the per-element artifacts).
|
|
5
|
+
*/
|
|
6
|
+
import type { ElementDeclaration } from '@xenosystem/elements/schema';
|
|
7
|
+
export interface ManifestEntry {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly kind: ElementDeclaration['kind'];
|
|
10
|
+
readonly viewBox: string;
|
|
11
|
+
readonly weight: string;
|
|
12
|
+
/** Axes the element honours β empty for a degenerate icon. */
|
|
13
|
+
readonly axes: readonly string[];
|
|
14
|
+
readonly signals: readonly string[];
|
|
15
|
+
/** Geometry variant keys beyond `base` (e.g. `selection:on`), sorted. */
|
|
16
|
+
readonly variants: readonly string[];
|
|
17
|
+
readonly tags: readonly string[];
|
|
18
|
+
readonly since: string | null;
|
|
19
|
+
readonly role: string;
|
|
20
|
+
readonly label: string;
|
|
21
|
+
}
|
|
22
|
+
export interface Manifest {
|
|
23
|
+
readonly count: number;
|
|
24
|
+
readonly elements: readonly ManifestEntry[];
|
|
25
|
+
}
|
|
26
|
+
export declare const toEntry: (d: ElementDeclaration) => ManifestEntry;
|
|
27
|
+
export declare const buildManifest: (decls: readonly ElementDeclaration[]) => Manifest;
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
2
|
+
export const toEntry = (d) => ({
|
|
3
|
+
id: d.id,
|
|
4
|
+
kind: d.kind,
|
|
5
|
+
viewBox: d.contract.viewBox,
|
|
6
|
+
weight: d.contract.weight,
|
|
7
|
+
axes: d.contract.axes,
|
|
8
|
+
signals: d.contract.signals,
|
|
9
|
+
variants: Object.keys(d.geometry)
|
|
10
|
+
.filter((k) => k !== 'base')
|
|
11
|
+
.sort(),
|
|
12
|
+
tags: d.meta?.tags ?? [],
|
|
13
|
+
since: d.meta?.since ?? null,
|
|
14
|
+
role: d.a11y.role,
|
|
15
|
+
label: d.a11y.label,
|
|
16
|
+
});
|
|
17
|
+
export const buildManifest = (decls) => {
|
|
18
|
+
const elements = decls.map(toEntry).sort(byId);
|
|
19
|
+
return { count: elements.length, elements };
|
|
20
|
+
};
|
package/dist/scene.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one INTERPRETATION of a declaration, shared by every web-side renderer. It resolves the axis
|
|
3
|
+
* state to a geometry variant, turns each primitive into a neutral draw node, and resolves fill
|
|
4
|
+
* TOKENS to paint β all the fail-closed decisions live here, exactly once. A serializer (SVG string,
|
|
5
|
+
* {@link ./svg.ts}) and a framework renderer (`elements-react`) each consume this Scene and map it to
|
|
6
|
+
* their own attribute dialect (`stroke-width` vs `strokeWidth`), so the two can never drift on what
|
|
7
|
+
* to draw β only on how to spell it.
|
|
8
|
+
*/
|
|
9
|
+
import type { ElementDeclaration, ElementState } from '@xenosystem/elements/schema';
|
|
10
|
+
export interface SceneOptions {
|
|
11
|
+
/** Discrete state to render. Missing axes fall back to the default (enabled / off / idle). */
|
|
12
|
+
readonly state?: Partial<ElementState>;
|
|
13
|
+
/** Pixel width/height on the root. Default 24. The viewBox is always the declared one. */
|
|
14
|
+
readonly size?: number;
|
|
15
|
+
/** Override the weight-derived stroke width. */
|
|
16
|
+
readonly strokeWidth?: number;
|
|
17
|
+
/** Extra class on the root element. */
|
|
18
|
+
readonly className?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A resolved draw node. `fill` is the RESOLVED paint (`currentColor`) for a solid part, or `undefined`
|
|
22
|
+
* for an outline part that inherits the root's `stroke:currentColor; fill:none`.
|
|
23
|
+
*/
|
|
24
|
+
export interface RectNode {
|
|
25
|
+
readonly kind: 'rect';
|
|
26
|
+
readonly x: number;
|
|
27
|
+
readonly y: number;
|
|
28
|
+
readonly w: number;
|
|
29
|
+
readonly h: number;
|
|
30
|
+
readonly rx?: number;
|
|
31
|
+
readonly fill?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface PathNode {
|
|
34
|
+
readonly kind: 'path';
|
|
35
|
+
readonly d: string;
|
|
36
|
+
readonly fill?: string;
|
|
37
|
+
readonly fillRule?: 'nonzero' | 'evenodd';
|
|
38
|
+
}
|
|
39
|
+
export type ShapeNode = RectNode | PathNode;
|
|
40
|
+
/** The fully-resolved thing to draw. Neutral: no attribute-name dialect committed to yet. */
|
|
41
|
+
export interface Scene {
|
|
42
|
+
readonly viewBox: string;
|
|
43
|
+
readonly size: number;
|
|
44
|
+
readonly strokeWidth: number;
|
|
45
|
+
readonly role: string;
|
|
46
|
+
readonly label: string;
|
|
47
|
+
/** Short glyph name (id without the `xeno.` prefix) β emitted as `data-glyph`, the hook the
|
|
48
|
+
* animated-icon stylesheet targets so each glyph can carry its own motion. */
|
|
49
|
+
readonly glyph: string;
|
|
50
|
+
/** `data-<axis>` values for the axes the element DECLARES it honours (empty for a degenerate icon). */
|
|
51
|
+
readonly data: Readonly<Record<`data-${string}`, string>>;
|
|
52
|
+
readonly className: string | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Whether what is being drawn can be interpolated back to `base` β same primitive count, same kind at
|
|
55
|
+
* every index.
|
|
56
|
+
*
|
|
57
|
+
* The interpreter does not animate anything; it says only that an animation is POSSIBLE, which is the
|
|
58
|
+
* whole point. A scene that reports `morph` lets a stylesheet interpolate the path data, one that does
|
|
59
|
+
* not lets it swap, and neither needs an animation runtime to decide which. `geometryMorphable` has sat
|
|
60
|
+
* in the schema since the start as a check nothing consumed; this is what consumes it.
|
|
61
|
+
*
|
|
62
|
+
* `base` itself counts as morphable, because a control that morphs has to morph BACK β and the state
|
|
63
|
+
* it returns to is base. Answering `false` there would animate the outbound half and snap the return.
|
|
64
|
+
*/
|
|
65
|
+
readonly morph: boolean;
|
|
66
|
+
readonly shapes: readonly ShapeNode[];
|
|
67
|
+
}
|
|
68
|
+
/** Interpret a declaration + state into a neutral, fully-resolved {@link Scene}. Fails closed. */
|
|
69
|
+
export declare const interpret: (decl: ElementDeclaration, opts?: SceneOptions) => Scene;
|
package/dist/scene.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { geometryMorphable } from '@xenosystem/elements/schema';
|
|
2
|
+
/**
|
|
3
|
+
* The state a renderer shows when an axis is unspecified β the same values as the contract's
|
|
4
|
+
* `DEFAULT_STATE`, kept local so the interpreter stays a pure emitter with no runtime import from the
|
|
5
|
+
* contract package (type-only imports keep the shipped code platform-free).
|
|
6
|
+
*/
|
|
7
|
+
const DEFAULT_STATE = {
|
|
8
|
+
availability: 'enabled',
|
|
9
|
+
selection: 'off',
|
|
10
|
+
interaction: 'idle',
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Weight β stroke width, in viewBox units. A RENDERER decision (the declaration names a `weight`
|
|
14
|
+
* family, not a number), so it lives here and is revisable without touching a single declaration.
|
|
15
|
+
* `regular` = 1.75 matches the value the foundry workbench shipped and the design approved.
|
|
16
|
+
*/
|
|
17
|
+
const WEIGHT_STROKE = { light: 1.25, regular: 1.75, bold: 2.25 };
|
|
18
|
+
/**
|
|
19
|
+
* Fill token β CSS paint. The XENO grammar is monochrome: the single ink is `currentColor`, so it
|
|
20
|
+
* inherits the surrounding text colour on every surface. Extend this map when the design system adds
|
|
21
|
+
* a painted role β never by letting a literal colour through.
|
|
22
|
+
*/
|
|
23
|
+
const FILL_PAINT = { foreground: 'currentColor' };
|
|
24
|
+
/**
|
|
25
|
+
* When more than one axis has a matching `<axis>:<value>` geometry variant, the more TRANSIENT axis
|
|
26
|
+
* wins β a press should read over a selection, a selection over mere availability. Rarely collides
|
|
27
|
+
* (most elements vary on one axis), but the order must be defined, not incidental.
|
|
28
|
+
*/
|
|
29
|
+
const AXIS_PRECEDENCE = ['availability', 'selection', 'interaction'];
|
|
30
|
+
const finite = (n) => {
|
|
31
|
+
if (!Number.isFinite(n))
|
|
32
|
+
throw new Error(`interpret: non-finite coordinate ${String(n)}`);
|
|
33
|
+
return n;
|
|
34
|
+
};
|
|
35
|
+
const paintFor = (token) => {
|
|
36
|
+
if (token === 'none')
|
|
37
|
+
return 'none';
|
|
38
|
+
const paint = FILL_PAINT[token];
|
|
39
|
+
if (paint === undefined) {
|
|
40
|
+
throw new Error(`interpret: unknown fill token '${token}'. A fill must reference a design-system token, ` +
|
|
41
|
+
`never a literal colour (SPEC Β§14.2 β the guard fails closed).`);
|
|
42
|
+
}
|
|
43
|
+
return paint;
|
|
44
|
+
};
|
|
45
|
+
const listOf = (g) => typeof g === 'string' ? [{ kind: 'path', d: g }] : g;
|
|
46
|
+
/** Pick the geometry variant the state addresses: `base`, overridden by any matching `<axis>:<value>`. */
|
|
47
|
+
const activeGeometry = (decl, state) => {
|
|
48
|
+
let geometry = decl.geometry.base;
|
|
49
|
+
for (const axis of AXIS_PRECEDENCE) {
|
|
50
|
+
const variant = decl.geometry[`${axis}:${state[axis]}`];
|
|
51
|
+
if (variant !== undefined)
|
|
52
|
+
geometry = variant;
|
|
53
|
+
}
|
|
54
|
+
return geometry;
|
|
55
|
+
};
|
|
56
|
+
const shapeOf = (p) => {
|
|
57
|
+
if (p.kind === 'rect') {
|
|
58
|
+
const base = {
|
|
59
|
+
kind: 'rect',
|
|
60
|
+
x: finite(p.x),
|
|
61
|
+
y: finite(p.y),
|
|
62
|
+
w: finite(p.w),
|
|
63
|
+
h: finite(p.h),
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
...base,
|
|
67
|
+
...(p.rx !== undefined ? { rx: finite(p.rx) } : {}),
|
|
68
|
+
...(p.fill !== undefined ? { fill: paintFor(p.fill) } : {}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
kind: 'path',
|
|
73
|
+
d: p.d,
|
|
74
|
+
...(p.fill !== undefined ? { fill: paintFor(p.fill) } : {}),
|
|
75
|
+
...(p.fillRule ? { fillRule: p.fillRule } : {}),
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
/** Interpret a declaration + state into a neutral, fully-resolved {@link Scene}. Fails closed. */
|
|
79
|
+
export const interpret = (decl, opts = {}) => {
|
|
80
|
+
const state = { ...DEFAULT_STATE, ...opts.state };
|
|
81
|
+
const data = {};
|
|
82
|
+
for (const axis of decl.contract.axes)
|
|
83
|
+
data[`data-${axis}`] = state[axis];
|
|
84
|
+
const active = activeGeometry(decl, state);
|
|
85
|
+
return {
|
|
86
|
+
viewBox: decl.contract.viewBox,
|
|
87
|
+
size: opts.size ?? 24,
|
|
88
|
+
strokeWidth: opts.strokeWidth ?? WEIGHT_STROKE[decl.contract.weight],
|
|
89
|
+
role: decl.a11y.role,
|
|
90
|
+
label: decl.a11y.label,
|
|
91
|
+
glyph: decl.id.replace(/^xeno\./, ''),
|
|
92
|
+
data,
|
|
93
|
+
className: opts.className,
|
|
94
|
+
// A declaration with only `base` has nowhere to morph TO. It would trivially pass the check, and
|
|
95
|
+
// marking it would put a `d` transition on every path of every icon in the set to animate nothing.
|
|
96
|
+
morph: Object.keys(decl.geometry).length > 1 && geometryMorphable(decl.geometry.base, active),
|
|
97
|
+
shapes: listOf(active).map(shapeOf),
|
|
98
|
+
};
|
|
99
|
+
};
|
package/dist/svg.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG-string serializer: one dialect of the shared {@link interpret} Scene. Turns a declaration into
|
|
3
|
+
* a self-contained `<svg>` string β the artifact the build writes to disk, the foundry previews, and
|
|
4
|
+
* the reference for what "visually indistinguishable" means on the web (SPEC Β§6).
|
|
5
|
+
*
|
|
6
|
+
* All the drawing DECISIONS (variant selection, tokenβpaint, fail-closed guards) live in
|
|
7
|
+
* {@link ./scene.ts}; this file only spells the resolved Scene as SVG markup.
|
|
8
|
+
*/
|
|
9
|
+
import type { ElementDeclaration } from '@xenosystem/elements/schema';
|
|
10
|
+
import { type Scene, type SceneOptions } from './scene.js';
|
|
11
|
+
export type RenderOptions = SceneOptions;
|
|
12
|
+
/** Serialize a resolved {@link Scene} to an SVG string. */
|
|
13
|
+
export declare const sceneToSvg: (scene: Scene) => string;
|
|
14
|
+
/**
|
|
15
|
+
* Interpret a declaration into an SVG string. The root carries the outline defaults plus one
|
|
16
|
+
* `data-<axis>` attribute for every axis the element declares β the CSS-first seam a stylesheet
|
|
17
|
+
* targets to drive state transitions with zero JS. Degenerate icons (no axes) get none.
|
|
18
|
+
*/
|
|
19
|
+
export declare const renderSvg: (decl: ElementDeclaration, opts?: RenderOptions) => string;
|
package/dist/svg.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { interpret } from './scene.js';
|
|
2
|
+
const esc = (s) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
3
|
+
/**
|
|
4
|
+
* Paint attributes for one shape. An outline part (no `fill`) inherits the root's
|
|
5
|
+
* `fill:none; stroke:currentColor`, so we emit nothing. A solid part is painted and explicitly
|
|
6
|
+
* un-stroked, so its ink is crisp instead of haloed by the outline stroke.
|
|
7
|
+
*/
|
|
8
|
+
const paintAttrs = (fill) => fill === undefined ? '' : ` fill="${fill}" stroke="none"`;
|
|
9
|
+
// Each shape carries a stable `xeno-part` class + `data-part` index (its position in the geometry),
|
|
10
|
+
// so a stylesheet can animate an icon's INDIVIDUAL parts β the hook for representative icon motion.
|
|
11
|
+
const shapeSvg = (s, i, morph) => {
|
|
12
|
+
const part = ` class="xeno-part" data-part="${i}"`;
|
|
13
|
+
// A morphable path carries its own `d` a SECOND time, as a custom property. The attribute stays the
|
|
14
|
+
// fallback (and stays authoritative where CSS `d` is unsupported); the property is what a stylesheet
|
|
15
|
+
// can interpolate, because a custom property changing is a computed-value change and `d` transitions.
|
|
16
|
+
// The quotes are written as entities on purpose: React escapes apostrophes inside a style attribute
|
|
17
|
+
// and this serializer does not, and the two renderers are held to byte-for-byte parity (SPEC Β§6). One
|
|
18
|
+
// of them had to give, and the entity is what both agree on.
|
|
19
|
+
const morphD = morph && s.kind === 'path' ? ` style="--part-d:path('${esc(s.d)}')"` : '';
|
|
20
|
+
if (s.kind === 'rect') {
|
|
21
|
+
const rx = s.rx !== undefined ? ` rx="${s.rx}"` : '';
|
|
22
|
+
return `<rect x="${s.x}" y="${s.y}" width="${s.w}" height="${s.h}"${rx}${paintAttrs(s.fill)}${part}/>`;
|
|
23
|
+
}
|
|
24
|
+
const fillRule = s.fillRule ? ` fill-rule="${s.fillRule}"` : '';
|
|
25
|
+
// `d` is escaped like every other attribute value. It has never needed it β no shipped glyph has a
|
|
26
|
+
// quote in its path data β but it was the one place this serializer was fail-OPEN, in a pipeline
|
|
27
|
+
// whose whole point is that it is not: `interpret` throws on a non-finite coordinate and on a fill
|
|
28
|
+
// that is not a token, and then this let an arbitrary string through into markup unchecked.
|
|
29
|
+
return `<path d="${esc(s.d)}"${fillRule}${paintAttrs(s.fill)}${part}${morphD}/>`;
|
|
30
|
+
};
|
|
31
|
+
/** Serialize a resolved {@link Scene} to an SVG string. */
|
|
32
|
+
export const sceneToSvg = (scene) => {
|
|
33
|
+
const data = Object.entries(scene.data)
|
|
34
|
+
.map(([k, v]) => ` ${k}="${v}"`)
|
|
35
|
+
.join('');
|
|
36
|
+
const className = scene.className ? ` class="${esc(scene.className)}"` : '';
|
|
37
|
+
return (`<svg xmlns="http://www.w3.org/2000/svg" width="${scene.size}" height="${scene.size}" ` +
|
|
38
|
+
`viewBox="${scene.viewBox}" fill="none" stroke="currentColor" ` +
|
|
39
|
+
`stroke-width="${scene.strokeWidth}" stroke-linecap="butt" stroke-linejoin="round" ` +
|
|
40
|
+
`role="${esc(scene.role)}" aria-label="${esc(scene.label)}" data-glyph="${esc(scene.glyph)}"` +
|
|
41
|
+
`${scene.morph ? ' data-morph="on"' : ''}${data}${className}>` +
|
|
42
|
+
scene.shapes.map((s, i) => shapeSvg(s, i, scene.morph)).join('') +
|
|
43
|
+
`</svg>`);
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Interpret a declaration into an SVG string. The root carries the outline defaults plus one
|
|
47
|
+
* `data-<axis>` attribute for every axis the element declares β the CSS-first seam a stylesheet
|
|
48
|
+
* targets to drive state transitions with zero JS. Degenerate icons (no axes) get none.
|
|
49
|
+
*/
|
|
50
|
+
export const renderSvg = (decl, opts = {}) => sceneToSvg(interpret(decl, opts));
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xenosystem/generate",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "UNLICENSED",
|
|
5
|
+
"description": "XENO declarations \u2192 per-platform artifacts. The only writer of generated output.",
|
|
6
|
+
"author": "XENO Corporation <emilian@bnkrsys.com>",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"sideEffects": false,
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"prebuild": "npm run build -w @xenosystem/elements",
|
|
23
|
+
"build": "tsc -p tsconfig.json",
|
|
24
|
+
"pretypecheck": "npm run build -w @xenosystem/elements",
|
|
25
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"artifacts": "npm run build && node scripts/build-artifacts.mjs"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@xenosystem/elements": "0.0.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"typescript": "^5.4.0"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
}
|
|
38
|
+
}
|