@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.
Files changed (69) hide show
  1. package/README.md +4 -4
  2. package/dist/canvas/dirty.d.ts +40 -0
  3. package/dist/canvas/dirty.js +100 -0
  4. package/dist/canvas/dirty.js.map +1 -0
  5. package/dist/canvas/events.d.ts +5 -0
  6. package/dist/canvas/events.js +30 -3
  7. package/dist/canvas/events.js.map +1 -1
  8. package/dist/canvas/index.d.ts +5 -1
  9. package/dist/canvas/index.js +4 -0
  10. package/dist/canvas/index.js.map +1 -1
  11. package/dist/canvas/layout.d.ts +12 -2
  12. package/dist/canvas/layout.js +593 -63
  13. package/dist/canvas/layout.js.map +1 -1
  14. package/dist/canvas/paint.d.ts +2 -0
  15. package/dist/canvas/paint.js +88 -11
  16. package/dist/canvas/paint.js.map +1 -1
  17. package/dist/canvas/renderer.d.ts +16 -0
  18. package/dist/canvas/renderer.js +154 -15
  19. package/dist/canvas/renderer.js.map +1 -1
  20. package/dist/canvas/scroll.d.ts +82 -0
  21. package/dist/canvas/scroll.js +639 -0
  22. package/dist/canvas/scroll.js.map +1 -0
  23. package/dist/canvas/selection.d.ts +63 -0
  24. package/dist/canvas/selection.js +466 -0
  25. package/dist/canvas/selection.js.map +1 -0
  26. package/dist/canvas/text.d.ts +6 -3
  27. package/dist/canvas/text.js +82 -43
  28. package/dist/canvas/text.js.map +1 -1
  29. package/dist/canvas/types.d.ts +18 -0
  30. package/dist/canvas/utils.d.ts +8 -2
  31. package/dist/canvas/utils.js +42 -16
  32. package/dist/canvas/utils.js.map +1 -1
  33. package/dist/canvas/virtualize.d.ts +25 -0
  34. package/dist/canvas/virtualize.js +65 -0
  35. package/dist/canvas/virtualize.js.map +1 -0
  36. package/dist/dom/applicators/index.js +0 -1
  37. package/dist/dom/applicators/index.js.map +1 -1
  38. package/dist/dom/applicators/margin.js +58 -17
  39. package/dist/dom/applicators/margin.js.map +1 -1
  40. package/dist/dom/applicators/padding.js +42 -17
  41. package/dist/dom/applicators/padding.js.map +1 -1
  42. package/dist/dom/components/icon.d.ts +9 -0
  43. package/dist/dom/components/icon.js +67 -0
  44. package/dist/dom/components/icon.js.map +1 -0
  45. package/dist/dom/components/index.js +2 -0
  46. package/dist/dom/components/index.js.map +1 -1
  47. package/dist/dom/renderer.d.ts +19 -5
  48. package/dist/dom/renderer.js +147 -17
  49. package/dist/dom/renderer.js.map +1 -1
  50. package/package.json +4 -2
  51. package/src/canvas/QUICKSTART.md +5 -5
  52. package/src/canvas/dirty.ts +116 -0
  53. package/src/canvas/events.ts +34 -3
  54. package/src/canvas/index.ts +5 -0
  55. package/src/canvas/layout.ts +653 -100
  56. package/src/canvas/paint.ts +109 -11
  57. package/src/canvas/renderer.ts +189 -16
  58. package/src/canvas/scroll.ts +729 -0
  59. package/src/canvas/selection.ts +560 -0
  60. package/src/canvas/text.ts +97 -54
  61. package/src/canvas/types.ts +26 -0
  62. package/src/canvas/utils.ts +47 -17
  63. package/src/canvas/virtualize.ts +81 -0
  64. package/src/dom/applicators/index.ts +1 -1
  65. package/src/dom/applicators/margin.ts +57 -11
  66. package/src/dom/applicators/padding.ts +45 -11
  67. package/src/dom/components/icon.ts +84 -0
  68. package/src/dom/components/index.ts +2 -0
  69. 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
+ }
@@ -5,7 +5,8 @@
5
5
  */
6
6
 
7
7
  import type { VirtualNode, Point } from "./types.js";
8
- import { isPointInRoundedRect, getAbsoluteBounds } from "./utils.js";
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 = getAbsoluteBounds(node);
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
- const actionName = node.props[`on${eventType}`] || node.props[eventType];
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;
@@ -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,