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

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.
Files changed (113) hide show
  1. package/assets/fonts/dejavu/DejaVuSans-Bold.ttf +0 -0
  2. package/assets/fonts/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  3. package/assets/fonts/dejavu/DejaVuSans-Oblique.ttf +0 -0
  4. package/assets/fonts/dejavu/DejaVuSans.ttf +0 -0
  5. package/assets/fonts/dejavu/DejaVuSansMono-Bold.ttf +0 -0
  6. package/assets/fonts/dejavu/DejaVuSansMono.ttf +0 -0
  7. package/assets/fonts/dejavu/LICENSE +187 -0
  8. package/assets/fonts/dejavu/README.md +49 -0
  9. package/dist/engine-index.js +2 -0
  10. package/dist/preview/build-program.d.ts +7 -1
  11. package/dist/preview/build-program.js +94 -15
  12. package/dist/preview/host-gfx.d.ts +3 -0
  13. package/dist/preview/host-gfx.js +30 -3
  14. package/dist/preview/host-ui-runtime.d.ts +115 -0
  15. package/dist/preview/host-ui-runtime.js +928 -46
  16. package/dist/ui-engine/block-layout.js +25 -11
  17. package/dist/ui-engine/color.d.ts +6 -0
  18. package/dist/ui-engine/color.js +80 -2
  19. package/dist/ui-engine/compat-report.d.ts +8 -0
  20. package/dist/ui-engine/compat-report.js +124 -0
  21. package/dist/ui-engine/css-imports.d.ts +4 -0
  22. package/dist/ui-engine/css-imports.js +70 -0
  23. package/dist/ui-engine/css-parser.js +58 -14
  24. package/dist/ui-engine/default-font.d.ts +25 -0
  25. package/dist/ui-engine/default-font.js +80 -0
  26. package/dist/ui-engine/font-assets.d.ts +2 -2
  27. package/dist/ui-engine/font-assets.js +22 -5
  28. package/dist/ui-engine/html-parser.d.ts +4 -0
  29. package/dist/ui-engine/html-parser.js +241 -26
  30. package/dist/ui-engine/image-assets.d.ts +8 -1
  31. package/dist/ui-engine/image-assets.js +35 -2
  32. package/dist/ui-engine/image-decode.d.ts +22 -0
  33. package/dist/ui-engine/image-decode.js +194 -0
  34. package/dist/ui-engine/inline-parser.js +1 -1
  35. package/dist/ui-engine/layout-engine.d.ts +19 -0
  36. package/dist/ui-engine/layout-engine.js +59 -3
  37. package/dist/ui-engine/model.d.ts +18 -0
  38. package/dist/ui-engine/model.js +75 -7
  39. package/dist/ui-engine/runtime-header/blend-bodies.js +29 -9
  40. package/dist/ui-engine/runtime-header/canvas-helpers.js +3 -0
  41. package/dist/ui-engine/runtime-header/canvas-scrollbar.js +115 -111
  42. package/dist/ui-engine/runtime-header/cuttlefish-gfx.js +21 -0
  43. package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +182 -179
  44. package/dist/ui-engine/runtime-header/forward-decls.js +215 -167
  45. package/dist/ui-engine/runtime-header/init-press-input.js +138 -135
  46. package/dist/ui-engine/runtime-header/keyboard.js +682 -380
  47. package/dist/ui-engine/runtime-header/node-draw-body.js +1656 -1386
  48. package/dist/ui-engine/runtime-header/paint-order-coords.js +259 -214
  49. package/dist/ui-engine/runtime-header/paint-rects-repair.js +736 -568
  50. package/dist/ui-engine/runtime-header/scroll-physics.js +25 -1
  51. package/dist/ui-engine/runtime-header/state-bindings-nav.js +9 -0
  52. package/dist/ui-engine/runtime-header/structs.js +228 -216
  53. package/dist/ui-engine/runtime-header/text-rendering.js +888 -770
  54. package/dist/ui-engine/runtime-header/tick/bindings-phase.js +118 -116
  55. package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +897 -633
  56. package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +24 -25
  57. package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +78 -23
  58. package/dist/ui-engine/runtime-header/types-defines.js +110 -93
  59. package/dist/ui-engine/shadcn-kit.d.ts +1 -0
  60. package/dist/ui-engine/shadcn-kit.js +502 -0
  61. package/dist/ui-engine/style-resolver.d.ts +4 -0
  62. package/dist/ui-engine/style-resolver.js +58 -6
  63. package/dist/ui-engine/transpile-ui.js +12 -5
  64. package/dist/ui-engine/ua-stylesheet.d.ts +22 -1
  65. package/dist/ui-engine/ua-stylesheet.js +119 -32
  66. package/dist/ui-engine/ui-lowering.js +37 -3
  67. package/dist/ui-engine/ui-registry.js +32 -4
  68. package/dist/ui-engine/yoga-layout.js +57 -52
  69. package/package.json +9 -3
  70. package/src/engine-index.ts +53 -51
  71. package/src/preview/build-program.ts +810 -734
  72. package/src/preview/host-gfx.ts +30 -3
  73. package/src/preview/host-ui-runtime.ts +4133 -3289
  74. package/src/ui-engine/block-layout.ts +24 -11
  75. package/src/ui-engine/color.ts +77 -2
  76. package/src/ui-engine/compat-report.ts +139 -0
  77. package/src/ui-engine/css-imports.ts +69 -0
  78. package/src/ui-engine/css-parser.ts +64 -15
  79. package/src/ui-engine/default-font.ts +95 -0
  80. package/src/ui-engine/font-assets.ts +545 -525
  81. package/src/ui-engine/html-parser.ts +615 -397
  82. package/src/ui-engine/image-assets.ts +37 -2
  83. package/src/ui-engine/image-decode.ts +244 -0
  84. package/src/ui-engine/inline-parser.ts +1 -1
  85. package/src/ui-engine/layout-engine.ts +61 -3
  86. package/src/ui-engine/model.ts +1317 -1230
  87. package/src/ui-engine/runtime-header/blend-bodies.ts +29 -9
  88. package/src/ui-engine/runtime-header/canvas-helpers.ts +3 -0
  89. package/src/ui-engine/runtime-header/canvas-scrollbar.ts +121 -117
  90. package/src/ui-engine/runtime-header/cuttlefish-gfx.ts +21 -0
  91. package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +188 -185
  92. package/src/ui-engine/runtime-header/forward-decls.ts +227 -179
  93. package/src/ui-engine/runtime-header/init-press-input.ts +144 -141
  94. package/src/ui-engine/runtime-header/keyboard.ts +689 -386
  95. package/src/ui-engine/runtime-header/node-draw-body.ts +1681 -1411
  96. package/src/ui-engine/runtime-header/paint-order-coords.ts +265 -220
  97. package/src/ui-engine/runtime-header/paint-rects-repair.ts +742 -574
  98. package/src/ui-engine/runtime-header/scroll-physics.ts +25 -1
  99. package/src/ui-engine/runtime-header/state-bindings-nav.ts +9 -0
  100. package/src/ui-engine/runtime-header/structs.ts +234 -222
  101. package/src/ui-engine/runtime-header/text-rendering.ts +894 -776
  102. package/src/ui-engine/runtime-header/tick/bindings-phase.ts +124 -122
  103. package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +902 -638
  104. package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +30 -31
  105. package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +78 -23
  106. package/src/ui-engine/runtime-header/types-defines.ts +116 -99
  107. package/src/ui-engine/shadcn-kit.ts +503 -0
  108. package/src/ui-engine/style-resolver.ts +471 -416
  109. package/src/ui-engine/transpile-ui.ts +12 -5
  110. package/src/ui-engine/ua-stylesheet.ts +137 -31
  111. package/src/ui-engine/ui-lowering.ts +486 -452
  112. package/src/ui-engine/ui-registry.ts +556 -524
  113. package/src/ui-engine/yoga-layout.ts +307 -309
