@hypen-space/web 0.2.0

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 (195) hide show
  1. package/dist/chunk-2s02mkzs.js +32 -0
  2. package/dist/chunk-2s02mkzs.js.map +9 -0
  3. package/dist/src/canvas/accessibility.js +152 -0
  4. package/dist/src/canvas/accessibility.js.map +10 -0
  5. package/dist/src/canvas/events.js +198 -0
  6. package/dist/src/canvas/events.js.map +10 -0
  7. package/dist/src/canvas/index.js +28 -0
  8. package/dist/src/canvas/index.js.map +9 -0
  9. package/dist/src/canvas/input.js +132 -0
  10. package/dist/src/canvas/input.js.map +10 -0
  11. package/dist/src/canvas/layout.js +309 -0
  12. package/dist/src/canvas/layout.js.map +10 -0
  13. package/dist/src/canvas/paint.js +878 -0
  14. package/dist/src/canvas/paint.js.map +10 -0
  15. package/dist/src/canvas/renderer.js +276 -0
  16. package/dist/src/canvas/renderer.js.map +10 -0
  17. package/dist/src/canvas/text.js +118 -0
  18. package/dist/src/canvas/text.js.map +10 -0
  19. package/dist/src/canvas/types.js +2 -0
  20. package/dist/src/canvas/types.js.map +9 -0
  21. package/dist/src/canvas/utils.js +139 -0
  22. package/dist/src/canvas/utils.js.map +10 -0
  23. package/dist/src/dom/applicators/advanced-layout.js +111 -0
  24. package/dist/src/dom/applicators/advanced-layout.js.map +10 -0
  25. package/dist/src/dom/applicators/background.js +54 -0
  26. package/dist/src/dom/applicators/background.js.map +10 -0
  27. package/dist/src/dom/applicators/border.js +33 -0
  28. package/dist/src/dom/applicators/border.js.map +10 -0
  29. package/dist/src/dom/applicators/color.js +36 -0
  30. package/dist/src/dom/applicators/color.js.map +10 -0
  31. package/dist/src/dom/applicators/display.js +57 -0
  32. package/dist/src/dom/applicators/display.js.map +10 -0
  33. package/dist/src/dom/applicators/effects.js +89 -0
  34. package/dist/src/dom/applicators/effects.js.map +10 -0
  35. package/dist/src/dom/applicators/events.js +518 -0
  36. package/dist/src/dom/applicators/events.js.map +10 -0
  37. package/dist/src/dom/applicators/font.js +39 -0
  38. package/dist/src/dom/applicators/font.js.map +10 -0
  39. package/dist/src/dom/applicators/index.js +296 -0
  40. package/dist/src/dom/applicators/index.js.map +10 -0
  41. package/dist/src/dom/applicators/layout.js +86 -0
  42. package/dist/src/dom/applicators/layout.js.map +10 -0
  43. package/dist/src/dom/applicators/margin.js +32 -0
  44. package/dist/src/dom/applicators/margin.js.map +10 -0
  45. package/dist/src/dom/applicators/padding.js +35 -0
  46. package/dist/src/dom/applicators/padding.js.map +10 -0
  47. package/dist/src/dom/applicators/size.js +42 -0
  48. package/dist/src/dom/applicators/size.js.map +10 -0
  49. package/dist/src/dom/applicators/transform.js +92 -0
  50. package/dist/src/dom/applicators/transform.js.map +10 -0
  51. package/dist/src/dom/applicators/transition.js +66 -0
  52. package/dist/src/dom/applicators/transition.js.map +10 -0
  53. package/dist/src/dom/applicators/typography.js +87 -0
  54. package/dist/src/dom/applicators/typography.js.map +10 -0
  55. package/dist/src/dom/canvas/index.js +50 -0
  56. package/dist/src/dom/canvas/index.js.map +10 -0
  57. package/dist/src/dom/components/audio.js +48 -0
  58. package/dist/src/dom/components/audio.js.map +10 -0
  59. package/dist/src/dom/components/avatar.js +58 -0
  60. package/dist/src/dom/components/avatar.js.map +10 -0
  61. package/dist/src/dom/components/badge.js +55 -0
  62. package/dist/src/dom/components/badge.js.map +10 -0
  63. package/dist/src/dom/components/button.js +29 -0
  64. package/dist/src/dom/components/button.js.map +10 -0
  65. package/dist/src/dom/components/card.js +33 -0
  66. package/dist/src/dom/components/card.js.map +10 -0
  67. package/dist/src/dom/components/center.js +32 -0
  68. package/dist/src/dom/components/center.js.map +10 -0
  69. package/dist/src/dom/components/checkbox.js +54 -0
  70. package/dist/src/dom/components/checkbox.js.map +10 -0
  71. package/dist/src/dom/components/column.js +31 -0
  72. package/dist/src/dom/components/column.js.map +10 -0
  73. package/dist/src/dom/components/container.js +29 -0
  74. package/dist/src/dom/components/container.js.map +10 -0
  75. package/dist/src/dom/components/divider.js +45 -0
  76. package/dist/src/dom/components/divider.js.map +10 -0
  77. package/dist/src/dom/components/grid.js +44 -0
  78. package/dist/src/dom/components/grid.js.map +10 -0
  79. package/dist/src/dom/components/heading.js +47 -0
  80. package/dist/src/dom/components/heading.js.map +10 -0
  81. package/dist/src/dom/components/image.js +39 -0
  82. package/dist/src/dom/components/image.js.map +10 -0
  83. package/dist/src/dom/components/index.js +217 -0
  84. package/dist/src/dom/components/index.js.map +10 -0
  85. package/dist/src/dom/components/input.js +41 -0
  86. package/dist/src/dom/components/input.js.map +10 -0
  87. package/dist/src/dom/components/link.js +42 -0
  88. package/dist/src/dom/components/link.js.map +10 -0
  89. package/dist/src/dom/components/list.js +42 -0
  90. package/dist/src/dom/components/list.js.map +10 -0
  91. package/dist/src/dom/components/paragraph.js +35 -0
  92. package/dist/src/dom/components/paragraph.js.map +10 -0
  93. package/dist/src/dom/components/progressbar.js +57 -0
  94. package/dist/src/dom/components/progressbar.js.map +10 -0
  95. package/dist/src/dom/components/route.js +44 -0
  96. package/dist/src/dom/components/route.js.map +10 -0
  97. package/dist/src/dom/components/router.js +33 -0
  98. package/dist/src/dom/components/router.js.map +10 -0
  99. package/dist/src/dom/components/row.js +31 -0
  100. package/dist/src/dom/components/row.js.map +10 -0
  101. package/dist/src/dom/components/select.js +57 -0
  102. package/dist/src/dom/components/select.js.map +10 -0
  103. package/dist/src/dom/components/slider.js +48 -0
  104. package/dist/src/dom/components/slider.js.map +10 -0
  105. package/dist/src/dom/components/spacer.js +30 -0
  106. package/dist/src/dom/components/spacer.js.map +10 -0
  107. package/dist/src/dom/components/spinner.js +65 -0
  108. package/dist/src/dom/components/spinner.js.map +10 -0
  109. package/dist/src/dom/components/stack.js +45 -0
  110. package/dist/src/dom/components/stack.js.map +10 -0
  111. package/dist/src/dom/components/switch.js +83 -0
  112. package/dist/src/dom/components/switch.js.map +10 -0
  113. package/dist/src/dom/components/text.js +37 -0
  114. package/dist/src/dom/components/text.js.map +10 -0
  115. package/dist/src/dom/components/textarea.js +51 -0
  116. package/dist/src/dom/components/textarea.js.map +10 -0
  117. package/dist/src/dom/components/video.js +51 -0
  118. package/dist/src/dom/components/video.js.map +10 -0
  119. package/dist/src/dom/debug.js +170 -0
  120. package/dist/src/dom/debug.js.map +10 -0
  121. package/dist/src/dom/events.js +112 -0
  122. package/dist/src/dom/events.js.map +10 -0
  123. package/dist/src/dom/index.js +73 -0
  124. package/dist/src/dom/index.js.map +9 -0
  125. package/dist/src/dom/renderer.js +277 -0
  126. package/dist/src/dom/renderer.js.map +10 -0
  127. package/dist/src/index.js +89 -0
  128. package/dist/src/index.js.map +9 -0
  129. package/package.json +84 -0
  130. package/src/canvas/QUICKSTART.md +421 -0
  131. package/src/canvas/README.md +376 -0
  132. package/src/canvas/accessibility.ts +218 -0
  133. package/src/canvas/events.ts +307 -0
  134. package/src/canvas/index.ts +35 -0
  135. package/src/canvas/input.ts +210 -0
  136. package/src/canvas/layout.ts +401 -0
  137. package/src/canvas/paint.ts +1321 -0
  138. package/src/canvas/renderer.ts +422 -0
  139. package/src/canvas/text.ts +182 -0
  140. package/src/canvas/types.ts +137 -0
  141. package/src/canvas/utils.ts +218 -0
  142. package/src/dom/README.md +265 -0
  143. package/src/dom/applicators/advanced-layout.ts +128 -0
  144. package/src/dom/applicators/background.ts +50 -0
  145. package/src/dom/applicators/border.ts +19 -0
  146. package/src/dom/applicators/color.ts +23 -0
  147. package/src/dom/applicators/display.ts +54 -0
  148. package/src/dom/applicators/effects.ts +97 -0
  149. package/src/dom/applicators/events.ts +689 -0
  150. package/src/dom/applicators/font.ts +27 -0
  151. package/src/dom/applicators/index.ts +354 -0
  152. package/src/dom/applicators/layout.ts +92 -0
  153. package/src/dom/applicators/margin.ts +18 -0
  154. package/src/dom/applicators/padding.ts +18 -0
  155. package/src/dom/applicators/size.ts +31 -0
  156. package/src/dom/applicators/transform.ts +93 -0
  157. package/src/dom/applicators/transition.ts +65 -0
  158. package/src/dom/applicators/typography.ts +91 -0
  159. package/src/dom/canvas/index.ts +60 -0
  160. package/src/dom/components/audio.ts +45 -0
  161. package/src/dom/components/avatar.ts +49 -0
  162. package/src/dom/components/badge.ts +45 -0
  163. package/src/dom/components/button.ts +13 -0
  164. package/src/dom/components/card.ts +19 -0
  165. package/src/dom/components/center.ts +16 -0
  166. package/src/dom/components/checkbox.ts +54 -0
  167. package/src/dom/components/column.ts +15 -0
  168. package/src/dom/components/container.ts +13 -0
  169. package/src/dom/components/divider.ts +37 -0
  170. package/src/dom/components/grid.ts +40 -0
  171. package/src/dom/components/heading.ts +41 -0
  172. package/src/dom/components/image.ts +27 -0
  173. package/src/dom/components/index.ts +115 -0
  174. package/src/dom/components/input.ts +29 -0
  175. package/src/dom/components/link.ts +35 -0
  176. package/src/dom/components/list.ts +30 -0
  177. package/src/dom/components/paragraph.ts +23 -0
  178. package/src/dom/components/progressbar.ts +51 -0
  179. package/src/dom/components/route.ts +37 -0
  180. package/src/dom/components/router.ts +22 -0
  181. package/src/dom/components/row.ts +15 -0
  182. package/src/dom/components/select.ts +56 -0
  183. package/src/dom/components/slider.ts +45 -0
  184. package/src/dom/components/spacer.ts +16 -0
  185. package/src/dom/components/spinner.ts +60 -0
  186. package/src/dom/components/stack.ts +34 -0
  187. package/src/dom/components/switch.ts +86 -0
  188. package/src/dom/components/text.ts +24 -0
  189. package/src/dom/components/textarea.ts +50 -0
  190. package/src/dom/components/video.ts +50 -0
  191. package/src/dom/debug.ts +247 -0
  192. package/src/dom/events.ts +168 -0
  193. package/src/dom/index.ts +11 -0
  194. package/src/dom/renderer.ts +327 -0
  195. package/src/index.ts +56 -0
