@brunosouzas/design-system 0.1.0 → 0.1.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/README.md +2 -1
- package/dist/src/pieces/cover.js +25 -3
- package/dist/src/pieces/params.d.ts +2 -1
- package/dist/src/pieces/params.js +6 -4
- package/dist/src/pieces/piece.js +18 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ The components take every colour and size from the tokens, and what is visible a
|
|
|
130
130
|
|
|
131
131
|
- `toSvg(node)` writes a tree as SVG text; `toReact(node)` turns the same tree into React elements. Both describe the same elements, attributes and text, which the tests check.
|
|
132
132
|
- `pose(node, { x, y, tilt, squash, hop, scale, shake, opacity })` animates any piece around its base point; a scene recomputes the numbers every frame.
|
|
133
|
-
- Every colour parameter is a token path (`"color.healthy"`, `"illustration.accent"`), never a hex value, except the two line fills of the map pieces below. Each piece checks its parameters before drawing and names the first wrong one.
|
|
133
|
+
- Every colour parameter is a token path (`"color.healthy"`, `"illustration.accent"`), never a hex value, except the two line fills of the map pieces below. Each piece checks its parameters before drawing and names the first wrong one. Positions and points take any finite number, because a map has no edge; sizes stay within 10000. A drawing that would hold a non-finite number throws `<piece>: the drawing overflows`.
|
|
134
134
|
- The 35 pieces, with their anchors, parameters and defaults, are listed in [design §4](openspec/changes/archive/2026-10-23-illustration-pieces/design.md) (the 26 cover pieces) and in [design §3](openspec/changes/archive/2026-10-23-metro-and-diagram-pieces/design.md) of `metro-and-diagram-pieces` (the nine below); the rules are in [`openspec/specs/illustration-pieces/`](openspec/specs/illustration-pieces/spec.md). `npm run pieces:sheet -- --out <file.svg>` draws them all.
|
|
135
135
|
|
|
136
136
|
### Maps and diagrams
|
|
@@ -169,6 +169,7 @@ A cover is a JSON spec kept with its article, outside this repository: drafts ar
|
|
|
169
169
|
|
|
170
170
|
- `background` names one of the 21 pairs in `coverBackground` (a background and its ring colour, measured from the published covers). Each pair is used by one cover only.
|
|
171
171
|
- `pieces` are drawn in order; their words go on top.
|
|
172
|
+
- The `rings` centre stays within ±10000 in a cover spec, and a piece that overflows is reported at its entry (`pieces[1]: the drawing overflows`).
|
|
172
173
|
- `custom` (optional) is the cover's one free-form object, a JSON node tree limited to drawing tags and a fixed list of attributes, with no external references. An object that repeats becomes a piece instead.
|
|
173
174
|
- The three published covers that proved the pieces are the test fixtures in `test/fixtures/covers/`.
|
|
174
175
|
|
package/dist/src/pieces/cover.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { tokens } from "../../tokens/generated/tokens.js";
|
|
11
11
|
import { PIECE_NAMES, PIECES, rings } from "./library.js";
|
|
12
12
|
import { el, g } from "./node.js";
|
|
13
|
-
import { COLOUR_TOKENS, checkParams, isColourLiteral, PATH_DATA } from "./params.js";
|
|
13
|
+
import { COLOUR_TOKENS, checkParams, isColourLiteral, MAX_SIZE, PATH_DATA } from "./params.js";
|
|
14
14
|
export const SAFE_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
15
15
|
const TOP_LEVEL = ["id", "title", "description", "background", "rings", "pieces", "custom"];
|
|
16
16
|
const CUSTOM_TAGS = ["g", "rect", "circle", "ellipse", "line", "polyline", "polygon", "path", "text"];
|
|
@@ -153,8 +153,20 @@ export const validateCoverSpec = (spec) => {
|
|
|
153
153
|
}
|
|
154
154
|
if (!isPlainObject(spec.rings))
|
|
155
155
|
out.push({ path: "rings", message: "missing or not an object (expected cx, cy and an optional radius)" });
|
|
156
|
-
else
|
|
157
|
-
|
|
156
|
+
else {
|
|
157
|
+
const ringProblems = checkParams(rings.schema, spec.rings, "rings").filter((problem) => problem.path !== "rings.color");
|
|
158
|
+
out.push(...ringProblems);
|
|
159
|
+
// The piece takes any finite centre; a cover spec keeps 0.1.0's ±10000, which bounds a banner's ring count.
|
|
160
|
+
for (const axis of ["cx", "cy"]) {
|
|
161
|
+
const value = spec.rings[axis];
|
|
162
|
+
if (ringProblems.some((p) => p.path === `rings.${axis}`) || typeof value !== "number")
|
|
163
|
+
continue;
|
|
164
|
+
if (value < -MAX_SIZE)
|
|
165
|
+
out.push({ path: `rings.${axis}`, message: `expected a number at least -${MAX_SIZE}, found ${value}` });
|
|
166
|
+
else if (value > MAX_SIZE)
|
|
167
|
+
out.push({ path: `rings.${axis}`, message: `expected a number at most ${MAX_SIZE}, found ${value}` });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
158
170
|
if (isPlainObject(spec.rings) && Object.hasOwn(spec.rings, "color"))
|
|
159
171
|
out.push({ path: "rings.color", message: "unknown parameter: the ring colour comes from the background" });
|
|
160
172
|
if (!Array.isArray(spec.pieces))
|
|
@@ -186,6 +198,16 @@ export const validateCoverSpec = (spec) => {
|
|
|
186
198
|
const reported = new Set(own.map((problem) => problem.path));
|
|
187
199
|
out.push(...related.map((problem) => ({ path: `${at}.${problem.path}`, message: problem.message })).filter((problem) => !reported.has(problem.path)));
|
|
188
200
|
}
|
|
201
|
+
// A valid entry is drawn once: an overflow is a spec problem at the entry, not a crash.
|
|
202
|
+
if (own.length === 0 && !(definition.relations?.(params) ?? []).length) {
|
|
203
|
+
try {
|
|
204
|
+
PIECES[piece](params);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
if (error instanceof RangeError && error.message.endsWith(": the drawing overflows"))
|
|
208
|
+
out.push({ path: at, message: "the drawing overflows" });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
189
211
|
});
|
|
190
212
|
if (spec.custom !== undefined) {
|
|
191
213
|
if (Array.isArray(spec.custom))
|
|
@@ -61,7 +61,7 @@ export declare const assertParams: (piece: string, schema: Schema, params: Recor
|
|
|
61
61
|
export declare const num: (extra?: Omit<Extract<ParamSpec, {
|
|
62
62
|
kind: "number";
|
|
63
63
|
}>, "kind">) => ParamSpec;
|
|
64
|
-
/** Largest size
|
|
64
|
+
/** Largest size a piece accepts, far above any banner. Positions and points have no bound. */
|
|
65
65
|
export declare const MAX_SIZE = 10000;
|
|
66
66
|
/** Most items in any list parameter (rows, lines, nodes, tiles). */
|
|
67
67
|
export declare const MAX_ITEMS = 50;
|
|
@@ -69,6 +69,7 @@ export declare const MAX_ITEMS = 50;
|
|
|
69
69
|
export declare const count: (min: number, max: number) => ParamSpec;
|
|
70
70
|
/** A size: greater than 0 (or at least `min`, where fixed insets need room), at most MAX_SIZE. */
|
|
71
71
|
export declare const size: (required?: boolean, min?: number) => ParamSpec;
|
|
72
|
+
/** A position: any finite number, because a map position has no maximum. */
|
|
72
73
|
export declare const pos: () => ParamSpec;
|
|
73
74
|
export declare const col: () => ParamSpec;
|
|
74
75
|
/** SVG path data: commands, digits, signs, dots, commas and spaces only. */
|
|
@@ -86,8 +86,9 @@ const checkValue = (spec, value, path, out) => {
|
|
|
86
86
|
return report(`expected true or false, found ${describe(value)}`);
|
|
87
87
|
return;
|
|
88
88
|
case "point":
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
// Any finite number: a map position has no maximum (spec illustration-pieces, "Positions of any size").
|
|
90
|
+
if (!Array.isArray(value) || value.length !== 2 || !value.every((n) => typeof n === "number" && Number.isFinite(n))) {
|
|
91
|
+
return report(`expected a point [x, y] of finite numbers, found ${describe(value)}`);
|
|
91
92
|
}
|
|
92
93
|
return;
|
|
93
94
|
case "array":
|
|
@@ -132,7 +133,7 @@ export const assertParams = (piece, schema, params) => {
|
|
|
132
133
|
};
|
|
133
134
|
// Shorthands for schemas.
|
|
134
135
|
export const num = (extra = {}) => ({ kind: "number", ...extra });
|
|
135
|
-
/** Largest size
|
|
136
|
+
/** Largest size a piece accepts, far above any banner. Positions and points have no bound. */
|
|
136
137
|
export const MAX_SIZE = 10000;
|
|
137
138
|
/** Most items in any list parameter (rows, lines, nodes, tiles). */
|
|
138
139
|
export const MAX_ITEMS = 50;
|
|
@@ -140,7 +141,8 @@ export const MAX_ITEMS = 50;
|
|
|
140
141
|
export const count = (min, max) => ({ kind: "number", integer: true, min, max });
|
|
141
142
|
/** A size: greater than 0 (or at least `min`, where fixed insets need room), at most MAX_SIZE. */
|
|
142
143
|
export const size = (required = false, min) => min === undefined ? { kind: "number", min: 0, exclusiveMin: true, max: MAX_SIZE, required } : { kind: "number", min, max: MAX_SIZE, required };
|
|
143
|
-
|
|
144
|
+
/** A position: any finite number, because a map position has no maximum. */
|
|
145
|
+
export const pos = () => ({ kind: "number", required: true });
|
|
144
146
|
export const col = () => ({ kind: "color" });
|
|
145
147
|
/** SVG path data: commands, digits, signs, dots, commas and spaces only. */
|
|
146
148
|
export const PATH_DATA = /^[MmLlHhVvCcSsQqTtAaZz0-9eE.,+\-\s]+$/;
|
package/dist/src/pieces/piece.js
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
import { assertParams } from "./params.js";
|
|
2
|
+
// A number the piece computed and would draw: finite, or the drawing overflows
|
|
3
|
+
// (spec illustration-pieces, "Drawing overflow"). Strings are the piece's own
|
|
4
|
+
// path data and transforms; caller path data (road.d, pipe.d) has its own check
|
|
5
|
+
// and only ever holds path characters, never the words Infinity or NaN.
|
|
6
|
+
const NON_FINITE = /Infinity|NaN/;
|
|
7
|
+
const overflows = (node) => {
|
|
8
|
+
if (typeof node === "string")
|
|
9
|
+
return false;
|
|
10
|
+
for (const value of Object.values(node.attrs)) {
|
|
11
|
+
if (typeof value === "number" ? !Number.isFinite(value) : NON_FINITE.test(value))
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return node.children.some(overflows);
|
|
15
|
+
};
|
|
2
16
|
export const definePiece = (name, schema, draw, relations) => {
|
|
3
17
|
const piece = (params) => {
|
|
4
18
|
assertParams(name, schema, params);
|
|
5
19
|
const problems = relations?.(params) ?? [];
|
|
6
20
|
if (problems.length > 0)
|
|
7
21
|
throw new RangeError(`${name}: ${problems[0].path}: ${problems[0].message}`);
|
|
8
|
-
|
|
22
|
+
const drawn = draw(params);
|
|
23
|
+
if ([...drawn.body, ...drawn.text].some(overflows))
|
|
24
|
+
throw new RangeError(`${name}: the drawing overflows`);
|
|
25
|
+
return drawn;
|
|
9
26
|
};
|
|
10
27
|
return Object.assign(piece, { pieceName: name, schema, relations });
|
|
11
28
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brunosouzas/design-system",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Design tokens and illustration pieces of brunosouzas.com: tokens as CSS, JSON and TypeScript, pieces that draw SVG (and React), and a cover command.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|