@jiujue/react-canvas-fiber 2.0.3 → 2.0.5
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/index.cjs +288 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +288 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -22,7 +22,7 @@ type CanvasProps = {
|
|
|
22
22
|
children?: ReactNode;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
type CanvasPointerEventType = 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel' | 'click';
|
|
25
|
+
type CanvasPointerEventType = 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel' | 'click' | 'pointerenter' | 'pointerleave';
|
|
26
26
|
type CanvasPointerEvent = {
|
|
27
27
|
type: CanvasPointerEventType;
|
|
28
28
|
x: number;
|
|
@@ -108,6 +108,8 @@ type ViewProps = {
|
|
|
108
108
|
onPointerCancel?: CanvasPointerEventHandler;
|
|
109
109
|
onClickCapture?: CanvasPointerEventHandler;
|
|
110
110
|
onClick?: CanvasPointerEventHandler;
|
|
111
|
+
onPointerEnter?: CanvasPointerEventHandler;
|
|
112
|
+
onPointerLeave?: CanvasPointerEventHandler;
|
|
111
113
|
};
|
|
112
114
|
type RectProps = {
|
|
113
115
|
children?: ReactNode;
|
|
@@ -127,6 +129,8 @@ type RectProps = {
|
|
|
127
129
|
onPointerCancel?: CanvasPointerEventHandler;
|
|
128
130
|
onClickCapture?: CanvasPointerEventHandler;
|
|
129
131
|
onClick?: CanvasPointerEventHandler;
|
|
132
|
+
onPointerEnter?: CanvasPointerEventHandler;
|
|
133
|
+
onPointerLeave?: CanvasPointerEventHandler;
|
|
130
134
|
};
|
|
131
135
|
type TextProps = {
|
|
132
136
|
children?: never;
|
|
@@ -145,6 +149,27 @@ type TextProps = {
|
|
|
145
149
|
onPointerCancel?: CanvasPointerEventHandler;
|
|
146
150
|
onClickCapture?: CanvasPointerEventHandler;
|
|
147
151
|
onClick?: CanvasPointerEventHandler;
|
|
152
|
+
onPointerEnter?: CanvasPointerEventHandler;
|
|
153
|
+
onPointerLeave?: CanvasPointerEventHandler;
|
|
154
|
+
};
|
|
155
|
+
type ImageProps = {
|
|
156
|
+
children?: never;
|
|
157
|
+
style?: YogaStyle;
|
|
158
|
+
src: string;
|
|
159
|
+
objectFit?: 'cover' | 'contain' | 'fill';
|
|
160
|
+
pointerEvents?: PointerEventsMode;
|
|
161
|
+
onPointerDownCapture?: CanvasPointerEventHandler;
|
|
162
|
+
onPointerDown?: CanvasPointerEventHandler;
|
|
163
|
+
onPointerMoveCapture?: CanvasPointerEventHandler;
|
|
164
|
+
onPointerMove?: CanvasPointerEventHandler;
|
|
165
|
+
onPointerUpCapture?: CanvasPointerEventHandler;
|
|
166
|
+
onPointerUp?: CanvasPointerEventHandler;
|
|
167
|
+
onPointerCancelCapture?: CanvasPointerEventHandler;
|
|
168
|
+
onPointerCancel?: CanvasPointerEventHandler;
|
|
169
|
+
onClickCapture?: CanvasPointerEventHandler;
|
|
170
|
+
onClick?: CanvasPointerEventHandler;
|
|
171
|
+
onPointerEnter?: CanvasPointerEventHandler;
|
|
172
|
+
onPointerLeave?: CanvasPointerEventHandler;
|
|
148
173
|
};
|
|
149
174
|
|
|
150
175
|
/**
|
|
@@ -162,9 +187,11 @@ declare function Canvas(props: CanvasProps): react_jsx_runtime.JSX.Element;
|
|
|
162
187
|
* - <View/> -> CanvasNode(type='View')
|
|
163
188
|
* - <Rect/> -> CanvasNode(type='Rect')
|
|
164
189
|
* - <Text/> -> CanvasNode(type='Text')
|
|
190
|
+
* - <Image/> -> CanvasNode(type='Image')
|
|
165
191
|
*/
|
|
166
192
|
declare function View(props: ViewProps): react.ReactElement<ViewProps, string | react.JSXElementConstructor<any>>;
|
|
167
193
|
declare function Rect(props: RectProps): react.ReactElement<RectProps, string | react.JSXElementConstructor<any>>;
|
|
168
194
|
declare function Text(props: TextProps): react.ReactElement<TextProps, string | react.JSXElementConstructor<any>>;
|
|
195
|
+
declare function Image(props: ImageProps): react.ReactElement<ImageProps, string | react.JSXElementConstructor<any>>;
|
|
169
196
|
|
|
170
|
-
export { Canvas, Rect, Text, View };
|
|
197
|
+
export { Canvas, Image, Rect, Text, View };
|
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ type CanvasProps = {
|
|
|
22
22
|
children?: ReactNode;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
type CanvasPointerEventType = 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel' | 'click';
|
|
25
|
+
type CanvasPointerEventType = 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel' | 'click' | 'pointerenter' | 'pointerleave';
|
|
26
26
|
type CanvasPointerEvent = {
|
|
27
27
|
type: CanvasPointerEventType;
|
|
28
28
|
x: number;
|
|
@@ -108,6 +108,8 @@ type ViewProps = {
|
|
|
108
108
|
onPointerCancel?: CanvasPointerEventHandler;
|
|
109
109
|
onClickCapture?: CanvasPointerEventHandler;
|
|
110
110
|
onClick?: CanvasPointerEventHandler;
|
|
111
|
+
onPointerEnter?: CanvasPointerEventHandler;
|
|
112
|
+
onPointerLeave?: CanvasPointerEventHandler;
|
|
111
113
|
};
|
|
112
114
|
type RectProps = {
|
|
113
115
|
children?: ReactNode;
|
|
@@ -127,6 +129,8 @@ type RectProps = {
|
|
|
127
129
|
onPointerCancel?: CanvasPointerEventHandler;
|
|
128
130
|
onClickCapture?: CanvasPointerEventHandler;
|
|
129
131
|
onClick?: CanvasPointerEventHandler;
|
|
132
|
+
onPointerEnter?: CanvasPointerEventHandler;
|
|
133
|
+
onPointerLeave?: CanvasPointerEventHandler;
|
|
130
134
|
};
|
|
131
135
|
type TextProps = {
|
|
132
136
|
children?: never;
|
|
@@ -145,6 +149,27 @@ type TextProps = {
|
|
|
145
149
|
onPointerCancel?: CanvasPointerEventHandler;
|
|
146
150
|
onClickCapture?: CanvasPointerEventHandler;
|
|
147
151
|
onClick?: CanvasPointerEventHandler;
|
|
152
|
+
onPointerEnter?: CanvasPointerEventHandler;
|
|
153
|
+
onPointerLeave?: CanvasPointerEventHandler;
|
|
154
|
+
};
|
|
155
|
+
type ImageProps = {
|
|
156
|
+
children?: never;
|
|
157
|
+
style?: YogaStyle;
|
|
158
|
+
src: string;
|
|
159
|
+
objectFit?: 'cover' | 'contain' | 'fill';
|
|
160
|
+
pointerEvents?: PointerEventsMode;
|
|
161
|
+
onPointerDownCapture?: CanvasPointerEventHandler;
|
|
162
|
+
onPointerDown?: CanvasPointerEventHandler;
|
|
163
|
+
onPointerMoveCapture?: CanvasPointerEventHandler;
|
|
164
|
+
onPointerMove?: CanvasPointerEventHandler;
|
|
165
|
+
onPointerUpCapture?: CanvasPointerEventHandler;
|
|
166
|
+
onPointerUp?: CanvasPointerEventHandler;
|
|
167
|
+
onPointerCancelCapture?: CanvasPointerEventHandler;
|
|
168
|
+
onPointerCancel?: CanvasPointerEventHandler;
|
|
169
|
+
onClickCapture?: CanvasPointerEventHandler;
|
|
170
|
+
onClick?: CanvasPointerEventHandler;
|
|
171
|
+
onPointerEnter?: CanvasPointerEventHandler;
|
|
172
|
+
onPointerLeave?: CanvasPointerEventHandler;
|
|
148
173
|
};
|
|
149
174
|
|
|
150
175
|
/**
|
|
@@ -162,9 +187,11 @@ declare function Canvas(props: CanvasProps): react_jsx_runtime.JSX.Element;
|
|
|
162
187
|
* - <View/> -> CanvasNode(type='View')
|
|
163
188
|
* - <Rect/> -> CanvasNode(type='Rect')
|
|
164
189
|
* - <Text/> -> CanvasNode(type='Text')
|
|
190
|
+
* - <Image/> -> CanvasNode(type='Image')
|
|
165
191
|
*/
|
|
166
192
|
declare function View(props: ViewProps): react.ReactElement<ViewProps, string | react.JSXElementConstructor<any>>;
|
|
167
193
|
declare function Rect(props: RectProps): react.ReactElement<RectProps, string | react.JSXElementConstructor<any>>;
|
|
168
194
|
declare function Text(props: TextProps): react.ReactElement<TextProps, string | react.JSXElementConstructor<any>>;
|
|
195
|
+
declare function Image(props: ImageProps): react.ReactElement<ImageProps, string | react.JSXElementConstructor<any>>;
|
|
169
196
|
|
|
170
|
-
export { Canvas, Rect, Text, View };
|
|
197
|
+
export { Canvas, Image, Rect, Text, View };
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ function createRootNode() {
|
|
|
15
15
|
children: [],
|
|
16
16
|
props: {},
|
|
17
17
|
layout: { x: 0, y: 0, width: 0, height: 0 },
|
|
18
|
+
contentBounds: void 0,
|
|
18
19
|
yogaNode: null,
|
|
19
20
|
scrollLeft: 0,
|
|
20
21
|
scrollTop: 0,
|
|
@@ -24,13 +25,14 @@ function createRootNode() {
|
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
27
|
function createNode(type, props) {
|
|
27
|
-
|
|
28
|
+
const node = {
|
|
28
29
|
type,
|
|
29
30
|
debugId: nextDebugId++,
|
|
30
31
|
parent: null,
|
|
31
32
|
children: [],
|
|
32
33
|
props,
|
|
33
34
|
layout: { x: 0, y: 0, width: 0, height: 0 },
|
|
35
|
+
contentBounds: void 0,
|
|
34
36
|
yogaNode: null,
|
|
35
37
|
scrollLeft: 0,
|
|
36
38
|
scrollTop: 0,
|
|
@@ -38,6 +40,10 @@ function createNode(type, props) {
|
|
|
38
40
|
scrollContentHeight: 0,
|
|
39
41
|
scrollbarDrag: null
|
|
40
42
|
};
|
|
43
|
+
if (type === "Image") {
|
|
44
|
+
node.imageInstance = null;
|
|
45
|
+
}
|
|
46
|
+
return node;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
// src/render/drawTree.ts
|
|
@@ -79,6 +85,9 @@ function drawRoundedRect(ctx, x, y, w, h, r) {
|
|
|
79
85
|
ctx.arcTo(x, y, x + w, y, radius);
|
|
80
86
|
ctx.closePath();
|
|
81
87
|
}
|
|
88
|
+
function rectsIntersect(ax, ay, aw, ah, bx, by, bw, bh) {
|
|
89
|
+
return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;
|
|
90
|
+
}
|
|
82
91
|
function drawNode(state, node, offsetX, offsetY) {
|
|
83
92
|
const { ctx } = state;
|
|
84
93
|
const x = offsetX + node.layout.x;
|
|
@@ -114,7 +123,32 @@ function drawNode(state, node, offsetX, offsetY) {
|
|
|
114
123
|
ctx.beginPath();
|
|
115
124
|
ctx.rect(x, y, w, h);
|
|
116
125
|
ctx.clip();
|
|
126
|
+
const cullPadding = 1;
|
|
127
|
+
const viewportX = x - cullPadding;
|
|
128
|
+
const viewportY = y - cullPadding;
|
|
129
|
+
const viewportW = w + cullPadding * 2;
|
|
130
|
+
const viewportH = h + cullPadding * 2;
|
|
117
131
|
for (const child of node.children) {
|
|
132
|
+
const bounds = child.contentBounds ?? {
|
|
133
|
+
x: 0,
|
|
134
|
+
y: 0,
|
|
135
|
+
width: child.layout.width,
|
|
136
|
+
height: child.layout.height
|
|
137
|
+
};
|
|
138
|
+
const bx = x - scrollLeft + child.layout.x + bounds.x;
|
|
139
|
+
const by = y - scrollTop + child.layout.y + bounds.y;
|
|
140
|
+
if (!rectsIntersect(
|
|
141
|
+
viewportX,
|
|
142
|
+
viewportY,
|
|
143
|
+
viewportW,
|
|
144
|
+
viewportH,
|
|
145
|
+
bx,
|
|
146
|
+
by,
|
|
147
|
+
bounds.width,
|
|
148
|
+
bounds.height
|
|
149
|
+
)) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
118
152
|
drawNode(state, child, x - scrollLeft, y - scrollTop);
|
|
119
153
|
}
|
|
120
154
|
ctx.restore();
|
|
@@ -196,6 +230,43 @@ function drawNode(state, node, offsetX, offsetY) {
|
|
|
196
230
|
}
|
|
197
231
|
ctx.restore();
|
|
198
232
|
}
|
|
233
|
+
if (node.type === "Image") {
|
|
234
|
+
const { imageInstance } = node;
|
|
235
|
+
if (imageInstance && imageInstance.complete && imageInstance.naturalWidth > 0) {
|
|
236
|
+
const objectFit = node.props.objectFit || "contain";
|
|
237
|
+
const srcW = imageInstance.naturalWidth;
|
|
238
|
+
const srcH = imageInstance.naturalHeight;
|
|
239
|
+
let dstX = x;
|
|
240
|
+
let dstY = y;
|
|
241
|
+
let dstW = w;
|
|
242
|
+
let dstH = h;
|
|
243
|
+
let srcX = 0;
|
|
244
|
+
let srcY = 0;
|
|
245
|
+
let finalSrcW = srcW;
|
|
246
|
+
let finalSrcH = srcH;
|
|
247
|
+
if (objectFit === "fill") ; else if (objectFit === "contain") {
|
|
248
|
+
const ratio = Math.min(w / srcW, h / srcH);
|
|
249
|
+
dstW = srcW * ratio;
|
|
250
|
+
dstH = srcH * ratio;
|
|
251
|
+
dstX = x + (w - dstW) / 2;
|
|
252
|
+
dstY = y + (h - dstH) / 2;
|
|
253
|
+
} else if (objectFit === "cover") {
|
|
254
|
+
const ratio = Math.max(w / srcW, h / srcH);
|
|
255
|
+
const renderW = srcW * ratio;
|
|
256
|
+
const renderH = srcH * ratio;
|
|
257
|
+
srcX = (renderW - w) / 2 / ratio;
|
|
258
|
+
srcY = (renderH - h) / 2 / ratio;
|
|
259
|
+
finalSrcW = w / ratio;
|
|
260
|
+
finalSrcH = h / ratio;
|
|
261
|
+
}
|
|
262
|
+
ctx.save();
|
|
263
|
+
ctx.beginPath();
|
|
264
|
+
drawRoundedRect(ctx, x, y, w, h, 0);
|
|
265
|
+
ctx.clip();
|
|
266
|
+
ctx.drawImage(imageInstance, srcX, srcY, finalSrcW, finalSrcH, dstX, dstY, dstW, dstH);
|
|
267
|
+
ctx.restore();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
199
270
|
for (const child of node.children) {
|
|
200
271
|
drawNode(state, child, x, y);
|
|
201
272
|
}
|
|
@@ -451,6 +522,31 @@ async function layoutTree(root, width, height, measureText, defaults) {
|
|
|
451
522
|
}
|
|
452
523
|
};
|
|
453
524
|
walk(root);
|
|
525
|
+
const computeSubtreeContentBounds = (node) => {
|
|
526
|
+
let minX = 0;
|
|
527
|
+
let minY = 0;
|
|
528
|
+
let maxX = node.layout.width;
|
|
529
|
+
let maxY = node.layout.height;
|
|
530
|
+
for (const child of node.children) {
|
|
531
|
+
if (child.children.length) computeSubtreeContentBounds(child);
|
|
532
|
+
const childBounds = child.contentBounds ?? {
|
|
533
|
+
x: 0,
|
|
534
|
+
y: 0,
|
|
535
|
+
width: child.layout.width,
|
|
536
|
+
height: child.layout.height
|
|
537
|
+
};
|
|
538
|
+
const bx = child.layout.x + childBounds.x;
|
|
539
|
+
const by = child.layout.y + childBounds.y;
|
|
540
|
+
const br = bx + childBounds.width;
|
|
541
|
+
const bb = by + childBounds.height;
|
|
542
|
+
minX = Math.min(minX, bx);
|
|
543
|
+
minY = Math.min(minY, by);
|
|
544
|
+
maxX = Math.max(maxX, br);
|
|
545
|
+
maxY = Math.max(maxY, bb);
|
|
546
|
+
}
|
|
547
|
+
node.contentBounds = { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
548
|
+
};
|
|
549
|
+
for (const child of root.children) computeSubtreeContentBounds(child);
|
|
454
550
|
}
|
|
455
551
|
function freeYogaSubtree(node) {
|
|
456
552
|
if (node.yogaNode) {
|
|
@@ -482,8 +578,22 @@ var hostConfig = {
|
|
|
482
578
|
shouldSetTextContent() {
|
|
483
579
|
return false;
|
|
484
580
|
},
|
|
485
|
-
createInstance(type, props) {
|
|
486
|
-
|
|
581
|
+
createInstance(type, props, rootContainer) {
|
|
582
|
+
const node = createNode(type, props);
|
|
583
|
+
if (type === "Image" && props.src) {
|
|
584
|
+
const imgNode = node;
|
|
585
|
+
const img = new Image();
|
|
586
|
+
img.crossOrigin = "anonymous";
|
|
587
|
+
img.src = props.src;
|
|
588
|
+
if (img.dataset) {
|
|
589
|
+
img.dataset.src = props.src;
|
|
590
|
+
}
|
|
591
|
+
imgNode.imageInstance = img;
|
|
592
|
+
if (!img.complete) {
|
|
593
|
+
img.onload = () => rootContainer.invalidate();
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return node;
|
|
487
597
|
},
|
|
488
598
|
createTextInstance() {
|
|
489
599
|
throw new Error('Text instances are not supported. Use <Text text="..."/>.');
|
|
@@ -496,7 +606,7 @@ var hostConfig = {
|
|
|
496
606
|
parent.children.push(child);
|
|
497
607
|
},
|
|
498
608
|
appendChildToContainer(container, child) {
|
|
499
|
-
child.parent =
|
|
609
|
+
child.parent = container.root;
|
|
500
610
|
container.root.children.push(child);
|
|
501
611
|
container.invalidate();
|
|
502
612
|
},
|
|
@@ -507,7 +617,7 @@ var hostConfig = {
|
|
|
507
617
|
else parent.children.push(child);
|
|
508
618
|
},
|
|
509
619
|
insertInContainerBefore(container, child, beforeChild) {
|
|
510
|
-
child.parent =
|
|
620
|
+
child.parent = container.root;
|
|
511
621
|
const idx = container.root.children.indexOf(beforeChild);
|
|
512
622
|
if (idx >= 0) container.root.children.splice(idx, 0, child);
|
|
513
623
|
else container.root.children.push(child);
|
|
@@ -534,6 +644,39 @@ var hostConfig = {
|
|
|
534
644
|
},
|
|
535
645
|
commitUpdate(instance, updatePayload) {
|
|
536
646
|
instance.props = updatePayload;
|
|
647
|
+
if (instance.type === "Image") {
|
|
648
|
+
const imgNode = instance;
|
|
649
|
+
const newSrc = instance.props.src;
|
|
650
|
+
const currentSrc = imgNode.imageInstance?.dataset?.src;
|
|
651
|
+
if (newSrc !== currentSrc) {
|
|
652
|
+
if (!newSrc) {
|
|
653
|
+
imgNode.imageInstance = null;
|
|
654
|
+
} else {
|
|
655
|
+
const img = new Image();
|
|
656
|
+
img.crossOrigin = "anonymous";
|
|
657
|
+
img.src = newSrc;
|
|
658
|
+
if (img.dataset) {
|
|
659
|
+
img.dataset.src = newSrc;
|
|
660
|
+
}
|
|
661
|
+
imgNode.imageInstance = img;
|
|
662
|
+
const invalidate = () => {
|
|
663
|
+
let p = imgNode;
|
|
664
|
+
while (p) {
|
|
665
|
+
if (p.type === "Root") {
|
|
666
|
+
p.container?.invalidate();
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
p = p.parent;
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
if (!img.complete) {
|
|
673
|
+
img.onload = invalidate;
|
|
674
|
+
} else {
|
|
675
|
+
invalidate();
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
537
680
|
},
|
|
538
681
|
commitTextUpdate() {
|
|
539
682
|
},
|
|
@@ -600,6 +743,7 @@ function createCanvasRoot(canvas, options) {
|
|
|
600
743
|
};
|
|
601
744
|
let hoverId = null;
|
|
602
745
|
let selectedId = null;
|
|
746
|
+
let lastHoveredNode = null;
|
|
603
747
|
const toCanvasPoint = (clientX, clientY) => {
|
|
604
748
|
const rect = canvas.getBoundingClientRect();
|
|
605
749
|
const scaleX = rect.width ? options.width / rect.width : 1;
|
|
@@ -628,7 +772,8 @@ function createCanvasRoot(canvas, options) {
|
|
|
628
772
|
let current = node;
|
|
629
773
|
while (current) {
|
|
630
774
|
path.push(current);
|
|
631
|
-
|
|
775
|
+
const nextParent = current.parent;
|
|
776
|
+
current = nextParent && nextParent.type !== "Root" ? nextParent : null;
|
|
632
777
|
}
|
|
633
778
|
let absLeft = 0;
|
|
634
779
|
let absTop = 0;
|
|
@@ -644,14 +789,15 @@ function createCanvasRoot(canvas, options) {
|
|
|
644
789
|
};
|
|
645
790
|
const getScrollClipRects = (node) => {
|
|
646
791
|
const rects = [];
|
|
647
|
-
let current = node.parent;
|
|
792
|
+
let current = node.parent && node.parent.type !== "Root" ? node.parent : null;
|
|
648
793
|
while (current) {
|
|
649
794
|
if (current.type === "View") {
|
|
650
795
|
const scrollX = !!current.props?.scrollX;
|
|
651
796
|
const scrollY = !!current.props?.scrollY;
|
|
652
797
|
if (scrollX || scrollY) rects.push(getAbsoluteRect(current));
|
|
653
798
|
}
|
|
654
|
-
|
|
799
|
+
const nextParent = current.parent;
|
|
800
|
+
current = nextParent && nextParent.type !== "Root" ? nextParent : null;
|
|
655
801
|
}
|
|
656
802
|
return rects.reverse();
|
|
657
803
|
};
|
|
@@ -681,7 +827,8 @@ function createCanvasRoot(canvas, options) {
|
|
|
681
827
|
return out;
|
|
682
828
|
};
|
|
683
829
|
let frameId = null;
|
|
684
|
-
let
|
|
830
|
+
let dirtyLayout = true;
|
|
831
|
+
let dirtyDraw = true;
|
|
685
832
|
const measureText = (text, font, maxWidth) => {
|
|
686
833
|
ctx.save();
|
|
687
834
|
ctx.font = font;
|
|
@@ -694,14 +841,77 @@ function createCanvasRoot(canvas, options) {
|
|
|
694
841
|
return { width, height: Math.max(1, height) };
|
|
695
842
|
};
|
|
696
843
|
const invalidate = () => {
|
|
697
|
-
|
|
844
|
+
dirtyLayout = true;
|
|
845
|
+
dirtyDraw = true;
|
|
698
846
|
if (frameId != null) return;
|
|
699
847
|
frameId = requestAnimationFrame(async () => {
|
|
700
848
|
frameId = null;
|
|
701
|
-
if (!
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
849
|
+
if (!dirtyLayout && !dirtyDraw) return;
|
|
850
|
+
const needsLayout = dirtyLayout;
|
|
851
|
+
const needsDraw = dirtyDraw || dirtyLayout;
|
|
852
|
+
dirtyLayout = false;
|
|
853
|
+
dirtyDraw = false;
|
|
854
|
+
if (needsLayout) {
|
|
855
|
+
await layoutTree(rootNode, options.width, options.height, measureText, options);
|
|
856
|
+
}
|
|
857
|
+
if (needsDraw) {
|
|
858
|
+
drawTree(rootNode, ctx, options.dpr, options.clearColor, options);
|
|
859
|
+
}
|
|
860
|
+
const overlayHover = typeof hoverId === "number" ? findNodeById(hoverId) : null;
|
|
861
|
+
const overlaySelected = typeof selectedId === "number" ? findNodeById(selectedId) : null;
|
|
862
|
+
if (overlayHover || overlaySelected) {
|
|
863
|
+
ctx.save();
|
|
864
|
+
ctx.setTransform(options.dpr, 0, 0, options.dpr, 0, 0);
|
|
865
|
+
if (overlayHover && (!overlaySelected || overlayHover.debugId !== overlaySelected.debugId)) {
|
|
866
|
+
const r = getAbsoluteRect(overlayHover);
|
|
867
|
+
ctx.save();
|
|
868
|
+
for (const clip of getScrollClipRects(overlayHover)) {
|
|
869
|
+
ctx.beginPath();
|
|
870
|
+
ctx.rect(clip.x, clip.y, clip.width, clip.height);
|
|
871
|
+
ctx.clip();
|
|
872
|
+
}
|
|
873
|
+
ctx.fillStyle = "rgba(59,130,246,0.12)";
|
|
874
|
+
ctx.strokeStyle = "rgba(59,130,246,0.9)";
|
|
875
|
+
ctx.lineWidth = 1;
|
|
876
|
+
ctx.fillRect(r.x, r.y, r.width, r.height);
|
|
877
|
+
ctx.strokeRect(r.x + 0.5, r.y + 0.5, Math.max(0, r.width - 1), Math.max(0, r.height - 1));
|
|
878
|
+
ctx.restore();
|
|
879
|
+
}
|
|
880
|
+
if (overlaySelected) {
|
|
881
|
+
const r = getAbsoluteRect(overlaySelected);
|
|
882
|
+
ctx.save();
|
|
883
|
+
for (const clip of getScrollClipRects(overlaySelected)) {
|
|
884
|
+
ctx.beginPath();
|
|
885
|
+
ctx.rect(clip.x, clip.y, clip.width, clip.height);
|
|
886
|
+
ctx.clip();
|
|
887
|
+
}
|
|
888
|
+
ctx.fillStyle = "rgba(16,185,129,0.12)";
|
|
889
|
+
ctx.strokeStyle = "rgba(16,185,129,0.95)";
|
|
890
|
+
ctx.lineWidth = 2;
|
|
891
|
+
ctx.fillRect(r.x, r.y, r.width, r.height);
|
|
892
|
+
ctx.strokeRect(r.x + 1, r.y + 1, Math.max(0, r.width - 2), Math.max(0, r.height - 2));
|
|
893
|
+
ctx.restore();
|
|
894
|
+
}
|
|
895
|
+
ctx.restore();
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
};
|
|
899
|
+
const invalidateDrawOnly = () => {
|
|
900
|
+
dirtyDraw = true;
|
|
901
|
+
if (frameId != null) return;
|
|
902
|
+
frameId = requestAnimationFrame(async () => {
|
|
903
|
+
frameId = null;
|
|
904
|
+
if (!dirtyLayout && !dirtyDraw) return;
|
|
905
|
+
const needsLayout = dirtyLayout;
|
|
906
|
+
const needsDraw = dirtyDraw || dirtyLayout;
|
|
907
|
+
dirtyLayout = false;
|
|
908
|
+
dirtyDraw = false;
|
|
909
|
+
if (needsLayout) {
|
|
910
|
+
await layoutTree(rootNode, options.width, options.height, measureText, options);
|
|
911
|
+
}
|
|
912
|
+
if (needsDraw) {
|
|
913
|
+
drawTree(rootNode, ctx, options.dpr, options.clearColor, options);
|
|
914
|
+
}
|
|
705
915
|
const overlayHover = typeof hoverId === "number" ? findNodeById(hoverId) : null;
|
|
706
916
|
const overlaySelected = typeof selectedId === "number" ? findNodeById(selectedId) : null;
|
|
707
917
|
if (overlayHover || overlaySelected) {
|
|
@@ -868,7 +1078,8 @@ function createCanvasRoot(canvas, options) {
|
|
|
868
1078
|
let current = target;
|
|
869
1079
|
while (current) {
|
|
870
1080
|
path.push(current);
|
|
871
|
-
|
|
1081
|
+
const nextParent = current.parent;
|
|
1082
|
+
current = nextParent && nextParent.type !== "Root" ? nextParent : null;
|
|
872
1083
|
}
|
|
873
1084
|
return path;
|
|
874
1085
|
};
|
|
@@ -970,7 +1181,7 @@ function createCanvasRoot(canvas, options) {
|
|
|
970
1181
|
capturedForScroll.scrollTop = clamped2;
|
|
971
1182
|
const onScroll = capturedForScroll.props?.onScroll;
|
|
972
1183
|
if (typeof onScroll === "function") onScroll(clamped2);
|
|
973
|
-
|
|
1184
|
+
invalidateDrawOnly();
|
|
974
1185
|
return { defaultPrevented: true };
|
|
975
1186
|
}
|
|
976
1187
|
const metrics = getScrollbarMetricsX(capturedForScroll, absLeft, absTop);
|
|
@@ -983,14 +1194,14 @@ function createCanvasRoot(canvas, options) {
|
|
|
983
1194
|
capturedForScroll.scrollLeft = clamped;
|
|
984
1195
|
const onScrollX = capturedForScroll.props?.onScrollX;
|
|
985
1196
|
if (typeof onScrollX === "function") onScrollX(clamped);
|
|
986
|
-
|
|
1197
|
+
invalidateDrawOnly();
|
|
987
1198
|
return { defaultPrevented: true };
|
|
988
1199
|
}
|
|
989
1200
|
if (eventType === "pointerup" || eventType === "pointercancel") {
|
|
990
1201
|
capturedForScroll.scrollbarDrag = null;
|
|
991
1202
|
pointerCapture.delete(pointerId);
|
|
992
1203
|
pointerDownTarget.delete(pointerId);
|
|
993
|
-
|
|
1204
|
+
invalidateDrawOnly();
|
|
994
1205
|
return { defaultPrevented: true };
|
|
995
1206
|
}
|
|
996
1207
|
}
|
|
@@ -1015,6 +1226,58 @@ function createCanvasRoot(canvas, options) {
|
|
|
1015
1226
|
if (eventType === "pointermove") {
|
|
1016
1227
|
const captured = pointerCapture.get(pointerId);
|
|
1017
1228
|
const target2 = captured ?? hitTest(init.x, init.y);
|
|
1229
|
+
if (target2 !== lastHoveredNode) {
|
|
1230
|
+
const prevChain = [];
|
|
1231
|
+
let p = lastHoveredNode;
|
|
1232
|
+
while (p) {
|
|
1233
|
+
prevChain.push(p);
|
|
1234
|
+
const nextParent = p.parent;
|
|
1235
|
+
p = nextParent && nextParent.type !== "Root" ? nextParent : null;
|
|
1236
|
+
}
|
|
1237
|
+
const nextChain = [];
|
|
1238
|
+
let n = target2;
|
|
1239
|
+
while (n) {
|
|
1240
|
+
nextChain.push(n);
|
|
1241
|
+
const nextParent = n.parent;
|
|
1242
|
+
n = nextParent && nextParent.type !== "Root" ? nextParent : null;
|
|
1243
|
+
}
|
|
1244
|
+
for (const node of prevChain) {
|
|
1245
|
+
if (!nextChain.includes(node)) {
|
|
1246
|
+
const handler = node.props?.onPointerLeave;
|
|
1247
|
+
if (typeof handler === "function") {
|
|
1248
|
+
handler({
|
|
1249
|
+
type: "pointerleave",
|
|
1250
|
+
...init,
|
|
1251
|
+
target: node,
|
|
1252
|
+
currentTarget: node,
|
|
1253
|
+
defaultPrevented: false,
|
|
1254
|
+
stopPropagation: () => {
|
|
1255
|
+
},
|
|
1256
|
+
preventDefault: () => {
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
const enteringNodes = nextChain.filter((node) => !prevChain.includes(node)).reverse();
|
|
1263
|
+
for (const node of enteringNodes) {
|
|
1264
|
+
const handler = node.props?.onPointerEnter;
|
|
1265
|
+
if (typeof handler === "function") {
|
|
1266
|
+
handler({
|
|
1267
|
+
type: "pointerenter",
|
|
1268
|
+
...init,
|
|
1269
|
+
target: node,
|
|
1270
|
+
currentTarget: node,
|
|
1271
|
+
defaultPrevented: false,
|
|
1272
|
+
stopPropagation: () => {
|
|
1273
|
+
},
|
|
1274
|
+
preventDefault: () => {
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
lastHoveredNode = target2;
|
|
1280
|
+
}
|
|
1018
1281
|
if (!target2) return { defaultPrevented: false };
|
|
1019
1282
|
return dispatchOnPath(eventType, buildPath(target2), init, target2);
|
|
1020
1283
|
}
|
|
@@ -1070,10 +1333,11 @@ function createCanvasRoot(canvas, options) {
|
|
|
1070
1333
|
}
|
|
1071
1334
|
if (remainingX === 0 && remainingY === 0) break;
|
|
1072
1335
|
}
|
|
1073
|
-
if (defaultPrevented)
|
|
1336
|
+
if (defaultPrevented) invalidateDrawOnly();
|
|
1074
1337
|
return { defaultPrevented };
|
|
1075
1338
|
};
|
|
1076
1339
|
const container = { root: rootNode, invalidate, notifyCommit };
|
|
1340
|
+
rootNode.container = container;
|
|
1077
1341
|
const reconcilerRoot = createReconcilerRoot(container);
|
|
1078
1342
|
invalidate();
|
|
1079
1343
|
const __devtools = {
|
|
@@ -1125,7 +1389,7 @@ function createCanvasRoot(canvas, options) {
|
|
|
1125
1389
|
setHighlight(next) {
|
|
1126
1390
|
if ("hoverId" in next) hoverId = next.hoverId ?? null;
|
|
1127
1391
|
if ("selectedId" in next) selectedId = next.selectedId ?? null;
|
|
1128
|
-
|
|
1392
|
+
invalidateDrawOnly();
|
|
1129
1393
|
},
|
|
1130
1394
|
subscribe(cb) {
|
|
1131
1395
|
commitSubscribers.add(cb);
|
|
@@ -1431,7 +1695,10 @@ function Rect(props) {
|
|
|
1431
1695
|
function Text(props) {
|
|
1432
1696
|
return createElement("Text", props);
|
|
1433
1697
|
}
|
|
1698
|
+
function Image2(props) {
|
|
1699
|
+
return createElement("Image", props);
|
|
1700
|
+
}
|
|
1434
1701
|
|
|
1435
|
-
export { Canvas, Rect, Text, View };
|
|
1702
|
+
export { Canvas, Image2 as Image, Rect, Text, View };
|
|
1436
1703
|
//# sourceMappingURL=index.js.map
|
|
1437
1704
|
//# sourceMappingURL=index.js.map
|