@@ -0,0 +1,376 @@
1
+ # Canvas Renderer
2
+
3
+ Browser-only module for rendering Hypen UI to a `<canvas>` element.
4
+
5
+ ## Overview
6
+
7
+ The Canvas Renderer is a complete alternative to the DOM renderer that draws all UI elements directly to a canvas using the Canvas 2D API. This provides:
8
+
9
+ - **Performance**: For very large UIs, canvas can be faster than DOM
10
+ - **Control**: Pixel-perfect control over rendering
11
+ - **Portability**: Can be adapted to other canvas-based platforms (WebGL, native canvas, etc.)
12
+ - **Consistency**: Same rendering behavior across all browsers
13
+
14
+ ## Quick Start
15
+
16
+ ```typescript
17
+ import { Engine, app } from "@hypen/core";
18
+ import { CanvasRenderer } from "@hypen/core/canvas";
19
+
20
+ // Setup canvas
21
+ const canvas = document.getElementById("app") as HTMLCanvasElement;
22
+
23
+ // Initialize engine
24
+ const engine = new Engine();
25
+ await engine.init();
26
+
27
+ // Create canvas renderer
28
+ const renderer = new CanvasRenderer(canvas, engine);
29
+
30
+ // Set render callback
31
+ engine.setRenderCallback((patches) => {
32
+ renderer.applyPatches(patches);
33
+ });
34
+
35
+ // Render UI
36
+ await engine.renderSource(`
37
+ Column {
38
+ Text("Hello, Canvas!")
39
+ Button("@actions.click") { Text("Click me") }
40
+ }
41
+ `);
42
+ ```
43
+
44
+ ## Architecture
45
+
46
+ ```
47
+ ┌─────────────────────────────────────────┐
48
+ │ CanvasRenderer │
49
+ │ - Orchestrates layout, paint, events │
50
+ │ - Maintains virtual node tree │
51
+ │ - Schedules redraws │
52
+ └─────────────────────────────────────────┘
53
+ ↓ ↓ ↓
54
+ ┌──────────┐ ┌──────┐ ┌───────┐
55
+ │ Layout │ │ Paint│ │Events │
56
+ │ Engine │ │System│ │Manager│
57
+ └──────────┘ └──────┘ └───────┘
58
+ ```
59
+
60
+ ### Virtual Node Tree
61
+
62
+ Unlike the DOM renderer which creates real HTML elements, the canvas renderer maintains an internal virtual node tree:
63
+
64
+ ```typescript
65
+ interface VirtualNode {
66
+ id: string
67
+ type: string
68
+ props: Record<string, any>
69
+ children: VirtualNode[]
70
+ layout?: Layout // Computed position/size
71
+ visible: boolean
72
+ opacity: number
73
+ clickable: boolean
74
+ hovered: boolean
75
+ focused: boolean
76
+ }
77
+ ```
78
+
79
+ ## Core Systems
80
+
81
+ ### 1. Layout Engine (`layout.ts`)
82
+
83
+ Implements a flexbox-like layout system:
84
+
85
+ - **Flexbox model**: Row/Column with `flex`, `gap`, alignment
86
+ - **Box model**: Margin, padding, border
87
+ - **Sizing**: width, height, min/max constraints
88
+ - **Text layout**: Automatic text measurement and wrapping
89
+
90
+ **Supported Properties:**
91
+ - `flexDirection`: "row" | "column"
92
+ - `justifyContent`: "flex-start" | "center" | "flex-end" | "space-between" | "space-around"
93
+ - `alignItems`: "flex-start" | "center" | "flex-end" | "stretch"
94
+ - `gap`: spacing between children
95
+ - `padding`, `margin`: box spacing
96
+ - `width`, `height`, `minWidth`, `maxWidth`, etc.
97
+
98
+ ### 2. Paint System (`paint.ts`)
99
+
100
+ Draws virtual nodes to canvas:
101
+
102
+ - **Background**: solid colors, border radius
103
+ - **Border**: width, color, radius
104
+ - **Text**: font rendering with alignment and wrapping
105
+ - **Components**: Button, Input, Image, etc.
106
+
107
+ **Custom Painters:**
108
+ ```typescript
109
+ renderer.registerPainter("MyComponent", (ctx, node) => {
110
+ // Custom drawing code
111
+ ctx.fillStyle = node.props.color;
112
+ ctx.fillRect(
113
+ node.layout!.x,
114
+ node.layout!.y,
115
+ node.layout!.width,
116
+ node.layout!.height
117
+ );
118
+ });
119
+ ```
120
+
121
+ ### 3. Event System (`events.ts`)
122
+
123
+ Maps canvas events to virtual nodes:
124
+
125
+ - **Hit Testing**: Determines which node was clicked/hovered
126
+ - **Mouse Events**: click, mousedown, mouseup, mouseenter, mouseleave
127
+ - **Keyboard Events**: keydown, keyup (for focused elements)
128
+ - **Hover States**: Automatic cursor changes and hover effects
129
+
130
+ **How It Works:**
131
+ 1. Mouse event occurs on canvas
132
+ 2. Convert to canvas coordinates
133
+ 3. Walk virtual tree to find node at coordinates (back to front)
134
+ 4. Dispatch action to engine if node has handler
135
+
136
+ ### 4. Text System (`text.ts`)
137
+
138
+ Handles text measurement and rendering:
139
+
140
+ - **Text Measurement**: Accurate width/height calculation
141
+ - **Line Wrapping**: Automatic word wrapping to fit width
142
+ - **Font Loading**: Ensures fonts are loaded before rendering
143
+ - **Text Alignment**: left, center, right, top, middle, bottom
144
+ - **Caching**: Text metrics are cached for performance
145
+
146
+ ### 5. Input Overlay (`input.ts`)
147
+
148
+ Since canvas can't handle text input natively, we use DOM overlays:
149
+
150
+ **Strategy:**
151
+ 1. When canvas input is focused, create a real `<input>` element
152
+ 2. Position it exactly over the canvas input (invisible to user)
153
+ 3. Style it to match the canvas input appearance
154
+ 4. Capture input and update state
155
+ 5. Remove overlay when focus is lost
156
+
157
+ **Supports:**
158
+ - Single-line text input
159
+ - Multi-line textarea
160
+ - Number input
161
+ - Custom styling
162
+
163
+ ### 6. Accessibility (`accessibility.ts`)
164
+
165
+ Maintains a shadow DOM tree for screen readers:
166
+
167
+ **Strategy:**
168
+ 1. Create hidden DOM tree that mirrors canvas structure
169
+ 2. Use semantic HTML (button, input, etc.)
170
+ 3. Update shadow DOM when patches are applied
171
+ 4. Support keyboard navigation
172
+ 5. ARIA labels and roles
173
+
174
+ This makes the canvas renderer fully accessible without impacting visual rendering.
175
+
176
+ ## Supported Components
177
+
178
+ All Hypen components work with the canvas renderer:
179
+
180
+ ### Layout Components
181
+ - **Column**: Vertical flex container
182
+ - **Row**: Horizontal flex container
183
+ - **Container/Box**: Generic container
184
+
185
+ ### Text Components
186
+ - **Text**: Text with automatic wrapping
187
+
188
+ ### Interactive Components
189
+ - **Button**: Clickable button with hover states
190
+ - **Input**: Single-line text input
191
+ - **Textarea**: Multi-line text input (future)
192
+
193
+ ### Media Components
194
+ - **Image**: Image rendering (basic support)
195
+
196
+ ## Configuration Options
197
+
198
+ ```typescript
199
+ const renderer = new CanvasRenderer(canvas, engine, {
200
+ // Display
201
+ devicePixelRatio: window.devicePixelRatio, // HiDPI support
202
+ backgroundColor: "#ffffff", // Canvas background
203
+
204
+ // Features
205
+ enableAccessibility: true, // Shadow DOM for screen readers
206
+ enableHitTesting: true, // Mouse event handling
207
+ enableInputOverlay: true, // DOM overlays for text input
208
+
209
+ // Performance (future)
210
+ enableDirtyRects: false, // Only redraw changed regions
211
+ enableLayerCaching: false, // Cache static subtrees
212
+ maxLayerCacheSize: 10, // Max cached layers
213
+
214
+ // Debug
215
+ showLayoutBounds: false, // Draw red boxes around nodes
216
+ showDirtyRects: false, // Highlight redrawn regions
217
+ logPerformance: false, // Log FPS and render time
218
+ });
219
+ ```
220
+
221
+ ## Examples
222
+
223
+ See `examples/canvas-counter.ts` for a complete example.
224
+
225
+ ### Running Examples
226
+
227
+ ```bash
228
+ # Using Bun
229
+ bun examples/canvas-counter.ts
230
+
231
+ # Or open HTML file directly
232
+ open examples/canvas-counter.html
233
+ ```
234
+
235
+ ## Performance
236
+
237
+ ### Current Performance
238
+ - **Typical UI**: 60fps with < 100 nodes
239
+ - **Layout**: ~2-5ms for moderate trees
240
+ - **Paint**: ~5-10ms for moderate trees
241
+ - **Text**: Cached measurements are nearly free
242
+
243
+ ### Optimization Opportunities
244
+ - ✅ Text metrics caching (implemented)
245
+ - ⬜ Dirty rectangle tracking (planned)
246
+ - ⬜ Layer caching (planned)
247
+ - ⬜ Offscreen canvases (planned)
248
+ - ⬜ WebGL backend (future)
249
+
250
+ ## Limitations
251
+
252
+ ### Current Limitations
253
+ - ❌ No rich text (bold/italic within text)
254
+ - ❌ No text selection/copy
255
+ - ❌ No gradients
256
+ - ❌ No shadows
257
+ - ❌ No transforms (rotate/scale/skew)
258
+ - ❌ Limited image support
259
+
260
+ ### Future Improvements
261
+ All of these are planned for future releases!
262
+
263
+ ## Comparison: Canvas vs DOM
264
+
265
+ | Feature | Canvas Renderer | DOM Renderer |
266
+ |---------|----------------|--------------|
267
+ | Performance (large UIs) | ✅ Better | ⚠️ Slower |
268
+ | Text input | ⚠️ Overlay | ✅ Native |
269
+ | Accessibility | ⚠️ Shadow DOM | ✅ Native |
270
+ | Browser DevTools | ❌ No inspection | ✅ Full support |
271
+ | CSS styling | ❌ Props only | ✅ CSS support |
272
+ | Animations | ⚠️ Manual | ✅ CSS animations |
273
+ | Memory usage | ✅ Lower | ⚠️ Higher |
274
+ | Initial render | ✅ Faster | ⚠️ Slower |
275
+
276
+ ## When to Use Canvas Renderer
277
+
278
+ **Use Canvas Renderer when:**
279
+ - Building dashboards with many elements (100+)
280
+ - Need consistent cross-browser rendering
281
+ - Building games or creative tools
282
+ - Performance is critical
283
+ - Building for canvas-based platforms
284
+
285
+ **Use DOM Renderer when:**
286
+ - Building standard web apps
287
+ - Need rich text formatting
288
+ - Need CSS animations
289
+ - Want browser DevTools support
290
+ - Accessibility is paramount
291
+
292
+ ## API Reference
293
+
294
+ ### CanvasRenderer
295
+
296
+ ```typescript
297
+ class CanvasRenderer implements Renderer {
298
+ constructor(
299
+ canvas: HTMLCanvasElement,
300
+ engine: Engine,
301
+ options?: Partial<CanvasRendererOptions>
302
+ )
303
+
304
+ // Renderer interface
305
+ applyPatches(patches: Patch[]): void
306
+ getNode(id: string): VirtualNode | undefined
307
+ clear(): void
308
+
309
+ // Canvas-specific
310
+ registerPainter(type: string, painter: PainterFunction): void
311
+ setOptions(options: Partial<CanvasRendererOptions>): void
312
+ destroy(): void
313
+ }
314
+ ```
315
+
316
+ ### Custom Painters
317
+
318
+ ```typescript
319
+ type PainterFunction = (
320
+ ctx: CanvasRenderingContext2D,
321
+ node: VirtualNode
322
+ ) => void;
323
+
324
+ renderer.registerPainter("Circle", (ctx, node) => {
325
+ const layout = node.layout!;
326
+ const radius = Math.min(layout.width, layout.height) / 2;
327
+ const centerX = layout.x + layout.width / 2;
328
+ const centerY = layout.y + layout.height / 2;
329
+
330
+ ctx.beginPath();
331
+ ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
332
+ ctx.fillStyle = node.props.color || "#000";
333
+ ctx.fill();
334
+ });
335
+ ```
336
+
337
+ ## Testing
338
+
339
+ The canvas renderer includes comprehensive tests:
340
+
341
+ ```bash
342
+ # Run tests
343
+ bun test src/canvas
344
+
345
+ # Run with coverage
346
+ bun test --coverage src/canvas
347
+ ```
348
+
349
+ ## Contributing
350
+
351
+ The canvas renderer is under active development. Contributions welcome!
352
+
353
+ **Current Priorities:**
354
+ 1. Text selection support
355
+ 2. Gradient/shadow support
356
+ 3. Performance optimizations (dirty rects, layer caching)
357
+ 4. Advanced text (rich text, ligatures)
358
+ 5. More component painters
359
+
360
+ ## See Also
361
+
362
+ - [Main README](../../README.md) - SDK documentation
363
+ - [DOM Renderer](../dom/README.md) - DOM renderer documentation
364
+ - [Plan](./plan.md) - Implementation plan and roadmap
365
+ - [Canvas API Docs](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)
366
+
367
+
368
+
369
+
370
+
371
+
372
+
373
+
374
+
375
+
376
+
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Accessibility Layer
3
+ *
4
+ * Maintain shadow DOM for screen readers
5
+ */
6
+
7
+ import type { VirtualNode } from "./types.js";
8
+ import { walkTree } from "./utils.js";
9
+
10
+ /**
11
+ * Accessibility Manager
12
+ */
13
+ export class AccessibilityLayer {
14
+ private shadowRoot: HTMLElement;
15
+ private nodeMap = new Map<string, HTMLElement>();
16
+ private enabled: boolean;
17
+
18
+ constructor(container: HTMLElement | null, enabled: boolean = true) {
19
+ this.enabled = enabled && typeof document !== "undefined";
20
+
21
+ // Create shadow root container (only in browser)
22
+ if (this.enabled && typeof document !== "undefined") {
23
+ this.shadowRoot = document.createElement("div");
24
+ this.shadowRoot.setAttribute("role", "application");
25
+ this.shadowRoot.setAttribute("aria-label", "Hypen Canvas Application");
26
+ this.shadowRoot.style.position = "absolute";
27
+ this.shadowRoot.style.left = "-9999px";
28
+ this.shadowRoot.style.width = "1px";
29
+ this.shadowRoot.style.height = "1px";
30
+ this.shadowRoot.style.overflow = "hidden";
31
+
32
+ if (container) {
33
+ container.appendChild(this.shadowRoot);
34
+ }
35
+ } else {
36
+ // Fallback for non-browser environments
37
+ this.shadowRoot = {} as HTMLElement;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Sync shadow DOM with virtual tree
43
+ */
44
+ syncTree(root: VirtualNode): void {
45
+ if (!this.enabled) return;
46
+
47
+ // Clear existing
48
+ this.shadowRoot.innerHTML = "";
49
+ this.nodeMap.clear();
50
+
51
+ // Build shadow DOM
52
+ const shadowNode = this.createShadowNode(root);
53
+ if (shadowNode) {
54
+ this.shadowRoot.appendChild(shadowNode);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Create shadow DOM element for virtual node
60
+ */
61
+ private createShadowNode(node: VirtualNode): HTMLElement | null {
62
+ if (!node.visible) return null;
63
+
64
+ let element: HTMLElement;
65
+
66
+ // Create appropriate HTML element
67
+ switch (node.type.toLowerCase()) {
68
+ case "button":
69
+ element = document.createElement("button");
70
+ element.textContent = this.getNodeText(node);
71
+ if (node.props.onclick) {
72
+ element.setAttribute("aria-label", "Clickable button");
73
+ }
74
+ break;
75
+
76
+ case "input":
77
+ element = document.createElement("input");
78
+ (element as HTMLInputElement).type = node.props.type || "text";
79
+ (element as HTMLInputElement).value = node.props.value || "";
80
+ if (node.props.placeholder) {
81
+ (element as HTMLInputElement).placeholder = node.props.placeholder;
82
+ }
83
+ break;
84
+
85
+ case "textarea":
86
+ element = document.createElement("textarea");
87
+ (element as HTMLTextAreaElement).value = node.props.value || "";
88
+ if (node.props.placeholder) {
89
+ (element as HTMLTextAreaElement).placeholder = node.props.placeholder;
90
+ }
91
+ break;
92
+
93
+ case "image":
94
+ element = document.createElement("img");
95
+ (element as HTMLImageElement).src = node.props.src || "";
96
+ (element as HTMLImageElement).alt = node.props.alt || "Image";
97
+ break;
98
+
99
+ case "text":
100
+ element = document.createElement("span");
101
+ element.textContent = String(node.props[0] || node.props.text || "");
102
+ break;
103
+
104
+ case "column":
105
+ case "row":
106
+ case "container":
107
+ case "box":
108
+ element = document.createElement("div");
109
+ element.setAttribute("role", node.type === "column" ? "group" : "group");
110
+ break;
111
+
112
+ default:
113
+ element = document.createElement("div");
114
+ }
115
+
116
+ // Set common attributes
117
+ element.setAttribute("data-hypen-id", node.id);
118
+
119
+ if (node.props["aria-label"]) {
120
+ element.setAttribute("aria-label", node.props["aria-label"]);
121
+ }
122
+
123
+ if (node.focusable) {
124
+ element.tabIndex = 0;
125
+ }
126
+
127
+ // Store mapping
128
+ this.nodeMap.set(node.id, element);
129
+
130
+ // Add children
131
+ for (const child of node.children) {
132
+ const childElement = this.createShadowNode(child);
133
+ if (childElement) {
134
+ element.appendChild(childElement);
135
+ }
136
+ }
137
+
138
+ return element;
139
+ }
140
+
141
+ /**
142
+ * Get text content from node tree
143
+ */
144
+ private getNodeText(node: VirtualNode): string {
145
+ if (node.type === "text") {
146
+ return String(node.props[0] || node.props.text || "");
147
+ }
148
+
149
+ let text = "";
150
+ for (const child of node.children) {
151
+ text += this.getNodeText(child);
152
+ }
153
+ return text;
154
+ }
155
+
156
+ /**
157
+ * Focus node in shadow DOM
158
+ */
159
+ focusNode(nodeId: string): void {
160
+ if (!this.enabled) return;
161
+
162
+ const element = this.nodeMap.get(nodeId);
163
+ if (element) {
164
+ element.focus();
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Update single node
170
+ */
171
+ updateNode(node: VirtualNode): void {
172
+ if (!this.enabled) return;
173
+
174
+ const element = this.nodeMap.get(node.id);
175
+ if (!element) return;
176
+
177
+ // Update text content
178
+ if (node.type === "text") {
179
+ element.textContent = String(node.props[0] || node.props.text || "");
180
+ }
181
+
182
+ // Update input value
183
+ if (node.type === "input" || node.type === "textarea") {
184
+ (element as HTMLInputElement | HTMLTextAreaElement).value = node.props.value || "";
185
+ }
186
+
187
+ // Update visibility
188
+ element.style.display = node.visible ? "" : "none";
189
+ }
190
+
191
+ /**
192
+ * Get shadow element by node ID
193
+ */
194
+ getElement(nodeId: string): HTMLElement | undefined {
195
+ return this.nodeMap.get(nodeId);
196
+ }
197
+
198
+ /**
199
+ * Enable or disable accessibility layer
200
+ */
201
+ setEnabled(enabled: boolean): void {
202
+ this.enabled = enabled;
203
+ if (!enabled && this.shadowRoot.parentElement) {
204
+ this.shadowRoot.remove();
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Cleanup
210
+ */
211
+ destroy(): void {
212
+ if (this.shadowRoot.parentElement) {
213
+ this.shadowRoot.remove();
214
+ }
215
+ this.nodeMap.clear();
216
+ }
217
+ }
218
+