tsquare 0.3.0 → 0.3.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/CHANGELOG.md +91 -0
- package/README.md +1 -1
- package/dist/catalog.d.ts +2 -18
- package/dist/chunks/{chunk-46MGJLGB.js → chunk-X46EDSCQ.js} +157 -25
- package/dist/cli.js +1 -1
- package/dist/icons.d.ts +2 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -2
- package/dist/playground/index.js +13 -5
- package/dist/playground/playground.js +17 -14
- package/dist/share.d.ts +4 -0
- package/dist/upgrade.d.ts +41 -0
- package/package.json +5 -3
- package/skill/tsquare/reference.md +1 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.2
|
|
4
|
+
|
|
5
|
+
- **`grow` on Select**, like Input: `select "State" grow` fills the rest of a row. Sonnet wrote it in 5 of 9 eval runs, its only remaining mistake; with it, every stored Sonnet text output is valid.
|
|
6
|
+
- **Docs:** a new [Embedding](docs/embedding.md) page for Notion, GitHub and your own site, and the Radio page shows radios side by side (`stack row` + `radio`).
|
|
7
|
+
- **Tests and CI:** a test suite (`npm test`), run on Node 20, 22 and 24 for every pull request, plus a check that eval scores, docs and the skill match what's committed.
|
|
8
|
+
- `CHANGELOG.md` is now included in the npm package.
|
|
9
|
+
|
|
10
|
+
## 0.3.1
|
|
11
|
+
|
|
12
|
+
### Older wireframes are upgraded in one step
|
|
13
|
+
|
|
14
|
+
- All support for older syntax now lives in one module (`upgradeWireframe`, `upgradeSpec`), run before parsing. The parser, catalog and renderer only know the current language.
|
|
15
|
+
- **Links made before 0.3.0 render again** when a comment follows an element on the same line (`screen phone "Home" # main`): the comment moves onto its own line. Text you write or paste still gets the error, so new comments go on their own line.
|
|
16
|
+
- `decodeWireframe` returns the upgraded text, so a decoded link always means what it did when it was made.
|
|
17
|
+
|
|
18
|
+
### Better suggestions for unknown icons
|
|
19
|
+
|
|
20
|
+
- Common guesses that aren't Lucide names now suggest what was meant: `compose` → `square-pen`, `call` → `phone`, `person` → `user`, `bag` → `shopping-bag`, `notification` → `bell`, `close` → `x`.
|
|
21
|
+
- Among equally close matches, the usual directions come first: `chevron` suggests `chevron-right, chevron-left, chevron-down, chevron-up`.
|
|
22
|
+
- On a list item, `trailingIcon=chevron` (or `toggle`, `avatar`…) says to write `trailing=chevron`, since that's a kind, not an icon.
|
|
23
|
+
|
|
24
|
+
## 0.3.0
|
|
25
|
+
|
|
26
|
+
### Renamed: `icon` is now `leadingIcon` on Button and ListItem
|
|
27
|
+
|
|
28
|
+
Buttons and list items take an icon on either side, so the prop names now say which side:
|
|
29
|
+
|
|
30
|
+
| Before | Now |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `button "Download" icon=download` | `button "Download" leadingIcon=download` |
|
|
33
|
+
| (not possible) | `button "Next" trailingIcon=chevron-right` |
|
|
34
|
+
| `listitem "Inbox" leading=icon icon=inbox` | `listitem "Inbox" leadingIcon=inbox` |
|
|
35
|
+
| `listitem "Delete" trailing=icon trailingIcon=trash-2` | `listitem "Delete" trailingIcon=trash-2` |
|
|
36
|
+
|
|
37
|
+
- **Nothing breaks.** The old `icon=` spelling still works on both components, so existing files and shared render links render the same. `tsquare fmt` rewrites it to `leadingIcon=`. The prompt, the skill and the docs only teach the new names.
|
|
38
|
+
- **Buttons can have a trailing icon**, for Next, external links or dropdowns. Both sides work together.
|
|
39
|
+
- `leading` and `trailing` on ListItem stay as before. They pick what fills each side (`avatar`, `image`, `checkbox`, `chevron`, `badge`, `toggle`, …). An icon or text value implies its kind, so `leading=icon` and `trailing=icon` are optional.
|
|
40
|
+
- `icon` stays as it is on TabBar items (`{label=Home icon=house}`) and on the Icon component.
|
|
41
|
+
|
|
42
|
+
### Changed: comments go on their own line
|
|
43
|
+
|
|
44
|
+
A line that starts with `#` is a comment. Anywhere else, `#` is ordinary text, so order numbers, tags and hex colors need no quotes:
|
|
45
|
+
|
|
46
|
+
```tsquare
|
|
47
|
+
# the orders page
|
|
48
|
+
screen desktop "Orders"
|
|
49
|
+
table columns=[Order, Customer] data=[[#1001, Ana Torres], [#1002, Ben Lee]]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- **Why:** comments used to start at any `#` after a space. That cut off `[#1001, Ana Torres]` and broke the table, which happened to Sonnet in 3 of 4 eval runs. Across 160 eval outputs, models never wrote a comment.
|
|
53
|
+
- **Upgrading:** a comment after an element on the same line (`screen phone "Home" # main`) is now an error that tells you to move it to its own line. Nothing is dropped silently.
|
|
54
|
+
|
|
55
|
+
### Clearer errors for props on the wrong component
|
|
56
|
+
|
|
57
|
+
When a word or prop belongs to a different component, the error now says which one:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
Input: fullWidth is a Button prop, not an Input one; an Input already fills its width (width=… sets a fixed one), so remove it
|
|
61
|
+
Card has no prop "border": border is a Stack prop, not a Card one
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
A prop or list wrapped onto its own line says where it belongs, instead of "unknown component":
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
trailing=…: props go on the same line as their component; move it to the end of line 15
|
|
68
|
+
a list must stay on one line; join this to line 6
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Playground
|
|
72
|
+
|
|
73
|
+
- A CodeMirror editor: highlighting, autocomplete for components, options, props, Lucide icons and accent colors, and problems underlined on their line.
|
|
74
|
+
- Light and dark themes.
|
|
75
|
+
- Zoom and pan, zoom to a single screen, full screen and a resizable split.
|
|
76
|
+
- Copy link: share link, SVG/PNG image links, Markdown and HTML snippets.
|
|
77
|
+
- An examples gallery with thumbnails.
|
|
78
|
+
|
|
79
|
+
### Library
|
|
80
|
+
|
|
81
|
+
- `encodeWireframe` / `decodeWireframe` for share and render links.
|
|
82
|
+
- `boardLayout()` returns the board size and each screen's position.
|
|
83
|
+
|
|
84
|
+
### List items: a value implies its kind
|
|
85
|
+
|
|
86
|
+
- `trailingIcon=trash-2` without `trailing=icon` used to render nothing, silently. Now the value implies the kind.
|
|
87
|
+
- A value that contradicts an explicit kind is an error that names the fix, for example: `leadingIcon only shows with leading=icon (this item has leading=avatar); remove one of them`.
|
|
88
|
+
|
|
89
|
+
### Examples
|
|
90
|
+
|
|
91
|
+
- New: `sign-in`, `dashboard`, `checkout` and `states`.
|
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ A wireframe can be embedded anywhere an image works (Notion, GitHub, docs sites)
|
|
|
50
50
|

