@typecad/ui 1.0.0-alpha.13 → 1.0.0-alpha.14

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/src/cli.ts CHANGED
@@ -1,87 +1,90 @@
1
- #!/usr/bin/env node
2
- // ---------------------------------------------------------------------------
3
- // @typecad/ui CLI — display integration wizard.
4
- //
5
- // npx @typecad/ui --config Run the interactive integration wizard
6
- // npx @typecad/ui --help Show usage
7
- // npx @typecad/ui --version Show the installed version
8
- // ---------------------------------------------------------------------------
9
-
10
- import { runIntegrationWizard } from "./wizard/integration-wizard.js";
11
-
12
- const USAGE = `
13
- @typecad/ui — HTML/CSS-driven graphics for microcontrollers
14
-
15
- Usage:
16
- npx @typecad/ui --config Configure a display (+ touch) for your
17
- cuttlefish project interactively. Writes the
18
- \`display\` section of cuttlefish.config.ts.
19
-
20
- Options:
21
- --config Run the integration wizard
22
- --help, -h Show this help
23
- --version, -v Print the installed @typecad/ui version
24
-
25
- The wizard asks which display module you are using (ILI9341 / ST7796S SPI TFT,
26
- SSD1309 I2C OLED, desktop simulator, or custom), then walks through bus pins,
27
- SPI/I2C speed, rotation, and touch controller wiring with hardware-aware
28
- defaults. It never touches the rest of your cuttlefish.config.ts.
29
-
30
- Docs: https://github.com/justind000/typecode/tree/main/packages/ui
31
- `.trim();
32
-
33
- function printUsage(): void {
34
- console.log(USAGE);
35
- }
36
-
37
- async function main(argv: string[]): Promise<number> {
38
- const args = argv.slice(2);
39
-
40
- if (args.includes("--help") || args.includes("-h")) {
41
- printUsage();
42
- return 0;
43
- }
44
-
45
- if (args.includes("--version") || args.includes("-v")) {
46
- // Lazily read the version so importing this module stays side-effect free.
47
- const { readFile } = await import("node:fs/promises");
48
- const { fileURLToPath } = await import("node:url");
49
- const { dirname, join } = await import("node:path");
50
- try {
51
- const packageJson = JSON.parse(
52
- await readFile(join(dirname(dirname(fileURLToPath(import.meta.url))), "package.json"), "utf-8"),
53
- ) as { version?: string };
54
- console.log(packageJson.version ?? "unknown");
55
- return 0;
56
- } catch {
57
- console.log("unknown");
58
- return 0;
59
- }
60
- }
61
-
62
- if (args.length === 0 || (args.length === 1 && (args[0] === "--config" || args[0] === "config"))) {
63
- if (!process.stdin.isTTY) {
64
- console.error("The integration wizard is interactive and needs a terminal.");
65
- console.error("Run `npx @typecad/ui --config` from your project directory, or configure");
66
- console.error("the `display` section of cuttlefish.config.ts manually:");
67
- console.error("https://github.com/justind000/typecode/tree/main/packages/ui#display-configuration");
68
- return 1;
69
- }
70
- const result = await runIntegrationWizard(process.cwd());
71
- return result.exitCode;
72
- }
73
-
74
- console.error(`Unknown option${args.length > 1 ? "s" : ""}: ${args.join(" ")}\n`);
75
- printUsage();
76
- return 2;
77
- }
78
-
79
- main(process.argv).then(
80
- (code) => {
81
- process.exitCode = code;
82
- },
83
- (error) => {
84
- console.error(error instanceof Error ? error.message : error);
85
- process.exitCode = 1;
86
- },
87
- );
1
+ #!/usr/bin/env node
2
+ // ---------------------------------------------------------------------------
3
+ // @typecad/ui CLI — display integration wizard.
4
+ //
5
+ // npx @typecad/ui --config Run the interactive integration wizard
6
+ // npx @typecad/ui --help Show usage
7
+ // npx @typecad/ui --version Show the installed version
8
+ // ---------------------------------------------------------------------------
9
+
10
+ import { runIntegrationWizard } from "./wizard/integration-wizard.js";
11
+
12
+ const USAGE = `
13
+ @typecad/ui — HTML/CSS-driven graphics for microcontrollers
14
+
15
+ Usage:
16
+ npx @typecad/ui --config Configure a display (+ touch) for your
17
+ cuttlefish project interactively. Writes the
18
+ \`display\` section of cuttlefish.config.ts
19
+ and a \`preview\` npm script.
20
+
21
+ Options:
22
+ --config Run the integration wizard
23
+ --help, -h Show this help
24
+ --version, -v Print the installed @typecad/ui version
25
+
26
+ The wizard asks which display module you are using (ILI9341 / ST7796S SPI TFT,
27
+ SSD1309 I2C OLED, desktop simulator, or custom), then walks through bus pins,
28
+ SPI/I2C speed, rotation, and touch controller wiring with hardware-aware
29
+ defaults. It never touches the rest of your cuttlefish.config.ts, and adds a
30
+ \`preview\` script to package.json so the desktop preview renderer starts with
31
+ \`npm run preview\`.
32
+
33
+ Docs: https://github.com/justind000/typecode/tree/main/packages/ui
34
+ `.trim();
35
+
36
+ function printUsage(): void {
37
+ console.log(USAGE);
38
+ }
39
+
40
+ async function main(argv: string[]): Promise<number> {
41
+ const args = argv.slice(2);
42
+
43
+ if (args.includes("--help") || args.includes("-h")) {
44
+ printUsage();
45
+ return 0;
46
+ }
47
+
48
+ if (args.includes("--version") || args.includes("-v")) {
49
+ // Lazily read the version so importing this module stays side-effect free.
50
+ const { readFile } = await import("node:fs/promises");
51
+ const { fileURLToPath } = await import("node:url");
52
+ const { dirname, join } = await import("node:path");
53
+ try {
54
+ const packageJson = JSON.parse(
55
+ await readFile(join(dirname(dirname(fileURLToPath(import.meta.url))), "package.json"), "utf-8"),
56
+ ) as { version?: string };
57
+ console.log(packageJson.version ?? "unknown");
58
+ return 0;
59
+ } catch {
60
+ console.log("unknown");
61
+ return 0;
62
+ }
63
+ }
64
+
65
+ if (args.length === 0 || (args.length === 1 && (args[0] === "--config" || args[0] === "config"))) {
66
+ if (!process.stdin.isTTY) {
67
+ console.error("The integration wizard is interactive and needs a terminal.");
68
+ console.error("Run `npx @typecad/ui --config` from your project directory, or configure");
69
+ console.error("the `display` section of cuttlefish.config.ts manually:");
70
+ console.error("https://github.com/justind000/typecode/tree/main/packages/ui#display-configuration");
71
+ return 1;
72
+ }
73
+ const result = await runIntegrationWizard(process.cwd());
74
+ return result.exitCode;
75
+ }
76
+
77
+ console.error(`Unknown option${args.length > 1 ? "s" : ""}: ${args.join(" ")}\n`);
78
+ printUsage();
79
+ return 2;
80
+ }
81
+
82
+ main(process.argv).then(
83
+ (code) => {
84
+ process.exitCode = code;
85
+ },
86
+ (error) => {
87
+ console.error(error instanceof Error ? error.message : error);
88
+ process.exitCode = 1;
89
+ },
90
+ );
@@ -665,7 +665,6 @@ export class PreviewUIRuntime {
665
665
  }
