@jiujue/react-canvas-fiber 2.1.0 → 2.1.2

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 ADDED
@@ -0,0 +1,1913 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var load = require('yoga-layout/load');
5
+ var Reconciler = require('react-reconciler');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var Reconciler__default = /*#__PURE__*/_interopDefault(Reconciler);
11
+
12
+ // src/components/Canvas.tsx
13
+
14
+ // src/runtime/nodes.ts
15
+ var nextDebugId = 1;
16
+ function createRootNode() {
17
+ return {
18
+ type: "Root",
19
+ debugId: nextDebugId++,
20
+ parent: null,
21
+ children: [],
22
+ props: {},
23
+ layout: { x: 0, y: 0, width: 0, height: 0 },
24
+ contentBounds: void 0,
25
+ yogaNode: null,
26
+ scrollLeft: 0,
27
+ scrollTop: 0,
28
+ scrollContentWidth: 0,
29
+ scrollContentHeight: 0,
30
+ scrollbarDrag: null
31
+ };
32
+ }
33
+ function createNode(type, props) {
34
+ const node = {
35
+ type,
36
+ debugId: nextDebugId++,
37
+ parent: null,
38
+ children: [],
39
+ props,
40
+ layout: { x: 0, y: 0, width: 0, height: 0 },
41
+ contentBounds: void 0,
42
+ yogaNode: null,
43
+ scrollLeft: 0,
44
+ scrollTop: 0,
45
+ scrollContentWidth: 0,
46
+ scrollContentHeight: 0,
47
+ scrollbarDrag: null
48
+ };
49
+ if (type === "Image") {
50
+ node.imageInstance = null;
51
+ } else if (type === "View") {
52
+ node.backgroundImageInstance = null;
53
+ }
54
+ return node;
55
+ }
56
+
57
+ // src/render/drawTree.ts
58
+ function resolveInheritedTextStyle(node, defaults) {
59
+ const own = node.props?.style ?? {};
60
+ let fontSize = own.fontSize;
61
+ let fontFamily = own.fontFamily;
62
+ let fontWeight = own.fontWeight;
63
+ let lineHeight = own.lineHeight;
64
+ let current = node.parent;
65
+ while (current && (fontSize == null || fontFamily == null || fontWeight == null || lineHeight == null)) {
66
+ const s = current.props?.style ?? {};
67
+ if (fontSize == null && typeof s.fontSize === "number") fontSize = s.fontSize;
68
+ if (fontFamily == null && typeof s.fontFamily === "string") fontFamily = s.fontFamily;
69
+ if (fontWeight == null && (typeof s.fontWeight === "number" || typeof s.fontWeight === "string"))
70
+ fontWeight = s.fontWeight;
71
+ if (lineHeight == null && typeof s.lineHeight === "number") lineHeight = s.lineHeight;
72
+ current = current.parent;
73
+ }
74
+ const resolvedFontSize = fontSize ?? defaults?.fontSize ?? 16;
75
+ const resolvedFontFamily = fontFamily ?? defaults?.fontFamily ?? "system-ui";
76
+ const resolvedFontWeight = fontWeight ?? defaults?.fontWeight ?? 400;
77
+ const resolvedLineHeight = lineHeight ?? defaults?.lineHeight ?? Math.round(resolvedFontSize * 1.2);
78
+ return {
79
+ fontSize: resolvedFontSize,
80
+ fontFamily: resolvedFontFamily,
81
+ fontWeight: resolvedFontWeight,
82
+ lineHeight: resolvedLineHeight,
83
+ font: `${resolvedFontWeight} ${resolvedFontSize}px ${resolvedFontFamily}`
84
+ };
85
+ }
86
+ function drawRoundedRect(ctx, x, y, w, h, r) {
87
+ const radius = Math.max(0, Math.min(r, Math.min(w, h) / 2));
88
+ ctx.beginPath();
89
+ ctx.moveTo(x + radius, y);
90
+ ctx.arcTo(x + w, y, x + w, y + h, radius);
91
+ ctx.arcTo(x + w, y + h, x, y + h, radius);
92
+ ctx.arcTo(x, y + h, x, y, radius);
93
+ ctx.arcTo(x, y, x + w, y, radius);
94
+ ctx.closePath();
95
+ }
96
+ function rectsIntersect(ax, ay, aw, ah, bx, by, bw, bh) {
97
+ return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;
98
+ }
99
+ function resolveBorder(value) {
100
+ if (typeof value !== "string") return null;
101
+ const raw = value.trim();
102
+ if (!raw) return null;
103
+ const normalized = raw.replace(/\s+/g, " ");
104
+ const match = normalized.match(/^([0-9]*\.?[0-9]+)(px)?\s+(?:solid\s+)?(.+)$/i);
105
+ if (match) {
106
+ const width = Number(match[1]);
107
+ const color = match[3]?.trim();
108
+ if (Number.isFinite(width) && width > 0 && color) return { width, color };
109
+ return null;
110
+ }
111
+ const withoutSolid = normalized.replace(/\bsolid\b/gi, " ").replace(/\s+/g, " ").trim();
112
+ const match2 = withoutSolid.match(/^([0-9]*\.?[0-9]+)(px)?\s+(.+)$/i);
113
+ if (match2) {
114
+ const width = Number(match2[1]);
115
+ const color = match2[3]?.trim();
116
+ if (Number.isFinite(width) && width > 0 && color) return { width, color };
117
+ return null;
118
+ }
119
+ if (/^[0-9]/.test(withoutSolid)) return null;
120
+ if (!withoutSolid) return null;
121
+ return { width: 1, color: withoutSolid };
122
+ }
123
+ function drawBorder(ctx, x, y, w, h, radius, border) {
124
+ if (!Number.isFinite(border.width) || border.width <= 0) return;
125
+ const inset = border.width / 2;
126
+ const bw = w - border.width;
127
+ const bh = h - border.width;
128
+ if (bw <= 0 || bh <= 0) return;
129
+ ctx.save();
130
+ ctx.strokeStyle = border.color;
131
+ ctx.lineWidth = border.width;
132
+ drawRoundedRect(ctx, x + inset, y + inset, bw, bh, Math.max(0, radius - inset));
133
+ ctx.stroke();
134
+ ctx.restore();
135
+ }
136
+ function resolveBgRepeat(repeat) {
137
+ if (!repeat) return "repeat";
138
+ if (repeat === "no-repeat" || repeat === "repeat-x" || repeat === "repeat-y" || repeat === "repeat")
139
+ return repeat;
140
+ return "repeat";
141
+ }
142
+ function parseBgSize(size, w, h, imgW, imgH) {
143
+ if (!size || size === "auto") return { width: imgW, height: imgH };
144
+ if (size === "cover") {
145
+ const scale = Math.max(w / imgW, h / imgH);
146
+ return { width: imgW * scale, height: imgH * scale };
147
+ }
148
+ if (size === "contain") {
149
+ const scale = Math.min(w / imgW, h / imgH);
150
+ return { width: imgW * scale, height: imgH * scale };
151
+ }
152
+ const parts = size.trim().split(/\s+/);
153
+ let rw = imgW;
154
+ let rh = imgH;
155
+ if (parts[0]) {
156
+ if (parts[0].endsWith("%")) {
157
+ rw = w * parseFloat(parts[0]) / 100;
158
+ } else if (parts[0] !== "auto") {
159
+ rw = parseFloat(parts[0]);
160
+ }
161
+ }
162
+ if (parts[1]) {
163
+ if (parts[1].endsWith("%")) {
164
+ rh = h * parseFloat(parts[1]) / 100;
165
+ } else if (parts[1] !== "auto") {
166
+ rh = parseFloat(parts[1]);
167
+ }
168
+ } else {
169
+ if (parts[0] !== "auto") {
170
+ rh = imgH * (rw / imgW);
171
+ }
172
+ }
173
+ return { width: rw, height: rh };
174
+ }
175
+ function parseBgPos(pos, w, h, targetW, targetH) {
176
+ const parts = (pos || "").trim().split(/\s+/);
177
+ if (parts.length === 0 || parts.length === 1 && !parts[0]) {
178
+ return { x: 0, y: 0 };
179
+ }
180
+ const xStr = parts[0];
181
+ let yStr = parts[1];
182
+ if (parts.length === 1) {
183
+ yStr = "center";
184
+ }
185
+ function resolve(val, containerDim, imgDim) {
186
+ if (val === "left" || val === "top") return 0;
187
+ if (val === "right" || val === "bottom") return containerDim - imgDim;
188
+ if (val === "center") return (containerDim - imgDim) / 2;
189
+ if (val.endsWith("%")) {
190
+ return (containerDim - imgDim) * parseFloat(val) / 100;
191
+ }
192
+ if (val.endsWith("px")) {
193
+ return parseFloat(val);
194
+ }
195
+ return parseFloat(val) || 0;
196
+ }
197
+ return {
198
+ x: resolve(xStr, w, targetW),
199
+ y: resolve(yStr, h, targetH)
200
+ };
201
+ }
202
+ function drawBackgroundImage(ctx, node, x, y, w, h, radius) {
203
+ const img = node.backgroundImageInstance;
204
+ if (!img || !img.complete || img.naturalWidth === 0 || img.naturalHeight === 0) return;
205
+ const props = node.props;
206
+ const bgSize = props.backgroundSize;
207
+ const bgPos = props.backgroundPosition;
208
+ const bgRepeat = resolveBgRepeat(props.backgroundRepeat);
209
+ const imgW = img.naturalWidth;
210
+ const imgH = img.naturalHeight;
211
+ const { width: targetW, height: targetH } = parseBgSize(bgSize, w, h, imgW, imgH);
212
+ const { x: posX, y: posY } = parseBgPos(bgPos, w, h, targetW, targetH);
213
+ ctx.save();
214
+ drawRoundedRect(ctx, x, y, w, h, radius);
215
+ ctx.clip();
216
+ if (bgRepeat === "no-repeat") {
217
+ ctx.drawImage(img, x + posX, y + posY, targetW, targetH);
218
+ } else {
219
+ const pattern = ctx.createPattern(img, bgRepeat);
220
+ if (pattern) {
221
+ const matrix = new DOMMatrix();
222
+ const scaleX = targetW / imgW;
223
+ const scaleY = targetH / imgH;
224
+ matrix.translateSelf(x + posX, y + posY);
225
+ matrix.scaleSelf(scaleX, scaleY);
226
+ pattern.setTransform(matrix);
227
+ ctx.fillStyle = pattern;
228
+ ctx.beginPath();
229
+ ctx.rect(x, y, w, h);
230
+ ctx.fill();
231
+ }
232
+ }
233
+ ctx.restore();
234
+ }
235
+ function drawNode(state, node, offsetX, offsetY) {
236
+ const { ctx } = state;
237
+ const x = offsetX + node.layout.x;
238
+ const y = offsetY + node.layout.y;
239
+ const w = node.layout.width;
240
+ const h = node.layout.height;
241
+ let viewBorder = null;
242
+ let viewRadius = 0;
243
+ let viewIsScroll = false;
244
+ if (node.type === "View") {
245
+ const background = node.props.background;
246
+ viewBorder = resolveBorder(node.props.border);
247
+ viewRadius = node.props.borderRadius ?? 0;
248
+ const scrollX = !!node.props?.scrollX;
249
+ const scrollY = !!node.props?.scrollY;
250
+ const scrollLeft = scrollX ? node.scrollLeft ?? 0 : 0;
251
+ const scrollTop = scrollY ? node.scrollTop ?? 0 : 0;
252
+ const scrollbarX = scrollX ? node.props?.scrollbarX !== false : false;
253
+ const scrollbarY = scrollY ? node.props?.scrollbarY !== false : false;
254
+ const scrollbarWidth = node.props?.scrollbarWidth ?? 10;
255
+ const scrollbarInset = node.props?.scrollbarInset ?? 6;
256
+ const scrollbarTrackColor = node.props?.scrollbarTrackColor ?? "rgba(255,255,255,0.12)";
257
+ const scrollbarThumbColor = node.props?.scrollbarThumbColor ?? "rgba(255,255,255,0.35)";
258
+ const contentWidth = node.scrollContentWidth ?? 0;
259
+ const contentHeight = node.scrollContentHeight ?? 0;
260
+ const maxScrollLeft = Math.max(0, contentWidth - w);
261
+ const maxScrollTop = Math.max(0, contentHeight - h);
262
+ if (background) {
263
+ ctx.save();
264
+ ctx.fillStyle = background;
265
+ drawRoundedRect(ctx, x, y, w, h, viewRadius);
266
+ ctx.fill();
267
+ ctx.restore();
268
+ }
269
+ const viewNode = node;
270
+ if (viewNode.backgroundImageInstance) {
271
+ drawBackgroundImage(ctx, viewNode, x, y, w, h, viewRadius);
272
+ }
273
+ if (scrollX || scrollY) {
274
+ viewIsScroll = true;
275
+ ctx.save();
276
+ ctx.beginPath();
277
+ ctx.rect(x, y, w, h);
278
+ ctx.clip();
279
+ const cullPadding = 1;
280
+ const viewportX = x - cullPadding;
281
+ const viewportY = y - cullPadding;
282
+ const viewportW = w + cullPadding * 2;
283
+ const viewportH = h + cullPadding * 2;
284
+ for (const child of node.children) {
285
+ const bounds = child.contentBounds ?? {
286
+ x: 0,
287
+ y: 0,
288
+ width: child.layout.width,
289
+ height: child.layout.height
290
+ };
291
+ const bx = x - scrollLeft + child.layout.x + bounds.x;
292
+ const by = y - scrollTop + child.layout.y + bounds.y;
293
+ if (!rectsIntersect(
294
+ viewportX,
295
+ viewportY,
296
+ viewportW,
297
+ viewportH,
298
+ bx,
299
+ by,
300
+ bounds.width,
301
+ bounds.height
302
+ )) {
303
+ continue;
304
+ }
305
+ drawNode(state, child, x - scrollLeft, y - scrollTop);
306
+ }
307
+ ctx.restore();
308
+ const corner = scrollbarInset + scrollbarWidth;
309
+ if (scrollbarY && maxScrollTop > 0) {
310
+ const trackX = x + w - scrollbarInset - scrollbarWidth;
311
+ const trackY = y + scrollbarInset;
312
+ const trackH = Math.max(
313
+ 0,
314
+ h - scrollbarInset * 2 - (scrollbarX && maxScrollLeft > 0 ? corner : 0)
315
+ );
316
+ const minThumbH = 16;
317
+ const thumbH = contentHeight > 0 ? Math.max(minThumbH, Math.min(trackH, trackH * h / contentHeight)) : trackH;
318
+ const travel = Math.max(0, trackH - thumbH);
319
+ const thumbY = travel > 0 ? trackY + scrollTop / maxScrollTop * travel : trackY;
320
+ const radius = Math.min(6, scrollbarWidth / 2);
321
+ ctx.save();
322
+ ctx.fillStyle = scrollbarTrackColor;
323
+ drawRoundedRect(ctx, trackX, trackY, scrollbarWidth, trackH, radius);
324
+ ctx.fill();
325
+ ctx.fillStyle = scrollbarThumbColor;
326
+ drawRoundedRect(ctx, trackX, thumbY, scrollbarWidth, thumbH, radius);
327
+ ctx.fill();
328
+ ctx.restore();
329
+ }
330
+ if (scrollbarX && maxScrollLeft > 0) {
331
+ const trackX = x + scrollbarInset;
332
+ const trackY = y + h - scrollbarInset - scrollbarWidth;
333
+ const trackW = Math.max(
334
+ 0,
335
+ w - scrollbarInset * 2 - (scrollbarY && maxScrollTop > 0 ? corner : 0)
336
+ );
337
+ const minThumbW = 16;
338
+ const thumbW = contentWidth > 0 ? Math.max(minThumbW, Math.min(trackW, trackW * w / contentWidth)) : trackW;
339
+ const travel = Math.max(0, trackW - thumbW);
340
+ const thumbX = travel > 0 ? trackX + scrollLeft / maxScrollLeft * travel : trackX;
341
+ const radius = Math.min(6, scrollbarWidth / 2);
342
+ ctx.save();
343
+ ctx.fillStyle = scrollbarTrackColor;
344
+ drawRoundedRect(ctx, trackX, trackY, trackW, scrollbarWidth, radius);
345
+ ctx.fill();
346
+ ctx.fillStyle = scrollbarThumbColor;
347
+ drawRoundedRect(ctx, thumbX, trackY, thumbW, scrollbarWidth, radius);
348
+ ctx.fill();
349
+ ctx.restore();
350
+ }
351
+ if (viewBorder) drawBorder(ctx, x, y, w, h, viewRadius, viewBorder);
352
+ return;
353
+ }
354
+ }
355
+ if (node.type === "Rect") {
356
+ const fill = node.props.fill ?? "#ffffff";
357
+ const stroke = node.props.stroke;
358
+ const lineWidth = node.props.lineWidth ?? 1;
359
+ const radius = node.props.borderRadius ?? 0;
360
+ ctx.save();
361
+ drawRoundedRect(ctx, x, y, w, h, radius);
362
+ if (fill) {
363
+ ctx.fillStyle = fill;
364
+ ctx.fill();
365
+ }
366
+ if (stroke) {
367
+ ctx.strokeStyle = stroke;
368
+ ctx.lineWidth = lineWidth;
369
+ ctx.stroke();
370
+ }
371
+ ctx.restore();
372
+ }
373
+ if (node.type === "Text") {
374
+ const text = node.props.text;
375
+ const color = node.props.color ?? "#ffffff";
376
+ const { font, lineHeight } = resolveInheritedTextStyle(node, state.defaults);
377
+ ctx.save();
378
+ ctx.font = font;
379
+ ctx.fillStyle = color;
380
+ ctx.textBaseline = "top";
381
+ const lines = String(text).split("\n");
382
+ for (let i = 0; i < lines.length; i += 1) {
383
+ ctx.fillText(lines[i], x, y + i * lineHeight);
384
+ }
385
+ ctx.restore();
386
+ }
387
+ if (node.type === "Image") {
388
+ const { imageInstance } = node;
389
+ if (imageInstance && imageInstance.complete && imageInstance.naturalWidth > 0) {
390
+ const objectFit = node.props.objectFit || "contain";
391
+ const radius = node.props.borderRadius ?? 0;
392
+ const srcW = imageInstance.naturalWidth;
393
+ const srcH = imageInstance.naturalHeight;
394
+ let dstX = x;
395
+ let dstY = y;
396
+ let dstW = w;
397
+ let dstH = h;
398
+ let srcX = 0;
399
+ let srcY = 0;
400
+ let finalSrcW = srcW;
401
+ let finalSrcH = srcH;
402
+ if (objectFit === "fill") ; else if (objectFit === "contain") {
403
+ const ratio = Math.min(w / srcW, h / srcH);
404
+ dstW = srcW * ratio;
405
+ dstH = srcH * ratio;
406
+ dstX = x + (w - dstW) / 2;
407
+ dstY = y + (h - dstH) / 2;
408
+ } else if (objectFit === "cover") {
409
+ const ratio = Math.max(w / srcW, h / srcH);
410
+ const renderW = srcW * ratio;
411
+ const renderH = srcH * ratio;
412
+ srcX = (renderW - w) / 2 / ratio;
413
+ srcY = (renderH - h) / 2 / ratio;
414
+ finalSrcW = w / ratio;
415
+ finalSrcH = h / ratio;
416
+ }
417
+ ctx.save();
418
+ ctx.beginPath();
419
+ drawRoundedRect(ctx, x, y, w, h, radius);
420
+ ctx.clip();
421
+ ctx.drawImage(imageInstance, srcX, srcY, finalSrcW, finalSrcH, dstX, dstY, dstW, dstH);
422
+ ctx.restore();
423
+ }
424
+ }
425
+ for (const child of node.children) {
426
+ drawNode(state, child, x, y);
427
+ }
428
+ if (node.type === "View" && !viewIsScroll && viewBorder) {
429
+ drawBorder(ctx, x, y, w, h, viewRadius, viewBorder);
430
+ }
431
+ }
432
+ function drawTree(root, ctx, dpr, clearColor, defaults) {
433
+ const w = ctx.canvas.width;
434
+ const h = ctx.canvas.height;
435
+ ctx.save();
436
+ if (clearColor) {
437
+ ctx.fillStyle = clearColor;
438
+ ctx.fillRect(0, 0, w, h);
439
+ } else {
440
+ ctx.clearRect(0, 0, w, h);
441
+ }
442
+ ctx.restore();
443
+ ctx.save();
444
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
445
+ const state = { ctx, defaults };
446
+ for (const child of root.children) {
447
+ drawNode(state, child, 0, 0);
448
+ }
449
+ ctx.restore();
450
+ }
451
+
452
+ // src/utils/style.ts
453
+ function normalizeInsets(style) {
454
+ const paddingHorizontal = style?.paddingHorizontal ?? style?.padding ?? 0;
455
+ const paddingVertical = style?.paddingVertical ?? style?.padding ?? 0;
456
+ const marginHorizontal = style?.marginHorizontal ?? style?.margin ?? 0;
457
+ const marginVertical = style?.marginVertical ?? style?.margin ?? 0;
458
+ return {
459
+ paddingTop: style?.paddingTop ?? paddingVertical,
460
+ paddingRight: style?.paddingRight ?? paddingHorizontal,
461
+ paddingBottom: style?.paddingBottom ?? paddingVertical,
462
+ paddingLeft: style?.paddingLeft ?? paddingHorizontal,
463
+ marginTop: style?.marginTop ?? marginVertical,
464
+ marginRight: style?.marginRight ?? marginHorizontal,
465
+ marginBottom: style?.marginBottom ?? marginVertical,
466
+ marginLeft: style?.marginLeft ?? marginHorizontal
467
+ };
468
+ }
469
+
470
+ // src/layout/layoutTree.ts
471
+ var layoutEnginePromise = null;
472
+ function getLayoutEngine() {
473
+ if (!layoutEnginePromise) {
474
+ layoutEnginePromise = (async () => {
475
+ const yoga = await load.loadYoga();
476
+ return { yoga };
477
+ })();
478
+ }
479
+ return layoutEnginePromise;
480
+ }
481
+ function applyYogaStyle(node, style) {
482
+ const s = style ?? {};
483
+ if (typeof s.width === "number") node.setWidth(s.width);
484
+ if (typeof s.height === "number") node.setHeight(s.height);
485
+ if (typeof s.minWidth === "number") node.setMinWidth(s.minWidth);
486
+ if (typeof s.minHeight === "number") node.setMinHeight(s.minHeight);
487
+ if (typeof s.maxWidth === "number") node.setMaxWidth(s.maxWidth);
488
+ if (typeof s.maxHeight === "number") node.setMaxHeight(s.maxHeight);
489
+ const insets = normalizeInsets(s);
490
+ node.setPadding(load.Edge.Top, insets.paddingTop);
491
+ node.setPadding(load.Edge.Right, insets.paddingRight);
492
+ node.setPadding(load.Edge.Bottom, insets.paddingBottom);
493
+ node.setPadding(load.Edge.Left, insets.paddingLeft);
494
+ node.setMargin(load.Edge.Top, insets.marginTop);
495
+ node.setMargin(load.Edge.Right, insets.marginRight);
496
+ node.setMargin(load.Edge.Bottom, insets.marginBottom);
497
+ node.setMargin(load.Edge.Left, insets.marginLeft);
498
+ if (typeof s.flexGrow === "number") node.setFlexGrow(s.flexGrow);
499
+ if (typeof s.flexShrink === "number") node.setFlexShrink(s.flexShrink);
500
+ if (typeof s.flexBasis === "number") node.setFlexBasis(s.flexBasis);
501
+ if (s.flexDirection === "row") node.setFlexDirection(load.FlexDirection.Row);
502
+ if (s.flexDirection === "column") node.setFlexDirection(load.FlexDirection.Column);
503
+ if (s.justifyContent === "flex-start") node.setJustifyContent(load.Justify.FlexStart);
504
+ if (s.justifyContent === "center") node.setJustifyContent(load.Justify.Center);
505
+ if (s.justifyContent === "flex-end") node.setJustifyContent(load.Justify.FlexEnd);
506
+ if (s.justifyContent === "space-between") node.setJustifyContent(load.Justify.SpaceBetween);
507
+ if (s.justifyContent === "space-around") node.setJustifyContent(load.Justify.SpaceAround);
508
+ if (s.alignItems === "stretch") node.setAlignItems(load.Align.Stretch);
509
+ if (s.alignItems === "flex-start") node.setAlignItems(load.Align.FlexStart);
510
+ if (s.alignItems === "center") node.setAlignItems(load.Align.Center);
511
+ if (s.alignItems === "flex-end") node.setAlignItems(load.Align.FlexEnd);
512
+ if (s.alignContent === "stretch") node.setAlignContent(load.Align.Stretch);
513
+ if (s.alignContent === "flex-start") node.setAlignContent(load.Align.FlexStart);
514
+ if (s.alignContent === "center") node.setAlignContent(load.Align.Center);
515
+ if (s.alignContent === "flex-end") node.setAlignContent(load.Align.FlexEnd);
516
+ if (s.alignContent === "space-between") node.setAlignContent(load.Align.SpaceBetween);
517
+ if (s.alignContent === "space-around") node.setAlignContent(load.Align.SpaceAround);
518
+ if (s.flexWrap === "no-wrap") node.setFlexWrap(load.Wrap.NoWrap);
519
+ if (s.flexWrap === "wrap") node.setFlexWrap(load.Wrap.Wrap);
520
+ if (s.position === "absolute") node.setPositionType(load.PositionType.Absolute);
521
+ if (s.position === "relative") node.setPositionType(load.PositionType.Relative);
522
+ if (typeof s.top === "number") node.setPosition(load.Edge.Top, s.top);
523
+ if (typeof s.right === "number") node.setPosition(load.Edge.Right, s.right);
524
+ if (typeof s.bottom === "number") node.setPosition(load.Edge.Bottom, s.bottom);
525
+ if (typeof s.left === "number") node.setPosition(load.Edge.Left, s.left);
526
+ if (typeof s.gap === "number") {
527
+ node.setGap(load.Gutter.All, s.gap);
528
+ }
529
+ }
530
+ function resolveInheritedTextStyle2(textNode, defaults) {
531
+ const own = textNode.props.style ?? {};
532
+ let fontSize = own.fontSize;
533
+ let fontFamily = own.fontFamily;
534
+ let fontWeight = own.fontWeight;
535
+ let lineHeight = own.lineHeight;
536
+ let current = textNode.parent;
537
+ while (current && (fontSize == null || fontFamily == null || fontWeight == null || lineHeight == null)) {
538
+ const s = current.props?.style ?? {};
539
+ if (fontSize == null && typeof s.fontSize === "number") fontSize = s.fontSize;
540
+ if (fontFamily == null && typeof s.fontFamily === "string") fontFamily = s.fontFamily;
541
+ if (fontWeight == null && (typeof s.fontWeight === "number" || typeof s.fontWeight === "string"))
542
+ fontWeight = s.fontWeight;
543
+ if (lineHeight == null && typeof s.lineHeight === "number") lineHeight = s.lineHeight;
544
+ current = current.parent;
545
+ }
546
+ const resolvedFontSize = fontSize ?? defaults?.fontSize ?? 16;
547
+ const resolvedFontFamily = fontFamily ?? defaults?.fontFamily ?? "system-ui";
548
+ const resolvedFontWeight = fontWeight ?? defaults?.fontWeight ?? 400;
549
+ const resolvedLineHeight = lineHeight ?? defaults?.lineHeight ?? Math.round(resolvedFontSize * 1.2);
550
+ return {
551
+ fontSize: resolvedFontSize,
552
+ fontFamily: resolvedFontFamily,
553
+ fontWeight: resolvedFontWeight,
554
+ lineHeight: resolvedLineHeight,
555
+ font: `${resolvedFontWeight} ${resolvedFontSize}px ${resolvedFontFamily}`
556
+ };
557
+ }
558
+ function ensureYogaNode(engine, node, measureText, defaults) {
559
+ if (node.yogaNode) return;
560
+ const yogaNode = engine.yoga.Node.create();
561
+ node.yogaNode = yogaNode;
562
+ if (node.type === "Text") {
563
+ const textNode = node;
564
+ yogaNode.setMeasureFunc(
565
+ (width, widthMode, _height, _heightMode) => {
566
+ const { font, fontSize, lineHeight } = resolveInheritedTextStyle2(textNode, defaults);
567
+ const maxWidth = widthMode === load.MeasureMode.Undefined ? textNode.props.maxWidth : width;
568
+ const measured = measureText?.(textNode.props.text, font, maxWidth);
569
+ const w = measured?.width ?? Math.min(
570
+ textNode.props.text.length * fontSize,
571
+ maxWidth ?? textNode.props.text.length * fontSize
572
+ );
573
+ const lines = measured?.height ? Math.max(1, Math.round(measured.height / lineHeight)) : 1;
574
+ return { width: w, height: lines * lineHeight };
575
+ }
576
+ );
577
+ }
578
+ }
579
+ function syncYogaTree(engine, rootYoga, root, measureText, defaults) {
580
+ const detachAll = (yogaNode) => {
581
+ const count = yogaNode.getChildCount();
582
+ for (let i = count - 1; i >= 0; i -= 1) {
583
+ yogaNode.removeChild(yogaNode.getChild(i));
584
+ }
585
+ };
586
+ const clearLinks = (parent) => {
587
+ if (parent.yogaNode) detachAll(parent.yogaNode);
588
+ for (const child of parent.children ?? []) clearLinks(child);
589
+ };
590
+ clearLinks(root);
591
+ detachAll(rootYoga);
592
+ const visit = (parentYoga, parent) => {
593
+ for (let i = 0; i < parent.children.length; i += 1) {
594
+ const child = parent.children[i];
595
+ ensureYogaNode(engine, child, measureText, defaults);
596
+ const style = child.props?.style;
597
+ applyYogaStyle(child.yogaNode, style);
598
+ const currentParent = child.yogaNode.getParent?.();
599
+ if (currentParent && currentParent !== parentYoga) {
600
+ currentParent.removeChild(child.yogaNode);
601
+ }
602
+ parentYoga.insertChild(child.yogaNode, parentYoga.getChildCount());
603
+ if (child.children.length) {
604
+ visit(child.yogaNode, child);
605
+ }
606
+ }
607
+ };
608
+ visit(rootYoga, root);
609
+ }
610
+ function readComputedLayout(node) {
611
+ if (!node.yogaNode) return;
612
+ const layout = node.yogaNode.getComputedLayout();
613
+ node.layout = {
614
+ x: layout.left,
615
+ y: layout.top,
616
+ width: layout.width,
617
+ height: layout.height
618
+ };
619
+ }
620
+ async function layoutTree(root, width, height, measureText, defaults) {
621
+ const engine = await getLayoutEngine();
622
+ if (!root.yogaNode) {
623
+ root.yogaNode = engine.yoga.Node.create();
624
+ root.yogaNode.setWidth(width);
625
+ root.yogaNode.setHeight(height);
626
+ } else {
627
+ root.yogaNode.setWidth(width);
628
+ root.yogaNode.setHeight(height);
629
+ }
630
+ root.yogaNode.setFlexDirection(load.FlexDirection.Column);
631
+ root.yogaNode.setAlignItems(load.Align.Stretch);
632
+ syncYogaTree(engine, root.yogaNode, root, measureText, defaults);
633
+ root.yogaNode.calculateLayout(width, height, load.Direction.LTR);
634
+ const computeContentWidth = (node) => {
635
+ let maxRight = 0;
636
+ for (const child of node.children) {
637
+ maxRight = Math.max(maxRight, child.layout.x + child.layout.width);
638
+ }
639
+ return maxRight;
640
+ };
641
+ const computeContentHeight = (node) => {
642
+ let maxBottom = 0;
643
+ for (const child of node.children) {
644
+ maxBottom = Math.max(maxBottom, child.layout.y + child.layout.height);
645
+ }
646
+ return maxBottom;
647
+ };
648
+ const clampScrollTop = (node) => {
649
+ if (node.type !== "View") return;
650
+ if (!node.props?.scrollY) return;
651
+ const viewportH = node.layout.height;
652
+ const contentH = node.scrollContentHeight ?? 0;
653
+ const maxScrollTop = Math.max(0, contentH - viewportH);
654
+ if (typeof node.scrollTop !== "number") node.scrollTop = 0;
655
+ node.scrollTop = Math.max(0, Math.min(node.scrollTop, maxScrollTop));
656
+ };
657
+ const clampScrollLeft = (node) => {
658
+ if (node.type !== "View") return;
659
+ if (!node.props?.scrollX) return;
660
+ const viewportW = node.layout.width;
661
+ const contentW = node.scrollContentWidth ?? 0;
662
+ const maxScrollLeft = Math.max(0, contentW - viewportW);
663
+ if (typeof node.scrollLeft !== "number") node.scrollLeft = 0;
664
+ node.scrollLeft = Math.max(0, Math.min(node.scrollLeft, maxScrollLeft));
665
+ };
666
+ const walk = (parent) => {
667
+ for (const child of parent.children) {
668
+ readComputedLayout(child);
669
+ if (child.children.length) walk(child);
670
+ if (child.type === "View" && (child.props?.scrollY || child.props?.scrollX)) {
671
+ if (child.props?.scrollY) {
672
+ child.scrollContentHeight = computeContentHeight(child);
673
+ clampScrollTop(child);
674
+ }
675
+ if (child.props?.scrollX) {
676
+ child.scrollContentWidth = computeContentWidth(child);
677
+ clampScrollLeft(child);
678
+ }
679
+ }
680
+ }
681
+ };
682
+ walk(root);
683
+ const computeSubtreeContentBounds = (node) => {
684
+ let minX = 0;
685
+ let minY = 0;
686
+ let maxX = node.layout.width;
687
+ let maxY = node.layout.height;
688
+ for (const child of node.children) {
689
+ if (child.children.length) computeSubtreeContentBounds(child);
690
+ const childBounds = child.contentBounds ?? {
691
+ x: 0,
692
+ y: 0,
693
+ width: child.layout.width,
694
+ height: child.layout.height
695
+ };
696
+ const bx = child.layout.x + childBounds.x;
697
+ const by = child.layout.y + childBounds.y;
698
+ const br = bx + childBounds.width;
699
+ const bb = by + childBounds.height;
700
+ minX = Math.min(minX, bx);
701
+ minY = Math.min(minY, by);
702
+ maxX = Math.max(maxX, br);
703
+ maxY = Math.max(maxY, bb);
704
+ }
705
+ node.contentBounds = { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
706
+ };
707
+ for (const child of root.children) computeSubtreeContentBounds(child);
708
+ }
709
+ function freeYogaSubtree(node) {
710
+ if (node.yogaNode) {
711
+ const parent = node.yogaNode.getParent?.();
712
+ if (parent) {
713
+ parent.removeChild(node.yogaNode);
714
+ }
715
+ node.yogaNode.freeRecursive();
716
+ }
717
+ const clearRefs = (target) => {
718
+ target.yogaNode = null;
719
+ for (const child of target.children) clearRefs(child);
720
+ };
721
+ clearRefs(node);
722
+ }
723
+ var hostConfig = {
724
+ now: Date.now,
725
+ supportsMutation: true,
726
+ isPrimaryRenderer: false,
727
+ scheduleTimeout: setTimeout,
728
+ cancelTimeout: clearTimeout,
729
+ noTimeout: -1,
730
+ getRootHostContext() {
731
+ return null;
732
+ },
733
+ getChildHostContext() {
734
+ return null;
735
+ },
736
+ shouldSetTextContent() {
737
+ return false;
738
+ },
739
+ createInstance(type, props, rootContainer) {
740
+ const node = createNode(type, props);
741
+ if (type === "Image" && props.src) {
742
+ const imgNode = node;
743
+ const img = new Image();
744
+ img.crossOrigin = "anonymous";
745
+ img.src = props.src;
746
+ if (img.dataset) {
747
+ img.dataset.src = props.src;
748
+ }
749
+ imgNode.imageInstance = img;
750
+ if (!img.complete) {
751
+ img.onload = () => rootContainer.invalidate();
752
+ }
753
+ } else if (type === "View" && props.backgroundImage) {
754
+ const viewNode = node;
755
+ const img = new Image();
756
+ img.crossOrigin = "anonymous";
757
+ img.src = props.backgroundImage;
758
+ if (img.dataset) {
759
+ img.dataset.src = props.backgroundImage;
760
+ }
761
+ viewNode.backgroundImageInstance = img;
762
+ if (!img.complete) {
763
+ img.onload = () => rootContainer.invalidate();
764
+ }
765
+ }
766
+ return node;
767
+ },
768
+ createTextInstance() {
769
+ throw new Error('Text instances are not supported. Use <Text text="..."/>.');
770
+ },
771
+ appendInitialChild(parent, child) {
772
+ hostConfig.appendChild(parent, child);
773
+ },
774
+ appendChild(parent, child) {
775
+ child.parent = parent;
776
+ parent.children.push(child);
777
+ },
778
+ appendChildToContainer(container, child) {
779
+ child.parent = container.root;
780
+ container.root.children.push(child);
781
+ container.invalidate();
782
+ },
783
+ insertBefore(parent, child, beforeChild) {
784
+ child.parent = parent;
785
+ const idx = parent.children.indexOf(beforeChild);
786
+ if (idx >= 0) parent.children.splice(idx, 0, child);
787
+ else parent.children.push(child);
788
+ },
789
+ insertInContainerBefore(container, child, beforeChild) {
790
+ child.parent = container.root;
791
+ const idx = container.root.children.indexOf(beforeChild);
792
+ if (idx >= 0) container.root.children.splice(idx, 0, child);
793
+ else container.root.children.push(child);
794
+ container.invalidate();
795
+ },
796
+ removeChild(parent, child) {
797
+ const idx = parent.children.indexOf(child);
798
+ if (idx >= 0) parent.children.splice(idx, 1);
799
+ child.parent = null;
800
+ freeYogaSubtree(child);
801
+ },
802
+ removeChildFromContainer(container, child) {
803
+ const idx = container.root.children.indexOf(child);
804
+ if (idx >= 0) container.root.children.splice(idx, 1);
805
+ child.parent = null;
806
+ freeYogaSubtree(child);
807
+ container.invalidate();
808
+ },
809
+ finalizeInitialChildren() {
810
+ return false;
811
+ },
812
+ prepareUpdate(_instance, _type, _oldProps, newProps) {
813
+ return newProps;
814
+ },
815
+ commitUpdate(instance, updatePayload) {
816
+ instance.props = updatePayload;
817
+ if (instance.type === "Image") {
818
+ const imgNode = instance;
819
+ const newSrc = instance.props.src;
820
+ const currentSrc = imgNode.imageInstance?.dataset?.src;
821
+ if (newSrc !== currentSrc) {
822
+ if (!newSrc) {
823
+ imgNode.imageInstance = null;
824
+ } else {
825
+ const img = new Image();
826
+ img.crossOrigin = "anonymous";
827
+ img.src = newSrc;
828
+ if (img.dataset) {
829
+ img.dataset.src = newSrc;
830
+ }
831
+ imgNode.imageInstance = img;
832
+ const invalidate = () => {
833
+ let p = imgNode;
834
+ while (p) {
835
+ if (p.type === "Root") {
836
+ p.container?.invalidate();
837
+ return;
838
+ }
839
+ p = p.parent;
840
+ }
841
+ };
842
+ if (!img.complete) {
843
+ img.onload = invalidate;
844
+ } else {
845
+ invalidate();
846
+ }
847
+ }
848
+ }
849
+ } else if (instance.type === "View") {
850
+ const viewNode = instance;
851
+ const newBg = instance.props.backgroundImage;
852
+ const currentBg = viewNode.backgroundImageInstance?.dataset?.src;
853
+ if (newBg !== currentBg) {
854
+ if (!newBg) {
855
+ viewNode.backgroundImageInstance = null;
856
+ } else {
857
+ const img = new Image();
858
+ img.crossOrigin = "anonymous";
859
+ img.src = newBg;
860
+ if (img.dataset) {
861
+ img.dataset.src = newBg;
862
+ }
863
+ viewNode.backgroundImageInstance = img;
864
+ const invalidate = () => {
865
+ let p = viewNode;
866
+ while (p) {
867
+ if (p.type === "Root") {
868
+ p.container?.invalidate();
869
+ return;
870
+ }
871
+ p = p.parent;
872
+ }
873
+ };
874
+ if (!img.complete) {
875
+ img.onload = invalidate;
876
+ } else {
877
+ invalidate();
878
+ }
879
+ }
880
+ }
881
+ }
882
+ },
883
+ commitTextUpdate() {
884
+ },
885
+ resetTextContent() {
886
+ },
887
+ prepareForCommit() {
888
+ return null;
889
+ },
890
+ resetAfterCommit(container) {
891
+ container.invalidate();
892
+ container.notifyCommit?.();
893
+ },
894
+ clearContainer(container) {
895
+ container.root.children = [];
896
+ container.invalidate();
897
+ return false;
898
+ },
899
+ getPublicInstance(instance) {
900
+ return instance;
901
+ },
902
+ hideInstance() {
903
+ },
904
+ unhideInstance() {
905
+ },
906
+ hideTextInstance() {
907
+ },
908
+ unhideTextInstance() {
909
+ },
910
+ detachDeletedInstance() {
911
+ }
912
+ };
913
+ var CanvasReconciler = Reconciler__default.default(hostConfig);
914
+ function createReconcilerRoot(container) {
915
+ const root = CanvasReconciler.createContainer(
916
+ container,
917
+ 0,
918
+ null,
919
+ false,
920
+ null,
921
+ "",
922
+ console.error,
923
+ null
924
+ );
925
+ return {
926
+ render(element) {
927
+ CanvasReconciler.updateContainer(element, root, null, () => {
928
+ });
929
+ },
930
+ unmount() {
931
+ CanvasReconciler.updateContainer(null, root, null, () => {
932
+ });
933
+ }
934
+ };
935
+ }
936
+
937
+ // src/runtime/root.ts
938
+ function createCanvasRoot(canvas, options) {
939
+ const ctx = canvas.getContext("2d");
940
+ if (!ctx) throw new Error("CanvasRenderingContext2D is not available");
941
+ const rootNode = createRootNode();
942
+ const commitSubscribers = /* @__PURE__ */ new Set();
943
+ const notifyCommit = () => {
944
+ for (const cb of commitSubscribers) cb();
945
+ };
946
+ let hoverId = null;
947
+ let selectedId = null;
948
+ let lastHoveredNode = null;
949
+ const toCanvasPoint = (clientX, clientY) => {
950
+ const rect = canvas.getBoundingClientRect();
951
+ const scaleX = rect.width ? options.width / rect.width : 1;
952
+ const scaleY = rect.height ? options.height / rect.height : 1;
953
+ const x = (clientX - rect.left) * scaleX;
954
+ const y = (clientY - rect.top) * scaleY;
955
+ return { x, y };
956
+ };
957
+ const findNodeById = (id) => {
958
+ const walk = (node) => {
959
+ if (node.debugId === id) return node;
960
+ for (const child of node.children) {
961
+ const hit = walk(child);
962
+ if (hit) return hit;
963
+ }
964
+ return null;
965
+ };
966
+ for (const child of rootNode.children) {
967
+ const hit = walk(child);
968
+ if (hit) return hit;
969
+ }
970
+ return null;
971
+ };
972
+ const getAbsoluteRect = (node) => {
973
+ const path = [];
974
+ let current = node;
975
+ while (current) {
976
+ path.push(current);
977
+ const nextParent = current.parent;
978
+ current = nextParent && nextParent.type !== "Root" ? nextParent : null;
979
+ }
980
+ let absLeft = 0;
981
+ let absTop = 0;
982
+ for (let i = path.length - 1; i >= 0; i -= 1) {
983
+ const n = path[i];
984
+ absLeft += n.layout.x;
985
+ absTop += n.layout.y;
986
+ const hasNext = i > 0;
987
+ if (hasNext && n.type === "View" && n.props?.scrollX) absLeft -= n.scrollLeft ?? 0;
988
+ if (hasNext && n.type === "View" && n.props?.scrollY) absTop -= n.scrollTop ?? 0;
989
+ }
990
+ return { x: absLeft, y: absTop, width: node.layout.width, height: node.layout.height };
991
+ };
992
+ const getScrollClipRects = (node) => {
993
+ const rects = [];
994
+ let current = node.parent && node.parent.type !== "Root" ? node.parent : null;
995
+ while (current) {
996
+ if (current.type === "View") {
997
+ const scrollX = !!current.props?.scrollX;
998
+ const scrollY = !!current.props?.scrollY;
999
+ if (scrollX || scrollY) rects.push(getAbsoluteRect(current));
1000
+ }
1001
+ const nextParent = current.parent;
1002
+ current = nextParent && nextParent.type !== "Root" ? nextParent : null;
1003
+ }
1004
+ return rects.reverse();
1005
+ };
1006
+ const serializeValue = (value, depth, seen) => {
1007
+ if (depth > 6) return "[MaxDepth]";
1008
+ if (value == null) return value;
1009
+ const t = typeof value;
1010
+ if (t === "string" || t === "number" || t === "boolean") return value;
1011
+ if (t === "bigint") return String(value);
1012
+ if (t === "symbol") return String(value);
1013
+ if (t === "function") return "[Function]";
1014
+ if (t !== "object") return String(value);
1015
+ if (seen.has(value)) return "[Circular]";
1016
+ seen.add(value);
1017
+ if (Array.isArray(value)) {
1018
+ const next = [];
1019
+ const len = Math.min(value.length, 50);
1020
+ for (let i = 0; i < len; i += 1) next.push(serializeValue(value[i], depth + 1, seen));
1021
+ if (value.length > len) next.push(`[+${value.length - len}]`);
1022
+ return next;
1023
+ }
1024
+ const out = {};
1025
+ const keys = Object.keys(value).slice(0, 80);
1026
+ for (const k of keys) out[k] = serializeValue(value[k], depth + 1, seen);
1027
+ const allKeys = Object.keys(value);
1028
+ if (allKeys.length > keys.length) out.__moreKeys = allKeys.length - keys.length;
1029
+ return out;
1030
+ };
1031
+ let frameId = null;
1032
+ let dirtyLayout = true;
1033
+ let dirtyDraw = true;
1034
+ const measureText = (text, font, maxWidth) => {
1035
+ ctx.save();
1036
+ ctx.font = font;
1037
+ const metrics = ctx.measureText(text);
1038
+ ctx.restore();
1039
+ const width = typeof maxWidth === "number" ? Math.min(metrics.width, maxWidth) : metrics.width;
1040
+ const height = Math.ceil(
1041
+ metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent || 0
1042
+ );
1043
+ return { width, height: Math.max(1, height) };
1044
+ };
1045
+ const invalidate = () => {
1046
+ dirtyLayout = true;
1047
+ dirtyDraw = true;
1048
+ if (frameId != null) return;
1049
+ frameId = requestAnimationFrame(async () => {
1050
+ frameId = null;
1051
+ if (!dirtyLayout && !dirtyDraw) return;
1052
+ const needsLayout = dirtyLayout;
1053
+ const needsDraw = dirtyDraw || dirtyLayout;
1054
+ dirtyLayout = false;
1055
+ dirtyDraw = false;
1056
+ if (needsLayout) {
1057
+ await layoutTree(rootNode, options.width, options.height, measureText, options);
1058
+ }
1059
+ if (needsDraw) {
1060
+ drawTree(rootNode, ctx, options.dpr, options.clearColor, options);
1061
+ }
1062
+ const overlayHover = typeof hoverId === "number" ? findNodeById(hoverId) : null;
1063
+ const overlaySelected = typeof selectedId === "number" ? findNodeById(selectedId) : null;
1064
+ if (overlayHover || overlaySelected) {
1065
+ ctx.save();
1066
+ ctx.setTransform(options.dpr, 0, 0, options.dpr, 0, 0);
1067
+ if (overlayHover && (!overlaySelected || overlayHover.debugId !== overlaySelected.debugId)) {
1068
+ const r = getAbsoluteRect(overlayHover);
1069
+ ctx.save();
1070
+ for (const clip of getScrollClipRects(overlayHover)) {
1071
+ ctx.beginPath();
1072
+ ctx.rect(clip.x, clip.y, clip.width, clip.height);
1073
+ ctx.clip();
1074
+ }
1075
+ ctx.fillStyle = "rgba(59,130,246,0.12)";
1076
+ ctx.strokeStyle = "rgba(59,130,246,0.9)";
1077
+ ctx.lineWidth = 1;
1078
+ ctx.fillRect(r.x, r.y, r.width, r.height);
1079
+ ctx.strokeRect(r.x + 0.5, r.y + 0.5, Math.max(0, r.width - 1), Math.max(0, r.height - 1));
1080
+ ctx.restore();
1081
+ }
1082
+ if (overlaySelected) {
1083
+ const r = getAbsoluteRect(overlaySelected);
1084
+ ctx.save();
1085
+ for (const clip of getScrollClipRects(overlaySelected)) {
1086
+ ctx.beginPath();
1087
+ ctx.rect(clip.x, clip.y, clip.width, clip.height);
1088
+ ctx.clip();
1089
+ }
1090
+ ctx.fillStyle = "rgba(16,185,129,0.12)";
1091
+ ctx.strokeStyle = "rgba(16,185,129,0.95)";
1092
+ ctx.lineWidth = 2;
1093
+ ctx.fillRect(r.x, r.y, r.width, r.height);
1094
+ ctx.strokeRect(r.x + 1, r.y + 1, Math.max(0, r.width - 2), Math.max(0, r.height - 2));
1095
+ ctx.restore();
1096
+ }
1097
+ ctx.restore();
1098
+ }
1099
+ });
1100
+ };
1101
+ const invalidateDrawOnly = () => {
1102
+ dirtyDraw = true;
1103
+ if (frameId != null) return;
1104
+ frameId = requestAnimationFrame(async () => {
1105
+ frameId = null;
1106
+ if (!dirtyLayout && !dirtyDraw) return;
1107
+ const needsLayout = dirtyLayout;
1108
+ const needsDraw = dirtyDraw || dirtyLayout;
1109
+ dirtyLayout = false;
1110
+ dirtyDraw = false;
1111
+ if (needsLayout) {
1112
+ await layoutTree(rootNode, options.width, options.height, measureText, options);
1113
+ }
1114
+ if (needsDraw) {
1115
+ drawTree(rootNode, ctx, options.dpr, options.clearColor, options);
1116
+ }
1117
+ const overlayHover = typeof hoverId === "number" ? findNodeById(hoverId) : null;
1118
+ const overlaySelected = typeof selectedId === "number" ? findNodeById(selectedId) : null;
1119
+ if (overlayHover || overlaySelected) {
1120
+ ctx.save();
1121
+ ctx.setTransform(options.dpr, 0, 0, options.dpr, 0, 0);
1122
+ if (overlayHover && (!overlaySelected || overlayHover.debugId !== overlaySelected.debugId)) {
1123
+ const r = getAbsoluteRect(overlayHover);
1124
+ ctx.save();
1125
+ for (const clip of getScrollClipRects(overlayHover)) {
1126
+ ctx.beginPath();
1127
+ ctx.rect(clip.x, clip.y, clip.width, clip.height);
1128
+ ctx.clip();
1129
+ }
1130
+ ctx.fillStyle = "rgba(59,130,246,0.12)";
1131
+ ctx.strokeStyle = "rgba(59,130,246,0.9)";
1132
+ ctx.lineWidth = 1;
1133
+ ctx.fillRect(r.x, r.y, r.width, r.height);
1134
+ ctx.strokeRect(r.x + 0.5, r.y + 0.5, Math.max(0, r.width - 1), Math.max(0, r.height - 1));
1135
+ ctx.restore();
1136
+ }
1137
+ if (overlaySelected) {
1138
+ const r = getAbsoluteRect(overlaySelected);
1139
+ ctx.save();
1140
+ for (const clip of getScrollClipRects(overlaySelected)) {
1141
+ ctx.beginPath();
1142
+ ctx.rect(clip.x, clip.y, clip.width, clip.height);
1143
+ ctx.clip();
1144
+ }
1145
+ ctx.fillStyle = "rgba(16,185,129,0.12)";
1146
+ ctx.strokeStyle = "rgba(16,185,129,0.95)";
1147
+ ctx.lineWidth = 2;
1148
+ ctx.fillRect(r.x, r.y, r.width, r.height);
1149
+ ctx.strokeRect(r.x + 1, r.y + 1, Math.max(0, r.width - 2), Math.max(0, r.height - 2));
1150
+ ctx.restore();
1151
+ }
1152
+ ctx.restore();
1153
+ }
1154
+ });
1155
+ };
1156
+ const getScrollbarMetricsY = (view, left, top) => {
1157
+ if (view.type !== "View") return null;
1158
+ if (!view.props?.scrollY) return null;
1159
+ if (view.props?.scrollbarY === false) return null;
1160
+ const w = view.layout.width;
1161
+ const h = view.layout.height;
1162
+ const contentHeight = view.scrollContentHeight ?? 0;
1163
+ const maxScrollTop = Math.max(0, contentHeight - h);
1164
+ if (maxScrollTop <= 0) return null;
1165
+ const scrollbarWidth = view.props?.scrollbarWidth ?? 10;
1166
+ const scrollbarInset = view.props?.scrollbarInset ?? 6;
1167
+ const corner = scrollbarInset + scrollbarWidth;
1168
+ const contentWidth = view.scrollContentWidth ?? 0;
1169
+ const maxScrollLeft = Math.max(0, contentWidth - w);
1170
+ const hasScrollX = !!view.props?.scrollX && view.props?.scrollbarX !== false && maxScrollLeft > 0;
1171
+ const trackX = left + w - scrollbarInset - scrollbarWidth;
1172
+ const trackY = top + scrollbarInset;
1173
+ const trackH = Math.max(0, h - scrollbarInset * 2 - (hasScrollX ? corner : 0));
1174
+ if (trackH <= 0) return null;
1175
+ const minThumbH = 16;
1176
+ const thumbH = contentHeight > 0 ? Math.max(minThumbH, Math.min(trackH, trackH * h / contentHeight)) : trackH;
1177
+ const travel = Math.max(0, trackH - thumbH);
1178
+ const scrollTop = view.scrollTop ?? 0;
1179
+ const thumbY = travel > 0 ? trackY + scrollTop / maxScrollTop * travel : trackY;
1180
+ return { maxScrollTop, trackX, trackY, trackH, thumbY, thumbH };
1181
+ };
1182
+ const getScrollbarMetricsX = (view, left, top) => {
1183
+ if (view.type !== "View") return null;
1184
+ if (!view.props?.scrollX) return null;
1185
+ if (view.props?.scrollbarX === false) return null;
1186
+ const w = view.layout.width;
1187
+ const h = view.layout.height;
1188
+ const contentWidth = view.scrollContentWidth ?? 0;
1189
+ const maxScrollLeft = Math.max(0, contentWidth - w);
1190
+ if (maxScrollLeft <= 0) return null;
1191
+ const scrollbarWidth = view.props?.scrollbarWidth ?? 10;
1192
+ const scrollbarInset = view.props?.scrollbarInset ?? 6;
1193
+ const corner = scrollbarInset + scrollbarWidth;
1194
+ const contentHeight = view.scrollContentHeight ?? 0;
1195
+ const maxScrollTop = Math.max(0, contentHeight - h);
1196
+ const hasScrollY = !!view.props?.scrollY && view.props?.scrollbarY !== false && maxScrollTop > 0;
1197
+ const trackX = left + scrollbarInset;
1198
+ const trackY = top + h - scrollbarInset - scrollbarWidth;
1199
+ const trackW = Math.max(0, w - scrollbarInset * 2 - (hasScrollY ? corner : 0));
1200
+ if (trackW <= 0) return null;
1201
+ const minThumbW = 16;
1202
+ const thumbW = contentWidth > 0 ? Math.max(minThumbW, Math.min(trackW, trackW * w / contentWidth)) : trackW;
1203
+ const travel = Math.max(0, trackW - thumbW);
1204
+ const scrollLeft = view.scrollLeft ?? 0;
1205
+ const thumbX = travel > 0 ? trackX + scrollLeft / maxScrollLeft * travel : trackX;
1206
+ return { maxScrollLeft, trackX, trackY, trackW, thumbX, thumbW };
1207
+ };
1208
+ const hitTestNode = (node, x, y, offsetX, offsetY) => {
1209
+ if (node.props?.pointerEvents === "none") return null;
1210
+ const left = offsetX + node.layout.x;
1211
+ const top = offsetY + node.layout.y;
1212
+ const right = left + node.layout.width;
1213
+ const bottom = top + node.layout.height;
1214
+ if (x < left || x > right || y < top || y > bottom) return null;
1215
+ const childOffsetX = node.type === "View" && node.props?.scrollX ? left - (node.scrollLeft ?? 0) : left;
1216
+ const childOffsetY = node.type === "View" && node.props?.scrollY ? top - (node.scrollTop ?? 0) : top;
1217
+ for (let i = node.children.length - 1; i >= 0; i -= 1) {
1218
+ const child = node.children[i];
1219
+ const hit = hitTestNode(child, x, y, childOffsetX, childOffsetY);
1220
+ if (hit) return hit;
1221
+ }
1222
+ return node;
1223
+ };
1224
+ const hitTestScrollbarThumbNode = (node, x, y, offsetX, offsetY) => {
1225
+ if (node.props?.pointerEvents === "none") return null;
1226
+ const left = offsetX + node.layout.x;
1227
+ const top = offsetY + node.layout.y;
1228
+ const right = left + node.layout.width;
1229
+ const bottom = top + node.layout.height;
1230
+ if (x < left || x > right || y < top || y > bottom) return null;
1231
+ const metricsY = getScrollbarMetricsY(node, left, top);
1232
+ if (metricsY) {
1233
+ const { trackX, thumbY, thumbH } = metricsY;
1234
+ const thumbLeft = trackX;
1235
+ const thumbRight = trackX + (node.props?.scrollbarWidth ?? 10);
1236
+ const thumbTop = thumbY;
1237
+ const thumbBottom = thumbY + thumbH;
1238
+ if (x >= thumbLeft && x <= thumbRight && y >= thumbTop && y <= thumbBottom) {
1239
+ return { view: node, axis: "y" };
1240
+ }
1241
+ }
1242
+ const metricsX = getScrollbarMetricsX(node, left, top);
1243
+ if (metricsX) {
1244
+ const { trackY, thumbX, thumbW } = metricsX;
1245
+ const thumbLeft = thumbX;
1246
+ const thumbRight = thumbX + thumbW;
1247
+ const thumbTop = trackY;
1248
+ const thumbBottom = trackY + (node.props?.scrollbarWidth ?? 10);
1249
+ if (x >= thumbLeft && x <= thumbRight && y >= thumbTop && y <= thumbBottom) {
1250
+ return { view: node, axis: "x" };
1251
+ }
1252
+ }
1253
+ const childOffsetX = node.type === "View" && node.props?.scrollX ? left - (node.scrollLeft ?? 0) : left;
1254
+ const childOffsetY = node.type === "View" && node.props?.scrollY ? top - (node.scrollTop ?? 0) : top;
1255
+ for (let i = node.children.length - 1; i >= 0; i -= 1) {
1256
+ const child = node.children[i];
1257
+ const hit = hitTestScrollbarThumbNode(child, x, y, childOffsetX, childOffsetY);
1258
+ if (hit) return hit;
1259
+ }
1260
+ return null;
1261
+ };
1262
+ const hitTestScrollbarThumb = (x, y) => {
1263
+ for (let i = rootNode.children.length - 1; i >= 0; i -= 1) {
1264
+ const child = rootNode.children[i];
1265
+ const hit = hitTestScrollbarThumbNode(child, x, y, 0, 0);
1266
+ if (hit) return hit;
1267
+ }
1268
+ return null;
1269
+ };
1270
+ const hitTest = (x, y) => {
1271
+ for (let i = rootNode.children.length - 1; i >= 0; i -= 1) {
1272
+ const child = rootNode.children[i];
1273
+ const hit = hitTestNode(child, x, y, 0, 0);
1274
+ if (hit) return hit;
1275
+ }
1276
+ return null;
1277
+ };
1278
+ const buildPath = (target) => {
1279
+ const path = [];
1280
+ let current = target;
1281
+ while (current) {
1282
+ path.push(current);
1283
+ const nextParent = current.parent;
1284
+ current = nextParent && nextParent.type !== "Root" ? nextParent : null;
1285
+ }
1286
+ return path;
1287
+ };
1288
+ const pointerCapture = /* @__PURE__ */ new Map();
1289
+ const pointerDownTarget = /* @__PURE__ */ new Map();
1290
+ const clamp = (value, min, max) => Math.max(min, Math.min(value, max));
1291
+ const applyScrollY = (node, delta) => {
1292
+ if (node.type !== "View") return false;
1293
+ if (!node.props?.scrollY) return false;
1294
+ const contentHeight = node.scrollContentHeight ?? 0;
1295
+ const maxScrollTop = Math.max(0, contentHeight - node.layout.height);
1296
+ if (maxScrollTop <= 0) return false;
1297
+ const current = node.scrollTop ?? 0;
1298
+ const next = clamp(current + delta, 0, maxScrollTop);
1299
+ if (next === current) return false;
1300
+ node.scrollTop = next;
1301
+ const onScroll = node.props?.onScroll;
1302
+ if (typeof onScroll === "function") onScroll(next);
1303
+ return true;
1304
+ };
1305
+ const applyScrollX = (node, delta) => {
1306
+ if (node.type !== "View") return false;
1307
+ if (!node.props?.scrollX) return false;
1308
+ const contentWidth = node.scrollContentWidth ?? 0;
1309
+ const maxScrollLeft = Math.max(0, contentWidth - node.layout.width);
1310
+ if (maxScrollLeft <= 0) return false;
1311
+ const current = node.scrollLeft ?? 0;
1312
+ const next = clamp(current + delta, 0, maxScrollLeft);
1313
+ if (next === current) return false;
1314
+ node.scrollLeft = next;
1315
+ const onScrollX = node.props?.onScrollX;
1316
+ if (typeof onScrollX === "function") onScrollX(next);
1317
+ return true;
1318
+ };
1319
+ const dispatchOnPath = (eventType, path, init, target) => {
1320
+ const baseName = eventType === "pointerdown" ? "onPointerDown" : eventType === "pointermove" ? "onPointerMove" : eventType === "pointerup" ? "onPointerUp" : eventType === "pointercancel" ? "onPointerCancel" : "onClick";
1321
+ let propagationStopped = false;
1322
+ let defaultPrevented = false;
1323
+ const eventObject = {
1324
+ type: eventType,
1325
+ ...init,
1326
+ target,
1327
+ currentTarget: null,
1328
+ defaultPrevented,
1329
+ stopPropagation() {
1330
+ propagationStopped = true;
1331
+ },
1332
+ preventDefault() {
1333
+ defaultPrevented = true;
1334
+ eventObject.defaultPrevented = true;
1335
+ }
1336
+ };
1337
+ for (let i = path.length - 1; i >= 0; i -= 1) {
1338
+ if (propagationStopped) break;
1339
+ const currentTarget = path[i];
1340
+ const handler = currentTarget.props?.[`${baseName}Capture`];
1341
+ if (typeof handler === "function") {
1342
+ eventObject.currentTarget = currentTarget;
1343
+ handler(eventObject);
1344
+ }
1345
+ }
1346
+ for (let i = 0; i < path.length; i += 1) {
1347
+ if (propagationStopped) break;
1348
+ const currentTarget = path[i];
1349
+ const handler = currentTarget.props?.[baseName];
1350
+ if (typeof handler === "function") {
1351
+ eventObject.currentTarget = currentTarget;
1352
+ handler(eventObject);
1353
+ }
1354
+ }
1355
+ return { defaultPrevented };
1356
+ };
1357
+ const dispatchPointerEvent = (eventType, init) => {
1358
+ const { pointerId } = init;
1359
+ const capturedForScroll = pointerCapture.get(pointerId);
1360
+ if (capturedForScroll && capturedForScroll.type === "View" && capturedForScroll.scrollbarDrag && capturedForScroll.scrollbarDrag.pointerId === pointerId) {
1361
+ if (eventType === "pointermove") {
1362
+ const drag = capturedForScroll.scrollbarDrag;
1363
+ const path = buildPath(capturedForScroll);
1364
+ let absLeft = 0;
1365
+ let absTop = 0;
1366
+ for (let i = path.length - 1; i >= 0; i -= 1) {
1367
+ const n = path[i];
1368
+ absLeft += n.layout.x;
1369
+ absTop += n.layout.y;
1370
+ const hasNext = i > 0;
1371
+ if (hasNext && n.type === "View" && n.props?.scrollX)
1372
+ absLeft -= n.scrollLeft ?? 0;
1373
+ if (hasNext && n.type === "View" && n.props?.scrollY) absTop -= n.scrollTop ?? 0;
1374
+ }
1375
+ if (drag.axis === "y") {
1376
+ const metrics2 = getScrollbarMetricsY(capturedForScroll, absLeft, absTop);
1377
+ if (!metrics2) return { defaultPrevented: true };
1378
+ const travel2 = Math.max(0, metrics2.trackH - metrics2.thumbH);
1379
+ if (travel2 <= 0) return { defaultPrevented: true };
1380
+ const deltaThumb2 = init.y - drag.startPointer;
1381
+ const nextScrollTop = drag.startScroll + deltaThumb2 * (metrics2.maxScrollTop / travel2);
1382
+ const clamped2 = Math.max(0, Math.min(nextScrollTop, metrics2.maxScrollTop));
1383
+ capturedForScroll.scrollTop = clamped2;
1384
+ const onScroll = capturedForScroll.props?.onScroll;
1385
+ if (typeof onScroll === "function") onScroll(clamped2);
1386
+ invalidateDrawOnly();
1387
+ return { defaultPrevented: true };
1388
+ }
1389
+ const metrics = getScrollbarMetricsX(capturedForScroll, absLeft, absTop);
1390
+ if (!metrics) return { defaultPrevented: true };
1391
+ const travel = Math.max(0, metrics.trackW - metrics.thumbW);
1392
+ if (travel <= 0) return { defaultPrevented: true };
1393
+ const deltaThumb = init.x - drag.startPointer;
1394
+ const nextScrollLeft = drag.startScroll + deltaThumb * (metrics.maxScrollLeft / travel);
1395
+ const clamped = Math.max(0, Math.min(nextScrollLeft, metrics.maxScrollLeft));
1396
+ capturedForScroll.scrollLeft = clamped;
1397
+ const onScrollX = capturedForScroll.props?.onScrollX;
1398
+ if (typeof onScrollX === "function") onScrollX(clamped);
1399
+ invalidateDrawOnly();
1400
+ return { defaultPrevented: true };
1401
+ }
1402
+ if (eventType === "pointerup" || eventType === "pointercancel") {
1403
+ capturedForScroll.scrollbarDrag = null;
1404
+ pointerCapture.delete(pointerId);
1405
+ pointerDownTarget.delete(pointerId);
1406
+ invalidateDrawOnly();
1407
+ return { defaultPrevented: true };
1408
+ }
1409
+ }
1410
+ if (eventType === "pointerdown") {
1411
+ const scrollbarTarget = hitTestScrollbarThumb(init.x, init.y);
1412
+ if (scrollbarTarget && scrollbarTarget.view.type === "View") {
1413
+ scrollbarTarget.view.scrollbarDrag = {
1414
+ axis: scrollbarTarget.axis,
1415
+ pointerId,
1416
+ startPointer: scrollbarTarget.axis === "y" ? init.y : init.x,
1417
+ startScroll: scrollbarTarget.axis === "y" ? scrollbarTarget.view.scrollTop ?? 0 : scrollbarTarget.view.scrollLeft ?? 0
1418
+ };
1419
+ pointerCapture.set(pointerId, scrollbarTarget.view);
1420
+ return { defaultPrevented: true };
1421
+ }
1422
+ const target2 = hitTest(init.x, init.y);
1423
+ if (!target2) return { defaultPrevented: false };
1424
+ pointerCapture.set(pointerId, target2);
1425
+ pointerDownTarget.set(pointerId, target2);
1426
+ return dispatchOnPath(eventType, buildPath(target2), init, target2);
1427
+ }
1428
+ if (eventType === "pointermove") {
1429
+ const captured = pointerCapture.get(pointerId);
1430
+ const target2 = captured ?? hitTest(init.x, init.y);
1431
+ if (target2 !== lastHoveredNode) {
1432
+ const prevChain = [];
1433
+ let p = lastHoveredNode;
1434
+ while (p) {
1435
+ prevChain.push(p);
1436
+ const nextParent = p.parent;
1437
+ p = nextParent && nextParent.type !== "Root" ? nextParent : null;
1438
+ }
1439
+ const nextChain = [];
1440
+ let n = target2;
1441
+ while (n) {
1442
+ nextChain.push(n);
1443
+ const nextParent = n.parent;
1444
+ n = nextParent && nextParent.type !== "Root" ? nextParent : null;
1445
+ }
1446
+ for (const node of prevChain) {
1447
+ if (!nextChain.includes(node)) {
1448
+ const handler = node.props?.onPointerLeave;
1449
+ if (typeof handler === "function") {
1450
+ handler({
1451
+ type: "pointerleave",
1452
+ ...init,
1453
+ target: node,
1454
+ currentTarget: node,
1455
+ defaultPrevented: false,
1456
+ stopPropagation: () => {
1457
+ },
1458
+ preventDefault: () => {
1459
+ }
1460
+ });
1461
+ }
1462
+ }
1463
+ }
1464
+ const enteringNodes = nextChain.filter((node) => !prevChain.includes(node)).reverse();
1465
+ for (const node of enteringNodes) {
1466
+ const handler = node.props?.onPointerEnter;
1467
+ if (typeof handler === "function") {
1468
+ handler({
1469
+ type: "pointerenter",
1470
+ ...init,
1471
+ target: node,
1472
+ currentTarget: node,
1473
+ defaultPrevented: false,
1474
+ stopPropagation: () => {
1475
+ },
1476
+ preventDefault: () => {
1477
+ }
1478
+ });
1479
+ }
1480
+ }
1481
+ lastHoveredNode = target2;
1482
+ }
1483
+ if (!target2) return { defaultPrevented: false };
1484
+ return dispatchOnPath(eventType, buildPath(target2), init, target2);
1485
+ }
1486
+ if (eventType === "pointerup") {
1487
+ const captured = pointerCapture.get(pointerId);
1488
+ const target2 = captured ?? hitTest(init.x, init.y);
1489
+ if (!target2) return { defaultPrevented: false };
1490
+ const res = dispatchOnPath(eventType, buildPath(target2), init, target2);
1491
+ const downTarget = pointerDownTarget.get(pointerId);
1492
+ pointerCapture.delete(pointerId);
1493
+ pointerDownTarget.delete(pointerId);
1494
+ if (downTarget && downTarget === target2 && init.button === 0) {
1495
+ const clickRes = dispatchOnPath("click", buildPath(target2), init, target2);
1496
+ return { defaultPrevented: res.defaultPrevented || clickRes.defaultPrevented };
1497
+ }
1498
+ return res;
1499
+ }
1500
+ if (eventType === "pointercancel") {
1501
+ const captured = pointerCapture.get(pointerId);
1502
+ const target2 = captured ?? hitTest(init.x, init.y);
1503
+ if (!target2) return { defaultPrevented: false };
1504
+ const res = dispatchOnPath(eventType, buildPath(target2), init, target2);
1505
+ pointerCapture.delete(pointerId);
1506
+ pointerDownTarget.delete(pointerId);
1507
+ return res;
1508
+ }
1509
+ const target = hitTest(init.x, init.y);
1510
+ if (!target) return { defaultPrevented: false };
1511
+ return dispatchOnPath(eventType, buildPath(target), init, target);
1512
+ };
1513
+ const dispatchWheelEvent = (init) => {
1514
+ const target = hitTest(init.x, init.y);
1515
+ if (!target) return { defaultPrevented: false };
1516
+ const path = buildPath(target);
1517
+ let remainingX = init.deltaX;
1518
+ let remainingY = init.deltaY;
1519
+ let defaultPrevented = false;
1520
+ for (let i = 0; i < path.length; i += 1) {
1521
+ const node = path[i];
1522
+ if (remainingY !== 0) {
1523
+ const applied = applyScrollY(node, remainingY);
1524
+ if (applied) {
1525
+ remainingY = 0;
1526
+ defaultPrevented = true;
1527
+ }
1528
+ }
1529
+ if (remainingX !== 0) {
1530
+ const applied = applyScrollX(node, remainingX);
1531
+ if (applied) {
1532
+ remainingX = 0;
1533
+ defaultPrevented = true;
1534
+ }
1535
+ }
1536
+ if (remainingX === 0 && remainingY === 0) break;
1537
+ }
1538
+ if (defaultPrevented) invalidateDrawOnly();
1539
+ return { defaultPrevented };
1540
+ };
1541
+ const container = { root: rootNode, invalidate, notifyCommit };
1542
+ rootNode.container = container;
1543
+ const reconcilerRoot = createReconcilerRoot(container);
1544
+ invalidate();
1545
+ const __devtools = {
1546
+ rootId: rootNode.debugId,
1547
+ getSnapshot() {
1548
+ const nodesById = {};
1549
+ const walk = (node, parentId) => {
1550
+ nodesById[node.debugId] = {
1551
+ id: node.debugId,
1552
+ type: node.type,
1553
+ parentId,
1554
+ childrenIds: node.children.map((c) => c.debugId),
1555
+ layout: node.layout
1556
+ };
1557
+ for (const child of node.children) walk(child, node.debugId);
1558
+ };
1559
+ for (const child of rootNode.children) walk(child, null);
1560
+ return {
1561
+ rootId: rootNode.debugId,
1562
+ rootChildrenIds: rootNode.children.map((c) => c.debugId),
1563
+ nodesById
1564
+ };
1565
+ },
1566
+ getNode(id) {
1567
+ const node = findNodeById(id);
1568
+ if (!node) return null;
1569
+ return {
1570
+ id: node.debugId,
1571
+ type: node.type,
1572
+ parentId: node.parent ? node.parent.debugId : null,
1573
+ childrenIds: node.children.map((c) => c.debugId),
1574
+ layout: node.layout
1575
+ };
1576
+ },
1577
+ getNodeProps(id) {
1578
+ const node = findNodeById(id);
1579
+ if (!node) return null;
1580
+ return serializeValue(node.props, 0, /* @__PURE__ */ new WeakSet());
1581
+ },
1582
+ hitTestCanvas(x, y) {
1583
+ const node = hitTest(x, y);
1584
+ return node ? node.debugId : null;
1585
+ },
1586
+ hitTestClient(clientX, clientY) {
1587
+ const { x, y } = toCanvasPoint(clientX, clientY);
1588
+ const node = hitTest(x, y);
1589
+ return node ? node.debugId : null;
1590
+ },
1591
+ setHighlight(next) {
1592
+ if ("hoverId" in next) hoverId = next.hoverId ?? null;
1593
+ if ("selectedId" in next) selectedId = next.selectedId ?? null;
1594
+ invalidateDrawOnly();
1595
+ },
1596
+ subscribe(cb) {
1597
+ commitSubscribers.add(cb);
1598
+ return () => {
1599
+ commitSubscribers.delete(cb);
1600
+ };
1601
+ }
1602
+ };
1603
+ const registryRootInstanceId = (() => {
1604
+ if (typeof window === "undefined") return null;
1605
+ const w = window;
1606
+ const key = "__REACT_CANVAS_FIBER_DEVTOOLS__";
1607
+ if (!w[key]) {
1608
+ w[key] = {
1609
+ version: 1,
1610
+ nextRootInstanceId: 1,
1611
+ roots: /* @__PURE__ */ new Map(),
1612
+ picker: {
1613
+ enabled: false,
1614
+ rootInstanceId: null,
1615
+ hoverId: null,
1616
+ selectedId: null
1617
+ },
1618
+ pickerCleanup: null,
1619
+ listRoots() {
1620
+ const res = [];
1621
+ for (const r of this.roots.values()) {
1622
+ const rect = r.canvas.getBoundingClientRect();
1623
+ res.push({
1624
+ id: r.id,
1625
+ rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height },
1626
+ options: r.options,
1627
+ revision: r.revision
1628
+ });
1629
+ }
1630
+ return res;
1631
+ },
1632
+ getRootDevtools(id) {
1633
+ return this.roots.get(id)?.devtools ?? null;
1634
+ },
1635
+ getSnapshot(rootInstanceId2) {
1636
+ return this.getRootDevtools(rootInstanceId2)?.getSnapshot?.() ?? null;
1637
+ },
1638
+ getNodeProps(rootInstanceId2, nodeId) {
1639
+ return this.getRootDevtools(rootInstanceId2)?.getNodeProps?.(nodeId) ?? null;
1640
+ },
1641
+ setHighlight(rootInstanceId2, next) {
1642
+ this.getRootDevtools(rootInstanceId2)?.setHighlight?.(next);
1643
+ },
1644
+ startPicker(rootInstanceId2) {
1645
+ this.stopPicker();
1646
+ const handle = this.roots.get(rootInstanceId2);
1647
+ if (!handle) return false;
1648
+ this.picker.enabled = true;
1649
+ this.picker.rootInstanceId = rootInstanceId2;
1650
+ this.picker.hoverId = null;
1651
+ this.picker.selectedId = null;
1652
+ const onMove = (e) => {
1653
+ if (!this.picker.enabled) return;
1654
+ if (e.target !== handle.canvas) {
1655
+ if (this.picker.hoverId != null) {
1656
+ this.picker.hoverId = null;
1657
+ handle.devtools.setHighlight({ hoverId: null });
1658
+ }
1659
+ return;
1660
+ }
1661
+ const id = handle.devtools.hitTestClient(e.clientX, e.clientY);
1662
+ if (id !== this.picker.hoverId) {
1663
+ this.picker.hoverId = id;
1664
+ handle.devtools.setHighlight({ hoverId: id, selectedId: this.picker.selectedId });
1665
+ }
1666
+ };
1667
+ const onDown = (e) => {
1668
+ if (!this.picker.enabled) return;
1669
+ if (e.target !== handle.canvas) return;
1670
+ if (e.button !== 0) return;
1671
+ const id = handle.devtools.hitTestClient(e.clientX, e.clientY);
1672
+ this.picker.selectedId = id;
1673
+ handle.devtools.setHighlight({ hoverId: null, selectedId: id });
1674
+ e.preventDefault();
1675
+ e.stopPropagation();
1676
+ this.stopPicker();
1677
+ };
1678
+ const onKeyDown = (e) => {
1679
+ if (e.key !== "Escape") return;
1680
+ this.stopPicker();
1681
+ };
1682
+ window.addEventListener("pointermove", onMove, true);
1683
+ window.addEventListener("pointerdown", onDown, true);
1684
+ window.addEventListener("keydown", onKeyDown, true);
1685
+ this.pickerCleanup = () => {
1686
+ window.removeEventListener("pointermove", onMove, true);
1687
+ window.removeEventListener("pointerdown", onDown, true);
1688
+ window.removeEventListener("keydown", onKeyDown, true);
1689
+ handle.devtools.setHighlight({ hoverId: null });
1690
+ };
1691
+ return true;
1692
+ },
1693
+ stopPicker() {
1694
+ if (!this.picker.enabled) return;
1695
+ this.picker.enabled = false;
1696
+ this.picker.hoverId = null;
1697
+ this.pickerCleanup?.();
1698
+ this.pickerCleanup = null;
1699
+ },
1700
+ getPickerState() {
1701
+ return { ...this.picker };
1702
+ },
1703
+ unregisterRoot(id) {
1704
+ const handle = this.roots.get(id);
1705
+ if (!handle) return;
1706
+ if (this.picker.rootInstanceId === id) {
1707
+ if (this.picker.enabled) this.stopPicker();
1708
+ this.picker.rootInstanceId = null;
1709
+ this.picker.hoverId = null;
1710
+ this.picker.selectedId = null;
1711
+ }
1712
+ handle.unsubscribe?.();
1713
+ this.roots.delete(id);
1714
+ }
1715
+ };
1716
+ }
1717
+ const registry = w[key];
1718
+ const rootInstanceId = registry.nextRootInstanceId++;
1719
+ const entry = {
1720
+ id: rootInstanceId,
1721
+ canvas,
1722
+ options: { width: options.width, height: options.height, dpr: options.dpr },
1723
+ devtools: __devtools,
1724
+ revision: 0,
1725
+ unsubscribe: null
1726
+ };
1727
+ entry.unsubscribe = __devtools.subscribe(() => {
1728
+ entry.revision += 1;
1729
+ });
1730
+ registry.roots.set(rootInstanceId, entry);
1731
+ return rootInstanceId;
1732
+ })();
1733
+ return {
1734
+ render(element) {
1735
+ reconcilerRoot.render(element);
1736
+ },
1737
+ unmount() {
1738
+ reconcilerRoot.unmount();
1739
+ if (typeof window !== "undefined" && registryRootInstanceId != null) {
1740
+ const w = window;
1741
+ w.__REACT_CANVAS_FIBER_DEVTOOLS__?.unregisterRoot?.(registryRootInstanceId);
1742
+ }
1743
+ },
1744
+ dispatchPointerEvent,
1745
+ dispatchWheelEvent,
1746
+ __devtools
1747
+ };
1748
+ }
1749
+ function Canvas(props) {
1750
+ const canvasRef = react.useRef(null);
1751
+ const rootRef = react.useRef(null);
1752
+ const dpr = props.dpr ?? 1;
1753
+ const canvasStyle = react.useMemo(
1754
+ () => ({
1755
+ width: props.width,
1756
+ height: props.height,
1757
+ display: "block",
1758
+ touchAction: "none",
1759
+ ...props.style
1760
+ }),
1761
+ [props.width, props.height, props.style]
1762
+ );
1763
+ const dispatchPointer = react.useCallback(
1764
+ (type, e) => {
1765
+ const canvas = canvasRef.current;
1766
+ const root = rootRef.current;
1767
+ if (!canvas || !root) return;
1768
+ const rect = canvas.getBoundingClientRect();
1769
+ const scaleX = rect.width ? props.width / rect.width : 1;
1770
+ const scaleY = rect.height ? props.height / rect.height : 1;
1771
+ const x = (e.clientX - rect.left) * scaleX;
1772
+ const y = (e.clientY - rect.top) * scaleY;
1773
+ const res = root.dispatchPointerEvent(type, {
1774
+ x,
1775
+ y,
1776
+ pointerId: e.pointerId ?? 0,
1777
+ button: e.button ?? 0,
1778
+ buttons: e.buttons ?? 0,
1779
+ altKey: !!e.altKey,
1780
+ ctrlKey: !!e.ctrlKey,
1781
+ shiftKey: !!e.shiftKey,
1782
+ metaKey: !!e.metaKey
1783
+ });
1784
+ if (res?.defaultPrevented && typeof e.preventDefault === "function") {
1785
+ e.preventDefault();
1786
+ }
1787
+ },
1788
+ [props.width, props.height]
1789
+ );
1790
+ const handleWheel = react.useCallback(
1791
+ (e) => {
1792
+ const canvas = canvasRef.current;
1793
+ const root = rootRef.current;
1794
+ if (!canvas || !root) return;
1795
+ const rect = canvas.getBoundingClientRect();
1796
+ const scaleX = rect.width ? props.width / rect.width : 1;
1797
+ const scaleY = rect.height ? props.height / rect.height : 1;
1798
+ let deltaX = e.deltaX ?? 0;
1799
+ let deltaY = e.deltaY ?? 0;
1800
+ if (e.deltaMode === 1) {
1801
+ deltaX *= 16;
1802
+ deltaY *= 16;
1803
+ } else if (e.deltaMode === 2) {
1804
+ deltaX *= props.width;
1805
+ deltaY *= props.height;
1806
+ }
1807
+ const x = (e.clientX - rect.left) * scaleX;
1808
+ const y = (e.clientY - rect.top) * scaleY;
1809
+ const payload = {
1810
+ x,
1811
+ y,
1812
+ deltaX: deltaX * scaleX,
1813
+ deltaY: deltaY * scaleY,
1814
+ altKey: !!e.altKey,
1815
+ ctrlKey: !!e.ctrlKey,
1816
+ shiftKey: !!e.shiftKey,
1817
+ metaKey: !!e.metaKey
1818
+ };
1819
+ const res = root.dispatchWheelEvent(payload);
1820
+ if (res?.defaultPrevented && typeof e.preventDefault === "function") {
1821
+ e.preventDefault();
1822
+ }
1823
+ },
1824
+ [props.width, props.height]
1825
+ );
1826
+ const handlePointerDown = react.useCallback(
1827
+ (e) => {
1828
+ const canvas = canvasRef.current;
1829
+ if (canvas && typeof canvas.setPointerCapture === "function") {
1830
+ try {
1831
+ canvas.setPointerCapture(e.pointerId);
1832
+ } catch (err) {
1833
+ }
1834
+ }
1835
+ dispatchPointer("pointerdown", e);
1836
+ },
1837
+ [dispatchPointer]
1838
+ );
1839
+ react.useLayoutEffect(() => {
1840
+ const canvas = canvasRef.current;
1841
+ if (!canvas) return;
1842
+ const listener = (e) => {
1843
+ handleWheel(e);
1844
+ };
1845
+ canvas.addEventListener("wheel", listener, { passive: false });
1846
+ return () => {
1847
+ canvas.removeEventListener("wheel", listener);
1848
+ };
1849
+ }, [handleWheel]);
1850
+ react.useLayoutEffect(() => {
1851
+ const canvas = canvasRef.current;
1852
+ if (!canvas) return;
1853
+ rootRef.current = createCanvasRoot(canvas, {
1854
+ width: props.width,
1855
+ height: props.height,
1856
+ dpr,
1857
+ clearColor: props.clearColor,
1858
+ fontFamily: props.fontFamily,
1859
+ fontSize: props.fontSize,
1860
+ fontWeight: props.fontWeight,
1861
+ lineHeight: props.lineHeight
1862
+ });
1863
+ return () => {
1864
+ rootRef.current?.unmount();
1865
+ rootRef.current = null;
1866
+ };
1867
+ }, [
1868
+ props.width,
1869
+ props.height,
1870
+ props.clearColor,
1871
+ props.fontFamily,
1872
+ props.fontSize,
1873
+ props.fontWeight,
1874
+ props.lineHeight,
1875
+ dpr
1876
+ ]);
1877
+ react.useLayoutEffect(() => {
1878
+ rootRef.current?.render(props.children ?? null);
1879
+ }, [props.children]);
1880
+ return /* @__PURE__ */ jsxRuntime.jsx(
1881
+ "canvas",
1882
+ {
1883
+ ref: canvasRef,
1884
+ style: canvasStyle,
1885
+ width: Math.floor(props.width * dpr),
1886
+ height: Math.floor(props.height * dpr),
1887
+ onPointerDown: handlePointerDown,
1888
+ onPointerMove: (e) => dispatchPointer("pointermove", e),
1889
+ onPointerUp: (e) => dispatchPointer("pointerup", e),
1890
+ onPointerCancel: (e) => dispatchPointer("pointercancel", e)
1891
+ }
1892
+ );
1893
+ }
1894
+ function View(props) {
1895
+ return react.createElement("View", props);
1896
+ }
1897
+ function Rect(props) {
1898
+ return react.createElement("Rect", props);
1899
+ }
1900
+ function Text(props) {
1901
+ return react.createElement("Text", props);
1902
+ }
1903
+ function Image2(props) {
1904
+ return react.createElement("Image", props);
1905
+ }
1906
+
1907
+ exports.Canvas = Canvas;
1908
+ exports.Image = Image2;
1909
+ exports.Rect = Rect;
1910
+ exports.Text = Text;
1911
+ exports.View = View;
1912
+ //# sourceMappingURL=index.cjs.map
1913
+ //# sourceMappingURL=index.cjs.map