|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
The playground's **Copy link** menu makes these. The text is compressed into the URL, not encrypted, so anyone with the link can read it; see [privacy](https://tsquare.dev/privacy). `encodeWireframe` and `decodeWireframe` in the library produce and read the same links.
|
|
53
|
+
The playground's **Copy link** menu makes these; [Embedding](docs/embedding.md) has the steps for Notion, GitHub and your own site. The text is compressed into the URL, not encrypted, so anyone with the link can read it; see [privacy](https://tsquare.dev/privacy). `encodeWireframe` and `decodeWireframe` in the library produce and read the same links.
|
|
54
54
|
|
|
55
55
|
## The language
|
|
56
56
|
|
package/dist/catalog.d.ts
CHANGED
|
@@ -341,6 +341,7 @@ export declare const componentDefinitions: {
|
|
|
341
341
|
label: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
342
342
|
value: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
343
343
|
placeholder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
344
|
+
grow: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
344
345
|
width: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>>;
|
|
345
346
|
}, z.core.$strip>;
|
|
346
347
|
slots: never[];
|
|
@@ -864,6 +865,7 @@ export declare const catalog: import("@json-render/core").Catalog<{
|
|
|
864
865
|
label: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
865
866
|
value: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
866
867
|
placeholder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
868
|
+
grow: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
867
869
|
width: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>>;
|
|
868
870
|
}, z.core.$strip>;
|
|
869
871
|
slots: never[];
|
|
@@ -1035,21 +1037,3 @@ export declare function listItemEnds(p: {
|
|
|
1035
1037
|
leading: string | undefined;
|
|
1036
1038
|
trailing: string | undefined;
|
|
1037
1039
|
};
|
|
1038
|
-
/**
|
|
1039
|
-
* Props renamed in 0.3.0, still accepted under their old names so existing
|
|
1040
|
-
* wireframes and render URLs keep working. The prompt, docs and autocomplete
|
|
1041
|
-
* only teach the new names, and `tsquare fmt` rewrites the old ones.
|
|
1042
|
-
*/
|
|
1043
|
-
export declare const RENAMED_PROPS: Record<string, Record<string, string>>;
|
|
1044
|
-
/** Rewrites renamed props to their current names, in place. Safe to call more than once. */
|
|
1045
|
-
export declare function upgradeRenamedProps(spec: {
|
|
1046
|
-
elements: Record<string, {
|
|
1047
|
-
type: string;
|
|
1048
|
-
props?: Record<string, unknown>;
|
|
1049
|
-
}>;
|
|
1050
|
-
}): {
|
|
1051
|
-
elements: Record<string, {
|
|
1052
|
-
type: string;
|
|
1053
|
-
props?: Record<string, unknown>;
|
|
1054
|
-
}>;
|
|
1055
|
-
};
|
|
@@ -139,8 +139,25 @@ function iconNode(name) {
|
|
|
139
139
|
return table[pascal(name)];
|
|
140
140
|
}
|
|
141
141
|
var isIcon = (name) => iconNode(name) !== void 0;
|
|
142
|
-
|
|
142
|
+
var GUESSES = {
|
|
143
|
+
call: ["phone"],
|
|
144
|
+
compose: ["square-pen", "pencil"],
|
|
145
|
+
person: ["user"],
|
|
146
|
+
bag: ["shopping-bag"],
|
|
147
|
+
notification: ["bell"],
|
|
148
|
+
notifications: ["bell"],
|
|
149
|
+
profile: ["user", "circle-user"],
|
|
150
|
+
envelope: ["mail"],
|
|
151
|
+
close: ["x"]
|
|
152
|
+
};
|
|
153
|
+
var DIRECTIONS = ["right", "left", "down", "up"];
|
|
154
|
+
var directionRank = (n) => {
|
|
155
|
+
const i = DIRECTIONS.indexOf(n.split("-").pop());
|
|
156
|
+
return i < 0 ? DIRECTIONS.length : i;
|
|
157
|
+
};
|
|
158
|
+
function suggestIcons(name, limit = 4) {
|
|
143
159
|
const q = name.toLowerCase().replace(/[_ ]/g, "-");
|
|
160
|
+
const guessed = GUESSES[q] ?? [];
|
|
144
161
|
const words = q.split("-").filter((w) => w.length > 2);
|
|
145
162
|
const maxEdits = Math.max(1, Math.floor(q.length / 3));
|
|
146
163
|
const scored = [];
|
|
@@ -152,7 +169,8 @@ function suggestIcons(name, limit = 3) {
|
|
|
152
169
|
const score = Math.min(wordScore, d <= maxEdits ? 3 * d / q.length : Infinity);
|
|
153
170
|
if (score < Infinity) scored.push([n, score]);
|
|
154
171
|
}
|
|
155
|
-
|
|
172
|
+
const close = scored.sort((a, b) => a[1] - b[1] || directionRank(a[0]) - directionRank(b[0]) || a[0].localeCompare(b[0])).map(([n]) => n);
|
|
173
|
+
return [.../* @__PURE__ */ new Set([...guessed, ...close])].slice(0, limit);
|
|
156
174
|
}
|
|
157
175
|
function unknownIconMessage(name) {
|
|
158
176
|
const close = suggestIcons(name);
|
|
@@ -415,6 +433,7 @@ var componentDefinitions = {
|
|
|
415
433
|
label: o(z.string()),
|
|
416
434
|
value: o(z.string()),
|
|
417
435
|
placeholder: o(z.string()),
|
|
436
|
+
grow: o(z.boolean()).describe("Fill the remaining space in a row"),
|
|
418
437
|
width: o(z.union([z.number(), z.string()])).describe("Fixed width, e.g. 320. Default fills the space.")
|
|
419
438
|
}),
|
|
420
439
|
slots: [],
|
|
@@ -514,23 +533,6 @@ function listItemEnds(p) {
|
|
|
514
533
|
trailing: p.trailing ?? (p.trailingIcon ? "icon" : p.trailingText ? "text" : void 0)
|
|
515
534
|
};
|
|
516
535
|
}
|
|
517
|
-
var RENAMED_PROPS = {
|
|
518
|
-
Button: { icon: "leadingIcon" },
|
|
519
|
-
ListItem: { icon: "leadingIcon" }
|
|
520
|
-
};
|
|
521
|
-
function upgradeRenamedProps(spec) {
|
|
522
|
-
for (const el of Object.values(spec.elements)) {
|
|
523
|
-
const renames = RENAMED_PROPS[el.type];
|
|
524
|
-
if (!renames || !el.props) continue;
|
|
525
|
-
for (const [from, to] of Object.entries(renames)) {
|
|
526
|
-
if (from in el.props) {
|
|
527
|
-
if (!(to in el.props)) el.props[to] = el.props[from];
|
|
528
|
-
delete el.props[from];
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
return spec;
|
|
533
|
-
}
|
|
534
536
|
|
|
535
537
|
// src/components.tsx
|
|
536
538
|
import React, { Children, cloneElement, isValidElement } from "react";
|
|
@@ -1052,7 +1054,7 @@ function Input({ element, dir }) {
|
|
|
1052
1054
|
}
|
|
1053
1055
|
function Select({ element, dir }) {
|
|
1054
1056
|
const p = element.props;
|
|
1055
|
-
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 6, ...fieldWidth(p.width, dir) }, children: [
|
|
1057
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 6, ...fieldWidth(p.width, dir), ...p.grow ? { flexGrow: 1 } : {} }, children: [
|
|
1056
1058
|
/* @__PURE__ */ jsx(FieldLabel, { text: p.label }),
|
|
1057
1059
|
/* @__PURE__ */ jsxs(Field, { children: [
|
|
1058
1060
|
/* @__PURE__ */ jsx(Box, { style: { flexGrow: 1, color: p.value ? theme.ink : theme.muted }, children: p.value ?? p.placeholder ?? "Select\u2026" }),
|
|
@@ -1629,7 +1631,7 @@ function parseWireframeText(source) {
|
|
|
1629
1631
|
if (t.kind !== "sym" && (c.isSym("=", 1) || c.isSym(":", 1))) {
|
|
1630
1632
|
c.next();
|
|
1631
1633
|
c.next();
|
|
1632
|
-
const key =
|
|
1634
|
+
const key = t.value;
|
|
1633
1635
|
if (!(key in info.props)) {
|
|
1634
1636
|
const elsewhere = belongsElsewhere(info.name, key, true);
|
|
1635
1637
|
issues.push({ line: lineNo, message: `${info.name} has no prop "${key}"` + (elsewhere ? `: ${elsewhere}` : "") + ` (its props: ${Object.keys(info.props).join(", ")})` });
|
|
@@ -1687,6 +1689,122 @@ function parseWireframeText(source) {
|
|
|
1687
1689
|
return { spec: root ? { root, elements } : null, issues, lines };
|
|
1688
1690
|
}
|
|
1689
1691
|
|
|
1692
|
+
// src/upgrade.ts
|
|
1693
|
+
var RENAMED_PROPS = {
|
|
1694
|
+
Button: { icon: "leadingIcon" },
|
|
1695
|
+
ListItem: { icon: "leadingIcon" }
|
|
1696
|
+
};
|
|
1697
|
+
var RENAMED_BY_WORD = new Map(Object.entries(RENAMED_PROPS).map(([type, renames]) => [type.toLowerCase(), renames]));
|
|
1698
|
+
function upgradeWireframe(text, options = {}) {
|
|
1699
|
+
const lines = text.replace(/\r\n?/g, "\n").split("\n");
|
|
1700
|
+
const out = [];
|
|
1701
|
+
for (const line of lines) {
|
|
1702
|
+
const trimmed = line.trim();
|
|
1703
|
+
const component = trimmed.match(/^([A-Za-z][\w-]*)/)?.[1];
|
|
1704
|
+
if (!component || trimmed.startsWith("```")) {
|
|
1705
|
+
out.push(line);
|
|
1706
|
+
continue;
|
|
1707
|
+
}
|
|
1708
|
+
const tokens = scanLine(line, component.length + (line.length - line.trimStart().length));
|
|
1709
|
+
let upgraded = line;
|
|
1710
|
+
let comment = "";
|
|
1711
|
+
if (options.fromLink) {
|
|
1712
|
+
const hash = tokens.find((t) => t.kind === "word" && t.depth === 0 && upgraded[t.start] === "#" && !t.afterEquals);
|
|
1713
|
+
if (hash && !fillsMainText(component, tokens, hash)) {
|
|
1714
|
+
comment = upgraded.slice(hash.start).trimEnd();
|
|
1715
|
+
upgraded = upgraded.slice(0, hash.start).trimEnd();
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
const renames = RENAMED_BY_WORD.get(component.toLowerCase().replace(/[-_]/g, ""));
|
|
1719
|
+
if (renames) {
|
|
1720
|
+
for (const t of [...tokens].reverse()) {
|
|
1721
|
+
if (t.kind === "key" && t.depth === 0 && t.start < upgraded.length && renames[t.text]) {
|
|
1722
|
+
upgraded = upgraded.slice(0, t.start) + renames[t.text] + upgraded.slice(t.start + t.text.length);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
if (comment) out.push(line.slice(0, line.length - line.trimStart().length) + comment);
|
|
1727
|
+
out.push(upgraded);
|
|
1728
|
+
}
|
|
1729
|
+
return out.join("\n");
|
|
1730
|
+
}
|
|
1731
|
+
function upgradeSpec(spec) {
|
|
1732
|
+
for (const el of Object.values(spec.elements)) {
|
|
1733
|
+
const renames = RENAMED_PROPS[el.type];
|
|
1734
|
+
if (!renames || !el.props) continue;
|
|
1735
|
+
for (const [from, to] of Object.entries(renames)) {
|
|
1736
|
+
if (from in el.props) {
|
|
1737
|
+
if (!(to in el.props)) el.props[to] = el.props[from];
|
|
1738
|
+
delete el.props[from];
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
return spec;
|
|
1743
|
+
}
|
|
1744
|
+
var PLAIN_WORD_MAIN_TEXT = ["avatar", "icon"];
|
|
1745
|
+
function fillsMainText(component, tokens, hash) {
|
|
1746
|
+
if (!PLAIN_WORD_MAIN_TEXT.includes(component.toLowerCase())) return false;
|
|
1747
|
+
for (const t of tokens) {
|
|
1748
|
+
if (t === hash) return true;
|
|
1749
|
+
if (t.depth > 0 || t.afterEquals || t.kind === "key") continue;
|
|
1750
|
+
return false;
|
|
1751
|
+
}
|
|
1752
|
+
return false;
|
|
1753
|
+
}
|
|
1754
|
+
function scanLine(line, from) {
|
|
1755
|
+
const tokens = [];
|
|
1756
|
+
let depth = 0;
|
|
1757
|
+
let afterEquals = false;
|
|
1758
|
+
let i = from;
|
|
1759
|
+
while (i < line.length) {
|
|
1760
|
+
const c = line[i];
|
|
1761
|
+
if (c === " " || c === " ") {
|
|
1762
|
+
i++;
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
if (c === '"' || c === "'") {
|
|
1766
|
+
let j2 = i + 1;
|
|
1767
|
+
while (j2 < line.length && line[j2] !== c) j2 += line[j2] === "\\" ? 2 : 1;
|
|
1768
|
+
tokens.push({ kind: "string", text: line.slice(i, j2 + 1), start: i, depth, afterEquals });
|
|
1769
|
+
afterEquals = false;
|
|
1770
|
+
i = j2 + 1;
|
|
1771
|
+
continue;
|
|
1772
|
+
}
|
|
1773
|
+
if (c === "[" || c === "{") {
|
|
1774
|
+
depth++;
|
|
1775
|
+
afterEquals = false;
|
|
1776
|
+
i++;
|
|
1777
|
+
continue;
|
|
1778
|
+
}
|
|
1779
|
+
if (c === "]" || c === "}") {
|
|
1780
|
+
depth = Math.max(0, depth - 1);
|
|
1781
|
+
afterEquals = false;
|
|
1782
|
+
i++;
|
|
1783
|
+
continue;
|
|
1784
|
+
}
|
|
1785
|
+
if (c === ",") {
|
|
1786
|
+
afterEquals = false;
|
|
1787
|
+
i++;
|
|
1788
|
+
continue;
|
|
1789
|
+
}
|
|
1790
|
+
if (c === "=" || c === ":") {
|
|
1791
|
+
afterEquals = true;
|
|
1792
|
+
i++;
|
|
1793
|
+
continue;
|
|
1794
|
+
}
|
|
1795
|
+
let j = i;
|
|
1796
|
+
while (j < line.length && !` ,=[]{}":`.includes(line[j])) j++;
|
|
1797
|
+
const text = line.slice(i, j);
|
|
1798
|
+
let k = j;
|
|
1799
|
+
while (line[k] === " " || line[k] === " ") k++;
|
|
1800
|
+
const isKey = !afterEquals && (line[k] === "=" || line[k] === ":");
|
|
1801
|
+
tokens.push({ kind: isKey ? "key" : "word", text, start: i, depth, afterEquals });
|
|
1802
|
+
afterEquals = false;
|
|
1803
|
+
i = j;
|
|
1804
|
+
}
|
|
1805
|
+
return tokens;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1690
1808
|
// src/render.ts
|
|
1691
1809
|
import { readFile } from "node:fs/promises";
|
|
1692
1810
|
import { createRequire } from "node:module";
|
|
@@ -1711,8 +1829,19 @@ var SpecError = class extends Error {
|
|
|
1711
1829
|
}
|
|
1712
1830
|
issues;
|
|
1713
1831
|
};
|
|
1832
|
+
function listItemKindHint(el, path) {
|
|
1833
|
+
const prop = path[0];
|
|
1834
|
+
if (el.type !== "ListItem" || path.length !== 1 || prop !== "leadingIcon" && prop !== "trailingIcon") return void 0;
|
|
1835
|
+
const side = prop === "leadingIcon" ? "leading" : "trailing";
|
|
1836
|
+
const value = String(el.props?.[prop]);
|
|
1837
|
+
let schema = componentDefinitions.ListItem.props.shape[side];
|
|
1838
|
+
while (schema && ["optional", "nullable", "default"].includes(schema.def?.type)) schema = schema.def.innerType;
|
|
1839
|
+
const kinds = schema?.options ?? [];
|
|
1840
|
+
if (value === "icon" || !kinds.includes(value)) return void 0;
|
|
1841
|
+
return `${value} is a ${side} kind, not an icon name: write ${side}=${value} instead of ${prop}=${value}`;
|
|
1842
|
+
}
|
|
1714
1843
|
function checkSpec(spec) {
|
|
1715
|
-
|
|
1844
|
+
upgradeSpec(spec);
|
|
1716
1845
|
const issues = [];
|
|
1717
1846
|
const result = catalog.validate(spec);
|
|
1718
1847
|
if (!result.success) {
|
|
@@ -1738,7 +1867,7 @@ function checkSpec(spec) {
|
|
|
1738
1867
|
const parsed = def.props.safeParse(el.props ?? {});
|
|
1739
1868
|
if (!parsed.success) {
|
|
1740
1869
|
for (const i of parsed.error.issues) {
|
|
1741
|
-
issues.push(`${id}.props${i.path.length ? "." + i.path.join(".") : ""}: ${i.message}`);
|
|
1870
|
+
issues.push(`${id}.props${i.path.length ? "." + i.path.join(".") : ""}: ${listItemKindHint(el, i.path) ?? i.message}`);
|
|
1742
1871
|
}
|
|
1743
1872
|
}
|
|
1744
1873
|
for (const key of Object.keys(el.props ?? {})) {
|
|
@@ -1826,7 +1955,7 @@ function boardSize(spec) {
|
|
|
1826
1955
|
return { width, height };
|
|
1827
1956
|
}
|
|
1828
1957
|
async function renderWireframeSvg(spec, opts = {}) {
|
|
1829
|
-
|
|
1958
|
+
upgradeSpec(spec);
|
|
1830
1959
|
if (!opts.skipValidation) {
|
|
1831
1960
|
const issues = checkSpec(spec);
|
|
1832
1961
|
if (issues.length) throw new SpecError(issues);
|
|
@@ -1849,7 +1978,7 @@ async function renderWireframePng(spec, opts = {}) {
|
|
|
1849
1978
|
|
|
1850
1979
|
// src/compile.ts
|
|
1851
1980
|
function compileWireframe(source) {
|
|
1852
|
-
const parsed = parseWireframeText(source);
|
|
1981
|
+
const parsed = parseWireframeText(upgradeWireframe(source));
|
|
1853
1982
|
const issues = [...parsed.issues];
|
|
1854
1983
|
if (parsed.spec) {
|
|
1855
1984
|
for (const msg of checkSpec(parsed.spec)) {
|
|
@@ -2110,6 +2239,9 @@ export {
|
|
|
2110
2239
|
PRIMARY_PROP,
|
|
2111
2240
|
bareWords,
|
|
2112
2241
|
parseWireframeText,
|
|
2242
|
+
RENAMED_PROPS,
|
|
2243
|
+
upgradeWireframe,
|
|
2244
|
+
upgradeSpec,
|
|
2113
2245
|
checkSpec,
|
|
2114
2246
|
boardLayout,
|
|
2115
2247
|
boardSize,
|
package/dist/cli.js
CHANGED
package/dist/icons.d.ts
CHANGED
|
@@ -4,9 +4,8 @@ export declare const iconNames: string[];
|
|
|
4
4
|
export declare function iconNode(name: string): any;
|
|
5
5
|
export declare const isIcon: (name: string) => boolean;
|
|
6
6
|
/**
|
|
7
|
-
* Close matches for an unknown name:
|
|
8
|
-
* share a word with it ("cart" → shopping-cart
|
|
9
|
-
* There are no synonyms: "notification" won't find bell.
|
|
7
|
+
* Close matches for an unknown name: known guesses ("compose" → square-pen), near spellings
|
|
8
|
+
* ("serach" → search) and names that share a word with it ("cart" → shopping-cart).
|
|
10
9
|
*/
|
|
11
10
|
export declare function suggestIcons(name: string, limit?: number): string[];
|
|
12
11
|
export declare function unknownIconMessage(name: string): string;
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { renderWireframe, compileWireframe, formatIssues, WireframeError, type C
|
|
|
3
3
|
export { wireframePrompt, repairPrompt } from "./prompt.js";
|
|
4
4
|
export { printWireframeText as formatWireframe } from "./print.js";
|
|
5
5
|
export { encodeWireframe, decodeWireframe, MAX_SHARED_TEXT } from "./share.js";
|
|
6
|
+
export { upgradeWireframe, upgradeSpec, RENAMED_PROPS, type UpgradeOptions } from "./upgrade.js";
|
|
6
7
|
export { renderWireframeSvg, renderWireframePng, checkSpec, boardSize, boardLayout, type BoardItem, type RenderWireframeOptions } from "./render.js";
|
|
7
8
|
export { parseWireframeText, type TextIssue, type ParseResult } from "./text.js";
|
|
8
9
|
export { catalog, componentDefinitions } from "./catalog.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEVICES,
|
|
3
|
+
RENAMED_PROPS,
|
|
3
4
|
WireframeError,
|
|
4
5
|
boardLayout,
|
|
5
6
|
boardSize,
|
|
@@ -16,8 +17,10 @@ import {
|
|
|
16
17
|
renderWireframeSvg,
|
|
17
18
|
repairPrompt,
|
|
18
19
|
theme,
|
|
20
|
+
upgradeSpec,
|
|
21
|
+
upgradeWireframe,
|
|
19
22
|
wireframePrompt
|
|
20
|
-
} from "./chunks/chunk-
|
|
23
|
+
} from "./chunks/chunk-X46EDSCQ.js";
|
|
21
24
|
|
|
22
25
|
// src/share.ts
|
|
23
26
|
import { deflateRawSync, inflateRawSync } from "node:zlib";
|
|
@@ -27,11 +30,13 @@ function encodeWireframe(text) {
|
|
|
27
30
|
}
|
|
28
31
|
function decodeWireframe(data) {
|
|
29
32
|
if (!data.startsWith("z")) throw new Error(`unknown encoding "${data.slice(0, 1)}" (expected a "z" prefix)`);
|
|
30
|
-
|
|
33
|
+
const text = inflateRawSync(Buffer.from(data.slice(1), "base64url"), { maxOutputLength: MAX_SHARED_TEXT }).toString("utf8");
|
|
34
|
+
return upgradeWireframe(text, { fromLink: true });
|
|
31
35
|
}
|
|
32
36
|
export {
|
|
33
37
|
DEVICES,
|
|
34
38
|
MAX_SHARED_TEXT,
|
|
39
|
+
RENAMED_PROPS,
|
|
35
40
|
WireframeError,
|
|
36
41
|
boardLayout,
|
|
37
42
|
boardSize,
|
|
@@ -50,5 +55,7 @@ export {
|
|
|
50
55
|
renderWireframeSvg,
|
|
51
56
|
repairPrompt,
|
|
52
57
|
theme,
|
|
58
|
+
upgradeSpec,
|
|
59
|
+
upgradeWireframe,
|
|
53
60
|
wireframePrompt
|
|
54
61
|
};
|
package/dist/playground/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
renderWireframePng,
|
|
12
12
|
renderWireframeSvg,
|
|
13
13
|
wireframePrompt
|
|
14
|
-
} from "../chunks/chunk-
|
|
14
|
+
} from "../chunks/chunk-X46EDSCQ.js";
|
|
15
15
|
|
|
16
16
|
// src/playground/index.ts
|
|
17
17
|
import { createServer } from "node:http";
|
|
@@ -242,18 +242,26 @@ var EXAMPLES = {
|
|
|
242
242
|
checkbox "Checked" checked
|
|
243
243
|
checkbox "Unchecked"`,
|
|
244
244
|
Radio: `board
|
|
245
|
-
screen custom width=
|
|
245
|
+
screen custom width=380 height=230
|
|
246
246
|
radio "Selected" checked
|
|
247
247
|
radio "Not selected"
|
|
248
|
-
radio "Not selected"
|
|
248
|
+
radio "Not selected"
|
|
249
|
+
text "Side by side: put them in a row" sm muted
|
|
250
|
+
stack row gap=20
|
|
251
|
+
radio "Standard" checked
|
|
252
|
+
radio "Express"
|
|
253
|
+
radio "Pickup"`,
|
|
249
254
|
Toggle: `board
|
|
250
255
|
screen custom width=300 height=100
|
|
251
256
|
toggle "On" on
|
|
252
257
|
toggle "Off" off`,
|
|
253
258
|
Select: `board
|
|
254
|
-
screen custom width=
|
|
259
|
+
screen custom width=380 height=260
|
|
255
260
|
select "Country" value="Mexico"
|
|
256
|
-
select "Size" placeholder="Choose one" width=160
|
|
261
|
+
select "Size" placeholder="Choose one" width=160
|
|
262
|
+
stack row gap=12
|
|
263
|
+
input "City" placeholder="Monterrey" grow
|
|
264
|
+
select "State" value="Nuevo Le\xF3n" grow`,
|
|
257
265
|
NavBar: `board
|
|
258
266
|
screen custom width=380 height=240 padding=0 gap=0
|
|
259
267
|
navbar "leading=menu" leading=menu actions=[search, bell]
|