666
666
 
667
667
  private navigate(screenIdx: number): void {
668
- this.onDiagnostics?.(`NAV -> screen ${screenIdx}`);
669
668
  const next = Math.trunc(Number(screenIdx));
670
669
  if (!Number.isFinite(next) || next < 0 || next >= this.screenCount || next === this.activeScreen) return;
671
670
  this.activeScreen = next;
@@ -322,9 +322,14 @@ function borderStyle(style: CSSProperty): 0 | 1 | 2 {
322
322
  }
323
323
 
324
324
  function borderWidthOf(style: CSSProperty): number {
325
+ // A none-style border renders nothing and must consume no space either:
326
+ // the draw path subtracts borderWidth*2 from the text max width, so a
327
+ // residual UA width on a borderless kit button wrapped its last glyph
328
+ // ("Primar" / "y"). Style none wins over any declared width.
329
+ if (borderStyle(style) === 0) return 0;
325
330
  const px = cssPx(style.borderWidth);
326
331
  if (px > 0) return Math.max(1, Math.min(8, px));
327
- return borderStyle(style) === 0 ? 0 : 1;
332
+ return 1;
328
333
  }
329
334
 
330
335
  /** Per-side border width: the per-side field wins when set, else falls back to
@@ -1237,10 +1237,16 @@ static inline uint8_t ui_render_screen_bands(int16_t rx, int16_t ry, int16_t rw,
1237
1237
  int16_t origBoxH = __ui_nodes[c].box.h;
1238
1238
  __ui_nodes[c].box.x = static_cast<int16_t>(origBoxX - rx);
1239
1239
  __ui_nodes[c].box.y = static_cast<int16_t>(origBoxY - ry - bandTop);
1240
- // Clamp the face to the scroll viewport (band-local coords). The
1241
- // shifted box/draw geometry stays within the candidate's clip bounds;
1242
- // text wrapping and layout metrics shrink with the box. Fully clipped
1243
- // faces drop out here.
1240
+ // Clamp the face to the scroll viewport (band-local DRAW coords). The
1241
+ // clamp must operate on the node's DRAW position — box.y is
1242
+ // content-local and the ancestor scroll is subtracted later, inside
1243
+ // ui_draw_y_for_node, so comparing box.y against display-space clip
1244
+ // bounds passed scrolled content whose CONTENT coordinate numerically
1245
+ // fell inside the viewport: a button scrolled up past the viewport top
1246
+ // painted over the header, and the smearing stuck (nothing repaints
1247
+ // the header afterwards). Clamp the draw position and shift the box by
1248
+ // the delta; text wrapping and layout metrics shrink with the box.
1249
+ // Fully clipped faces drop out here.
1244
1250
  {
1245
1251
  // int32 math: the unclipped sentinels are ±32767 and the band-local
1246
1252
  // conversion subtracts region/band offsets — int16 arithmetic wraps
@@ -1251,29 +1257,25 @@ static inline uint8_t ui_render_screen_bands(int16_t rx, int16_t ry, int16_t rw,
1251
1257
  int32_t clipBotL = static_cast<int32_t>(__ui_scroll_candidates[ci].clipBottom) - ry - bandTop;
1252
1258
  int32_t clipLeftL = static_cast<int32_t>(__ui_scroll_candidates[ci].clipLeft) - rx;
1253
1259
  int32_t clipRightL = static_cast<int32_t>(__ui_scroll_candidates[ci].clipRight) - rx;
1254
- int16_t boxY0 = __ui_nodes[c].box.y;
1255
- if (boxY0 < clipTopL) {
1256
- __ui_nodes[c].box.y = static_cast<int16_t>(clipTopL);
1257
- __ui_nodes[c].box.h = static_cast<int16_t>(__ui_nodes[c].box.h - (clipTopL - boxY0));
1258
- }
1259
- if (static_cast<int32_t>(__ui_nodes[c].box.y) + __ui_nodes[c].box.h > clipBotL) {
1260
- __ui_nodes[c].box.h = static_cast<int16_t>(clipBotL - __ui_nodes[c].box.y);
1261
- }
1262
- int16_t boxX0 = __ui_nodes[c].box.x;
1263
- if (boxX0 < clipLeftL) {
1264
- __ui_nodes[c].box.x = static_cast<int16_t>(clipLeftL);
1265
- __ui_nodes[c].box.w = static_cast<int16_t>(__ui_nodes[c].box.w - (clipLeftL - boxX0));
1266
- }
1267
- if (static_cast<int32_t>(__ui_nodes[c].box.x) + __ui_nodes[c].box.w > clipRightL) {
1268
- __ui_nodes[c].box.w = static_cast<int16_t>(clipRightL - __ui_nodes[c].box.x);
1269
- }
1270
- if (__ui_nodes[c].box.w <= 0 || __ui_nodes[c].box.h <= 0) {
1260
+ int16_t dispY0 = ui_draw_y_for_node(c);
1261
+ int16_t dispX0 = ui_draw_x_for_node(c);
1262
+ int32_t y0 = dispY0 < clipTopL ? clipTopL : dispY0;
1263
+ int32_t y1 = static_cast<int32_t>(dispY0) + __ui_nodes[c].box.h > clipBotL ? clipBotL : static_cast<int32_t>(dispY0) + __ui_nodes[c].box.h;
1264
+ int32_t x0 = dispX0 < clipLeftL ? clipLeftL : dispX0;
1265
+ int32_t x1 = static_cast<int32_t>(dispX0) + __ui_nodes[c].box.w > clipRightL ? clipRightL : static_cast<int32_t>(dispX0) + __ui_nodes[c].box.w;
1266
+ if (x1 - x0 <= 0 || y1 - y0 <= 0) {
1271
1267
  __ui_nodes[c].box.x = origBoxX;
1272
1268
  __ui_nodes[c].box.y = origBoxY;
1273
1269
  __ui_nodes[c].box.w = origBoxW;
1274
1270
  __ui_nodes[c].box.h = origBoxH;
1275
1271
  continue;
1276
1272
  }
1273
+ // box.y/box.x already carry the band shift; add the clamp delta on
1274
+ // top (y0 - dispY0 is 0 when unclipped, preserving the shift).
1275
+ __ui_nodes[c].box.y = static_cast<int16_t>(__ui_nodes[c].box.y + (y0 - dispY0));
1276
+ __ui_nodes[c].box.h = static_cast<int16_t>(y1 - y0);
1277
+ __ui_nodes[c].box.x = static_cast<int16_t>(__ui_nodes[c].box.x + (x0 - dispX0));
1278
+ __ui_nodes[c].box.w = static_cast<int16_t>(x1 - x0);
1277
1279
  }
1278
1280
  int16_t baseDrawX = ui_base_draw_x_for_node(c);
1279
1281
  int16_t baseDrawY = ui_base_draw_y_for_node(c);
@@ -136,6 +136,10 @@ export const SHADCN_KIT_CSS = `/* ----------------------------------------------
136
136
  padding: 10px 16px;
137
137
  font-weight: bold;
138
138
  transition: background 80ms;
139
+ /* Reset the UA's browser-default button border (shadcn's button reset):
140
+ ghost must be truly borderless — variants that want one (outline)
141
+ re-declare it themselves. */
142
+ border: none;
139
143
  }
