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 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
- function suggestIcons(name, limit = 3) {
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
- return scored.sort((a, b) => a[1] - b[1] || a[0].localeCompare(b[0])).slice(0, limit).map(([n]) => n);
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 = RENAMED_PROPS[info.name]?.[t.value] ?? t.value;
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
- upgradeRenamedProps(spec);
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
- upgradeRenamedProps(spec);
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
@@ -6,7 +6,7 @@ import {
6
6
  printWireframeText,
7
7
  renderWireframe,
8
8
  wireframePrompt
9
- } from "./chunks/chunk-46MGJLGB.js";
9
+ } from "./chunks/chunk-YCIRLKKD.js";
10
10
 
11
11
  // src/cli.ts
12
12
  import { readFile, writeFile } from "node:fs/promises";
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: near spellings ("serach" → search) and names that
8
- * share a word with it ("cart" → shopping-cart, "call" → phone-call).
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-46MGJLGB.js";
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
- return inflateRawSync(Buffer.from(data.slice(1), "base64url"), { maxOutputLength: MAX_SHARED_TEXT }).toString("utf8");
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
  };
@@ -11,7 +11,7 @@ import {
11
11
  renderWireframePng,
12
12
  renderWireframeSvg,
13
13
  wireframePrompt
14
- } from "../chunks/chunk-46MGJLGB.js";
14
+ } from "../chunks/chunk-YCIRLKKD.js";
15
15
 
16
16
  // src/playground/index.ts
17
17
  import { createServer } from "node:http";