@jiujue/react-canvas-fiber 1.1.1

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.js ADDED
@@ -0,0 +1,1443 @@
1
+ import { useRef, useMemo, useCallback, useLayoutEffect, createElement } from 'react';
2
+ import { FlexDirection, Align, Direction, loadYoga, MeasureMode, Edge, Justify, Wrap, PositionType, Gutter } from 'yoga-layout/load';
3
+ import Reconciler from 'react-reconciler';
4
+ import { DefaultEventPriority, LegacyRoot } from 'react-reconciler/constants';
5
+ import { jsx } from 'react/jsx-runtime';
6
+
7
+ // src/components/Canvas.tsx
8
+
9
+ // src/runtime/nodes.ts
10
+ var nextDebugId = 1;
11
+ function createRootNode() {
12
+ return {
13
+ type: "Root",
14
+ debugId: nextDebugId++,
15
+ parent: null,
16
+ children: [],
17
+ props: {},
18
+ layout: { x: 0, y: 0, width: 0, height: 0 },
19
+ yogaNode: null,
20
+ scrollLeft: 0,
21
+ scrollTop: 0,
22
+ scrollContentWidth: 0,
23
+ scrollContentHeight: 0,
24
+ scrollbarDrag: null
25
+ };
26
+ }
27
+ function createNode(type, props) {
28
+ return {
29
+ type,
30
+ debugId: nextDebugId++,
31
+ parent: null,
32
+ children: [],
33
+ props,
34
+ layout: { x: 0, y: 0, width: 0, height: 0 },
35
+ yogaNode: null,
36
+ scrollLeft: 0,
37
+ scrollTop: 0,
38
+ scrollContentWidth: 0,
39
+ scrollContentHeight: 0,
40
+ scrollbarDrag: null
41
+ };
42
+ }
43
+
44
+ // src/render/drawTree.ts
45
+ function resolveInheritedTextStyle(node, defaults) {
46
+ const own = node.props?.style ?? {};
47
+ let fontSize = own.fontSize;
48
+ let fontFamily = own.fontFamily;
49
+ let fontWeight = own.fontWeight;
50
+ let lineHeight = own.lineHeight;
51
+ let current = node.parent;
52
+ while (current && (fontSize == null || fontFamily == null || fontWeight == null || lineHeight == null)) {
53
+ const s = current.props?.style ?? {};
54
+ if (fontSize == null && typeof s.fontSize === "number") fontSize = s.fontSize;
55
+ if (fontFamily == null && typeof s.fontFamily === "string") fontFamily = s.fontFamily;
56
+ if (fontWeight == null && (typeof s.fontWeight === "number" || typeof s.fontWeight === "string"))
57
+ fontWeight = s.fontWeight;
58
+ if (lineHeight == null && typeof s.lineHeight === "number") lineHeight = s.lineHeight;
59
+ current = current.parent;
60
+ }
61
+ const resolvedFontSize = fontSize ?? defaults?.fontSize ?? 16;
62
+ const resolvedFontFamily = fontFamily ?? defaults?.fontFamily ?? "system-ui";
63
+ const resolvedFontWeight = fontWeight ?? defaults?.fontWeight ?? 400;
64
+ const resolvedLineHeight = lineHeight ?? defaults?.lineHeight ?? Math.round(resolvedFontSize * 1.2);
65
+ return {
66
+ fontSize: resolvedFontSize,
67
+ fontFamily: resolvedFontFamily,
68
+ fontWeight: resolvedFontWeight,
69
+ lineHeight: resolvedLineHeight,
70
+ font: `${resolvedFontWeight} ${resolvedFontSize}px ${resolvedFontFamily}`
71
+ };
72
+ }
73
+ function drawRoundedRect(ctx, x, y, w, h, r) {
74
+ const radius = Math.max(0, Math.min(r, Math.min(w, h) / 2));
75
+ ctx.beginPath();
76
+ ctx.moveTo(x + radius, y);
77
+ ctx.arcTo(x + w, y, x + w, y + h, radius);
78
+ ctx.arcTo(x + w, y + h, x, y + h, radius);
79
+ ctx.arcTo(x, y + h, x, y, radius);
80
+ ctx.arcTo(x, y, x + w, y, radius);
81
+ ctx.closePath();
82
+ }
83
+ function drawNode(state, node, offsetX, offsetY) {
84
+ const { ctx } = state;
85
+ const x = offsetX + node.layout.x;
86
+ const y = offsetY + node.layout.y;
87
+ const w = node.layout.width;
88
+ const h = node.layout.height;
89
+ if (node.type === "View") {
90
+ const background = node.props.background;
91
+ const scrollX = !!node.props?.scrollX;
92
+ const scrollY = !!node.props?.scrollY;
93
+ const scrollLeft = scrollX ? node.scrollLeft ?? 0 : 0;
94
+ const scrollTop = scrollY ? node.scrollTop ?? 0 : 0;
95
+ const scrollbarX = scrollX ? node.props?.scrollbarX !== false : false;
96
+ const scrollbarY = scrollY ? node.props?.scrollbarY !== false : false;
97
+ const scrollbarWidth = node.props?.scrollbarWidth ?? 10;
98
+ const scrollbarInset = node.props?.scrollbarInset ?? 6;
99
+ const scrollbarTrackColor = node.props?.scrollbarTrackColor ?? "rgba(255,255,255,0.12)";
100
+ const scrollbarThumbColor = node.props?.scrollbarThumbColor ?? "rgba(255,255,255,0.35)";
101
+ const contentWidth = node.scrollContentWidth ?? 0;
102
+ const contentHeight = node.scrollContentHeight ?? 0;
103
+ const maxScrollLeft = Math.max(0, contentWidth - w);
104
+ const maxScrollTop = Math.max(0, contentHeight - h);
105
+ if (background) {
106
+ const radius = node.props.borderRadius ?? 0;
107
+ ctx.save();
108
+ ctx.fillStyle = background;
109
+ drawRoundedRect(ctx, x, y, w, h, radius);
110
+ ctx.fill();
111
+ ctx.restore();
112
+ }
113
+ if (scrollX || scrollY) {
114
+ ctx.save();
115
+ ctx.beginPath();
116
+ ctx.rect(x, y, w, h);
117
+ ctx.clip();
118
+ for (const child of node.children) {
119
+ drawNode(state, child, x - scrollLeft, y - scrollTop);
120
+ }
121
+ ctx.restore();
122
+ const corner = scrollbarInset + scrollbarWidth;
123
+ if (scrollbarY && maxScrollTop > 0) {
124
+ const trackX = x + w - scrollbarInset - scrollbarWidth;
125
+ const trackY = y + scrollbarInset;
126
+ const trackH = Math.max(
127
+ 0,
128
+ h - scrollbarInset * 2 - (scrollbarX && maxScrollLeft > 0 ? corner : 0)
129
+ );
130
+ const minThumbH = 16;
131
+ const thumbH = contentHeight > 0 ? Math.max(minThumbH, Math.min(trackH, trackH * h / contentHeight)) : trackH;
132
+ const travel = Math.max(0, trackH - thumbH);
133
+ const thumbY = travel > 0 ? trackY + scrollTop / maxScrollTop * travel : trackY;
134
+ const radius = Math.min(6, scrollbarWidth / 2);
135
+ ctx.save();
136
+ ctx.fillStyle = scrollbarTrackColor;
137
+ drawRoundedRect(ctx, trackX, trackY, scrollbarWidth, trackH, radius);
138
+ ctx.fill();
139
+ ctx.fillStyle = scrollbarThumbColor;
140
+ drawRoundedRect(ctx, trackX, thumbY, scrollbarWidth, thumbH, radius);
141
+ ctx.fill();
142
+ ctx.restore();
143
+ }
144
+ if (scrollbarX && maxScrollLeft > 0) {
145
+ const trackX = x + scrollbarInset;
146
+ const trackY = y + h - scrollbarInset - scrollbarWidth;
147
+ const trackW = Math.max(
148
+ 0,
149
+ w - scrollbarInset * 2 - (scrollbarY && maxScrollTop > 0 ? corner : 0)
150
+ );
151
+ const minThumbW = 16;
152
+ const thumbW = contentWidth > 0 ? Math.max(minThumbW, Math.min(trackW, trackW * w / contentWidth)) : trackW;
153
+ const travel = Math.max(0, trackW - thumbW);
154
+ const thumbX = travel > 0 ? trackX + scrollLeft / maxScrollLeft * travel : trackX;
155
+ const radius = Math.min(6, scrollbarWidth / 2);
156
+ ctx.save();
157
+ ctx.fillStyle = scrollbarTrackColor;
158
+ drawRoundedRect(ctx, trackX, trackY, trackW, scrollbarWidth, radius);
159
+ ctx.fill();
160
+ ctx.fillStyle = scrollbarThumbColor;
161
+ drawRoundedRect(ctx, thumbX, trackY, thumbW, scrollbarWidth, radius);
162
+ ctx.fill();
163
+ ctx.restore();
164
+ }
165
+ return;
166
+ }
167
+ }
168
+ if (node.type === "Rect") {
169
+ const fill = node.props.fill ?? "#ffffff";
170
+ const stroke = node.props.stroke;
171
+ const lineWidth = node.props.lineWidth ?? 1;
172
+ const radius = node.props.borderRadius ?? 0;
173
+ ctx.save();
174
+ drawRoundedRect(ctx, x, y, w, h, radius);
175
+ if (fill) {
176
+ ctx.fillStyle = fill;
177
+ ctx.fill();
178
+ }
179
+ if (stroke) {
180
+ ctx.strokeStyle = stroke;
181
+ ctx.lineWidth = lineWidth;
182
+ ctx.stroke();
183
+ }
184
+ ctx.restore();
185
+ }
186
+ if (node.type === "Text") {
187
+ const text = node.props.text;
188
+ const color = node.props.color ?? "#ffffff";
189
+ const { font, lineHeight } = resolveInheritedTextStyle(node, state.defaults);
190
+ ctx.save();
191
+ ctx.font = font;
192
+ ctx.fillStyle = color;
193
+ ctx.textBaseline = "top";
194
+ const lines = String(text).split("\n");
195
+ for (let i = 0; i < lines.length; i += 1) {
196
+ ctx.fillText(lines[i], x, y + i * lineHeight);
197
+ }
198
+ ctx.restore();
199
+ }
200
+ for (const child of node.children) {
201
+ drawNode(state, child, x, y);
202
+ }
203
+ }
204
+ function drawTree(root, ctx, dpr, clearColor, defaults) {
205
+ const w = ctx.canvas.width;
206
+ const h = ctx.canvas.height;
207
+ ctx.save();
208
+ if (clearColor) {
209
+ ctx.fillStyle = clearColor;
210
+ ctx.fillRect(0, 0, w, h);
211
+ } else {
212
+ ctx.clearRect(0, 0, w, h);
213
+ }
214
+ ctx.restore();
215
+ ctx.save();
216
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
217
+ const state = { ctx, defaults };
218
+ for (const child of root.children) {
219
+ drawNode(state, child, 0, 0);
220
+ }
221
+ ctx.restore();
222
+ }
223
+
224
+ // src/utils/style.ts
225
+ function normalizeInsets(style) {
226
+ const paddingHorizontal = style?.paddingHorizontal ?? style?.padding ?? 0;
227
+ const paddingVertical = style?.paddingVertical ?? style?.padding ?? 0;
228
+ const marginHorizontal = style?.marginHorizontal ?? style?.margin ?? 0;
229
+ const marginVertical = style?.marginVertical ?? style?.margin ?? 0;
230
+ return {
231
+ paddingTop: style?.paddingTop ?? paddingVertical,
232
+ paddingRight: style?.paddingRight ?? paddingHorizontal,
233
+ paddingBottom: style?.paddingBottom ?? paddingVertical,
234
+ paddingLeft: style?.paddingLeft ?? paddingHorizontal,
235
+ marginTop: style?.marginTop ?? marginVertical,
236
+ marginRight: style?.marginRight ?? marginHorizontal,
237
+ marginBottom: style?.marginBottom ?? marginVertical,
238
+ marginLeft: style?.marginLeft ?? marginHorizontal
239
+ };
240
+ }
241
+
242
+ // src/layout/layoutTree.ts
243
+ var layoutEnginePromise = null;
244
+ function getLayoutEngine() {
245
+ if (!layoutEnginePromise) {
246
+ layoutEnginePromise = (async () => {
247
+ const yoga = await loadYoga();
248
+ return { yoga };
249
+ })();
250
+ }
251
+ return layoutEnginePromise;
252
+ }
253
+ function applyYogaStyle(node, style) {
254
+ const s = style ?? {};
255
+ if (typeof s.width === "number") node.setWidth(s.width);
256
+ if (typeof s.height === "number") node.setHeight(s.height);
257
+ if (typeof s.minWidth === "number") node.setMinWidth(s.minWidth);
258
+ if (typeof s.minHeight === "number") node.setMinHeight(s.minHeight);
259
+ if (typeof s.maxWidth === "number") node.setMaxWidth(s.maxWidth);
260
+ if (typeof s.maxHeight === "number") node.setMaxHeight(s.maxHeight);
261
+ const insets = normalizeInsets(s);
262
+ node.setPadding(Edge.Top, insets.paddingTop);
263
+ node.setPadding(Edge.Right, insets.paddingRight);
264
+ node.setPadding(Edge.Bottom, insets.paddingBottom);
265
+ node.setPadding(Edge.Left, insets.paddingLeft);
266
+ node.setMargin(Edge.Top, insets.marginTop);
267
+ node.setMargin(Edge.Right, insets.marginRight);
268
+ node.setMargin(Edge.Bottom, insets.marginBottom);
269
+ node.setMargin(Edge.Left, insets.marginLeft);
270
+ if (typeof s.flexGrow === "number") node.setFlexGrow(s.flexGrow);
271
+ if (typeof s.flexShrink === "number") node.setFlexShrink(s.flexShrink);
272
+ if (typeof s.flexBasis === "number") node.setFlexBasis(s.flexBasis);
273
+ if (s.flexDirection === "row") node.setFlexDirection(FlexDirection.Row);
274
+ if (s.flexDirection === "column") node.setFlexDirection(FlexDirection.Column);
275
+ if (s.justifyContent === "flex-start") node.setJustifyContent(Justify.FlexStart);
276
+ if (s.justifyContent === "center") node.setJustifyContent(Justify.Center);
277
+ if (s.justifyContent === "flex-end") node.setJustifyContent(Justify.FlexEnd);
278
+ if (s.justifyContent === "space-between") node.setJustifyContent(Justify.SpaceBetween);
279
+ if (s.justifyContent === "space-around") node.setJustifyContent(Justify.SpaceAround);
280
+ if (s.alignItems === "stretch") node.setAlignItems(Align.Stretch);
281
+ if (s.alignItems === "flex-start") node.setAlignItems(Align.FlexStart);
282
+ if (s.alignItems === "center") node.setAlignItems(Align.Center);
283
+ if (s.alignItems === "flex-end") node.setAlignItems(Align.FlexEnd);
284
+ if (s.alignContent === "stretch") node.setAlignContent(Align.Stretch);
285
+ if (s.alignContent === "flex-start") node.setAlignContent(Align.FlexStart);
286
+ if (s.alignContent === "center") node.setAlignContent(Align.Center);
287
+ if (s.alignContent === "flex-end") node.setAlignContent(Align.FlexEnd);
288
+ if (s.alignContent === "space-between") node.setAlignContent(Align.SpaceBetween);
289
+ if (s.alignContent === "space-around") node.setAlignContent(Align.SpaceAround);
290
+ if (s.flexWrap === "no-wrap") node.setFlexWrap(Wrap.NoWrap);
291
+ if (s.flexWrap === "wrap") node.setFlexWrap(Wrap.Wrap);
292
+ if (s.position === "absolute") node.setPositionType(PositionType.Absolute);
293
+ if (s.position === "relative") node.setPositionType(PositionType.Relative);
294
+ if (typeof s.top === "number") node.setPosition(Edge.Top, s.top);
295
+ if (typeof s.right === "number") node.setPosition(Edge.Right, s.right);
296
+ if (typeof s.bottom === "number") node.setPosition(Edge.Bottom, s.bottom);
297
+ if (typeof s.left === "number") node.setPosition(Edge.Left, s.left);
298
+ if (typeof s.gap === "number") {
299
+ node.setGap(Gutter.All, s.gap);
300
+ }
301
+ }
302
+ function resolveInheritedTextStyle2(textNode, defaults) {
303
+ const own = textNode.props.style ?? {};
304
+ let fontSize = own.fontSize;
305
+ let fontFamily = own.fontFamily;
306
+ let fontWeight = own.fontWeight;
307
+ let lineHeight = own.lineHeight;
308
+ let current = textNode.parent;
309
+ while (current && (fontSize == null || fontFamily == null || fontWeight == null || lineHeight == null)) {
310
+ const s = current.props?.style ?? {};
311
+ if (fontSize == null && typeof s.fontSize === "number") fontSize = s.fontSize;
312
+ if (fontFamily == null && typeof s.fontFamily === "string") fontFamily = s.fontFamily;
313
+ if (fontWeight == null && (typeof s.fontWeight === "number" || typeof s.fontWeight === "string"))
314
+ fontWeight = s.fontWeight;
315
+ if (lineHeight == null && typeof s.lineHeight === "number") lineHeight = s.lineHeight;
316
+ current = current.parent;
317
+ }
318
+ const resolvedFontSize = fontSize ?? defaults?.fontSize ?? 16;
319
+ const resolvedFontFamily = fontFamily ?? defaults?.fontFamily ?? "system-ui";
320
+ const resolvedFontWeight = fontWeight ?? defaults?.fontWeight ?? 400;
321
+ const resolvedLineHeight = lineHeight ?? defaults?.lineHeight ?? Math.round(resolvedFontSize * 1.2);
322
+ return {
323
+ fontSize: resolvedFontSize,
324
+ fontFamily: resolvedFontFamily,
325
+ fontWeight: resolvedFontWeight,
326
+ lineHeight: resolvedLineHeight,
327
+ font: `${resolvedFontWeight} ${resolvedFontSize}px ${resolvedFontFamily}`
328
+ };
329
+ }
330
+ function ensureYogaNode(engine, node, measureText, defaults) {
331
+ if (node.yogaNode) return;
332
+ const yogaNode = engine.yoga.Node.create();
333
+ node.yogaNode = yogaNode;
334
+ if (node.type === "Text") {
335
+ const textNode = node;
336
+ yogaNode.setMeasureFunc(
337
+ (width, widthMode, _height, _heightMode) => {
338
+ const { font, fontSize, lineHeight } = resolveInheritedTextStyle2(textNode, defaults);
339
+ const maxWidth = widthMode === MeasureMode.Undefined ? textNode.props.maxWidth : width;
340
+ const measured = measureText?.(textNode.props.text, font, maxWidth);
341
+ const w = measured?.width ?? Math.min(
342
+ textNode.props.text.length * fontSize,
343
+ maxWidth ?? textNode.props.text.length * fontSize
344
+ );
345
+ const lines = measured?.height ? Math.max(1, Math.round(measured.height / lineHeight)) : 1;
346
+ return { width: w, height: lines * lineHeight };
347
+ }
348
+ );
349
+ }
350
+ }
351
+ function syncYogaTree(engine, rootYoga, root, measureText, defaults) {
352
+ const detachAll = (yogaNode) => {
353
+ const count = yogaNode.getChildCount();
354
+ for (let i = count - 1; i >= 0; i -= 1) {
355
+ yogaNode.removeChild(yogaNode.getChild(i));
356
+ }
357
+ };
358
+ const clearLinks = (parent) => {
359
+ if (parent.yogaNode) detachAll(parent.yogaNode);
360
+ for (const child of parent.children ?? []) clearLinks(child);
361
+ };
362
+ clearLinks(root);
363
+ detachAll(rootYoga);
364
+ const visit = (parentYoga, parent) => {
365
+ for (let i = 0; i < parent.children.length; i += 1) {
366
+ const child = parent.children[i];
367
+ ensureYogaNode(engine, child, measureText, defaults);
368
+ const style = child.props?.style;
369
+ applyYogaStyle(child.yogaNode, style);
370
+ const currentParent = child.yogaNode.getParent?.();
371
+ if (currentParent && currentParent !== parentYoga) {
372
+ currentParent.removeChild(child.yogaNode);
373
+ }
374
+ parentYoga.insertChild(child.yogaNode, parentYoga.getChildCount());
375
+ if (child.children.length) {
376
+ visit(child.yogaNode, child);
377
+ }
378
+ }
379
+ };
380
+ visit(rootYoga, root);
381
+ }
382
+ function readComputedLayout(node) {
383
+ if (!node.yogaNode) return;
384
+ const layout = node.yogaNode.getComputedLayout();
385
+ node.layout = {
386
+ x: layout.left,
387
+ y: layout.top,
388
+ width: layout.width,
389
+ height: layout.height
390
+ };
391
+ }
392
+ async function layoutTree(root, width, height, measureText, defaults) {
393
+ const engine = await getLayoutEngine();
394
+ if (!root.yogaNode) {
395
+ root.yogaNode = engine.yoga.Node.create();
396
+ root.yogaNode.setWidth(width);
397
+ root.yogaNode.setHeight(height);
398
+ } else {
399
+ root.yogaNode.setWidth(width);
400
+ root.yogaNode.setHeight(height);
401
+ }
402
+ root.yogaNode.setFlexDirection(FlexDirection.Column);
403
+ root.yogaNode.setAlignItems(Align.Stretch);
404
+ syncYogaTree(engine, root.yogaNode, root, measureText, defaults);
405
+ root.yogaNode.calculateLayout(width, height, Direction.LTR);
406
+ const computeContentWidth = (node) => {
407
+ let maxRight = 0;
408
+ for (const child of node.children) {
409
+ maxRight = Math.max(maxRight, child.layout.x + child.layout.width);
410
+ }
411
+ return maxRight;
412
+ };
413
+ const computeContentHeight = (node) => {
414
+ let maxBottom = 0;
415
+ for (const child of node.children) {
416
+ maxBottom = Math.max(maxBottom, child.layout.y + child.layout.height);
417
+ }
418
+ return maxBottom;
419
+ };
420
+ const clampScrollTop = (node) => {
421
+ if (node.type !== "View") return;
422
+ if (!node.props?.scrollY) return;
423
+ const viewportH = node.layout.height;
424
+ const contentH = node.scrollContentHeight ?? 0;
425
+ const maxScrollTop = Math.max(0, contentH - viewportH);
426
+ if (typeof node.scrollTop !== "number") node.scrollTop = 0;
427
+ node.scrollTop = Math.max(0, Math.min(node.scrollTop, maxScrollTop));
428
+ };
429
+ const clampScrollLeft = (node) => {
430
+ if (node.type !== "View") return;
431
+ if (!node.props?.scrollX) return;
432
+ const viewportW = node.layout.width;
433
+ const contentW = node.scrollContentWidth ?? 0;
434
+ const maxScrollLeft = Math.max(0, contentW - viewportW);
435
+ if (typeof node.scrollLeft !== "number") node.scrollLeft = 0;
436
+ node.scrollLeft = Math.max(0, Math.min(node.scrollLeft, maxScrollLeft));
437
+ };
438
+ const walk = (parent) => {
439
+ for (const child of parent.children) {
440
+ readComputedLayout(child);
441
+ if (child.children.length) walk(child);
442
+ if (child.type === "View" && (child.props?.scrollY || child.props?.scrollX)) {
443
+ if (child.props?.scrollY) {
444
+ child.scrollContentHeight = computeContentHeight(child);
445
+ clampScrollTop(child);
446
+ }
447
+ if (child.props?.scrollX) {
448
+ child.scrollContentWidth = computeContentWidth(child);
449
+ clampScrollLeft(child);
450
+ }
451
+ }
452
+ }
453
+ };
454
+ walk(root);
455
+ }
456
+ function freeYogaSubtree(node) {
457
+ if (node.yogaNode) {
458
+ const parent = node.yogaNode.getParent?.();
459
+ if (parent) {
460
+ parent.removeChild(node.yogaNode);
461
+ }
462
+ node.yogaNode.freeRecursive();
463
+ }
464
+ const clearRefs = (target) => {
465
+ target.yogaNode = null;
466
+ for (const child of target.children) clearRefs(child);
467
+ };
468
+ clearRefs(node);
469
+ }
470
+ var hostConfig = {
471
+ now: Date.now,
472
+ supportsMutation: true,
473
+ isPrimaryRenderer: false,
474
+ scheduleTimeout: setTimeout,
475
+ cancelTimeout: clearTimeout,
476
+ noTimeout: -1,
477
+ supportsMicrotasks: true,
478
+ scheduleMicrotask: typeof queueMicrotask === "function" ? queueMicrotask : (cb) => void Promise.resolve().then(cb),
479
+ getCurrentEventPriority() {
480
+ return DefaultEventPriority;
481
+ },
482
+ getRootHostContext() {
483
+ return null;
484
+ },
485
+ getChildHostContext() {
486
+ return null;
487
+ },
488
+ shouldSetTextContent() {
489
+ return false;
490
+ },
491
+ createInstance(type, props) {
492
+ return createNode(type, props);
493
+ },
494
+ createTextInstance() {
495
+ throw new Error('Text instances are not supported. Use <Text text="..."/>.');
496
+ },
497
+ appendInitialChild(parent, child) {
498
+ hostConfig.appendChild(parent, child);
499
+ },
500
+ appendChild(parent, child) {
501
+ child.parent = parent;
502
+ parent.children.push(child);
503
+ },
504
+ appendChildToContainer(container, child) {
505
+ child.parent = null;
506
+ container.root.children.push(child);
507
+ container.invalidate();
508
+ },
509
+ insertBefore(parent, child, beforeChild) {
510
+ child.parent = parent;
511
+ const idx = parent.children.indexOf(beforeChild);
512
+ if (idx >= 0) parent.children.splice(idx, 0, child);
513
+ else parent.children.push(child);
514
+ },
515
+ insertInContainerBefore(container, child, beforeChild) {
516
+ child.parent = null;
517
+ const idx = container.root.children.indexOf(beforeChild);
518
+ if (idx >= 0) container.root.children.splice(idx, 0, child);
519
+ else container.root.children.push(child);
520
+ container.invalidate();
521
+ },
522
+ removeChild(parent, child) {
523
+ const idx = parent.children.indexOf(child);
524
+ if (idx >= 0) parent.children.splice(idx, 1);
525
+ child.parent = null;
526
+ freeYogaSubtree(child);
527
+ },
528
+ removeChildFromContainer(container, child) {
529
+ const idx = container.root.children.indexOf(child);
530
+ if (idx >= 0) container.root.children.splice(idx, 1);
531
+ child.parent = null;
532
+ freeYogaSubtree(child);
533
+ container.invalidate();
534
+ },
535
+ finalizeInitialChildren() {
536
+ return false;
537
+ },
538
+ prepareUpdate(_instance, _type, _oldProps, newProps) {
539
+ return newProps;
540
+ },
541
+ commitUpdate(instance, updatePayload) {
542
+ instance.props = updatePayload;
543
+ },
544
+ commitTextUpdate() {
545
+ },
546
+ resetTextContent() {
547
+ },
548
+ prepareForCommit() {
549
+ return null;
550
+ },
551
+ resetAfterCommit(container) {
552
+ container.invalidate();
553
+ container.notifyCommit?.();
554
+ },
555
+ clearContainer(container) {
556
+ container.root.children = [];
557
+ container.invalidate();
558
+ return false;
559
+ },
560
+ getPublicInstance(instance) {
561
+ return instance;
562
+ },
563
+ hideInstance() {
564
+ },
565
+ unhideInstance() {
566
+ },
567
+ hideTextInstance() {
568
+ },
569
+ unhideTextInstance() {
570
+ },
571
+ detachDeletedInstance() {
572
+ }
573
+ };
574
+ var CanvasReconciler = Reconciler(hostConfig);
575
+ function createReconcilerRoot(container) {
576
+ const root = CanvasReconciler.createContainer(
577
+ container,
578
+ LegacyRoot,
579
+ null,
580
+ false,
581
+ null,
582
+ "",
583
+ console.error,
584
+ null
585
+ );
586
+ return {
587
+ render(element) {
588
+ CanvasReconciler.updateContainer(element, root, null, () => {
589
+ });
590
+ },
591
+ unmount() {
592
+ CanvasReconciler.updateContainer(null, root, null, () => {
593
+ });
594
+ }
595
+ };
596
+ }
597
+
598
+ // src/runtime/root.ts
599
+ function createCanvasRoot(canvas, options) {
600
+ const ctx = canvas.getContext("2d");
601
+ if (!ctx) throw new Error("CanvasRenderingContext2D is not available");
602
+ const rootNode = createRootNode();
603
+ const commitSubscribers = /* @__PURE__ */ new Set();
604
+ const notifyCommit = () => {
605
+ for (const cb of commitSubscribers) cb();
606
+ };
607
+ let hoverId = null;
608
+ let selectedId = null;
609
+ const toCanvasPoint = (clientX, clientY) => {
610
+ const rect = canvas.getBoundingClientRect();
611
+ const scaleX = rect.width ? options.width / rect.width : 1;
612
+ const scaleY = rect.height ? options.height / rect.height : 1;
613
+ const x = (clientX - rect.left) * scaleX;
614
+ const y = (clientY - rect.top) * scaleY;
615
+ return { x, y };
616
+ };
617
+ const findNodeById = (id) => {
618
+ const walk = (node) => {
619
+ if (node.debugId === id) return node;
620
+ for (const child of node.children) {
621
+ const hit = walk(child);
622
+ if (hit) return hit;
623
+ }
624
+ return null;
625
+ };
626
+ for (const child of rootNode.children) {
627
+ const hit = walk(child);
628
+ if (hit) return hit;
629
+ }
630
+ return null;
631
+ };
632
+ const getAbsoluteRect = (node) => {
633
+ const path = [];
634
+ let current = node;
635
+ while (current) {
636
+ path.push(current);
637
+ current = current.parent;
638
+ }
639
+ let absLeft = 0;
640
+ let absTop = 0;
641
+ for (let i = path.length - 1; i >= 0; i -= 1) {
642
+ const n = path[i];
643
+ absLeft += n.layout.x;
644
+ absTop += n.layout.y;
645
+ const hasNext = i > 0;
646
+ if (hasNext && n.type === "View" && n.props?.scrollX) absLeft -= n.scrollLeft ?? 0;
647
+ if (hasNext && n.type === "View" && n.props?.scrollY) absTop -= n.scrollTop ?? 0;
648
+ }
649
+ return { x: absLeft, y: absTop, width: node.layout.width, height: node.layout.height };
650
+ };
651
+ const getScrollClipRects = (node) => {
652
+ const rects = [];
653
+ let current = node.parent;
654
+ while (current) {
655
+ if (current.type === "View") {
656
+ const scrollX = !!current.props?.scrollX;
657
+ const scrollY = !!current.props?.scrollY;
658
+ if (scrollX || scrollY) rects.push(getAbsoluteRect(current));
659
+ }
660
+ current = current.parent;
661
+ }
662
+ return rects.reverse();
663
+ };
664
+ const serializeValue = (value, depth, seen) => {
665
+ if (depth > 6) return "[MaxDepth]";
666
+ if (value == null) return value;
667
+ const t = typeof value;
668
+ if (t === "string" || t === "number" || t === "boolean") return value;
669
+ if (t === "bigint") return String(value);
670
+ if (t === "symbol") return String(value);
671
+ if (t === "function") return "[Function]";
672
+ if (t !== "object") return String(value);
673
+ if (seen.has(value)) return "[Circular]";
674
+ seen.add(value);
675
+ if (Array.isArray(value)) {
676
+ const next = [];
677
+ const len = Math.min(value.length, 50);
678
+ for (let i = 0; i < len; i += 1) next.push(serializeValue(value[i], depth + 1, seen));
679
+ if (value.length > len) next.push(`[+${value.length - len}]`);
680
+ return next;
681
+ }
682
+ const out = {};
683
+ const keys = Object.keys(value).slice(0, 80);
684
+ for (const k of keys) out[k] = serializeValue(value[k], depth + 1, seen);
685
+ const allKeys = Object.keys(value);
686
+ if (allKeys.length > keys.length) out.__moreKeys = allKeys.length - keys.length;
687
+ return out;
688
+ };
689
+ let frameId = null;
690
+ let dirty = true;
691
+ const measureText = (text, font, maxWidth) => {
692
+ ctx.save();
693
+ ctx.font = font;
694
+ const metrics = ctx.measureText(text);
695
+ ctx.restore();
696
+ const width = typeof maxWidth === "number" ? Math.min(metrics.width, maxWidth) : metrics.width;
697
+ const height = Math.ceil(
698
+ metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent || 0
699
+ );
700
+ return { width, height: Math.max(1, height) };
701
+ };
702
+ const invalidate = () => {
703
+ dirty = true;
704
+ if (frameId != null) return;
705
+ frameId = requestAnimationFrame(async () => {
706
+ frameId = null;
707
+ if (!dirty) return;
708
+ dirty = false;
709
+ await layoutTree(rootNode, options.width, options.height, measureText, options);
710
+ drawTree(rootNode, ctx, options.dpr, options.clearColor, options);
711
+ const overlayHover = typeof hoverId === "number" ? findNodeById(hoverId) : null;
712
+ const overlaySelected = typeof selectedId === "number" ? findNodeById(selectedId) : null;
713
+ if (overlayHover || overlaySelected) {
714
+ ctx.save();
715
+ ctx.setTransform(options.dpr, 0, 0, options.dpr, 0, 0);
716
+ if (overlayHover && (!overlaySelected || overlayHover.debugId !== overlaySelected.debugId)) {
717
+ const r = getAbsoluteRect(overlayHover);
718
+ ctx.save();
719
+ for (const clip of getScrollClipRects(overlayHover)) {
720
+ ctx.beginPath();
721
+ ctx.rect(clip.x, clip.y, clip.width, clip.height);
722
+ ctx.clip();
723
+ }
724
+ ctx.fillStyle = "rgba(59,130,246,0.12)";
725
+ ctx.strokeStyle = "rgba(59,130,246,0.9)";
726
+ ctx.lineWidth = 1;
727
+ ctx.fillRect(r.x, r.y, r.width, r.height);
728
+ ctx.strokeRect(r.x + 0.5, r.y + 0.5, Math.max(0, r.width - 1), Math.max(0, r.height - 1));
729
+ ctx.restore();
730
+ }
731
+ if (overlaySelected) {
732
+ const r = getAbsoluteRect(overlaySelected);
733
+ ctx.save();
734
+ for (const clip of getScrollClipRects(overlaySelected)) {
735
+ ctx.beginPath();
736
+ ctx.rect(clip.x, clip.y, clip.width, clip.height);
737
+ ctx.clip();
738
+ }
739
+ ctx.fillStyle = "rgba(16,185,129,0.12)";
740
+ ctx.strokeStyle = "rgba(16,185,129,0.95)";
741
+ ctx.lineWidth = 2;
742
+ ctx.fillRect(r.x, r.y, r.width, r.height);
743
+ ctx.strokeRect(r.x + 1, r.y + 1, Math.max(0, r.width - 2), Math.max(0, r.height - 2));
744
+ ctx.restore();
745
+ }
746
+ ctx.restore();
747
+ }
748
+ });
749
+ };
750
+ const getScrollbarMetricsY = (view, left, top) => {
751
+ if (view.type !== "View") return null;
752
+ if (!view.props?.scrollY) return null;
753
+ if (view.props?.scrollbarY === false) return null;
754
+ const w = view.layout.width;
755
+ const h = view.layout.height;
756
+ const contentHeight = view.scrollContentHeight ?? 0;
757
+ const maxScrollTop = Math.max(0, contentHeight - h);
758
+ if (maxScrollTop <= 0) return null;
759
+ const scrollbarWidth = view.props?.scrollbarWidth ?? 10;
760
+ const scrollbarInset = view.props?.scrollbarInset ?? 6;
761
+ const corner = scrollbarInset + scrollbarWidth;
762
+ const contentWidth = view.scrollContentWidth ?? 0;
763
+ const maxScrollLeft = Math.max(0, contentWidth - w);
764
+ const hasScrollX = !!view.props?.scrollX && view.props?.scrollbarX !== false && maxScrollLeft > 0;
765
+ const trackX = left + w - scrollbarInset - scrollbarWidth;
766
+ const trackY = top + scrollbarInset;
767
+ const trackH = Math.max(0, h - scrollbarInset * 2 - (hasScrollX ? corner : 0));
768
+ if (trackH <= 0) return null;
769
+ const minThumbH = 16;
770
+ const thumbH = contentHeight > 0 ? Math.max(minThumbH, Math.min(trackH, trackH * h / contentHeight)) : trackH;
771
+ const travel = Math.max(0, trackH - thumbH);
772
+ const scrollTop = view.scrollTop ?? 0;
773
+ const thumbY = travel > 0 ? trackY + scrollTop / maxScrollTop * travel : trackY;
774
+ return { maxScrollTop, trackX, trackY, trackH, thumbY, thumbH };
775
+ };
776
+ const getScrollbarMetricsX = (view, left, top) => {
777
+ if (view.type !== "View") return null;
778
+ if (!view.props?.scrollX) return null;
779
+ if (view.props?.scrollbarX === false) return null;
780
+ const w = view.layout.width;
781
+ const h = view.layout.height;
782
+ const contentWidth = view.scrollContentWidth ?? 0;
783
+ const maxScrollLeft = Math.max(0, contentWidth - w);
784
+ if (maxScrollLeft <= 0) return null;
785
+ const scrollbarWidth = view.props?.scrollbarWidth ?? 10;
786
+ const scrollbarInset = view.props?.scrollbarInset ?? 6;
787
+ const corner = scrollbarInset + scrollbarWidth;
788
+ const contentHeight = view.scrollContentHeight ?? 0;
789
+ const maxScrollTop = Math.max(0, contentHeight - h);
790
+ const hasScrollY = !!view.props?.scrollY && view.props?.scrollbarY !== false && maxScrollTop > 0;
791
+ const trackX = left + scrollbarInset;
792
+ const trackY = top + h - scrollbarInset - scrollbarWidth;
793
+ const trackW = Math.max(0, w - scrollbarInset * 2 - (hasScrollY ? corner : 0));
794
+ if (trackW <= 0) return null;
795
+ const minThumbW = 16;
796
+ const thumbW = contentWidth > 0 ? Math.max(minThumbW, Math.min(trackW, trackW * w / contentWidth)) : trackW;
797
+ const travel = Math.max(0, trackW - thumbW);
798
+ const scrollLeft = view.scrollLeft ?? 0;
799
+ const thumbX = travel > 0 ? trackX + scrollLeft / maxScrollLeft * travel : trackX;
800
+ return { maxScrollLeft, trackX, trackY, trackW, thumbX, thumbW };
801
+ };
802
+ const hitTestNode = (node, x, y, offsetX, offsetY) => {
803
+ if (node.props?.pointerEvents === "none") return null;
804
+ const left = offsetX + node.layout.x;
805
+ const top = offsetY + node.layout.y;
806
+ const right = left + node.layout.width;
807
+ const bottom = top + node.layout.height;
808
+ if (x < left || x > right || y < top || y > bottom) return null;
809
+ const childOffsetX = node.type === "View" && node.props?.scrollX ? left - (node.scrollLeft ?? 0) : left;
810
+ const childOffsetY = node.type === "View" && node.props?.scrollY ? top - (node.scrollTop ?? 0) : top;
811
+ for (let i = node.children.length - 1; i >= 0; i -= 1) {
812
+ const child = node.children[i];
813
+ const hit = hitTestNode(child, x, y, childOffsetX, childOffsetY);
814
+ if (hit) return hit;
815
+ }
816
+ return node;
817
+ };
818
+ const hitTestScrollbarThumbNode = (node, x, y, offsetX, offsetY) => {
819
+ if (node.props?.pointerEvents === "none") return null;
820
+ const left = offsetX + node.layout.x;
821
+ const top = offsetY + node.layout.y;
822
+ const right = left + node.layout.width;
823
+ const bottom = top + node.layout.height;
824
+ if (x < left || x > right || y < top || y > bottom) return null;
825
+ const metricsY = getScrollbarMetricsY(node, left, top);
826
+ if (metricsY) {
827
+ const { trackX, thumbY, thumbH } = metricsY;
828
+ const thumbLeft = trackX;
829
+ const thumbRight = trackX + (node.props?.scrollbarWidth ?? 10);
830
+ const thumbTop = thumbY;
831
+ const thumbBottom = thumbY + thumbH;
832
+ if (x >= thumbLeft && x <= thumbRight && y >= thumbTop && y <= thumbBottom) {
833
+ return { view: node, axis: "y" };
834
+ }
835
+ }
836
+ const metricsX = getScrollbarMetricsX(node, left, top);
837
+ if (metricsX) {
838
+ const { trackY, thumbX, thumbW } = metricsX;
839
+ const thumbLeft = thumbX;
840
+ const thumbRight = thumbX + thumbW;
841
+ const thumbTop = trackY;
842
+ const thumbBottom = trackY + (node.props?.scrollbarWidth ?? 10);
843
+ if (x >= thumbLeft && x <= thumbRight && y >= thumbTop && y <= thumbBottom) {
844
+ return { view: node, axis: "x" };
845
+ }
846
+ }
847
+ const childOffsetX = node.type === "View" && node.props?.scrollX ? left - (node.scrollLeft ?? 0) : left;
848
+ const childOffsetY = node.type === "View" && node.props?.scrollY ? top - (node.scrollTop ?? 0) : top;
849
+ for (let i = node.children.length - 1; i >= 0; i -= 1) {
850
+ const child = node.children[i];
851
+ const hit = hitTestScrollbarThumbNode(child, x, y, childOffsetX, childOffsetY);
852
+ if (hit) return hit;
853
+ }
854
+ return null;
855
+ };
856
+ const hitTestScrollbarThumb = (x, y) => {
857
+ for (let i = rootNode.children.length - 1; i >= 0; i -= 1) {
858
+ const child = rootNode.children[i];
859
+ const hit = hitTestScrollbarThumbNode(child, x, y, 0, 0);
860
+ if (hit) return hit;
861
+ }
862
+ return null;
863
+ };
864
+ const hitTest = (x, y) => {
865
+ for (let i = rootNode.children.length - 1; i >= 0; i -= 1) {
866
+ const child = rootNode.children[i];
867
+ const hit = hitTestNode(child, x, y, 0, 0);
868
+ if (hit) return hit;
869
+ }
870
+ return null;
871
+ };
872
+ const buildPath = (target) => {
873
+ const path = [];
874
+ let current = target;
875
+ while (current) {
876
+ path.push(current);
877
+ current = current.parent;
878
+ }
879
+ return path;
880
+ };
881
+ const pointerCapture = /* @__PURE__ */ new Map();
882
+ const pointerDownTarget = /* @__PURE__ */ new Map();
883
+ const clamp = (value, min, max) => Math.max(min, Math.min(value, max));
884
+ const applyScrollY = (node, delta) => {
885
+ if (node.type !== "View") return false;
886
+ if (!node.props?.scrollY) return false;
887
+ const contentHeight = node.scrollContentHeight ?? 0;
888
+ const maxScrollTop = Math.max(0, contentHeight - node.layout.height);
889
+ if (maxScrollTop <= 0) return false;
890
+ const current = node.scrollTop ?? 0;
891
+ const next = clamp(current + delta, 0, maxScrollTop);
892
+ if (next === current) return false;
893
+ node.scrollTop = next;
894
+ const onScroll = node.props?.onScroll;
895
+ if (typeof onScroll === "function") onScroll(next);
896
+ return true;
897
+ };
898
+ const applyScrollX = (node, delta) => {
899
+ if (node.type !== "View") return false;
900
+ if (!node.props?.scrollX) return false;
901
+ const contentWidth = node.scrollContentWidth ?? 0;
902
+ const maxScrollLeft = Math.max(0, contentWidth - node.layout.width);
903
+ if (maxScrollLeft <= 0) return false;
904
+ const current = node.scrollLeft ?? 0;
905
+ const next = clamp(current + delta, 0, maxScrollLeft);
906
+ if (next === current) return false;
907
+ node.scrollLeft = next;
908
+ const onScrollX = node.props?.onScrollX;
909
+ if (typeof onScrollX === "function") onScrollX(next);
910
+ return true;
911
+ };
912
+ const dispatchOnPath = (eventType, path, init, target) => {
913
+ const baseName = eventType === "pointerdown" ? "onPointerDown" : eventType === "pointermove" ? "onPointerMove" : eventType === "pointerup" ? "onPointerUp" : eventType === "pointercancel" ? "onPointerCancel" : "onClick";
914
+ let propagationStopped = false;
915
+ let defaultPrevented = false;
916
+ const eventObject = {
917
+ type: eventType,
918
+ ...init,
919
+ target,
920
+ currentTarget: null,
921
+ defaultPrevented,
922
+ stopPropagation() {
923
+ propagationStopped = true;
924
+ },
925
+ preventDefault() {
926
+ defaultPrevented = true;
927
+ eventObject.defaultPrevented = true;
928
+ }
929
+ };
930
+ for (let i = path.length - 1; i >= 0; i -= 1) {
931
+ if (propagationStopped) break;
932
+ const currentTarget = path[i];
933
+ const handler = currentTarget.props?.[`${baseName}Capture`];
934
+ if (typeof handler === "function") {
935
+ eventObject.currentTarget = currentTarget;
936
+ handler(eventObject);
937
+ }
938
+ }
939
+ for (let i = 0; i < path.length; i += 1) {
940
+ if (propagationStopped) break;
941
+ const currentTarget = path[i];
942
+ const handler = currentTarget.props?.[baseName];
943
+ if (typeof handler === "function") {
944
+ eventObject.currentTarget = currentTarget;
945
+ handler(eventObject);
946
+ }
947
+ }
948
+ return { defaultPrevented };
949
+ };
950
+ const dispatchPointerEvent = (eventType, init) => {
951
+ const { pointerId } = init;
952
+ const capturedForScroll = pointerCapture.get(pointerId);
953
+ if (capturedForScroll && capturedForScroll.type === "View" && capturedForScroll.scrollbarDrag && capturedForScroll.scrollbarDrag.pointerId === pointerId) {
954
+ if (eventType === "pointermove") {
955
+ const drag = capturedForScroll.scrollbarDrag;
956
+ const path = buildPath(capturedForScroll);
957
+ let absLeft = 0;
958
+ let absTop = 0;
959
+ for (let i = path.length - 1; i >= 0; i -= 1) {
960
+ const n = path[i];
961
+ absLeft += n.layout.x;
962
+ absTop += n.layout.y;
963
+ const hasNext = i > 0;
964
+ if (hasNext && n.type === "View" && n.props?.scrollX)
965
+ absLeft -= n.scrollLeft ?? 0;
966
+ if (hasNext && n.type === "View" && n.props?.scrollY) absTop -= n.scrollTop ?? 0;
967
+ }
968
+ if (drag.axis === "y") {
969
+ const metrics2 = getScrollbarMetricsY(capturedForScroll, absLeft, absTop);
970
+ if (!metrics2) return { defaultPrevented: true };
971
+ const travel2 = Math.max(0, metrics2.trackH - metrics2.thumbH);
972
+ if (travel2 <= 0) return { defaultPrevented: true };
973
+ const deltaThumb2 = init.y - drag.startPointer;
974
+ const nextScrollTop = drag.startScroll + deltaThumb2 * (metrics2.maxScrollTop / travel2);
975
+ const clamped2 = Math.max(0, Math.min(nextScrollTop, metrics2.maxScrollTop));
976
+ capturedForScroll.scrollTop = clamped2;
977
+ const onScroll = capturedForScroll.props?.onScroll;
978
+ if (typeof onScroll === "function") onScroll(clamped2);
979
+ invalidate();
980
+ return { defaultPrevented: true };
981
+ }
982
+ const metrics = getScrollbarMetricsX(capturedForScroll, absLeft, absTop);
983
+ if (!metrics) return { defaultPrevented: true };
984
+ const travel = Math.max(0, metrics.trackW - metrics.thumbW);
985
+ if (travel <= 0) return { defaultPrevented: true };
986
+ const deltaThumb = init.x - drag.startPointer;
987
+ const nextScrollLeft = drag.startScroll + deltaThumb * (metrics.maxScrollLeft / travel);
988
+ const clamped = Math.max(0, Math.min(nextScrollLeft, metrics.maxScrollLeft));
989
+ capturedForScroll.scrollLeft = clamped;
990
+ const onScrollX = capturedForScroll.props?.onScrollX;
991
+ if (typeof onScrollX === "function") onScrollX(clamped);
992
+ invalidate();
993
+ return { defaultPrevented: true };
994
+ }
995
+ if (eventType === "pointerup" || eventType === "pointercancel") {
996
+ capturedForScroll.scrollbarDrag = null;
997
+ pointerCapture.delete(pointerId);
998
+ pointerDownTarget.delete(pointerId);
999
+ invalidate();
1000
+ return { defaultPrevented: true };
1001
+ }
1002
+ }
1003
+ if (eventType === "pointerdown") {
1004
+ const scrollbarTarget = hitTestScrollbarThumb(init.x, init.y);
1005
+ if (scrollbarTarget && scrollbarTarget.view.type === "View") {
1006
+ scrollbarTarget.view.scrollbarDrag = {
1007
+ axis: scrollbarTarget.axis,
1008
+ pointerId,
1009
+ startPointer: scrollbarTarget.axis === "y" ? init.y : init.x,
1010
+ startScroll: scrollbarTarget.axis === "y" ? scrollbarTarget.view.scrollTop ?? 0 : scrollbarTarget.view.scrollLeft ?? 0
1011
+ };
1012
+ pointerCapture.set(pointerId, scrollbarTarget.view);
1013
+ return { defaultPrevented: true };
1014
+ }
1015
+ const target2 = hitTest(init.x, init.y);
1016
+ if (!target2) return { defaultPrevented: false };
1017
+ pointerCapture.set(pointerId, target2);
1018
+ pointerDownTarget.set(pointerId, target2);
1019
+ return dispatchOnPath(eventType, buildPath(target2), init, target2);
1020
+ }
1021
+ if (eventType === "pointermove") {
1022
+ const captured = pointerCapture.get(pointerId);
1023
+ const target2 = captured ?? hitTest(init.x, init.y);
1024
+ if (!target2) return { defaultPrevented: false };
1025
+ return dispatchOnPath(eventType, buildPath(target2), init, target2);
1026
+ }
1027
+ if (eventType === "pointerup") {
1028
+ const captured = pointerCapture.get(pointerId);
1029
+ const target2 = captured ?? hitTest(init.x, init.y);
1030
+ if (!target2) return { defaultPrevented: false };
1031
+ const res = dispatchOnPath(eventType, buildPath(target2), init, target2);
1032
+ const downTarget = pointerDownTarget.get(pointerId);
1033
+ pointerCapture.delete(pointerId);
1034
+ pointerDownTarget.delete(pointerId);
1035
+ if (downTarget && downTarget === target2 && init.button === 0) {
1036
+ const clickRes = dispatchOnPath("click", buildPath(target2), init, target2);
1037
+ return { defaultPrevented: res.defaultPrevented || clickRes.defaultPrevented };
1038
+ }
1039
+ return res;
1040
+ }
1041
+ if (eventType === "pointercancel") {
1042
+ const captured = pointerCapture.get(pointerId);
1043
+ const target2 = captured ?? hitTest(init.x, init.y);
1044
+ if (!target2) return { defaultPrevented: false };
1045
+ const res = dispatchOnPath(eventType, buildPath(target2), init, target2);
1046
+ pointerCapture.delete(pointerId);
1047
+ pointerDownTarget.delete(pointerId);
1048
+ return res;
1049
+ }
1050
+ const target = hitTest(init.x, init.y);
1051
+ if (!target) return { defaultPrevented: false };
1052
+ return dispatchOnPath(eventType, buildPath(target), init, target);
1053
+ };
1054
+ const dispatchWheelEvent = (init) => {
1055
+ const target = hitTest(init.x, init.y);
1056
+ if (!target) return { defaultPrevented: false };
1057
+ const path = buildPath(target);
1058
+ let remainingX = init.deltaX;
1059
+ let remainingY = init.deltaY;
1060
+ let defaultPrevented = false;
1061
+ for (let i = 0; i < path.length; i += 1) {
1062
+ const node = path[i];
1063
+ if (remainingY !== 0) {
1064
+ const applied = applyScrollY(node, remainingY);
1065
+ if (applied) {
1066
+ remainingY = 0;
1067
+ defaultPrevented = true;
1068
+ }
1069
+ }
1070
+ if (remainingX !== 0) {
1071
+ const applied = applyScrollX(node, remainingX);
1072
+ if (applied) {
1073
+ remainingX = 0;
1074
+ defaultPrevented = true;
1075
+ }
1076
+ }
1077
+ if (remainingX === 0 && remainingY === 0) break;
1078
+ }
1079
+ if (defaultPrevented) invalidate();
1080
+ return { defaultPrevented };
1081
+ };
1082
+ const container = { root: rootNode, invalidate, notifyCommit };
1083
+ const reconcilerRoot = createReconcilerRoot(container);
1084
+ invalidate();
1085
+ const __devtools = {
1086
+ rootId: rootNode.debugId,
1087
+ getSnapshot() {
1088
+ const nodesById = {};
1089
+ const walk = (node, parentId) => {
1090
+ nodesById[node.debugId] = {
1091
+ id: node.debugId,
1092
+ type: node.type,
1093
+ parentId,
1094
+ childrenIds: node.children.map((c) => c.debugId),
1095
+ layout: node.layout
1096
+ };
1097
+ for (const child of node.children) walk(child, node.debugId);
1098
+ };
1099
+ for (const child of rootNode.children) walk(child, null);
1100
+ return {
1101
+ rootId: rootNode.debugId,
1102
+ rootChildrenIds: rootNode.children.map((c) => c.debugId),
1103
+ nodesById
1104
+ };
1105
+ },
1106
+ getNode(id) {
1107
+ const node = findNodeById(id);
1108
+ if (!node) return null;
1109
+ return {
1110
+ id: node.debugId,
1111
+ type: node.type,
1112
+ parentId: node.parent ? node.parent.debugId : null,
1113
+ childrenIds: node.children.map((c) => c.debugId),
1114
+ layout: node.layout
1115
+ };
1116
+ },
1117
+ getNodeProps(id) {
1118
+ const node = findNodeById(id);
1119
+ if (!node) return null;
1120
+ return serializeValue(node.props, 0, /* @__PURE__ */ new WeakSet());
1121
+ },
1122
+ hitTestCanvas(x, y) {
1123
+ const node = hitTest(x, y);
1124
+ return node ? node.debugId : null;
1125
+ },
1126
+ hitTestClient(clientX, clientY) {
1127
+ const { x, y } = toCanvasPoint(clientX, clientY);
1128
+ const node = hitTest(x, y);
1129
+ return node ? node.debugId : null;
1130
+ },
1131
+ setHighlight(next) {
1132
+ if ("hoverId" in next) hoverId = next.hoverId ?? null;
1133
+ if ("selectedId" in next) selectedId = next.selectedId ?? null;
1134
+ invalidate();
1135
+ },
1136
+ subscribe(cb) {
1137
+ commitSubscribers.add(cb);
1138
+ return () => {
1139
+ commitSubscribers.delete(cb);
1140
+ };
1141
+ }
1142
+ };
1143
+ const registryRootInstanceId = (() => {
1144
+ if (typeof window === "undefined") return null;
1145
+ const w = window;
1146
+ const key = "__REACT_CANVAS_FIBER_DEVTOOLS__";
1147
+ if (!w[key]) {
1148
+ w[key] = {
1149
+ version: 1,
1150
+ nextRootInstanceId: 1,
1151
+ roots: /* @__PURE__ */ new Map(),
1152
+ picker: {
1153
+ enabled: false,
1154
+ rootInstanceId: null,
1155
+ hoverId: null,
1156
+ selectedId: null
1157
+ },
1158
+ pickerCleanup: null,
1159
+ listRoots() {
1160
+ const res = [];
1161
+ for (const r of this.roots.values()) {
1162
+ const rect = r.canvas.getBoundingClientRect();
1163
+ res.push({
1164
+ id: r.id,
1165
+ rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height },
1166
+ options: r.options,
1167
+ revision: r.revision
1168
+ });
1169
+ }
1170
+ return res;
1171
+ },
1172
+ getRootDevtools(id) {
1173
+ return this.roots.get(id)?.devtools ?? null;
1174
+ },
1175
+ getSnapshot(rootInstanceId2) {
1176
+ return this.getRootDevtools(rootInstanceId2)?.getSnapshot?.() ?? null;
1177
+ },
1178
+ getNodeProps(rootInstanceId2, nodeId) {
1179
+ return this.getRootDevtools(rootInstanceId2)?.getNodeProps?.(nodeId) ?? null;
1180
+ },
1181
+ setHighlight(rootInstanceId2, next) {
1182
+ this.getRootDevtools(rootInstanceId2)?.setHighlight?.(next);
1183
+ },
1184
+ startPicker(rootInstanceId2) {
1185
+ this.stopPicker();
1186
+ const handle = this.roots.get(rootInstanceId2);
1187
+ if (!handle) return false;
1188
+ this.picker.enabled = true;
1189
+ this.picker.rootInstanceId = rootInstanceId2;
1190
+ this.picker.hoverId = null;
1191
+ this.picker.selectedId = null;
1192
+ const onMove = (e) => {
1193
+ if (!this.picker.enabled) return;
1194
+ if (e.target !== handle.canvas) {
1195
+ if (this.picker.hoverId != null) {
1196
+ this.picker.hoverId = null;
1197
+ handle.devtools.setHighlight({ hoverId: null });
1198
+ }
1199
+ return;
1200
+ }
1201
+ const id = handle.devtools.hitTestClient(e.clientX, e.clientY);
1202
+ if (id !== this.picker.hoverId) {
1203
+ this.picker.hoverId = id;
1204
+ handle.devtools.setHighlight({ hoverId: id, selectedId: this.picker.selectedId });
1205
+ }
1206
+ };
1207
+ const onDown = (e) => {
1208
+ if (!this.picker.enabled) return;
1209
+ if (e.target !== handle.canvas) return;
1210
+ if (e.button !== 0) return;
1211
+ const id = handle.devtools.hitTestClient(e.clientX, e.clientY);
1212
+ this.picker.selectedId = id;
1213
+ handle.devtools.setHighlight({ hoverId: null, selectedId: id });
1214
+ e.preventDefault();
1215
+ e.stopPropagation();
1216
+ this.stopPicker();
1217
+ };
1218
+ const onKeyDown = (e) => {
1219
+ if (e.key !== "Escape") return;
1220
+ this.stopPicker();
1221
+ };
1222
+ window.addEventListener("pointermove", onMove, true);
1223
+ window.addEventListener("pointerdown", onDown, true);
1224
+ window.addEventListener("keydown", onKeyDown, true);
1225
+ this.pickerCleanup = () => {
1226
+ window.removeEventListener("pointermove", onMove, true);
1227
+ window.removeEventListener("pointerdown", onDown, true);
1228
+ window.removeEventListener("keydown", onKeyDown, true);
1229
+ handle.devtools.setHighlight({ hoverId: null });
1230
+ };
1231
+ return true;
1232
+ },
1233
+ stopPicker() {
1234
+ if (!this.picker.enabled) return;
1235
+ this.picker.enabled = false;
1236
+ this.picker.rootInstanceId = null;
1237
+ this.picker.hoverId = null;
1238
+ this.picker.selectedId = null;
1239
+ this.pickerCleanup?.();
1240
+ this.pickerCleanup = null;
1241
+ },
1242
+ getPickerState() {
1243
+ return { ...this.picker };
1244
+ },
1245
+ unregisterRoot(id) {
1246
+ const handle = this.roots.get(id);
1247
+ if (!handle) return;
1248
+ if (this.picker.rootInstanceId === id) this.stopPicker();
1249
+ handle.unsubscribe?.();
1250
+ this.roots.delete(id);
1251
+ }
1252
+ };
1253
+ }
1254
+ const registry = w[key];
1255
+ const rootInstanceId = registry.nextRootInstanceId++;
1256
+ const entry = {
1257
+ id: rootInstanceId,
1258
+ canvas,
1259
+ options: { width: options.width, height: options.height, dpr: options.dpr },
1260
+ devtools: __devtools,
1261
+ revision: 0,
1262
+ unsubscribe: null
1263
+ };
1264
+ entry.unsubscribe = __devtools.subscribe(() => {
1265
+ entry.revision += 1;
1266
+ });
1267
+ registry.roots.set(rootInstanceId, entry);
1268
+ return rootInstanceId;
1269
+ })();
1270
+ return {
1271
+ render(element) {
1272
+ reconcilerRoot.render(element);
1273
+ },
1274
+ unmount() {
1275
+ reconcilerRoot.unmount();
1276
+ if (typeof window !== "undefined" && registryRootInstanceId != null) {
1277
+ const w = window;
1278
+ w.__REACT_CANVAS_FIBER_DEVTOOLS__?.unregisterRoot?.(registryRootInstanceId);
1279
+ }
1280
+ },
1281
+ dispatchPointerEvent,
1282
+ dispatchWheelEvent,
1283
+ __devtools
1284
+ };
1285
+ }
1286
+ function Canvas(props) {
1287
+ const canvasRef = useRef(null);
1288
+ const rootRef = useRef(null);
1289
+ const dpr = props.dpr ?? 1;
1290
+ const canvasStyle = useMemo(
1291
+ () => ({
1292
+ width: props.width,
1293
+ height: props.height,
1294
+ display: "block",
1295
+ touchAction: "none",
1296
+ ...props.style
1297
+ }),
1298
+ [props.width, props.height, props.style]
1299
+ );
1300
+ const dispatchPointer = useCallback(
1301
+ (type, e) => {
1302
+ const canvas = canvasRef.current;
1303
+ const root = rootRef.current;
1304
+ if (!canvas || !root) return;
1305
+ const rect = canvas.getBoundingClientRect();
1306
+ const scaleX = rect.width ? props.width / rect.width : 1;
1307
+ const scaleY = rect.height ? props.height / rect.height : 1;
1308
+ const x = (e.clientX - rect.left) * scaleX;
1309
+ const y = (e.clientY - rect.top) * scaleY;
1310
+ const res = root.dispatchPointerEvent(type, {
1311
+ x,
1312
+ y,
1313
+ pointerId: e.pointerId ?? 0,
1314
+ button: e.button ?? 0,
1315
+ buttons: e.buttons ?? 0,
1316
+ altKey: !!e.altKey,
1317
+ ctrlKey: !!e.ctrlKey,
1318
+ shiftKey: !!e.shiftKey,
1319
+ metaKey: !!e.metaKey
1320
+ });
1321
+ if (res?.defaultPrevented && typeof e.preventDefault === "function") {
1322
+ e.preventDefault();
1323
+ }
1324
+ },
1325
+ [props.width, props.height]
1326
+ );
1327
+ const handleWheel = useCallback(
1328
+ (e) => {
1329
+ const canvas = canvasRef.current;
1330
+ const root = rootRef.current;
1331
+ if (!canvas || !root) return;
1332
+ const rect = canvas.getBoundingClientRect();
1333
+ const scaleX = rect.width ? props.width / rect.width : 1;
1334
+ const scaleY = rect.height ? props.height / rect.height : 1;
1335
+ let deltaX = e.deltaX ?? 0;
1336
+ let deltaY = e.deltaY ?? 0;
1337
+ if (e.deltaMode === 1) {
1338
+ deltaX *= 16;
1339
+ deltaY *= 16;
1340
+ } else if (e.deltaMode === 2) {
1341
+ deltaX *= props.width;
1342
+ deltaY *= props.height;
1343
+ }
1344
+ const x = (e.clientX - rect.left) * scaleX;
1345
+ const y = (e.clientY - rect.top) * scaleY;
1346
+ const payload = {
1347
+ x,
1348
+ y,
1349
+ deltaX: deltaX * scaleX,
1350
+ deltaY: deltaY * scaleY,
1351
+ altKey: !!e.altKey,
1352
+ ctrlKey: !!e.ctrlKey,
1353
+ shiftKey: !!e.shiftKey,
1354
+ metaKey: !!e.metaKey
1355
+ };
1356
+ const res = root.dispatchWheelEvent(payload);
1357
+ if (res?.defaultPrevented && typeof e.preventDefault === "function") {
1358
+ e.preventDefault();
1359
+ }
1360
+ },
1361
+ [props.width, props.height]
1362
+ );
1363
+ const handlePointerDown = useCallback(
1364
+ (e) => {
1365
+ const canvas = canvasRef.current;
1366
+ if (canvas && typeof canvas.setPointerCapture === "function") {
1367
+ try {
1368
+ canvas.setPointerCapture(e.pointerId);
1369
+ } catch (err) {
1370
+ }
1371
+ }
1372
+ dispatchPointer("pointerdown", e);
1373
+ },
1374
+ [dispatchPointer]
1375
+ );
1376
+ useLayoutEffect(() => {
1377
+ const canvas = canvasRef.current;
1378
+ if (!canvas) return;
1379
+ const listener = (e) => {
1380
+ handleWheel(e);
1381
+ };
1382
+ canvas.addEventListener("wheel", listener, { passive: false });
1383
+ return () => {
1384
+ canvas.removeEventListener("wheel", listener);
1385
+ };
1386
+ }, [handleWheel]);
1387
+ useLayoutEffect(() => {
1388
+ const canvas = canvasRef.current;
1389
+ if (!canvas) return;
1390
+ rootRef.current = createCanvasRoot(canvas, {
1391
+ width: props.width,
1392
+ height: props.height,
1393
+ dpr,
1394
+ clearColor: props.clearColor,
1395
+ fontFamily: props.fontFamily,
1396
+ fontSize: props.fontSize,
1397
+ fontWeight: props.fontWeight,
1398
+ lineHeight: props.lineHeight
1399
+ });
1400
+ return () => {
1401
+ rootRef.current?.unmount();
1402
+ rootRef.current = null;
1403
+ };
1404
+ }, [
1405
+ props.width,
1406
+ props.height,
1407
+ props.clearColor,
1408
+ props.fontFamily,
1409
+ props.fontSize,
1410
+ props.fontWeight,
1411
+ props.lineHeight,
1412
+ dpr
1413
+ ]);
1414
+ useLayoutEffect(() => {
1415
+ rootRef.current?.render(props.children ?? null);
1416
+ }, [props.children]);
1417
+ return /* @__PURE__ */ jsx(
1418
+ "canvas",
1419
+ {
1420
+ ref: canvasRef,
1421
+ style: canvasStyle,
1422
+ width: Math.floor(props.width * dpr),
1423
+ height: Math.floor(props.height * dpr),
1424
+ onPointerDown: handlePointerDown,
1425
+ onPointerMove: (e) => dispatchPointer("pointermove", e),
1426
+ onPointerUp: (e) => dispatchPointer("pointerup", e),
1427
+ onPointerCancel: (e) => dispatchPointer("pointercancel", e)
1428
+ }
1429
+ );
1430
+ }
1431
+ function View(props) {
1432
+ return createElement("View", props);
1433
+ }
1434
+ function Rect(props) {
1435
+ return createElement("Rect", props);
1436
+ }
1437
+ function Text(props) {
1438
+ return createElement("Text", props);
1439
+ }
1440
+
1441
+ export { Canvas, Rect, Text, View };
1442
+ //# sourceMappingURL=index.js.map
1443
+ //# sourceMappingURL=index.js.map