@@ -18,6 +18,9 @@ import { measureWithFonts, Box } from "./layout-engine.js";
18
18
  import { lowerUIToCpp, LoweredUI } from "./ui-lowering.js";
19
19
  import { buildUIFontAssets } from "./font-assets.js";
20
20
  import { buildKeyframeSets } from "./keyframes.js";
21
+ import { injectDefaultFontFaces } from "./default-font.js";
22
+ import { cssCompatDiagnostics } from "./compat-report.js";
23
+ import { expandCssImports } from "./css-imports.js";
21
24
 
22
25
  export interface TranspileUIOptions {
23
26
  colorFormat: "rgb565" | "mono";
@@ -28,11 +31,13 @@ export interface TranspileUIOptions {
28
31
 
29
32
  export function transpileUI(html: string, css: string, opts: TranspileUIOptions): LoweredUI {
30
33
  const tree = parseHtml(html);
31
- const rules = parseCss(css);
32
- const fontFaces = parseFontFaces(css);
33
- const rawKeyframes = parseKeyframes(css);
34
+ const cssBaseDir = path.resolve(opts.assetBaseDir ?? process.cwd());
35
+ const expandedCss = expandCssImports(css, cssBaseDir);
36
+ const rules = parseCss(expandedCss);
37
+ const fontFaces = injectDefaultFontFaces(parseFontFaces(expandedCss), opts.colorFormat);
38
+ const rawKeyframes = parseKeyframes(expandedCss);
34
39
  const styled = resolveStyles(tree, rules);
35
- const fontAssets = buildUIFontAssets(styled, fontFaces, path.resolve(opts.assetBaseDir ?? process.cwd()));
40
+ const fontAssets = buildUIFontAssets(styled, fontFaces, cssBaseDir);
36
41
 
37
42
  // Select layout engine: Yoga for flexbox, BlockLayout as fallback.
38
43
  const engine = selectEngine(styled);
@@ -41,5 +46,7 @@ export function transpileUI(html: string, css: string, opts: TranspileUIOptions)
41
46
 
42
47
  const keyframeSets = buildKeyframeSets(rawKeyframes, opts.colorFormat);
43
48
 
44
- return lowerUIToCpp(styled, boxes, opts.colorFormat, opts.storage, [], rules, undefined, fontAssets, [], new Map(), keyframeSets);
49
+ const result = lowerUIToCpp(styled, boxes, opts.colorFormat, opts.storage, [], rules, undefined, fontAssets, [], new Map(), keyframeSets);
50
+ result.diagnostics.push(...cssCompatDiagnostics([styled], fontAssets, opts.viewport, opts.colorFormat, "inline"));
51
+ return result;
45
52
  }
@@ -1,14 +1,91 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // UA (User-Agent) default stylesheet.
3
3
  // Applied before user CSS so elements have sensible built-in behavior without
4
- // requiring explicit CSS. User CSS overrides (cascade: last-wins).
5
- // Minimal and embedded-oriented, not a browser clone.
4
+ // requiring explicit CSS. Author CSS overrides it (cascade: the UA layer loses
5
+ // to any author rule; see style-resolver.ts).
6
+ //
7
+ // The sheet is display-anchored, like a browser's per-device-class defaults:
8
+ // the root type size (what a browser fixes at 16px on desktop) scales with the
9
+ // target display — 16px on large color panels, ~14px on 320×240 TFTs, ~12px on
10
+ // tiny panels — and every UA size derives from that root, so heading ratios
11
+ // and touch-target minimums stay web-familiar at every pixel budget.
12
+ // Monochrome displays additionally snap sizes to the stock font's buckets
13
+ // (only ~4 distinct rendered sizes exist there).
6
14
  // ---------------------------------------------------------------------------
7
15
 
8
16
  import { parseCss } from "./css-parser.js";
9
17
  import type { CSSRule } from "./css-parser.js";
18
+ import { getDisplayProfile } from "@typecad/cuttlefish/stores/display-profile-store";
19
+ import { DEFAULT_FONT_FAMILY, DEFAULT_MONO_FAMILY, defaultFontApplies } from "./default-font.js";
10
20
 
11
- const UA_CSS = `
21
+ /** The derived UA scale for one display profile. */
22
+ export interface UAScale {
23
+ /** Root (body text) size in px — the display-anchored "16px". */
24
+ root: number;
25
+ /** Monochrome display: stock bitmap font, quantized text sizes. */
26
+ mono: boolean;
27
+ /** Heading sizes h1..h6 in px. */
28
+ h: [number, number, number, number, number, number];
29
+ /** Vertical margin for headings and paragraphs (browser-like spacing). */
30
+ blockMargin: number;
31
+ /** Minimum touch-target height for buttons/selects/checks/radios. */
32
+ controlMinHeight: number;
33
+ /** Button/select vertical + horizontal padding. */
34
+ buttonPadV: number;
35
+ buttonPadH: number;
36
+ }
37
+
38
+ /** Derive the UA type scale from the display class. Height is the portrait
39
+ * dimension the profile reports; an unbound profile gets the color/large
40
+ * default (root 16 — the classic browser default). */
41
+ export function uaScaleFor(height: number | undefined, mono: boolean): UAScale {
42
+ if (mono) {
43
+ // Stock font buckets: ≤12 → size 1, 13–20 → 2, 21–28 → 3. Snap UA sizes
44
+ // to 12/20/28 so every declared size renders at a DISTINCT bucket.
45
+ const tall = (height ?? 128) >= 96;
46
+ return {
47
+ root: 12,
48
+ mono: true,
49
+ h: tall ? [28, 20, 20, 12, 12, 12] : [20, 20, 12, 12, 12, 12],
50
+ blockMargin: 6,
51
+ controlMinHeight: 32,
52
+ buttonPadV: 6,
53
+ buttonPadH: 12,
54
+ };
55
+ }
56
+ const root = height === undefined ? 16 : height >= 300 ? 16 : height >= 150 ? 14 : 12;
57
+ const r = (m: number): number => Math.round(root * m);
58
+ return {
59
+ root,
60
+ mono: false,
61
+ h: [r(1.5), r(1.25), r(1.125), r(1), r(0.875), r(0.75)],
62
+ blockMargin: r(0.5),
63
+ controlMinHeight: Math.max(32, Math.min(48, r(3))),
64
+ buttonPadV: r(0.625),
65
+ buttonPadH: r(1),
66
+ };
67
+ }
68
+
69
+ /** Read the active display profile (null when unbound — the default scale). */
70
+ function profileFor(): { height: number | undefined; mono: boolean; colorFormat: string | undefined } {
71
+ try {
72
+ const p = getDisplayProfile();
73
+ return {
74
+ height: p.height,
75
+ mono: p.colorFormat === "mono",
76
+ colorFormat: p.colorFormat,
77
+ };
78
+ } catch {
79
+ return { height: undefined, mono: false, colorFormat: undefined };
80
+ }
81
+ }
82
+
83
+ /** Build the UA stylesheet text for a scale. Sizes interpolate as concrete px
84
+ * so the CSS pipeline stays unit-simple. */
85
+ function uaCssFor(s: UAScale, useDefaultFont: boolean): string {
86
+ const gap = Math.round(s.root * 0.625);
87
+ const headerGap = Math.round(s.root * 0.5);
88
+ return `
12
89
  /* Browser-like light defaults: dark text on a near-white background.
13
90
  A dark theme overrides these via user CSS (e.g. screen { color: #f0f0f0;
14
91
  background: #1a1a1a }). Without these, screen fg defaults to white
@@ -17,7 +94,7 @@ screen {
17
94
  display: flex;
18
95
  flex-direction: column;
19
96
  color: #1a1a1a;
20
- background: #ffffff;
97
+ background: #ffffff;${useDefaultFont ? `\n /* Bundled default font: AA + continuous sizes out of the box. */\n font-family: "${DEFAULT_FONT_FAMILY}";` : ""}
21
98
  }
22
99
  body {
23
100
  display: flex;
@@ -30,40 +107,41 @@ view {
30
107
  flex-direction: column;
31
108
  }
32
109
  /* Scrollable content area — the standard pattern for multi-screen apps.
33
- align-items:center + gap give readable card-style layouts out of the box. */
34
- .scrollBody {
110
+ align-items:center + gap give readable card-style layouts out of the box.
111
+ .ui-scroll-body / .ui-screen-header are the framework-prefixed spellings. */
112
+ .scrollBody, .ui-scroll-body {
35
113
  overflow: scroll;
36
114
  flex: 1;
37
115
  align-items: center;
38
- gap: 10px;
116
+ gap: ${gap}px;
39
117
  }
40
118
  /* Header bar (back link + title). flex-direction:row is the expected layout;
41
119
  without it the back link and title stack vertically. */
42
- .screenHeader {
120
+ .screenHeader, .ui-screen-header {
43
121
  flex-direction: row;
44
122
  align-items: center;
45
- gap: 8px;
123
+ gap: ${headerGap}px;
46
124
  }
47
125
  button {
48
- padding: 10px 16px;
126
+ padding: ${s.buttonPadV}px ${s.buttonPadH}px;
49
127
  border: 1px solid;
50
- min-height: 48px;
128
+ min-height: ${s.controlMinHeight}px;
51
129
  text-align: center;
52
130
  }
53
131
  input {
54
- padding: 8px;
132
+ padding: ${Math.round(s.root * 0.5)}px;
55
133
  border: 1px solid;
56
134
  }
57
135
  select {
58
- padding: 10px 12px;
136
+ padding: ${s.buttonPadV}px ${s.buttonPadH}px;
59
137
  border: 1px solid;
60
- min-height: 48px;
138
+ min-height: ${s.controlMinHeight}px;
61
139
  }
62
140
  check, radio {
63
- min-height: 48px;
141
+ min-height: ${s.controlMinHeight}px;
64
142
  }
65
143
  range {
66
- height: 40px;
144
+ height: ${Math.round(s.root * 2.5)}px;
67
145
  }
68
146
  list {
69
147
  overflow: scroll;
@@ -75,17 +153,35 @@ a {
75
153
  label {
76
154
  text-align: left;
77
155
  }
78
- /* h3 is non-bold by default: the runtime bumps textSize for bold text
79
- (roughly doubling per-char advance), which wraps multi-word captions
80
- like "width / height / min / max" to 3 lines. Normal weight keeps
81
- them readable. Users can override with font-weight:bold if needed. */
82
- h1 { font-size: 24px; font-weight: bold; }
83
- h2 { font-size: 20px; font-weight: bold; }
84
- h3 { font-size: 18px; font-weight: normal; }
85
- h4 { font-size: 16px; font-weight: bold; }
86
- h5 { font-size: 14px; }
87
- h6 { font-size: 12px; }
88
- p { font-size: 16px; }
156
+ /* Headings and paragraphs carry browser-like margins so a bare heading stack
157
+ doesn't render glued together. Sizes derive from the display-anchored root
158
+ (see uaScaleFor). */
159
+ h1 { font-size: ${s.h[0]}px; font-weight: bold; margin: ${s.blockMargin}px 0; }
160
+ h2 { font-size: ${s.h[1]}px; font-weight: bold; margin: ${s.blockMargin}px 0; }
161
+ h3 { font-size: ${s.h[2]}px; font-weight: bold; margin: ${s.blockMargin}px 0; }
162
+ h4 { font-size: ${s.h[3]}px; font-weight: bold; margin: ${s.blockMargin}px 0; }
163
+ h5 { font-size: ${s.h[4]}px; margin: ${s.blockMargin}px 0; }
164
+ h6 { font-size: ${s.h[5]}px; margin: ${s.blockMargin}px 0; }
165
+ p { font-size: ${s.root}px; margin: ${s.blockMargin}px 0; }
166
+ small { font-size: ${Math.round(s.root * 0.8)}px; }
167
+ hr { height: 1px; background: #808080; margin: ${s.blockMargin}px 0; }
168
+ /* Definition lists: bold term, indented description — the settings-screen
169
+ idiom web authors reach for. */
170
+ dt { font-weight: bold; }
171
+ dd { margin-left: ${Math.round(s.root * 0.75)}px; }
172
+ /* Tables: the equal-width flex approximation — tr is a row, td/th are
173
+ stretched cells (flex:1 basis 0), th bold + centered like browsers.
174
+ No auto column sizing; set widths/flex-grow on cells for custom columns. */
175
+ table { gap: 1px; }
176
+ tr { flex-direction: row; align-items: stretch; gap: 1px; }
177
+ td { flex: 1; padding: ${Math.round(s.root * 0.3)}px; text-align: left; }
178
+ th { flex: 1; padding: ${Math.round(s.root * 0.3)}px; text-align: center; font-weight: bold; }
179
+ caption { text-align: center; margin: ${Math.round(s.root * 0.25)}px 0; }
180
+ /* Grouped form chrome. */
181
+ fieldset { border: 1px solid; padding: ${s.blockMargin}px; }
182
+ /* Preformatted text keeps its whitespace (engine white-space modes); the
183
+ monospace family rides on the bundled DejaVu Sans Mono when available. */
184
+ pre { white-space: pre; }${useDefaultFont ? `\npre, code, kbd, samp { font-family: "${DEFAULT_MONO_FAMILY}"; }` : ""}
89
185
  /* Keyboard structural defaults (the OSK is runtime-generated, not
90
186
  user-authored HTML). Border keeps keys visually separated. Colors
91
187
  are design choices left to user CSS. */
@@ -93,11 +189,21 @@ p { font-size: 16px; }
93
189
  border: 1px solid;
94
190
  }
95
191
  `;
192
+ }
96
193
 
97
- let UA_RULES: CSSRule[] | null = null;
194
+ const cache = new Map<string, CSSRule[]>();
98
195
 
99
- /** Get the parsed UA default rules. Lazily computed, cached. */
196
+ /** Get the parsed UA default rules for the active display profile. Lazily
197
+ * computed, cached per scale (profile changes pick up a new scale key). */
100
198
  export function getUARules(): CSSRule[] {
101
- if (!UA_RULES) UA_RULES = parseCss(UA_CSS);
102
- return UA_RULES;
199
+ const profile = profileFor();
200
+ const scale = uaScaleFor(profile.height, profile.mono);
201
+ const useDefaultFont = defaultFontApplies(profile.colorFormat);
202
+ const key = `${scale.root}|${scale.mono}|${useDefaultFont}|${scale.h.join(",")}`;
203
+ let rules = cache.get(key);
204
+ if (!rules) {
205
+ rules = parseCss(uaCssFor(scale, useDefaultFont));
206
+ cache.set(key, rules);
207
+ }
208
+ return rules;
103
209
  }