@hypen-space/web 0.4.980 → 0.5.3

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 (229) hide show
  1. package/README.md +20 -13
  2. package/dist/canvas/accessibility.d.ts +118 -11
  3. package/dist/canvas/accessibility.js +470 -108
  4. package/dist/canvas/accessibility.js.map +1 -1
  5. package/dist/canvas/dispatch.d.ts +24 -0
  6. package/dist/canvas/dispatch.js +62 -0
  7. package/dist/canvas/dispatch.js.map +1 -0
  8. package/dist/canvas/editing.d.ts +178 -0
  9. package/dist/canvas/editing.js +590 -0
  10. package/dist/canvas/editing.js.map +1 -0
  11. package/dist/canvas/events.d.ts +28 -23
  12. package/dist/canvas/events.js +90 -103
  13. package/dist/canvas/events.js.map +1 -1
  14. package/dist/canvas/focus.d.ts +78 -0
  15. package/dist/canvas/focus.js +182 -0
  16. package/dist/canvas/focus.js.map +1 -0
  17. package/dist/canvas/index.d.ts +3 -2
  18. package/dist/canvas/index.js +3 -2
  19. package/dist/canvas/index.js.map +1 -1
  20. package/dist/canvas/layout.js +68 -98
  21. package/dist/canvas/layout.js.map +1 -1
  22. package/dist/canvas/paint.d.ts +16 -3
  23. package/dist/canvas/paint.js +449 -110
  24. package/dist/canvas/paint.js.map +1 -1
  25. package/dist/canvas/renderer.d.ts +28 -4
  26. package/dist/canvas/renderer.js +201 -81
  27. package/dist/canvas/renderer.js.map +1 -1
  28. package/dist/canvas/selection.js +31 -177
  29. package/dist/canvas/selection.js.map +1 -1
  30. package/dist/canvas/text-geometry.d.ts +72 -0
  31. package/dist/canvas/text-geometry.js +244 -0
  32. package/dist/canvas/text-geometry.js.map +1 -0
  33. package/dist/canvas/text.js +44 -18
  34. package/dist/canvas/text.js.map +1 -1
  35. package/dist/canvas/types.d.ts +10 -1
  36. package/dist/canvas/utils.d.ts +0 -23
  37. package/dist/canvas/utils.js +3 -67
  38. package/dist/canvas/utils.js.map +1 -1
  39. package/dist/canvas/variants.d.ts +52 -0
  40. package/dist/canvas/variants.js +156 -0
  41. package/dist/canvas/variants.js.map +1 -0
  42. package/dist/dom/a11y-styles.d.ts +20 -0
  43. package/dist/dom/a11y-styles.js +61 -0
  44. package/dist/dom/a11y-styles.js.map +1 -0
  45. package/dist/dom/applicators/aria.d.ts +19 -0
  46. package/dist/dom/applicators/aria.js +36 -0
  47. package/dist/dom/applicators/aria.js.map +1 -0
  48. package/dist/dom/applicators/events.d.ts +7 -0
  49. package/dist/dom/applicators/events.js +36 -8
  50. package/dist/dom/applicators/events.js.map +1 -1
  51. package/dist/dom/applicators/font.d.ts +11 -6
  52. package/dist/dom/applicators/font.js +16 -27
  53. package/dist/dom/applicators/font.js.map +1 -1
  54. package/dist/dom/applicators/index.js +51 -45
  55. package/dist/dom/applicators/index.js.map +1 -1
  56. package/dist/dom/canvas/index.js +2 -2
  57. package/dist/dom/canvas/index.js.map +1 -1
  58. package/dist/dom/components/app.js +2 -2
  59. package/dist/dom/components/app.js.map +1 -1
  60. package/dist/dom/components/audio.js +2 -2
  61. package/dist/dom/components/audio.js.map +1 -1
  62. package/dist/dom/components/avatar.js +3 -4
  63. package/dist/dom/components/avatar.js.map +1 -1
  64. package/dist/dom/components/badge.js +3 -4
  65. package/dist/dom/components/badge.js.map +1 -1
  66. package/dist/dom/components/button.js +2 -2
  67. package/dist/dom/components/button.js.map +1 -1
  68. package/dist/dom/components/card.js +2 -2
  69. package/dist/dom/components/card.js.map +1 -1
  70. package/dist/dom/components/center.js +2 -2
  71. package/dist/dom/components/center.js.map +1 -1
  72. package/dist/dom/components/checkbox.js +4 -5
  73. package/dist/dom/components/checkbox.js.map +1 -1
  74. package/dist/dom/components/column.js +2 -2
  75. package/dist/dom/components/column.js.map +1 -1
  76. package/dist/dom/components/container.js +2 -2
  77. package/dist/dom/components/container.js.map +1 -1
  78. package/dist/dom/components/divider.js +2 -2
  79. package/dist/dom/components/divider.js.map +1 -1
  80. package/dist/dom/components/grid.js +10 -10
  81. package/dist/dom/components/grid.js.map +1 -1
  82. package/dist/dom/components/heading.js +4 -6
  83. package/dist/dom/components/heading.js.map +1 -1
  84. package/dist/dom/components/hypenapp.d.ts +25 -1
  85. package/dist/dom/components/hypenapp.js +215 -247
  86. package/dist/dom/components/hypenapp.js.map +1 -1
  87. package/dist/dom/components/icon.js +4 -5
  88. package/dist/dom/components/icon.js.map +1 -1
  89. package/dist/dom/components/image.js +2 -2
  90. package/dist/dom/components/image.js.map +1 -1
  91. package/dist/dom/components/index.d.ts +2 -2
  92. package/dist/dom/components/index.js +9 -2
  93. package/dist/dom/components/index.js.map +1 -1
  94. package/dist/dom/components/input.js +2 -2
  95. package/dist/dom/components/input.js.map +1 -1
  96. package/dist/dom/components/link.js +2 -2
  97. package/dist/dom/components/link.js.map +1 -1
  98. package/dist/dom/components/list.js +2 -2
  99. package/dist/dom/components/list.js.map +1 -1
  100. package/dist/dom/components/paragraph.js +3 -4
  101. package/dist/dom/components/paragraph.js.map +1 -1
  102. package/dist/dom/components/progressbar.js +3 -3
  103. package/dist/dom/components/progressbar.js.map +1 -1
  104. package/dist/dom/components/route.js +2 -2
  105. package/dist/dom/components/route.js.map +1 -1
  106. package/dist/dom/components/router.js +2 -2
  107. package/dist/dom/components/router.js.map +1 -1
  108. package/dist/dom/components/row.js +10 -10
  109. package/dist/dom/components/row.js.map +1 -1
  110. package/dist/dom/components/select.js +3 -4
  111. package/dist/dom/components/select.js.map +1 -1
  112. package/dist/dom/components/slider.js +2 -2
  113. package/dist/dom/components/slider.js.map +1 -1
  114. package/dist/dom/components/spacer.js +2 -2
  115. package/dist/dom/components/spacer.js.map +1 -1
  116. package/dist/dom/components/spinner.js +27 -13
  117. package/dist/dom/components/spinner.js.map +1 -1
  118. package/dist/dom/components/stack.js +10 -10
  119. package/dist/dom/components/stack.js.map +1 -1
  120. package/dist/dom/components/switch.js +5 -6
  121. package/dist/dom/components/switch.js.map +1 -1
  122. package/dist/dom/components/tabs.d.ts +27 -0
  123. package/dist/dom/components/tabs.js +69 -0
  124. package/dist/dom/components/tabs.js.map +1 -0
  125. package/dist/dom/components/text.js +4 -7
  126. package/dist/dom/components/text.js.map +1 -1
  127. package/dist/dom/components/textarea.js +2 -2
  128. package/dist/dom/components/textarea.js.map +1 -1
  129. package/dist/dom/components/video.js +2 -2
  130. package/dist/dom/components/video.js.map +1 -1
  131. package/dist/dom/components/visuallyhidden.d.ts +9 -0
  132. package/dist/dom/components/visuallyhidden.js +26 -0
  133. package/dist/dom/components/visuallyhidden.js.map +1 -0
  134. package/dist/dom/debug.js +6 -8
  135. package/dist/dom/debug.js.map +1 -1
  136. package/dist/dom/index.d.ts +0 -1
  137. package/dist/dom/index.js +0 -1
  138. package/dist/dom/index.js.map +1 -1
  139. package/dist/dom/operability.d.ts +100 -0
  140. package/dist/dom/operability.js +298 -0
  141. package/dist/dom/operability.js.map +1 -0
  142. package/dist/dom/renderer.d.ts +98 -5
  143. package/dist/dom/renderer.js +400 -66
  144. package/dist/dom/renderer.js.map +1 -1
  145. package/dist/dom/route-focus.d.ts +42 -0
  146. package/dist/dom/route-focus.js +88 -0
  147. package/dist/dom/route-focus.js.map +1 -0
  148. package/dist/dom/semantics.d.ts +35 -0
  149. package/dist/dom/semantics.js +184 -0
  150. package/dist/dom/semantics.js.map +1 -0
  151. package/dist/variants.d.ts +90 -0
  152. package/dist/variants.js +206 -0
  153. package/dist/variants.js.map +1 -0
  154. package/package.json +2 -2
  155. package/src/canvas/QUICKSTART.md +16 -11
  156. package/src/canvas/README.md +63 -34
  157. package/src/canvas/accessibility.ts +507 -115
  158. package/src/canvas/dispatch.ts +78 -0
  159. package/src/canvas/editing.ts +697 -0
  160. package/src/canvas/events.ts +104 -115
  161. package/src/canvas/focus.ts +216 -0
  162. package/src/canvas/index.ts +8 -9
  163. package/src/canvas/layout.ts +68 -103
  164. package/src/canvas/paint.ts +509 -109
  165. package/src/canvas/renderer.ts +239 -99
  166. package/src/canvas/selection.ts +52 -210
  167. package/src/canvas/text-geometry.ts +311 -0
  168. package/src/canvas/text.ts +45 -18
  169. package/src/canvas/types.ts +28 -1
  170. package/src/canvas/utils.ts +3 -69
  171. package/src/canvas/variants.ts +172 -0
  172. package/src/dom/README.md +8 -7
  173. package/src/dom/a11y-styles.ts +65 -0
  174. package/src/dom/applicators/aria.ts +41 -0
  175. package/src/dom/applicators/events.ts +39 -7
  176. package/src/dom/applicators/font.ts +16 -29
  177. package/src/dom/applicators/index.ts +61 -46
  178. package/src/dom/canvas/index.ts +2 -2
  179. package/src/dom/components/app.ts +2 -2
  180. package/src/dom/components/audio.ts +2 -2
  181. package/src/dom/components/avatar.ts +3 -4
  182. package/src/dom/components/badge.ts +3 -4
  183. package/src/dom/components/button.ts +2 -2
  184. package/src/dom/components/card.ts +2 -2
  185. package/src/dom/components/center.ts +2 -2
  186. package/src/dom/components/checkbox.ts +4 -5
  187. package/src/dom/components/column.ts +2 -2
  188. package/src/dom/components/container.ts +2 -2
  189. package/src/dom/components/divider.ts +2 -2
  190. package/src/dom/components/grid.ts +10 -10
  191. package/src/dom/components/heading.ts +4 -6
  192. package/src/dom/components/hypenapp.ts +246 -274
  193. package/src/dom/components/icon.ts +4 -5
  194. package/src/dom/components/image.ts +2 -2
  195. package/src/dom/components/index.ts +10 -3
  196. package/src/dom/components/input.ts +2 -2
  197. package/src/dom/components/link.ts +2 -2
  198. package/src/dom/components/list.ts +2 -2
  199. package/src/dom/components/paragraph.ts +3 -4
  200. package/src/dom/components/progressbar.ts +3 -3
  201. package/src/dom/components/route.ts +2 -2
  202. package/src/dom/components/router.ts +2 -2
  203. package/src/dom/components/row.ts +10 -10
  204. package/src/dom/components/select.ts +3 -4
  205. package/src/dom/components/slider.ts +2 -2
  206. package/src/dom/components/spacer.ts +2 -2
  207. package/src/dom/components/spinner.ts +34 -16
  208. package/src/dom/components/stack.ts +10 -10
  209. package/src/dom/components/switch.ts +5 -6
  210. package/src/dom/components/tabs.ts +76 -0
  211. package/src/dom/components/text.ts +4 -7
  212. package/src/dom/components/textarea.ts +2 -2
  213. package/src/dom/components/video.ts +2 -2
  214. package/src/dom/components/visuallyhidden.ts +30 -0
  215. package/src/dom/debug.ts +6 -8
  216. package/src/dom/index.ts +0 -8
  217. package/src/dom/operability.ts +312 -0
  218. package/src/dom/renderer.ts +457 -66
  219. package/src/dom/route-focus.ts +98 -0
  220. package/src/dom/semantics.ts +199 -0
  221. package/src/variants.ts +249 -0
  222. package/dist/canvas/input.d.ts +0 -76
  223. package/dist/canvas/input.js +0 -207
  224. package/dist/canvas/input.js.map +0 -1
  225. package/dist/client.d.ts +0 -36
  226. package/dist/client.js +0 -47
  227. package/dist/client.js.map +0 -1
  228. package/src/canvas/input.ts +0 -251
  229. package/src/client.ts +0 -88
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Shared variant (responsive breakpoint + interaction state) support.
3
+ *
4
+ * The engine emits applicator props using the canonical key format:
5
+ *
6
+ * <camelBase><variant?><argSuffix>
7
+ *
8
+ * - camelBase: camelCase applicator name, e.g. "padding", "backgroundColor"
9
+ * - variant?: "@<bp>" and/or ":<state>", in that order if combined.
10
+ * bp in { sm, md, lg, xl, 2xl }
11
+ * state in { hover, focus, active, disabled,
12
+ * focus-visible, focus-within }
13
+ * combined example: "backgroundColor@md:hover" (BOTH md AND hover)
14
+ * - argSuffix: ".<index>" (almost always ".0") or ".<name>" for named args.
15
+ *
16
+ * Examples emitted by the engine:
17
+ * "padding.0", "padding@md.0", "backgroundColor:hover.0",
18
+ * "backgroundColor@md:hover.0".
19
+ *
20
+ * IMPORTANT: the variant marker sits BETWEEN the base name and the ".0" arg
21
+ * suffix. A lookup that forgets the ".0" (e.g. building just "padding@md")
22
+ * MISSES the real key. Always parse the variant out of the full key.
23
+ *
24
+ * Both the DOM applicator path and the Canvas renderer import this module so
25
+ * breakpoint pixels, state names, and precedence stay byte-compatible across
26
+ * renderers. The DOM renderer leans on the CSS engine for actual resolution
27
+ * (media queries + pseudo-classes), so it only needs PARSE_* here; Canvas does
28
+ * its own paint/layout and therefore also uses the runtime resolver.
29
+ */
30
+ /**
31
+ * Tailwind breakpoint min-widths (px). Order matters: ascending min-width.
32
+ * Mirrors the DOM `BREAKPOINTS` table and the iOS reference.
33
+ */
34
+ export const BREAKPOINTS = {
35
+ sm: 640,
36
+ md: 768,
37
+ lg: 1024,
38
+ xl: 1280,
39
+ "2xl": 1536,
40
+ };
41
+ /** Breakpoint names in ascending min-width order (sm < md < lg < xl < 2xl). */
42
+ export const BREAKPOINT_ORDER = ["sm", "md", "lg", "xl", "2xl"];
43
+ /** Interaction-state names recognised in variant keys (same set as the DOM). */
44
+ export const VALID_STATES = [
45
+ "hover",
46
+ "focus",
47
+ "active",
48
+ "disabled",
49
+ "focus-visible",
50
+ "focus-within",
51
+ ];
52
+ const VALID_STATE_SET = new Set(VALID_STATES);
53
+ /**
54
+ * Parse a prop key into `{ base, breakpoint, state, arg }`.
55
+ *
56
+ * Splits the arg suffix first (the LAST `.<token>`), then peels the variant
57
+ * markers (`@bp` and/or `:state`) off the remainder. An unknown breakpoint or
58
+ * state is left as part of the base name (caller decides what to do); this
59
+ * matches the DOM behaviour where an invalid `@invalid` / `:invalid` simply
60
+ * never matches a real variant.
61
+ *
62
+ * Examples:
63
+ * "padding.0" -> { base:"padding", breakpoint:null, state:null, arg:"0" }
64
+ * "padding@md.0" -> { base:"padding", breakpoint:"md", state:null, arg:"0" }
65
+ * "backgroundColor:hover.0" -> { base:"backgroundColor", breakpoint:null, state:"hover", arg:"0" }
66
+ * "backgroundColor@md:hover.0" -> { base:"backgroundColor", breakpoint:"md", state:"hover", arg:"0" }
67
+ * "backgroundColor@md:hover" -> { base:"backgroundColor", breakpoint:"md", state:"hover", arg:null }
68
+ */
69
+ export function parseVariantKey(key) {
70
+ let work = key;
71
+ let arg = null;
72
+ // Peel the arg suffix: the segment after the LAST dot, but only if that dot
73
+ // comes AFTER any variant markers (so we never split inside "focus-within"
74
+ // — that has no dot — and never treat "padding" alone as having an arg).
75
+ const lastDot = work.lastIndexOf(".");
76
+ if (lastDot > 0) {
77
+ arg = work.slice(lastDot + 1);
78
+ work = work.slice(0, lastDot);
79
+ }
80
+ // Peel the state marker (":state") off the end of the remainder.
81
+ let state = null;
82
+ const colon = work.indexOf(":");
83
+ if (colon !== -1) {
84
+ const candidate = work.slice(colon + 1);
85
+ if (VALID_STATE_SET.has(candidate)) {
86
+ state = candidate;
87
+ work = work.slice(0, colon);
88
+ }
89
+ }
90
+ // Peel the breakpoint marker ("@bp").
91
+ let breakpoint = null;
92
+ const at = work.indexOf("@");
93
+ if (at !== -1) {
94
+ const candidate = work.slice(at + 1);
95
+ if (candidate in BREAKPOINTS) {
96
+ breakpoint = candidate;
97
+ work = work.slice(0, at);
98
+ }
99
+ }
100
+ return { base: work, breakpoint, state, arg };
101
+ }
102
+ /**
103
+ * Precedence rank for a parsed variant (higher wins). Combines the breakpoint
104
+ * tier and the state tier into a single sortable number.
105
+ *
106
+ * Precedence (low -> high), later overrides earlier:
107
+ * base
108
+ * < breakpoints ascending (sm < md < lg < xl < 2xl)
109
+ * < disabled < hover < focus < active
110
+ *
111
+ * State always outranks any breakpoint (a `:hover` override beats a `@2xl`
112
+ * sizing override on the same prop), matching the iOS reference where state
113
+ * overrides are merged AFTER the responsive base. Within the same state tier,
114
+ * a higher breakpoint still breaks the tie so `@md:hover` beats `:hover`.
115
+ */
116
+ const STATE_RANK = {
117
+ disabled: 1,
118
+ hover: 2,
119
+ // focus-visible / focus-within are focus-flavoured states: they share the
120
+ // `focus` precedence slot (NOT base/0) so they outrank hover and lose to
121
+ // active, matching the engine's `state_rank` in portable/variant.rs.
122
+ focus: 3,
123
+ "focus-visible": 3,
124
+ "focus-within": 3,
125
+ active: 4,
126
+ };
127
+ // State tier must be weighted by a value strictly greater than the largest
128
+ // possible breakpoint tier so the bp tiebreak can never leak across state
129
+ // bands (mirrors the engine's STATE_STEP > max-breakpoint invariant). Derived
130
+ // from BREAKPOINT_ORDER.length so adding breakpoints can't silently break it.
131
+ const STATE_WEIGHT = BREAKPOINT_ORDER.length + 1;
132
+ function variantRank(breakpoint, state) {
133
+ const bpTier = breakpoint ? BREAKPOINT_ORDER.indexOf(breakpoint) + 1 : 0; // 0..len
134
+ const stateTier = state ? STATE_RANK[state] ?? 0 : 0; // 0..4
135
+ // State tier dominates; breakpoint tier breaks ties within a state tier.
136
+ return stateTier * STATE_WEIGHT + bpTier;
137
+ }
138
+ /**
139
+ * Is a parsed variant currently applicable, given the viewport width and the
140
+ * active interaction states? A combined key requires BOTH conditions.
141
+ */
142
+ function variantApplies(parsed, width, states) {
143
+ if (parsed.breakpoint) {
144
+ const minWidth = BREAKPOINTS[parsed.breakpoint];
145
+ if (minWidth === undefined || width < minWidth)
146
+ return false;
147
+ }
148
+ if (parsed.state) {
149
+ if (!states[parsed.state])
150
+ return false;
151
+ }
152
+ return true;
153
+ }
154
+ /**
155
+ * Resolve variant-bearing props against the current viewport width and active
156
+ * interaction states, producing a flat map of `base -> winning value`.
157
+ *
158
+ * Input keys are the already-normalised flat applicator keys (variant markers
159
+ * present, arg suffix already stripped by the applicator-grouping pass), e.g.
160
+ * `{ padding: 8, "padding@md": 16, "backgroundColor:hover": "red" }`.
161
+ *
162
+ * Only bases that actually have at least one applicable variant appear in the
163
+ * result with an overridden value; a base with only its plain entry resolves to
164
+ * that plain value. The caller merges the result over the node's base props.
165
+ */
166
+ export function resolveVariantProps(props, width, states) {
167
+ // base -> { value, rank }
168
+ const winners = new Map();
169
+ for (const key of Object.keys(props)) {
170
+ const parsed = parseVariantKey(key);
171
+ // Only consider entries that carry a variant marker. Plain keys (no bp/state)
172
+ // are the base and are handled implicitly via rank 0 below so a base with a
173
+ // single applicable variant still falls back to base when the variant is off.
174
+ if (!parsed.breakpoint && !parsed.state) {
175
+ // Seed the base value at rank 0 so variant lookups have a fallback and so
176
+ // a base that ALSO has variants resolves correctly when none apply.
177
+ const existing = winners.get(parsed.base);
178
+ if (!existing || existing.rank === 0) {
179
+ winners.set(parsed.base, { value: props[key], rank: 0 });
180
+ }
181
+ continue;
182
+ }
183
+ if (!variantApplies(parsed, width, states))
184
+ continue;
185
+ const rank = variantRank(parsed.breakpoint, parsed.state);
186
+ const existing = winners.get(parsed.base);
187
+ if (!existing || rank >= existing.rank) {
188
+ winners.set(parsed.base, { value: props[key], rank });
189
+ }
190
+ }
191
+ const out = {};
192
+ for (const [base, win] of winners) {
193
+ out[base] = win.value;
194
+ }
195
+ return out;
196
+ }
197
+ /** True if any key in `props` carries a variant marker (`@bp` or `:state`). */
198
+ export function hasVariantProps(props) {
199
+ for (const key of Object.keys(props)) {
200
+ const parsed = parseVariantKey(key);
201
+ if (parsed.breakpoint || parsed.state)
202
+ return true;
203
+ }
204
+ return false;
205
+ }
206
+ //# sourceMappingURL=variants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variants.js","sourceRoot":"","sources":["../src/variants.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypen-space/web",
3
- "version": "0.4.980",
3
+ "version": "0.5.3",
4
4
  "description": "Hypen web renderers - DOM and Canvas rendering for browsers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@chenglou/pretext": "^0.0.3",
