@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.
- package/dist/chunk-2s02mkzs.js +32 -0
- package/dist/chunk-2s02mkzs.js.map +9 -0
- package/dist/src/canvas/accessibility.js +152 -0
- package/dist/src/canvas/accessibility.js.map +10 -0
- package/dist/src/canvas/events.js +198 -0
- package/dist/src/canvas/events.js.map +10 -0
- package/dist/src/canvas/index.js +28 -0
- package/dist/src/canvas/index.js.map +9 -0
- package/dist/src/canvas/input.js +132 -0
- package/dist/src/canvas/input.js.map +10 -0
- package/dist/src/canvas/layout.js +309 -0
- package/dist/src/canvas/layout.js.map +10 -0
- package/dist/src/canvas/paint.js +878 -0
- package/dist/src/canvas/paint.js.map +10 -0
- package/dist/src/canvas/renderer.js +276 -0
- package/dist/src/canvas/renderer.js.map +10 -0
- package/dist/src/canvas/text.js +118 -0
- package/dist/src/canvas/text.js.map +10 -0
- package/dist/src/canvas/types.js +2 -0
- package/dist/src/canvas/types.js.map +9 -0
- package/dist/src/canvas/utils.js +139 -0
- package/dist/src/canvas/utils.js.map +10 -0
- package/dist/src/dom/applicators/advanced-layout.js +111 -0
- package/dist/src/dom/applicators/advanced-layout.js.map +10 -0
- package/dist/src/dom/applicators/background.js +54 -0
- package/dist/src/dom/applicators/background.js.map +10 -0
- package/dist/src/dom/applicators/border.js +33 -0
- package/dist/src/dom/applicators/border.js.map +10 -0
- package/dist/src/dom/applicators/color.js +36 -0
- package/dist/src/dom/applicators/color.js.map +10 -0
- package/dist/src/dom/applicators/display.js +57 -0
- package/dist/src/dom/applicators/display.js.map +10 -0
- package/dist/src/dom/applicators/effects.js +89 -0
- package/dist/src/dom/applicators/effects.js.map +10 -0
- package/dist/src/dom/applicators/events.js +518 -0
- package/dist/src/dom/applicators/events.js.map +10 -0
- package/dist/src/dom/applicators/font.js +39 -0
- package/dist/src/dom/applicators/font.js.map +10 -0
- package/dist/src/dom/applicators/index.js +296 -0
- package/dist/src/dom/applicators/index.js.map +10 -0
- package/dist/src/dom/applicators/layout.js +86 -0
- package/dist/src/dom/applicators/layout.js.map +10 -0
- package/dist/src/dom/applicators/margin.js +32 -0
- package/dist/src/dom/applicators/margin.js.map +10 -0
- package/dist/src/dom/applicators/padding.js +35 -0
- package/dist/src/dom/applicators/padding.js.map +10 -0
- package/dist/src/dom/applicators/size.js +42 -0
- package/dist/src/dom/applicators/size.js.map +10 -0
- package/dist/src/dom/applicators/transform.js +92 -0
- package/dist/src/dom/applicators/transform.js.map +10 -0
- package/dist/src/dom/applicators/transition.js +66 -0
- package/dist/src/dom/applicators/transition.js.map +10 -0
- package/dist/src/dom/applicators/typography.js +87 -0
- package/dist/src/dom/applicators/typography.js.map +10 -0
- package/dist/src/dom/canvas/index.js +50 -0
- package/dist/src/dom/canvas/index.js.map +10 -0
- package/dist/src/dom/components/audio.js +48 -0
- package/dist/src/dom/components/audio.js.map +10 -0
- package/dist/src/dom/components/avatar.js +58 -0
- package/dist/src/dom/components/avatar.js.map +10 -0
- package/dist/src/dom/components/badge.js +55 -0
- package/dist/src/dom/components/badge.js.map +10 -0
- package/dist/src/dom/components/button.js +29 -0
- package/dist/src/dom/components/button.js.map +10 -0
- package/dist/src/dom/components/card.js +33 -0
- package/dist/src/dom/components/card.js.map +10 -0
- package/dist/src/dom/components/center.js +32 -0
- package/dist/src/dom/components/center.js.map +10 -0
- package/dist/src/dom/components/checkbox.js +54 -0
- package/dist/src/dom/components/checkbox.js.map +10 -0
- package/dist/src/dom/components/column.js +31 -0
- package/dist/src/dom/components/column.js.map +10 -0
- package/dist/src/dom/components/container.js +29 -0
- package/dist/src/dom/components/container.js.map +10 -0
- package/dist/src/dom/components/divider.js +45 -0
- package/dist/src/dom/components/divider.js.map +10 -0
- package/dist/src/dom/components/grid.js +44 -0
- package/dist/src/dom/components/grid.js.map +10 -0
- package/dist/src/dom/components/heading.js +47 -0
- package/dist/src/dom/components/heading.js.map +10 -0
- package/dist/src/dom/components/image.js +39 -0
- package/dist/src/dom/components/image.js.map +10 -0
- package/dist/src/dom/components/index.js +217 -0
- package/dist/src/dom/components/index.js.map +10 -0
- package/dist/src/dom/components/input.js +41 -0
- package/dist/src/dom/components/input.js.map +10 -0
- package/dist/src/dom/components/link.js +42 -0
- package/dist/src/dom/components/link.js.map +10 -0
- package/dist/src/dom/components/list.js +42 -0
- package/dist/src/dom/components/list.js.map +10 -0
- package/dist/src/dom/components/paragraph.js +35 -0
- package/dist/src/dom/components/paragraph.js.map +10 -0
- package/dist/src/dom/components/progressbar.js +57 -0
- package/dist/src/dom/components/progressbar.js.map +10 -0
- package/dist/src/dom/components/route.js +44 -0
- package/dist/src/dom/components/route.js.map +10 -0
- package/dist/src/dom/components/router.js +33 -0
- package/dist/src/dom/components/router.js.map +10 -0
- package/dist/src/dom/components/row.js +31 -0
- package/dist/src/dom/components/row.js.map +10 -0
- package/dist/src/dom/components/select.js +57 -0
- package/dist/src/dom/components/select.js.map +10 -0
- package/dist/src/dom/components/slider.js +48 -0
- package/dist/src/dom/components/slider.js.map +10 -0
- package/dist/src/dom/components/spacer.js +30 -0
- package/dist/src/dom/components/spacer.js.map +10 -0
- package/dist/src/dom/components/spinner.js +65 -0
- package/dist/src/dom/components/spinner.js.map +10 -0
- package/dist/src/dom/components/stack.js +45 -0
- package/dist/src/dom/components/stack.js.map +10 -0
- package/dist/src/dom/components/switch.js +83 -0
- package/dist/src/dom/components/switch.js.map +10 -0
- package/dist/src/dom/components/text.js +37 -0
- package/dist/src/dom/components/text.js.map +10 -0
- package/dist/src/dom/components/textarea.js +51 -0
- package/dist/src/dom/components/textarea.js.map +10 -0
- package/dist/src/dom/components/video.js +51 -0
- package/dist/src/dom/components/video.js.map +10 -0
- package/dist/src/dom/debug.js +170 -0
- package/dist/src/dom/debug.js.map +10 -0
- package/dist/src/dom/events.js +112 -0
- package/dist/src/dom/events.js.map +10 -0
- package/dist/src/dom/index.js +73 -0
- package/dist/src/dom/index.js.map +9 -0
- package/dist/src/dom/renderer.js +277 -0
- package/dist/src/dom/renderer.js.map +10 -0
- package/dist/src/index.js +89 -0
- package/dist/src/index.js.map +9 -0
- package/package.json +84 -0
- package/src/canvas/QUICKSTART.md +421 -0
- package/src/canvas/README.md +376 -0
- package/src/canvas/accessibility.ts +218 -0
- package/src/canvas/events.ts +307 -0
- package/src/canvas/index.ts +35 -0
- package/src/canvas/input.ts +210 -0
- package/src/canvas/layout.ts +401 -0
- package/src/canvas/paint.ts +1321 -0
- package/src/canvas/renderer.ts +422 -0
- package/src/canvas/text.ts +182 -0
- package/src/canvas/types.ts +137 -0
- package/src/canvas/utils.ts +218 -0
- package/src/dom/README.md +265 -0
- package/src/dom/applicators/advanced-layout.ts +128 -0
- package/src/dom/applicators/background.ts +50 -0
- package/src/dom/applicators/border.ts +19 -0
- package/src/dom/applicators/color.ts +23 -0
- package/src/dom/applicators/display.ts +54 -0
- package/src/dom/applicators/effects.ts +97 -0
- package/src/dom/applicators/events.ts +689 -0
- package/src/dom/applicators/font.ts +27 -0
- package/src/dom/applicators/index.ts +354 -0
- package/src/dom/applicators/layout.ts +92 -0
- package/src/dom/applicators/margin.ts +18 -0
- package/src/dom/applicators/padding.ts +18 -0
- package/src/dom/applicators/size.ts +31 -0
- package/src/dom/applicators/transform.ts +93 -0
- package/src/dom/applicators/transition.ts +65 -0
- package/src/dom/applicators/typography.ts +91 -0
- package/src/dom/canvas/index.ts +60 -0
- package/src/dom/components/audio.ts +45 -0
- package/src/dom/components/avatar.ts +49 -0
- package/src/dom/components/badge.ts +45 -0
- package/src/dom/components/button.ts +13 -0
- package/src/dom/components/card.ts +19 -0
- package/src/dom/components/center.ts +16 -0
- package/src/dom/components/checkbox.ts +54 -0
- package/src/dom/components/column.ts +15 -0
- package/src/dom/components/container.ts +13 -0
- package/src/dom/components/divider.ts +37 -0
- package/src/dom/components/grid.ts +40 -0
- package/src/dom/components/heading.ts +41 -0
- package/src/dom/components/image.ts +27 -0
- package/src/dom/components/index.ts +115 -0
- package/src/dom/components/input.ts +29 -0
- package/src/dom/components/link.ts +35 -0
- package/src/dom/components/list.ts +30 -0
- package/src/dom/components/paragraph.ts +23 -0
- package/src/dom/components/progressbar.ts +51 -0
- package/src/dom/components/route.ts +37 -0
- package/src/dom/components/router.ts +22 -0
- package/src/dom/components/row.ts +15 -0
- package/src/dom/components/select.ts +56 -0
- package/src/dom/components/slider.ts +45 -0
- package/src/dom/components/spacer.ts +16 -0
- package/src/dom/components/spinner.ts +60 -0
- package/src/dom/components/stack.ts +34 -0
- package/src/dom/components/switch.ts +86 -0
- package/src/dom/components/text.ts +24 -0
- package/src/dom/components/textarea.ts +50 -0
- package/src/dom/components/video.ts +50 -0
- package/src/dom/debug.ts +247 -0
- package/src/dom/events.ts +168 -0
- package/src/dom/index.ts +11 -0
- package/src/dom/renderer.ts +327 -0
- package/src/index.ts +56 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas Renderer Types
|
|
3
|
+
*
|
|
4
|
+
* Shared type definitions for the canvas renderer
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface VirtualNode {
|
|
8
|
+
id: string;
|
|
9
|
+
type: string;
|
|
10
|
+
props: Record<string, any>;
|
|
11
|
+
children: VirtualNode[];
|
|
12
|
+
parent: VirtualNode | null;
|
|
13
|
+
|
|
14
|
+
// Computed layout
|
|
15
|
+
layout?: Layout;
|
|
16
|
+
|
|
17
|
+
// Rendering state
|
|
18
|
+
visible: boolean;
|
|
19
|
+
opacity: number;
|
|
20
|
+
|
|
21
|
+
// Interaction state
|
|
22
|
+
clickable: boolean;
|
|
23
|
+
hoverable: boolean;
|
|
24
|
+
focusable: boolean;
|
|
25
|
+
focused: boolean;
|
|
26
|
+
hovered: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Layout {
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
|
|
35
|
+
// Box model
|
|
36
|
+
margin: BoxSpacing;
|
|
37
|
+
padding: BoxSpacing;
|
|
38
|
+
border: BorderStyle;
|
|
39
|
+
|
|
40
|
+
// Content area (after padding)
|
|
41
|
+
contentX: number;
|
|
42
|
+
contentY: number;
|
|
43
|
+
contentWidth: number;
|
|
44
|
+
contentHeight: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface BoxSpacing {
|
|
48
|
+
top: number;
|
|
49
|
+
right: number;
|
|
50
|
+
bottom: number;
|
|
51
|
+
left: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface BorderStyle {
|
|
55
|
+
width: number;
|
|
56
|
+
color: string;
|
|
57
|
+
radius: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface Rectangle {
|
|
61
|
+
x: number;
|
|
62
|
+
y: number;
|
|
63
|
+
width: number;
|
|
64
|
+
height: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface Point {
|
|
68
|
+
x: number;
|
|
69
|
+
y: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface FontStyle {
|
|
73
|
+
fontFamily: string;
|
|
74
|
+
fontSize: number;
|
|
75
|
+
fontWeight: string | number;
|
|
76
|
+
lineHeight?: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface TextStyle extends FontStyle {
|
|
80
|
+
color: string;
|
|
81
|
+
textAlign: "left" | "center" | "right";
|
|
82
|
+
verticalAlign: "top" | "middle" | "bottom";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface TextMetrics {
|
|
86
|
+
width: number;
|
|
87
|
+
height: number;
|
|
88
|
+
lines: string[];
|
|
89
|
+
lineHeight: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface CanvasRendererOptions {
|
|
93
|
+
// Display
|
|
94
|
+
devicePixelRatio?: number;
|
|
95
|
+
backgroundColor?: string;
|
|
96
|
+
|
|
97
|
+
// Features
|
|
98
|
+
enableAccessibility?: boolean;
|
|
99
|
+
enableHitTesting?: boolean;
|
|
100
|
+
enableInputOverlay?: boolean;
|
|
101
|
+
|
|
102
|
+
// Performance
|
|
103
|
+
enableDirtyRects?: boolean;
|
|
104
|
+
enableLayerCaching?: boolean;
|
|
105
|
+
maxLayerCacheSize?: number;
|
|
106
|
+
|
|
107
|
+
// Debug
|
|
108
|
+
showLayoutBounds?: boolean;
|
|
109
|
+
showDirtyRects?: boolean;
|
|
110
|
+
logPerformance?: boolean;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface PainterFunction {
|
|
114
|
+
(ctx: CanvasRenderingContext2D, node: VirtualNode): void;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface LayoutFunction {
|
|
118
|
+
(node: VirtualNode, availableWidth: number, availableHeight: number): {
|
|
119
|
+
width: number;
|
|
120
|
+
height: number;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface DirtyRect extends Rectangle {
|
|
125
|
+
frameId: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas Renderer Utilities
|
|
3
|
+
*
|
|
4
|
+
* Common utility functions
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { BoxSpacing, Rectangle, Point, VirtualNode } from "./types.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Parse spacing value (margin, padding)
|
|
11
|
+
* Supports: number, "10", "10 20", "10 20 30 40"
|
|
12
|
+
*/
|
|
13
|
+
export function parseSpacing(value: any): BoxSpacing {
|
|
14
|
+
if (typeof value === "number") {
|
|
15
|
+
return { top: value, right: value, bottom: value, left: value };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (typeof value === "string") {
|
|
19
|
+
const parts = value.split(/\s+/).map((v) => parseFloat(v) || 0);
|
|
20
|
+
if (parts.length === 1) {
|
|
21
|
+
return { top: parts[0], right: parts[0], bottom: parts[0], left: parts[0] };
|
|
22
|
+
}
|
|
23
|
+
if (parts.length === 2) {
|
|
24
|
+
return { top: parts[0], right: parts[1], bottom: parts[0], left: parts[1] };
|
|
25
|
+
}
|
|
26
|
+
if (parts.length === 4) {
|
|
27
|
+
return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[3] };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (typeof value === "object" && value !== null) {
|
|
32
|
+
return {
|
|
33
|
+
top: parseFloat(value.top) || 0,
|
|
34
|
+
right: parseFloat(value.right) || 0,
|
|
35
|
+
bottom: parseFloat(value.bottom) || 0,
|
|
36
|
+
left: parseFloat(value.left) || 0,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parse size value (width, height)
|
|
45
|
+
* Returns pixel value or null for "auto"
|
|
46
|
+
*/
|
|
47
|
+
export function parseSize(value: any): number | null {
|
|
48
|
+
if (typeof value === "number") return value;
|
|
49
|
+
if (typeof value === "string") {
|
|
50
|
+
if (value === "auto") return null;
|
|
51
|
+
const num = parseFloat(value);
|
|
52
|
+
return isNaN(num) ? null : num;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if point is inside rectangle
|
|
59
|
+
*/
|
|
60
|
+
export function isPointInRect(point: Point, rect: Rectangle): boolean {
|
|
61
|
+
return (
|
|
62
|
+
point.x >= rect.x &&
|
|
63
|
+
point.x <= rect.x + rect.width &&
|
|
64
|
+
point.y >= rect.y &&
|
|
65
|
+
point.y <= rect.y + rect.height
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if point is inside rounded rectangle
|
|
71
|
+
*/
|
|
72
|
+
export function isPointInRoundedRect(
|
|
73
|
+
point: Point,
|
|
74
|
+
rect: Rectangle,
|
|
75
|
+
radius: number
|
|
76
|
+
): boolean {
|
|
77
|
+
const { x, y, width, height } = rect;
|
|
78
|
+
|
|
79
|
+
// Quick reject if outside bounding box
|
|
80
|
+
if (!isPointInRect(point, rect)) return false;
|
|
81
|
+
|
|
82
|
+
// No radius means simple rectangle
|
|
83
|
+
if (radius <= 0) return true;
|
|
84
|
+
|
|
85
|
+
const px = point.x;
|
|
86
|
+
const py = point.y;
|
|
87
|
+
|
|
88
|
+
// Check corners
|
|
89
|
+
// Top-left
|
|
90
|
+
if (px < x + radius && py < y + radius) {
|
|
91
|
+
return Math.pow(px - (x + radius), 2) + Math.pow(py - (y + radius), 2) <= Math.pow(radius, 2);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Top-right
|
|
95
|
+
if (px > x + width - radius && py < y + radius) {
|
|
96
|
+
return (
|
|
97
|
+
Math.pow(px - (x + width - radius), 2) + Math.pow(py - (y + radius), 2) <=
|
|
98
|
+
Math.pow(radius, 2)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Bottom-left
|
|
103
|
+
if (px < x + radius && py > y + height - radius) {
|
|
104
|
+
return (
|
|
105
|
+
Math.pow(px - (x + radius), 2) + Math.pow(py - (y + height - radius), 2) <=
|
|
106
|
+
Math.pow(radius, 2)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Bottom-right
|
|
111
|
+
if (px > x + width - radius && py > y + height - radius) {
|
|
112
|
+
return (
|
|
113
|
+
Math.pow(px - (x + width - radius), 2) + Math.pow(py - (y + height - radius), 2) <=
|
|
114
|
+
Math.pow(radius, 2)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Inside rectangle
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Merge rectangles into bounding box
|
|
124
|
+
*/
|
|
125
|
+
export function mergeRects(rects: Rectangle[]): Rectangle | null {
|
|
126
|
+
if (rects.length === 0) return null;
|
|
127
|
+
if (rects.length === 1) return rects[0];
|
|
128
|
+
|
|
129
|
+
let minX = Infinity;
|
|
130
|
+
let minY = Infinity;
|
|
131
|
+
let maxX = -Infinity;
|
|
132
|
+
let maxY = -Infinity;
|
|
133
|
+
|
|
134
|
+
for (const rect of rects) {
|
|
135
|
+
minX = Math.min(minX, rect.x);
|
|
136
|
+
minY = Math.min(minY, rect.y);
|
|
137
|
+
maxX = Math.max(maxX, rect.x + rect.width);
|
|
138
|
+
maxY = Math.max(maxY, rect.y + rect.height);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
x: minX,
|
|
143
|
+
y: minY,
|
|
144
|
+
width: maxX - minX,
|
|
145
|
+
height: maxY - minY,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Create a canvas font string from font style
|
|
151
|
+
*/
|
|
152
|
+
export function createFontString(
|
|
153
|
+
fontSize: number,
|
|
154
|
+
fontWeight: string | number,
|
|
155
|
+
fontFamily: string
|
|
156
|
+
): string {
|
|
157
|
+
return `${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Walk tree depth-first
|
|
162
|
+
*/
|
|
163
|
+
export function walkTree(node: VirtualNode, callback: (node: VirtualNode) => void): void {
|
|
164
|
+
callback(node);
|
|
165
|
+
for (const child of node.children) {
|
|
166
|
+
walkTree(child, callback);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Find node by ID in tree
|
|
172
|
+
*/
|
|
173
|
+
export function findNodeById(root: VirtualNode, id: string): VirtualNode | null {
|
|
174
|
+
if (root.id === id) return root;
|
|
175
|
+
|
|
176
|
+
for (const child of root.children) {
|
|
177
|
+
const found = findNodeById(child, id);
|
|
178
|
+
if (found) return found;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get absolute bounds of node (including transformations)
|
|
186
|
+
*/
|
|
187
|
+
export function getAbsoluteBounds(node: VirtualNode): Rectangle | null {
|
|
188
|
+
if (!node.layout) return null;
|
|
189
|
+
|
|
190
|
+
let x = node.layout.x;
|
|
191
|
+
let y = node.layout.y;
|
|
192
|
+
|
|
193
|
+
// Walk up tree to accumulate offsets
|
|
194
|
+
let current = node.parent;
|
|
195
|
+
while (current && current.layout) {
|
|
196
|
+
x += current.layout.contentX;
|
|
197
|
+
y += current.layout.contentY;
|
|
198
|
+
current = current.parent;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
x,
|
|
203
|
+
y,
|
|
204
|
+
width: node.layout.width,
|
|
205
|
+
height: node.layout.height,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# DOM Renderer
|
|
2
|
+
|
|
3
|
+
Browser-only module for rendering Hypen UI to the DOM.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/dom/
|
|
9
|
+
├── index.ts # Main exports
|
|
10
|
+
├── renderer.ts # DOMRenderer class
|
|
11
|
+
├── events.ts # Event management
|
|
12
|
+
├── components/ # Component handlers
|
|
13
|
+
│ ├── index.ts # Component registry
|
|
14
|
+
│ ├── column.ts
|
|
15
|
+
│ ├── row.ts
|
|
16
|
+
│ ├── text.ts
|
|
17
|
+
│ ├── image.ts
|
|
18
|
+
│ ├── button.ts
|
|
19
|
+
│ ├── container.ts
|
|
20
|
+
│ ├── center.ts
|
|
21
|
+
│ ├── list.ts
|
|
22
|
+
│ └── input.ts
|
|
23
|
+
├── applicators/ # Style applicators
|
|
24
|
+
│ ├── index.ts # Applicator registry
|
|
25
|
+
│ ├── padding.ts
|
|
26
|
+
│ ├── margin.ts
|
|
27
|
+
│ ├── color.ts
|
|
28
|
+
│ ├── border.ts
|
|
29
|
+
│ ├── size.ts
|
|
30
|
+
│ ├── font.ts
|
|
31
|
+
│ └── layout.ts
|
|
32
|
+
└── canvas/ # Canvas support
|
|
33
|
+
└── index.ts # Canvas component & applicators
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
### Browser Import
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { Engine, app, HypenModuleInstance } from "@hypen/core";
|
|
42
|
+
import { DOMRenderer } from "@hypen/core/dom";
|
|
43
|
+
|
|
44
|
+
const engine = new Engine();
|
|
45
|
+
await engine.init();
|
|
46
|
+
|
|
47
|
+
const renderer = new DOMRenderer(document.body, engine);
|
|
48
|
+
engine.setRenderCallback((patches) => {
|
|
49
|
+
renderer.applyPatches(patches);
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Server Import
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// This will fail in Node.js/Bun (no DOM)
|
|
57
|
+
import { DOMRenderer } from "@hypen/core/dom"; // ❌ Error
|
|
58
|
+
|
|
59
|
+
// Use server-safe renderer instead
|
|
60
|
+
import { ConsoleRenderer } from "@hypen/core"; // ✅ Works
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Components
|
|
64
|
+
|
|
65
|
+
All components are registered by default:
|
|
66
|
+
|
|
67
|
+
- **Column** - Vertical flex container
|
|
68
|
+
- **Row** - Horizontal flex container
|
|
69
|
+
- **Text** - Text content
|
|
70
|
+
- **Image** - Image element
|
|
71
|
+
- **Button** - Button element
|
|
72
|
+
- **Container/Box** - Generic div
|
|
73
|
+
- **Center** - Centered content
|
|
74
|
+
- **List** - Scrollable stack with gap
|
|
75
|
+
- **Input** - Input field
|
|
76
|
+
- **Canvas** - Canvas element
|
|
77
|
+
|
|
78
|
+
### Custom Components
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const renderer = new DOMRenderer(document.body, engine);
|
|
82
|
+
const registry = renderer.getComponentRegistry();
|
|
83
|
+
|
|
84
|
+
registry.register("mycomponent", {
|
|
85
|
+
create(): HTMLElement {
|
|
86
|
+
const el = document.createElement("div");
|
|
87
|
+
el.classList.add("my-component");
|
|
88
|
+
return el;
|
|
89
|
+
},
|
|
90
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
91
|
+
if (props.title) {
|
|
92
|
+
el.setAttribute("title", props.title);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Applicators
|
|
99
|
+
|
|
100
|
+
Style applicators are registered by default:
|
|
101
|
+
|
|
102
|
+
**Spacing:**
|
|
103
|
+
- `padding` - All sides or {left, right, top, bottom}
|
|
104
|
+
- `margin` - All sides or {left, right, top, bottom}
|
|
105
|
+
- `gap` - Flex/grid gap
|
|
106
|
+
|
|
107
|
+
**Colors:**
|
|
108
|
+
- `color` - Text color
|
|
109
|
+
- `backgroundColor` - Background color
|
|
110
|
+
- `borderColor` - Border color
|
|
111
|
+
- `opacity` - Opacity (0-1)
|
|
112
|
+
|
|
113
|
+
**Border:**
|
|
114
|
+
- `borderWidth` - Border width
|
|
115
|
+
- `borderStyle` - Border style (solid, dashed, etc.)
|
|
116
|
+
- `borderRadius` - Border radius
|
|
117
|
+
|
|
118
|
+
**Size:**
|
|
119
|
+
- `width`, `height` - Element size
|
|
120
|
+
- `minWidth`, `minHeight` - Minimum size
|
|
121
|
+
- `maxWidth`, `maxHeight` - Maximum size
|
|
122
|
+
|
|
123
|
+
**Font:**
|
|
124
|
+
- `fontSize` - Font size
|
|
125
|
+
- `fontWeight` - Font weight (normal, bold, 100-900)
|
|
126
|
+
- `fontFamily` - Font family
|
|
127
|
+
- `textAlign` - Text alignment
|
|
128
|
+
- `lineHeight` - Line height
|
|
129
|
+
|
|
130
|
+
**Layout (Flexbox):**
|
|
131
|
+
- `horizontalAlign` - justify-content
|
|
132
|
+
- `verticalAlign` - align-items
|
|
133
|
+
- `flex` - Flex shorthand
|
|
134
|
+
- `flexGrow`, `flexShrink` - Flex properties
|
|
135
|
+
|
|
136
|
+
**Misc:**
|
|
137
|
+
- `cursor` - Cursor style
|
|
138
|
+
- `overflow` - Overflow behavior
|
|
139
|
+
|
|
140
|
+
### Custom Applicators
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
const renderer = new DOMRenderer(document.body, engine);
|
|
144
|
+
const applicators = renderer.getApplicatorRegistry();
|
|
145
|
+
|
|
146
|
+
applicators.register("shadow", (el, value) => {
|
|
147
|
+
el.style.boxShadow = `0 ${value}px ${value * 2}px rgba(0,0,0,0.2)`;
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Events
|
|
152
|
+
|
|
153
|
+
Events are automatically wired up when patches include `attachEvent`:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// Hypen DSL:
|
|
157
|
+
Button("@actions.increment") { Text("+") }
|
|
158
|
+
|
|
159
|
+
// Becomes:
|
|
160
|
+
// 1. Create button
|
|
161
|
+
// 2. Attach "click" event
|
|
162
|
+
// 3. On click, dispatch "increment" action to engine
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Supported events:
|
|
166
|
+
- Mouse: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseenter`, `mouseleave`
|
|
167
|
+
- Keyboard: `keydown`, `keyup`, `keypress`
|
|
168
|
+
- Input: `input`, `change`, `focus`, `blur`
|
|
169
|
+
- Form: `submit`
|
|
170
|
+
- Touch: `touchstart`, `touchend`, `touchmove`
|
|
171
|
+
|
|
172
|
+
Event data includes:
|
|
173
|
+
- `type` - Event type
|
|
174
|
+
- `timestamp` - When it occurred
|
|
175
|
+
- `clientX`, `clientY` - Mouse position (mouse events)
|
|
176
|
+
- `key`, `code` - Key info (keyboard events)
|
|
177
|
+
- `value` - Input value (form elements)
|
|
178
|
+
|
|
179
|
+
## Canvas
|
|
180
|
+
|
|
181
|
+
Canvas components have special applicators for drawing:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// Register canvas applicators
|
|
185
|
+
const renderer = new DOMRenderer(document.body, engine);
|
|
186
|
+
const applicators = renderer.getApplicatorRegistry();
|
|
187
|
+
|
|
188
|
+
// Canvas-specific applicators
|
|
189
|
+
applicators.register("fillStyle", (el, value) => {
|
|
190
|
+
const canvas = el as HTMLCanvasElement;
|
|
191
|
+
const ctx = canvas.getContext("2d");
|
|
192
|
+
if (ctx) ctx.fillStyle = value;
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Built-in canvas applicators:
|
|
197
|
+
- `fillStyle` - Fill color
|
|
198
|
+
- `strokeStyle` - Stroke color
|
|
199
|
+
- `lineWidth` - Line width
|
|
200
|
+
|
|
201
|
+
## Extensibility
|
|
202
|
+
|
|
203
|
+
### Adding a New Component
|
|
204
|
+
|
|
205
|
+
1. Create `src/dom/components/mycomponent.ts`:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import type { ComponentHandler } from "./index.js";
|
|
209
|
+
|
|
210
|
+
export const myComponentHandler: ComponentHandler = {
|
|
211
|
+
create(): HTMLElement {
|
|
212
|
+
const el = document.createElement("div");
|
|
213
|
+
el.dataset.hypenType = "mycomponent";
|
|
214
|
+
return el;
|
|
215
|
+
},
|
|
216
|
+
applyProps(el, props) {
|
|
217
|
+
// Handle props
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
2. Register in `src/dom/components/index.ts`:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
const { myComponentHandler } = require("./mycomponent.js");
|
|
226
|
+
this.register("mycomponent", myComponentHandler);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Adding a New Applicator
|
|
230
|
+
|
|
231
|
+
1. Create `src/dom/applicators/myapplicator.ts`:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import type { ApplicatorHandler } from "./index.js";
|
|
235
|
+
|
|
236
|
+
export const myApplicatorHandler: ApplicatorHandler = (el, value) => {
|
|
237
|
+
el.style.someProperty = String(value);
|
|
238
|
+
};
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
2. Register in `src/dom/applicators/index.ts`:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
const { myApplicatorHandler } = require("./myapplicator.js");
|
|
245
|
+
this.register("myapplicator", myApplicatorHandler);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Performance
|
|
249
|
+
|
|
250
|
+
- **Minimal DOM operations** - Only patches are applied
|
|
251
|
+
- **Efficient updates** - Only changed props are updated
|
|
252
|
+
- **Event delegation** - Events managed per-element
|
|
253
|
+
- **Keyed reconciliation** - Use `key` prop for lists
|
|
254
|
+
|
|
255
|
+
## Browser Support
|
|
256
|
+
|
|
257
|
+
- Modern browsers with ES6+ support
|
|
258
|
+
- Requires: `Proxy`, `Map`, `Set`, `Promise`
|
|
259
|
+
- No IE11 support (use Babel + polyfills if needed)
|
|
260
|
+
|
|
261
|
+
## See Also
|
|
262
|
+
|
|
263
|
+
- [Main README](../../README.md) - Full SDK documentation
|
|
264
|
+
- [REFACTORING.md](../../REFACTORING.md) - State management details
|
|
265
|
+
- [IMPLEMENTATION.md](../../IMPLEMENTATION.md) - Implementation guide
|