140
144
  .btn:pressed { transform: translateY(1px); }
141
145
  .btn-primary { background: var(--primary); color: var(--primary-foreground); }
@@ -1,38 +1,46 @@
1
- // ---------------------------------------------------------------------------
2
- // Public surface of the @typecad/ui integration wizard (`npx @typecad/ui
3
- // --config`). Pure helpers (catalog, rendering, config splicing, starter
4
- // template) are exported for tests and tooling; the interactive flow lives in
5
- // integration-wizard.ts.
6
- // ---------------------------------------------------------------------------
7
-
8
- export {
9
- DISPLAY_CATALOG,
10
- TOUCH_CATALOG,
11
- findPinConflicts,
12
- } from "./display-catalog.js";
13
- export type {
14
- DisplayCatalogEntry,
15
- TouchCatalogEntry,
16
- WizardBus,
17
- WizardTouchKind,
18
- } from "./display-catalog.js";
19
-
20
- export {
21
- findCuttlefishConfig,
22
- findSyntaxError,
23
- readConfigSection,
24
- readEntryPath,
25
- renderDisplayBody,
26
- renderDisplayProperty,
27
- upsertDisplaySection,
28
- } from "./config-writer.js";
29
- export type {
30
- ConfigRecord,
31
- RenderDisplayOptions,
32
- UpsertDisplayResult,
33
- } from "./config-writer.js";
34
-
35
- export { renderStarterUi } from "./starter-ui.js";
36
-
37
- export { runIntegrationWizard } from "./integration-wizard.js";
38
- export type { WizardRunResult, WizardStreams } from "./integration-wizard.js";
1
+ // ---------------------------------------------------------------------------
2
+ // Public surface of the @typecad/ui integration wizard (`npx @typecad/ui
3
+ // --config`). Pure helpers (catalog, rendering, config splicing, starter
4
+ // template) are exported for tests and tooling; the interactive flow lives in
5
+ // integration-wizard.ts.
6
+ // ---------------------------------------------------------------------------
7
+
8
+ export {
9
+ DISPLAY_CATALOG,
10
+ TOUCH_CATALOG,
11
+ findPinConflicts,
12
+ } from "./display-catalog.js";
13
+ export type {
14
+ DisplayCatalogEntry,
15
+ TouchCatalogEntry,
16
+ WizardBus,
17
+ WizardTouchKind,
18
+ } from "./display-catalog.js";
19
+
20
+ export {
21
+ findCuttlefishConfig,
22
+ findSyntaxError,
23
+ readConfigSection,
24
+ readEntryPath,
25
+ renderDisplayBody,
26
+ renderDisplayProperty,
27
+ upsertDisplaySection,
28
+ } from "./config-writer.js";
29
+ export type {
30
+ ConfigRecord,
31
+ RenderDisplayOptions,
32
+ UpsertDisplayResult,
33
+ } from "./config-writer.js";
34
+
35
+ export {
36
+ findPackageJson,
37
+ previewScriptCommand,
38
+ readPackageScript,
39
+ upsertPackageScript,
40
+ } from "./package-writer.js";
41
+ export type { UpsertScriptResult } from "./package-writer.js";
42
+
43
+ export { renderStarterUi } from "./starter-ui.js";
44
+
45
+ export { runIntegrationWizard } from "./integration-wizard.js";
46
+ export type { WizardRunResult, WizardStreams } from "./integration-wizard.js";