@hypen-space/web 0.4.45 → 0.4.81
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/README.md +4 -4
- package/dist/canvas/dirty.d.ts +40 -0
- package/dist/canvas/dirty.js +100 -0
- package/dist/canvas/dirty.js.map +1 -0
- package/dist/canvas/events.d.ts +5 -0
- package/dist/canvas/events.js +30 -3
- package/dist/canvas/events.js.map +1 -1
- package/dist/canvas/index.d.ts +5 -1
- package/dist/canvas/index.js +4 -0
- package/dist/canvas/index.js.map +1 -1
- package/dist/canvas/layout.d.ts +12 -2
- package/dist/canvas/layout.js +593 -63
- package/dist/canvas/layout.js.map +1 -1
- package/dist/canvas/paint.d.ts +2 -0
- package/dist/canvas/paint.js +88 -11
- package/dist/canvas/paint.js.map +1 -1
- package/dist/canvas/renderer.d.ts +16 -0
- package/dist/canvas/renderer.js +154 -15
- package/dist/canvas/renderer.js.map +1 -1
- package/dist/canvas/scroll.d.ts +82 -0
- package/dist/canvas/scroll.js +639 -0
- package/dist/canvas/scroll.js.map +1 -0
- package/dist/canvas/selection.d.ts +63 -0
- package/dist/canvas/selection.js +466 -0
- package/dist/canvas/selection.js.map +1 -0
- package/dist/canvas/text.d.ts +6 -3
- package/dist/canvas/text.js +82 -43
- package/dist/canvas/text.js.map +1 -1
- package/dist/canvas/types.d.ts +18 -0
- package/dist/canvas/utils.d.ts +8 -2
- package/dist/canvas/utils.js +42 -16
- package/dist/canvas/utils.js.map +1 -1
- package/dist/canvas/virtualize.d.ts +25 -0
- package/dist/canvas/virtualize.js +65 -0
- package/dist/canvas/virtualize.js.map +1 -0
- package/dist/dom/applicators/index.js +0 -1
- package/dist/dom/applicators/index.js.map +1 -1
- package/dist/dom/applicators/margin.js +58 -17
- package/dist/dom/applicators/margin.js.map +1 -1
- package/dist/dom/applicators/padding.js +42 -17
- package/dist/dom/applicators/padding.js.map +1 -1
- package/dist/dom/components/icon.d.ts +9 -0
- package/dist/dom/components/icon.js +67 -0
- package/dist/dom/components/icon.js.map +1 -0
- package/dist/dom/components/index.js +2 -0
- package/dist/dom/components/index.js.map +1 -1
- package/dist/dom/renderer.d.ts +19 -5
- package/dist/dom/renderer.js +147 -17
- package/dist/dom/renderer.js.map +1 -1
- package/package.json +4 -2
- package/src/canvas/QUICKSTART.md +5 -5
- package/src/canvas/dirty.ts +116 -0
- package/src/canvas/events.ts +34 -3
- package/src/canvas/index.ts +5 -0
- package/src/canvas/layout.ts +653 -100
- package/src/canvas/paint.ts +109 -11
- package/src/canvas/renderer.ts +189 -16
- package/src/canvas/scroll.ts +729 -0
- package/src/canvas/selection.ts +560 -0
- package/src/canvas/text.ts +97 -54
- package/src/canvas/types.ts +26 -0
- package/src/canvas/utils.ts +47 -17
- package/src/canvas/virtualize.ts +81 -0
- package/src/dom/applicators/index.ts +1 -1
- package/src/dom/applicators/margin.ts +57 -11
- package/src/dom/applicators/padding.ts +45 -11
- package/src/dom/components/icon.ts +84 -0
- package/src/dom/components/index.ts +2 -0
- package/src/dom/renderer.ts +155 -17
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dirty Rect Tracker
|
|
3
|
+
*
|
|
4
|
+
* Tracks which rectangular regions of the canvas have changed and need repainting.
|
|
5
|
+
* Instead of per-node tracking, we accumulate dirty rectangles and merge them into
|
|
6
|
+
* a single bounding box. The canvas clip path ensures only dirty pixels are drawn.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Rectangle, VirtualNode } from "./types.js";
|
|
10
|
+
|
|
11
|
+
export class DirtyRectTracker {
|
|
12
|
+
private dirtyRegion: Rectangle | null = null;
|
|
13
|
+
private canvasWidth: number;
|
|
14
|
+
private canvasHeight: number;
|
|
15
|
+
|
|
16
|
+
constructor(canvasWidth: number, canvasHeight: number) {
|
|
17
|
+
this.canvasWidth = canvasWidth;
|
|
18
|
+
this.canvasHeight = canvasHeight;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Update canvas dimensions (e.g. on resize)
|
|
23
|
+
*/
|
|
24
|
+
setCanvasSize(width: number, height: number): void {
|
|
25
|
+
this.canvasWidth = width;
|
|
26
|
+
this.canvasHeight = height;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Union a rectangle into the dirty region
|
|
31
|
+
*/
|
|
32
|
+
markDirty(rect: Rectangle): void {
|
|
33
|
+
if (rect.width <= 0 || rect.height <= 0) return;
|
|
34
|
+
|
|
35
|
+
if (this.dirtyRegion === null) {
|
|
36
|
+
this.dirtyRegion = { ...rect };
|
|
37
|
+
} else {
|
|
38
|
+
this.dirtyRegion = unionRects(this.dirtyRegion, rect);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Mark both old and new bounds of a node as dirty.
|
|
44
|
+
* This ensures the old position is erased and the new position is painted.
|
|
45
|
+
*/
|
|
46
|
+
markNodeDirty(node: VirtualNode): void {
|
|
47
|
+
if (node.layout) {
|
|
48
|
+
// Mark current layout bounds (covers both erase of old content and paint of new)
|
|
49
|
+
this.markDirty({
|
|
50
|
+
x: node.layout.x,
|
|
51
|
+
y: node.layout.y,
|
|
52
|
+
width: node.layout.width,
|
|
53
|
+
height: node.layout.height,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Mark the entire canvas as dirty (first render, resize, etc.)
|
|
60
|
+
*/
|
|
61
|
+
markFullDirty(): void {
|
|
62
|
+
this.dirtyRegion = {
|
|
63
|
+
x: 0,
|
|
64
|
+
y: 0,
|
|
65
|
+
width: this.canvasWidth,
|
|
66
|
+
height: this.canvasHeight,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Returns the merged bounding box of all dirty rects, or null if clean.
|
|
72
|
+
* Adds a 1px padding to avoid sub-pixel clipping artifacts.
|
|
73
|
+
*/
|
|
74
|
+
getDirtyRegion(): Rectangle | null {
|
|
75
|
+
if (this.dirtyRegion === null) return null;
|
|
76
|
+
|
|
77
|
+
// Expand by 1px to avoid sub-pixel edge artifacts, clamped to canvas bounds
|
|
78
|
+
const padded: Rectangle = {
|
|
79
|
+
x: Math.max(0, Math.floor(this.dirtyRegion.x) - 1),
|
|
80
|
+
y: Math.max(0, Math.floor(this.dirtyRegion.y) - 1),
|
|
81
|
+
width: Math.min(
|
|
82
|
+
this.canvasWidth,
|
|
83
|
+
Math.ceil(this.dirtyRegion.width) + 2
|
|
84
|
+
),
|
|
85
|
+
height: Math.min(
|
|
86
|
+
this.canvasHeight,
|
|
87
|
+
Math.ceil(this.dirtyRegion.height) + 2
|
|
88
|
+
),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return padded;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Reset dirty state after a frame
|
|
96
|
+
*/
|
|
97
|
+
clear(): void {
|
|
98
|
+
this.dirtyRegion = null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Compute the union (bounding box) of two rectangles
|
|
104
|
+
*/
|
|
105
|
+
function unionRects(a: Rectangle, b: Rectangle): Rectangle {
|
|
106
|
+
const x = Math.min(a.x, b.x);
|
|
107
|
+
const y = Math.min(a.y, b.y);
|
|
108
|
+
const right = Math.max(a.x + a.width, b.x + b.width);
|
|
109
|
+
const bottom = Math.max(a.y + a.height, b.y + b.height);
|
|
110
|
+
return {
|
|
111
|
+
x,
|
|
112
|
+
y,
|
|
113
|
+
width: right - x,
|
|
114
|
+
height: bottom - y,
|
|
115
|
+
};
|
|
116
|
+
}
|
package/src/canvas/events.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { VirtualNode, Point } from "./types.js";
|
|
8
|
-
import { isPointInRoundedRect
|
|
8
|
+
import { isPointInRoundedRect } from "./utils.js";
|
|
9
|
+
import { getScrollAwareBounds } from "./scroll.js";
|
|
9
10
|
|
|
10
11
|
// Interface for the engine that CanvasEventManager needs
|
|
11
12
|
interface IEngine {
|
|
@@ -29,6 +30,7 @@ export class CanvasEventManager {
|
|
|
29
30
|
private boundOnMouseUp!: (e: MouseEvent) => void;
|
|
30
31
|
private boundOnClick!: (e: MouseEvent) => void;
|
|
31
32
|
private boundOnDoubleClick!: (e: MouseEvent) => void;
|
|
33
|
+
private boundOnContextMenu!: (e: MouseEvent) => void;
|
|
32
34
|
private boundOnKeyDown!: (e: KeyboardEvent) => void;
|
|
33
35
|
private boundOnKeyUp!: (e: KeyboardEvent) => void;
|
|
34
36
|
|
|
@@ -54,6 +56,7 @@ export class CanvasEventManager {
|
|
|
54
56
|
this.boundOnMouseUp = this.onMouseUp.bind(this);
|
|
55
57
|
this.boundOnClick = this.onClick.bind(this);
|
|
56
58
|
this.boundOnDoubleClick = this.onDoubleClick.bind(this);
|
|
59
|
+
this.boundOnContextMenu = this.onContextMenu.bind(this);
|
|
57
60
|
this.boundOnKeyDown = this.onKeyDown.bind(this);
|
|
58
61
|
this.boundOnKeyUp = this.onKeyUp.bind(this);
|
|
59
62
|
|
|
@@ -62,6 +65,7 @@ export class CanvasEventManager {
|
|
|
62
65
|
this.canvas.addEventListener("mouseup", this.boundOnMouseUp);
|
|
63
66
|
this.canvas.addEventListener("click", this.boundOnClick);
|
|
64
67
|
this.canvas.addEventListener("dblclick", this.boundOnDoubleClick);
|
|
68
|
+
this.canvas.addEventListener("contextmenu", this.boundOnContextMenu);
|
|
65
69
|
this.canvas.addEventListener("keydown", this.boundOnKeyDown);
|
|
66
70
|
this.canvas.addEventListener("keyup", this.boundOnKeyUp);
|
|
67
71
|
}
|
|
@@ -94,7 +98,7 @@ export class CanvasEventManager {
|
|
|
94
98
|
private hitTestNode(node: VirtualNode, point: Point): VirtualNode | null {
|
|
95
99
|
if (!node.visible || !node.layout) return null;
|
|
96
100
|
|
|
97
|
-
const bounds =
|
|
101
|
+
const bounds = getScrollAwareBounds(node);
|
|
98
102
|
if (!bounds) return null;
|
|
99
103
|
|
|
100
104
|
// Test children first (front to back)
|
|
@@ -218,6 +222,27 @@ export class CanvasEventManager {
|
|
|
218
222
|
}
|
|
219
223
|
}
|
|
220
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Handle context menu (right-click)
|
|
227
|
+
*/
|
|
228
|
+
private onContextMenu(e: MouseEvent): void {
|
|
229
|
+
const point = this.getCanvasCoordinates(e);
|
|
230
|
+
const node = this.hitTest(point);
|
|
231
|
+
|
|
232
|
+
if (node) {
|
|
233
|
+
const actionName = node.props["oncontextmenu"] || node.props["contextmenu"];
|
|
234
|
+
|
|
235
|
+
if (actionName && typeof actionName === "string") {
|
|
236
|
+
e.preventDefault();
|
|
237
|
+
this.dispatchNodeEvent(node, "contextmenu", {
|
|
238
|
+
button: e.button,
|
|
239
|
+
clientX: e.clientX,
|
|
240
|
+
clientY: e.clientY,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
221
246
|
/**
|
|
222
247
|
* Handle keyboard events
|
|
223
248
|
*/
|
|
@@ -284,7 +309,12 @@ export class CanvasEventManager {
|
|
|
284
309
|
*/
|
|
285
310
|
private dispatchNodeEvent(node: VirtualNode, eventType: string, data: any): void {
|
|
286
311
|
// Check if node has action handler for this event
|
|
287
|
-
|
|
312
|
+
let actionName = node.props[`on${eventType}`] || node.props[eventType];
|
|
313
|
+
|
|
314
|
+
// Actionable components: fall back to "action" prop for click events
|
|
315
|
+
if (!actionName && eventType === "click" && node.props.action) {
|
|
316
|
+
actionName = node.props.action;
|
|
317
|
+
}
|
|
288
318
|
|
|
289
319
|
if (actionName && typeof actionName === "string") {
|
|
290
320
|
// Dispatch to engine
|
|
@@ -315,6 +345,7 @@ export class CanvasEventManager {
|
|
|
315
345
|
this.canvas.removeEventListener("mouseup", this.boundOnMouseUp);
|
|
316
346
|
this.canvas.removeEventListener("click", this.boundOnClick);
|
|
317
347
|
this.canvas.removeEventListener("dblclick", this.boundOnDoubleClick);
|
|
348
|
+
this.canvas.removeEventListener("contextmenu", this.boundOnContextMenu);
|
|
318
349
|
this.canvas.removeEventListener("keydown", this.boundOnKeyDown);
|
|
319
350
|
this.canvas.removeEventListener("keyup", this.boundOnKeyUp);
|
|
320
351
|
this.rootNode = null;
|
package/src/canvas/index.ts
CHANGED
|
@@ -9,6 +9,10 @@ export { registerPainter } from "./paint.js";
|
|
|
9
9
|
export { CanvasEventManager } from "./events.js";
|
|
10
10
|
export { InputOverlay } from "./input.js";
|
|
11
11
|
export { AccessibilityLayer } from "./accessibility.js";
|
|
12
|
+
export { initTaffyLayout } from "./layout.js";
|
|
13
|
+
export { ScrollManager } from "./scroll.js";
|
|
14
|
+
export { DirtyRectTracker } from "./dirty.js";
|
|
15
|
+
export { SelectionManager } from "./selection.js";
|
|
12
16
|
|
|
13
17
|
export type {
|
|
14
18
|
VirtualNode,
|
|
@@ -18,6 +22,7 @@ export type {
|
|
|
18
22
|
FontStyle,
|
|
19
23
|
TextStyle,
|
|
20
24
|
TextMetrics,
|
|
25
|
+
ScrollState,
|
|
21
26
|
CanvasRendererOptions,
|
|
22
27
|
PainterFunction,
|
|
23
28
|
LayoutFunction,
|