46
- "@hypen-space/core": "^0.4.980",
46
+ "@hypen-space/core": "^0.5.3",
47
47
  "taffy-layout": "^2.0.3"
48
48
  },
49
49
  "devDependencies": {
@@ -30,7 +30,7 @@ bun add @hypen-space/core @hypen-space/web @hypen-space/web-engine
30
30
  ```typescript
31
31
  import { app, HypenModuleInstance } from "@hypen-space/core";
32
32
  import { Engine } from "@hypen-space/web-engine";
33
- import { createHypenClient } from "@hypen-space/web/canvas";
33
+ import { CanvasRenderer } from "@hypen-space/web/canvas";
34
34
 
35
35
  // Define state
36
36
  type AppState = {
@@ -54,12 +54,17 @@ async function main() {
54
54
  const engine = new Engine();
55
55
  await engine.init();
56
56
 
57
- // Create renderer + patch subscription in one call
58
- createHypenClient(canvas, engine, {
57
+ // Create renderer
58
+ const renderer = new CanvasRenderer(canvas, engine, {
59
59
  devicePixelRatio: window.devicePixelRatio,
60
60
  backgroundColor: "#ffffff",
61
61
  });
62
62
 
63
+ // Connect renderer
64
+ engine.setRenderCallback((patches) => {
65
+ renderer.applyPatches(patches);
66
+ });
67
+
63
68
  // Create module
64
69
  const instance = new HypenModuleInstance(engine, appModule);
65
70
 
@@ -244,15 +249,14 @@ Container {
244
249
  ## Configuration Options
245
250
 
246
251
  ```typescript
247
- const { renderer } = createHypenClient(canvas, engine, {
252
+ const renderer = new CanvasRenderer(canvas, engine, {
248
253
  // Display
249
254
  devicePixelRatio: window.devicePixelRatio, // HiDPI support
250
255
  backgroundColor: "#ffffff", // Canvas background
251
256
 
252
257
  // Features
253
- enableAccessibility: true, // Screen reader support
258
+ enableAccessibility: true, // Screen readers + Tab focus + text editing
254
259
  enableHitTesting: true, // Mouse events
255
- enableInputOverlay: true, // Text input support
256
260
 
257
261
  // Debug
258
262
  showLayoutBounds: false, // Show red boxes around elements
@@ -349,7 +353,7 @@ let timeoutId: number;
349
353
  ### 3. Measure Performance
350
354
 
351
355
  ```typescript
352
- const { renderer } = createHypenClient(canvas, engine, {
356
+ const renderer = new CanvasRenderer(canvas, engine, {
353
357
  logPerformance: true, // Logs FPS and frame times
354
358
  });
355
359
  ```
@@ -361,18 +365,19 @@ const { renderer } = createHypenClient(canvas, engine, {
361
365
  Make sure to set `devicePixelRatio`:
362
366
 
363
367
  ```typescript
364
- const { renderer } = createHypenClient(canvas, engine, {
368
+ const renderer = new CanvasRenderer(canvas, engine, {
365
369
  devicePixelRatio: window.devicePixelRatio,
366
370
  });
367
371
  ```
368
372
 
369
373
  ### Text Input Not Working
370
374
 
371
- Ensure `enableInputOverlay` is true:
375
+ Text editing runs through the accessibility mirror (canvas fallback
376
+ content), so `enableAccessibility` must be true (the default):
372
377
 
373
378
  ```typescript
374
- const { renderer } = createHypenClient(canvas, engine, {
375
- enableInputOverlay: true,
379
+ const renderer = new CanvasRenderer(canvas, engine, {
380
+ enableAccessibility: true,
376
381
  });
377
382
  ```
378
383
 
@@ -13,12 +13,10 @@ The Canvas Renderer is a complete alternative to the DOM renderer that draws all
13
13
 
14
14
  ## Quick Start
15
15
 
16
- `createHypenClient` is the recommended one-call wiring (constructs the renderer and subscribes to engine patches). `CanvasRenderer` is still exported directly for advanced setups.
17
-
18
16
  ```typescript
19
17
  import { app } from "@hypen-space/core";
20
18
  import { Engine } from "@hypen-space/web-engine";
21
- import { createHypenClient } from "@hypen-space/web/canvas";
19
+ import { CanvasRenderer } from "@hypen-space/web/canvas";
22
20
 
23
21
  // Setup canvas
24
22
  const canvas = document.getElementById("app") as HTMLCanvasElement;
@@ -27,8 +25,13 @@ const canvas = document.getElementById("app") as HTMLCanvasElement;
27
25
  const engine = new Engine();
28
26
  await engine.init();
29
27
 
30
- // Create canvas renderer + patch subscription in one call
31
- createHypenClient(canvas, engine);
28
+ // Create canvas renderer
29
+ const renderer = new CanvasRenderer(canvas, engine);
30
+
31
+ // Set render callback
32
+ engine.setRenderCallback((patches) => {
33
+ renderer.applyPatches(patches);
34
+ });
32
35
 
33
36
  // Render UI
34
37
  await engine.renderSource(`
@@ -141,35 +144,61 @@ Handles text measurement and rendering:
141
144
  - **Text Alignment**: left, center, right, top, middle, bottom
142
145
  - **Caching**: Text metrics are cached for performance
143
146
 
144
- ### 5. Input Overlay (`input.ts`)
145
-
146
- Since canvas can't handle text input natively, we use DOM overlays:
147
-
148
- **Strategy:**
149
- 1. When canvas input is focused, create a real `<input>` element
150
- 2. Position it exactly over the canvas input (invisible to user)
151
- 3. Style it to match the canvas input appearance
152
- 4. Capture input and update state
153
- 5. Remove overlay when focus is lost
154
-
155
- **Supports:**
156
- - Single-line text input
157
- - Multi-line textarea
158
- - Number input
159
- - Custom styling
160
-
161
- ### 6. Accessibility (`accessibility.ts`)
162
-
163
- Maintains a shadow DOM tree for screen readers:
147
+ ### 5. Native Text Editing (`editing.ts`)
148
+
149
+ Text, caret, and selection inside `Input`/`Textarea` are painted **on the
150
+ canvas** — there is no visible DOM overlay. The browser does the actual
151
+ editing (typing, caret movement, word-jumps, select-all, clipboard, undo,
152
+ IME composition) in a single hidden **proxy textarea** positioned at the
153
+ caret (the Monaco/xterm.js approach); the `TextEditController` reads
154
+ `value`/`selectionStart`/`selectionEnd` after each event and the canvas
155
+ paints the result. Focus and accessibility stay on the node's mirror
156
+ element (below) browsers won't run text editing on unrendered fallback
157
+ content, which is why the keystroke stream needs the proxy. Positioning the
158
+ proxy at the caret also puts the IME candidate window next to the painted
159
+ text.
160
+
161
+ **Editing visuals (painted in `paint.ts#paintInput`):**
162
+ - Selection highlight and blinking caret via shared `text-geometry.ts` math
163
+ - IME composition range underlined (dashed)
164
+ - Single-line inputs pan horizontally (`scrollX`) to keep the caret visible
165
+ - Click/drag on the canvas maps point → character offset → `setSelectionRange`
166
+
167
+ Two-way binding dispatches `__hypen_bind {path, value}` on input (suppressed
168
+ during IME composition; flushed at `compositionend`).
169
+
170
+ ### 6. Accessibility & Focus (`accessibility.ts`, `focus.ts`)
171
+
172
+ A live DOM mirror of the virtual tree is rendered as a **transparent
173
+ positioned overlay** above the canvas (the Flutter-web approach), with every
174
+ element absolutely positioned at its painted bounds and `pointer-events:
175
+ none` so the canvas keeps all pointer interaction.
176
+
177
+ Canvas *fallback content* was evaluated first and rejected on evidence:
178
+ Chromium exposes fallback elements to the accessibility tree (names, roles,
179
+ focus, Tab order all work) but gives them **zero geometry** — and screen
180
+ reader browse modes (VoiceOver cursor, rotor, touch exploration) are
181
+ geometry-driven, so they skip boundless elements entirely.
164
182
 
165
183
  **Strategy:**
166
- 1. Create hidden DOM tree that mirrors canvas structure
167
- 2. Use semantic HTML (button, input, etc.)
168
- 3. Update shadow DOM when patches are applied
169
- 4. Support keyboard navigation
170
- 5. ARIA labels and roles
171
-
172
- This makes the canvas renderer fully accessible without impacting visual rendering.
184
+ 1. Mirror elements use semantic HTML (button, input, h1-h6, ...) driven by
185
+ the engine-derived `Semantics` block, invisibly rendered (transparent
186
+ text/background; placeholders and selection suppressed via a shared
187
+ stylesheet) so AT gets real boxes and native focus rings land exactly
188
+ over the painted controls
189
+ 2. The mirror is synced **incrementally** from the patch stream
190
+ (create/insert/move/remove/detach/attach) element identity survives
191
+ re-renders and router navigation, so AT focus/virtual-cursor position
192
+ is never destroyed by an update; element positions are refreshed after
193
+ every canvas render
194
+ 3. Real DOM focus on mirror elements is the **single source of truth** for
195
+ focus: Tab/Shift+Tab work natively, `focusin`/`focusout` drive
196
+ `node.focused` and the painted focus ring, and the canvas hit-test path
197
+ funnels into the same place by focusing the node's mirror element
198
+ 4. Enter/Space on a mirror `<button>` (or AT activation) dispatches the
199
+ node's action — identical payload to a canvas click
200
+ 5. The `<canvas>` itself is `aria-hidden` — the overlay carries all
201
+ semantics
173
202
 
174
203
  ## Supported Components
175
204
 
@@ -200,9 +229,9 @@ const renderer = new CanvasRenderer(canvas, engine, {
200
229
  backgroundColor: "#ffffff", // Canvas background
201
230
 
202
231
  // Features
203
- enableAccessibility: true, // Shadow DOM for screen readers
232
+ enableAccessibility: true, // Fallback-content mirror: screen readers,
233
+ // Tab focus, and text-input editing
204
234
  enableHitTesting: true, // Mouse event handling
205
- enableInputOverlay: true, // DOM overlays for text input
206
235
 
207
236
  // Performance (future)
208
237
  enableDirtyRects: false, // Only redraw changed regions