tsquare 0.3.0 → 0.3.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/dist/catalog.d.ts +0 -18
- package/dist/chunks/{chunk-46MGJLGB.js → chunk-YCIRLKKD.js} +155 -24
- 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 +1 -1
- package/dist/playground/playground.js +17 -14
- package/dist/share.d.ts +4 -0
- package/dist/upgrade.d.ts +41 -0
- package/package.json +1 -1
package/dist/catalog.d.ts
CHANGED
|
@@ -1035,21 +1035,3 @@ export declare function listItemEnds(p: {
|
|
|
1035
1035
|
leading: string | undefined;
|
|
1036
1036
|
trailing: string | undefined;
|
|
1037
1037
|
};
|
|
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);
|
|
@@ -514,23 +532,6 @@ function listItemEnds(p) {
|
|
|
514
532
|
trailing: p.trailing ?? (p.trailingIcon ? "icon" : p.trailingText ? "text" : void 0)
|
|
515
533
|
};
|
|
516
534
|
}
|
|
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
535
|
|
|
535
536
|
// src/components.tsx
|
|
536
537
|
import React, { Children, cloneElement, isValidElement } from "react";
|
|
@@ -1629,7 +1630,7 @@ function parseWireframeText(source) {
|
|
|
1629
1630
|
if (t.kind !== "sym" && (c.isSym("=", 1) || c.isSym(":", 1))) {
|
|
1630
1631
|
c.next();
|
|
1631
1632
|
c.next();
|
|
1632
|
-
const key =
|
|
1633
|
+
const key = t.value;
|
|
1633
1634
|
if (!(key in info.props)) {
|
|
1634
1635
|
const elsewhere = belongsElsewhere(info.name, key, true);
|
|
1635
1636
|
issues.push({ line: lineNo, message: `${info.name} has no prop "${key}"` + (elsewhere ? `: ${elsewhere}` : "") + ` (its props: ${Object.keys(info.props).join(", ")})` });
|
|
@@ -1687,6 +1688,122 @@ function parseWireframeText(source) {
|
|
|
1687
1688
|
return { spec: root ? { root, elements } : null, issues, lines };
|
|
1688
1689
|
}
|
|
1689
1690
|
|
|
1691
|
+
// src/upgrade.ts
|
|
1692
|
+
var RENAMED_PROPS = {
|
|
1693
|
+
Button: { icon: "leadingIcon" },
|
|
1694
|
+
ListItem: { icon: "leadingIcon" }
|
|
1695
|
+
};
|
|
1696
|
+
var RENAMED_BY_WORD = new Map(Object.entries(RENAMED_PROPS).map(([type, renames]) => [type.toLowerCase(), renames]));
|
|
1697
|
+
function upgradeWireframe(text, options = {}) {
|
|
1698
|
+
const lines = text.replace(/\r\n?/g, "\n").split("\n");
|
|
1699
|
+
const out = [];
|
|
1700
|
+
for (const line of lines) {
|
|
1701
|
+
const trimmed = line.trim();
|
|
1702
|
+
const component = trimmed.match(/^([A-Za-z][\w-]*)/)?.[1];
|
|
1703
|
+
if (!component || trimmed.startsWith("```")) {
|
|
1704
|
+
out.push(line);
|
|
1705
|
+
continue;
|
|
1706
|
+
}
|
|
1707
|
+
const tokens = scanLine(line, component.length + (line.length - line.trimStart().length));
|
|
1708
|
+
let upgraded = line;
|
|
1709
|
+
let comment = "";
|
|
1710
|
+
if (options.fromLink) {
|
|
1711
|
+
const hash = tokens.find((t) => t.kind === "word" && t.depth === 0 && upgraded[t.start] === "#" && !t.afterEquals);
|
|
1712
|
+
if (hash && !fillsMainText(component, tokens, hash)) {
|
|
1713
|
+
comment = upgraded.slice(hash.start).trimEnd();
|
|
1714
|
+
upgraded = upgraded.slice(0, hash.start).trimEnd();
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
const renames = RENAMED_BY_WORD.get(component.toLowerCase().replace(/[-_]/g, ""));
|
|
1718
|
+
if (renames) {
|
|
1719
|
+
for (const t of [...tokens].reverse()) {
|
|
1720
|
+
if (t.kind === "key" && t.depth === 0 && t.start < upgraded.length && renames[t.text]) {
|
|
1721
|
+
upgraded = upgraded.slice(0, t.start) + renames[t.text] + upgraded.slice(t.start + t.text.length);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
if (comment) out.push(line.slice(0, line.length - line.trimStart().length) + comment);
|
|
1726
|
+
out.push(upgraded);
|
|
1727
|
+
}
|
|
1728
|
+
return out.join("\n");
|
|
1729
|
+
}
|
|
1730
|
+
function upgradeSpec(spec) {
|
|
1731
|
+
for (const el of Object.values(spec.elements)) {
|
|
1732
|
+
const renames = RENAMED_PROPS[el.type];
|
|
1733
|
+
if (!renames || !el.props) continue;
|
|
1734
|
+
for (const [from, to] of Object.entries(renames)) {
|
|
1735
|
+
if (from in el.props) {
|
|
1736
|
+
if (!(to in el.props)) el.props[to] = el.props[from];
|
|
1737
|
+
delete el.props[from];
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
return spec;
|
|
1742
|
+
}
|
|
1743
|
+
var PLAIN_WORD_MAIN_TEXT = ["avatar", "icon"];
|
|
1744
|
+
function fillsMainText(component, tokens, hash) {
|
|
1745
|
+
if (!PLAIN_WORD_MAIN_TEXT.includes(component.toLowerCase())) return false;
|
|
1746
|
+
for (const t of tokens) {
|
|
1747
|
+
if (t === hash) return true;
|
|
1748
|
+
if (t.depth > 0 || t.afterEquals || t.kind === "key") continue;
|
|
1749
|
+
return false;
|
|
1750
|
+
}
|
|
1751
|
+
return false;
|
|
1752
|
+
}
|
|
1753
|
+
function scanLine(line, from) {
|
|
1754
|
+
const tokens = [];
|
|
1755
|
+
let depth = 0;
|
|
1756
|
+
let afterEquals = false;
|
|
1757
|
+
let i = from;
|
|
1758
|
+
while (i < line.length) {
|
|
1759
|
+
const c = line[i];
|
|
1760
|
+
if (c === " " || c === " ") {
|
|
1761
|
+
i++;
|
|
1762
|
+
continue;
|
|
1763
|
+
}
|
|
1764
|
+
if (c === '"' || c === "'") {
|
|
1765
|
+
let j2 = i + 1;
|
|
1766
|
+
while (j2 < line.length && line[j2] !== c) j2 += line[j2] === "\\" ? 2 : 1;
|
|
1767
|
+
tokens.push({ kind: "string", text: line.slice(i, j2 + 1), start: i, depth, afterEquals });
|
|
1768
|
+
afterEquals = false;
|
|
1769
|
+
i = j2 + 1;
|
|
1770
|
+
continue;
|
|
1771
|
+
}
|
|
1772
|
+
if (c === "[" || c === "{") {
|
|
1773
|
+
depth++;
|
|
1774
|
+
afterEquals = false;
|
|
1775
|
+
i++;
|
|
1776
|
+
continue;
|
|
1777
|
+
}
|
|
1778
|
+
if (c === "]" || c === "}") {
|
|
1779
|
+
depth = Math.max(0, depth - 1);
|
|
1780
|
+
afterEquals = false;
|
|
1781
|
+
i++;
|
|
1782
|
+
continue;
|
|
1783
|
+
}
|
|
1784
|
+
if (c === ",") {
|
|
1785
|
+
afterEquals = false;
|
|
1786
|
+
i++;
|
|
1787
|
+
continue;
|
|
1788
|
+
}
|
|
1789
|
+
if (c === "=" || c === ":") {
|
|
1790
|
+
afterEquals = true;
|
|
1791
|
+
i++;
|
|
1792
|
+
continue;
|
|
1793
|
+
}
|
|
1794
|
+
let j = i;
|
|
1795
|
+
while (j < line.length && !` ,=[]{}":`.includes(line[j])) j++;
|
|
1796
|
+
const text = line.slice(i, j);
|
|
1797
|
+
let k = j;
|
|
1798
|
+
while (line[k] === " " || line[k] === " ") k++;
|
|
1799
|
+
const isKey = !afterEquals && (line[k] === "=" || line[k] === ":");
|
|
1800
|
+
tokens.push({ kind: isKey ? "key" : "word", text, start: i, depth, afterEquals });
|
|
1801
|
+
afterEquals = false;
|
|
1802
|
+
i = j;
|
|
1803
|
+
}
|
|
1804
|
+
return tokens;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1690
1807
|
// src/render.ts
|
|
1691
1808
|
import { readFile } from "node:fs/promises";
|
|
1692
1809
|
import { createRequire } from "node:module";
|
|
@@ -1711,8 +1828,19 @@ var SpecError = class extends Error {
|
|
|
1711
1828
|
}
|
|
1712
1829
|
issues;
|
|
1713
1830
|
};
|
|
1831
|
+
function listItemKindHint(el, path) {
|
|
1832
|
+
const prop = path[0];
|
|
1833
|
+
if (el.type !== "ListItem" || path.length !== 1 || prop !== "leadingIcon" && prop !== "trailingIcon") return void 0;
|
|
1834
|
+
const side = prop === "leadingIcon" ? "leading" : "trailing";
|
|
1835
|
+
const value = String(el.props?.[prop]);
|
|
1836
|
+
let schema = componentDefinitions.ListItem.props.shape[side];
|
|
1837
|
+
while (schema && ["optional", "nullable", "default"].includes(schema.def?.type)) schema = schema.def.innerType;
|
|
1838
|
+
const kinds = schema?.options ?? [];
|
|
1839
|
+
if (value === "icon" || !kinds.includes(value)) return void 0;
|
|
1840
|
+
return `${value} is a ${side} kind, not an icon name: write ${side}=${value} instead of ${prop}=${value}`;
|
|
1841
|
+
}
|
|
1714
1842
|
function checkSpec(spec) {
|
|
1715
|
-
|
|
1843
|
+
upgradeSpec(spec);
|
|
1716
1844
|
const issues = [];
|
|
1717
1845
|
const result = catalog.validate(spec);
|
|
1718
1846
|
if (!result.success) {
|
|
@@ -1738,7 +1866,7 @@ function checkSpec(spec) {
|
|
|
1738
1866
|
const parsed = def.props.safeParse(el.props ?? {});
|
|
1739
1867
|
if (!parsed.success) {
|
|
1740
1868
|
for (const i of parsed.error.issues) {
|
|
1741
|
-
issues.push(`${id}.props${i.path.length ? "." + i.path.join(".") : ""}: ${i.message}`);
|
|
1869
|
+
issues.push(`${id}.props${i.path.length ? "." + i.path.join(".") : ""}: ${listItemKindHint(el, i.path) ?? i.message}`);
|
|
1742
1870
|
}
|
|
1743
1871
|
}
|
|
1744
1872
|
for (const key of Object.keys(el.props ?? {})) {
|
|
@@ -1826,7 +1954,7 @@ function boardSize(spec) {
|
|
|
1826
1954
|
return { width, height };
|
|
1827
1955
|
}
|
|
1828
1956
|
async function renderWireframeSvg(spec, opts = {}) {
|
|
1829
|
-
|
|
1957
|
+
upgradeSpec(spec);
|
|
1830
1958
|
if (!opts.skipValidation) {
|
|
1831
1959
|
const issues = checkSpec(spec);
|
|
1832
1960
|
if (issues.length) throw new SpecError(issues);
|
|
@@ -1849,7 +1977,7 @@ async function renderWireframePng(spec, opts = {}) {
|
|
|
1849
1977
|
|
|
1850
1978
|
// src/compile.ts
|
|
1851
1979
|
function compileWireframe(source) {
|
|
1852
|
-
const parsed = parseWireframeText(source);
|
|
1980
|
+
const parsed = parseWireframeText(upgradeWireframe(source));
|
|
1853
1981
|
const issues = [...parsed.issues];
|
|
1854
1982
|
if (parsed.spec) {
|
|
1855
1983
|
for (const msg of checkSpec(parsed.spec)) {
|
|
@@ -2110,6 +2238,9 @@ export {
|
|
|
2110
2238
|
PRIMARY_PROP,
|
|
2111
2239
|
bareWords,
|
|
2112
2240
|
parseWireframeText,
|
|
2241
|
+
RENAMED_PROPS,
|
|
2242
|
+
upgradeWireframe,
|
|
2243
|
+
upgradeSpec,
|
|
2113
2244
|
checkSpec,
|
|
2114
2245
|
boardLayout,
|
|
2115
2246
|
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-YCIRLKKD.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