@xom11/whiteboard 0.24.0 → 0.24.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.
Files changed (70) hide show
  1. package/dist/{ExcalidrawWithMenus-KBLDWPM2.mjs → ExcalidrawWithMenus-WENZRYYE.mjs} +2 -2
  2. package/dist/{ExcalidrawWithMenus-KBLDWPM2.mjs.map → ExcalidrawWithMenus-WENZRYYE.mjs.map} +1 -1
  3. package/dist/catalog.json +4 -4
  4. package/dist/{chunk-TOOHCAWP.mjs → chunk-4D5CSIJO.mjs} +4 -4
  5. package/dist/{chunk-TOOHCAWP.mjs.map → chunk-4D5CSIJO.mjs.map} +1 -1
  6. package/dist/chunk-6V4SH4JJ.mjs +1801 -0
  7. package/dist/chunk-6V4SH4JJ.mjs.map +1 -0
  8. package/dist/{chunk-VBJLUHCY.mjs → chunk-AZIARTGX.mjs} +3 -3
  9. package/dist/{chunk-VBJLUHCY.mjs.map → chunk-AZIARTGX.mjs.map} +1 -1
  10. package/dist/{chunk-6XUPIGVD.mjs → chunk-BKSXPNPQ.mjs} +4 -123
  11. package/dist/chunk-BKSXPNPQ.mjs.map +1 -0
  12. package/dist/{chunk-O6QTYAKE.mjs → chunk-CRAPWQKJ.mjs} +4 -4
  13. package/dist/{chunk-O6QTYAKE.mjs.map → chunk-CRAPWQKJ.mjs.map} +1 -1
  14. package/dist/{chunk-RBUILBX3.mjs → chunk-CSCF3YFZ.mjs} +5 -5
  15. package/dist/{chunk-RBUILBX3.mjs.map → chunk-CSCF3YFZ.mjs.map} +1 -1
  16. package/dist/{chunk-7WG2KDRF.mjs → chunk-IBTRMWD6.mjs} +3 -3
  17. package/dist/{chunk-7WG2KDRF.mjs.map → chunk-IBTRMWD6.mjs.map} +1 -1
  18. package/dist/{chunk-RD34F5PM.mjs → chunk-ICR4CVOE.mjs} +2 -2
  19. package/dist/chunk-ICR4CVOE.mjs.map +1 -0
  20. package/dist/{chunk-33PEN2WC.mjs → chunk-LVNCYP4J.mjs} +6 -6
  21. package/dist/{chunk-33PEN2WC.mjs.map → chunk-LVNCYP4J.mjs.map} +1 -1
  22. package/dist/{chunk-FZY33J6Z.mjs → chunk-MFOGFFIL.mjs} +6 -6
  23. package/dist/{chunk-FZY33J6Z.mjs.map → chunk-MFOGFFIL.mjs.map} +1 -1
  24. package/dist/{chunk-TQYQVXNW.mjs → chunk-QGNU34T7.mjs} +2 -2
  25. package/dist/chunk-QGNU34T7.mjs.map +1 -0
  26. package/dist/{chunk-RXOFO64U.mjs → chunk-SGFJLHHG.mjs} +3 -3
  27. package/dist/{chunk-RXOFO64U.mjs.map → chunk-SGFJLHHG.mjs.map} +1 -1
  28. package/dist/{chunk-2SKXRBGS.mjs → chunk-WWMQ2VHZ.mjs} +4 -4
  29. package/dist/{chunk-2SKXRBGS.mjs.map → chunk-WWMQ2VHZ.mjs.map} +1 -1
  30. package/dist/{chunk-XVSO7FBM.mjs → chunk-YIPI3WUL.mjs} +5 -5
  31. package/dist/{chunk-XVSO7FBM.mjs.map → chunk-YIPI3WUL.mjs.map} +1 -1
  32. package/dist/{chunk-VRWZILTG.mjs → chunk-ZBJBQKJ2.mjs} +128 -3
  33. package/dist/chunk-ZBJBQKJ2.mjs.map +1 -0
  34. package/dist/geometry-2d.js +2056 -100
  35. package/dist/geometry-2d.js.map +1 -1
  36. package/dist/geometry-2d.mjs +6 -6
  37. package/dist/geometry-3d.js +2051 -15
  38. package/dist/geometry-3d.js.map +1 -1
  39. package/dist/geometry-3d.mjs +5 -5
  40. package/dist/graph-2d.js +1938 -8
  41. package/dist/graph-2d.js.map +1 -1
  42. package/dist/graph-2d.mjs +8 -8
  43. package/dist/{host-EVJT3LIF.mjs → host-DOAYVL35.mjs} +29 -28
  44. package/dist/host-DOAYVL35.mjs.map +1 -0
  45. package/dist/{host-3N4E4KJH.mjs → host-GKNQBBUE.mjs} +11 -11
  46. package/dist/{host-3N4E4KJH.mjs.map → host-GKNQBBUE.mjs.map} +1 -1
  47. package/dist/{host-6SNSZ332.mjs → host-QS2EOTRJ.mjs} +3 -3
  48. package/dist/{host-6SNSZ332.mjs.map → host-QS2EOTRJ.mjs.map} +1 -1
  49. package/dist/{host-HN4X3TBC.mjs → host-TLIXN4CF.mjs} +8 -8
  50. package/dist/{host-HN4X3TBC.mjs.map → host-TLIXN4CF.mjs.map} +1 -1
  51. package/dist/index.js +2044 -120
  52. package/dist/index.js.map +1 -1
  53. package/dist/index.mjs +22 -30
  54. package/dist/index.mjs.map +1 -1
  55. package/dist/latex.js.map +1 -1
  56. package/dist/latex.mjs +1 -1
  57. package/dist/render-SA4JTOW3.mjs +8 -0
  58. package/dist/{render-OCVGDKK6.mjs.map → render-SA4JTOW3.mjs.map} +1 -1
  59. package/dist/serialize-3NZS6A6Q.mjs +6 -0
  60. package/dist/{serialize-GKN6OVPM.mjs.map → serialize-3NZS6A6Q.mjs.map} +1 -1
  61. package/package.json +11 -2
  62. package/dist/chunk-3KBL77M6.mjs +0 -127
  63. package/dist/chunk-3KBL77M6.mjs.map +0 -1
  64. package/dist/chunk-6XUPIGVD.mjs.map +0 -1
  65. package/dist/chunk-RD34F5PM.mjs.map +0 -1
  66. package/dist/chunk-TQYQVXNW.mjs.map +0 -1
  67. package/dist/chunk-VRWZILTG.mjs.map +0 -1
  68. package/dist/host-EVJT3LIF.mjs.map +0 -1
  69. package/dist/render-OCVGDKK6.mjs +0 -8
  70. package/dist/serialize-GKN6OVPM.mjs +0 -6
@@ -73,10 +73,16 @@ var init_types = __esm({
73
73
  });
74
74
 
75
75
  // src/core/scene/registry.ts
76
+ function registerKind(def22) {
77
+ if (registry.has(def22.type)) {
78
+ console.warn(`[scene] kind "${def22.type}" \u0111\xE3 \u0111\u01B0\u1EE3c \u0111\u0103ng k\xFD \u2014 ghi \u0111\xE8 \u0111\u1ECBnh ngh\u0129a c\u0169`);
79
+ }
80
+ registry.set(def22.type, def22);
81
+ }
76
82
  function getKind(type) {
77
- const def = registry.get(type);
78
- if (!def) throw new Error(`[scene] unknown kind: ${type}`);
79
- return def;
83
+ const def22 = registry.get(type);
84
+ if (!def22) throw new Error(`[scene] unknown kind: ${type}`);
85
+ return def22;
80
86
  }
81
87
  var registry;
82
88
  var init_registry = __esm({
@@ -365,6 +371,2035 @@ var init_hooks = __esm({
365
371
  }
366
372
  });
367
373
 
374
+ // src/core/scene/kinds/3d-constraint.ts
375
+ function constraintRefs(c) {
376
+ switch (c.kind) {
377
+ case "onPlane":
378
+ return [c.planeId];
379
+ case "onLine":
380
+ return [c.lineId];
381
+ case "onPolygon":
382
+ return [c.polygonId];
383
+ case "onSphere":
384
+ return [c.sphereId];
385
+ default:
386
+ return [];
387
+ }
388
+ }
389
+ var init_d_constraint = __esm({
390
+ "src/core/scene/kinds/3d-constraint.ts"() {
391
+ }
392
+ });
393
+
394
+ // src/core/scene/kinds/point3d.ts
395
+ var def;
396
+ var init_point3d = __esm({
397
+ "src/core/scene/kinds/point3d.ts"() {
398
+ init_registry();
399
+ init_d_constraint();
400
+ def = {
401
+ type: "point3d",
402
+ schemaVersion: 1,
403
+ migrate: {},
404
+ validate: (a) => {
405
+ if (!a || !a.constraint || !a.constraint.kind) {
406
+ throw new Error("point3d: constraint required");
407
+ }
408
+ },
409
+ dependsOn: (a) => constraintRefs(a.constraint),
410
+ measure: (obj) => {
411
+ const c = obj.attrs.constraint;
412
+ if (c.kind === "free") {
413
+ return [
414
+ { label: "x", value: c.x },
415
+ { label: "y", value: c.y },
416
+ { label: "z", value: c.z }
417
+ ];
418
+ }
419
+ if (c.kind === "onGround") {
420
+ return [
421
+ { label: "x", value: c.x },
422
+ { label: "y", value: c.y },
423
+ { label: "z", value: 0 }
424
+ ];
425
+ }
426
+ return null;
427
+ },
428
+ describe: (obj) => {
429
+ const c = obj.attrs.constraint;
430
+ if (c.kind === "free") return `${obj.label} = (${c.x.toFixed(2)}, ${c.y.toFixed(2)}, ${c.z.toFixed(2)})`;
431
+ if (c.kind === "onGround") return `${obj.label} = (${c.x.toFixed(2)}, ${c.y.toFixed(2)}, 0)`;
432
+ if (c.kind === "onAxis") return `${obj.label} tr\xEAn tr\u1EE5c ${c.axis} (t=${c.t.toFixed(2)})`;
433
+ if (c.kind === "onPlane") return `${obj.label} tr\xEAn m\u1EB7t ${c.planeId}`;
434
+ if (c.kind === "onLine") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${c.lineId}`;
435
+ if (c.kind === "onPolygon") return `${obj.label} tr\xEAn \u0111a gi\xE1c ${c.polygonId}`;
436
+ if (c.kind === "onSphere") return `${obj.label} tr\xEAn m\u1EB7t c\u1EA7u ${c.sphereId}`;
437
+ return obj.label;
438
+ },
439
+ render: (obj, ctx) => {
440
+ const view = ctx.jxg;
441
+ const c = obj.attrs.constraint;
442
+ const opts = {
443
+ name: obj.label,
444
+ visible: obj.visible,
445
+ fixed: obj.locked,
446
+ strokeColor: obj.attrs.color ?? "#1e40af",
447
+ fillColor: obj.attrs.color ?? "#1e40af",
448
+ size: 4
449
+ };
450
+ if (c.kind === "free") {
451
+ return view.create("point3d", [c.x, c.y, c.z], opts);
452
+ } else if (c.kind === "onGround") {
453
+ return view.create("point3d", [c.x, c.y, 0], opts);
454
+ } else if (c.kind === "onAxis") {
455
+ const coords = c.axis === "x" ? [c.t, 0, 0] : c.axis === "y" ? [0, c.t, 0] : [0, 0, c.t];
456
+ return view.create("point3d", coords, opts);
457
+ } else if (c.kind === "onPlane") {
458
+ const plane = ctx.resolveRef(c.planeId);
459
+ return view.create("point3d", [
460
+ () => plane.F(c.u, c.v)[0],
461
+ () => plane.F(c.u, c.v)[1],
462
+ () => plane.F(c.u, c.v)[2]
463
+ ], opts);
464
+ } else if (c.kind === "onLine") {
465
+ const line = ctx.resolveRef(c.lineId);
466
+ return view.create("point3d", [
467
+ () => line.F(c.t)[0],
468
+ () => line.F(c.t)[1],
469
+ () => line.F(c.t)[2]
470
+ ], opts);
471
+ } else if (c.kind === "onPolygon") {
472
+ const poly = ctx.resolveRef(c.polygonId);
473
+ return view.create("point3d", [
474
+ () => poly.F(c.u, c.v)[0],
475
+ () => poly.F(c.u, c.v)[1],
476
+ () => poly.F(c.u, c.v)[2]
477
+ ], opts);
478
+ } else if (c.kind === "onSphere") {
479
+ const sph = ctx.resolveRef(c.sphereId);
480
+ return view.create("point3d", [
481
+ () => sph.F(c.theta, c.phi)[0],
482
+ () => sph.F(c.theta, c.phi)[1],
483
+ () => sph.F(c.theta, c.phi)[2]
484
+ ], opts);
485
+ }
486
+ return view.create("point3d", [0, 0, 0], opts);
487
+ }
488
+ };
489
+ registerKind(def);
490
+ }
491
+ });
492
+
493
+ // src/core/scene/kinds/labelOf.ts
494
+ function labelOf(id, state) {
495
+ return state?.objects[id]?.label ?? id;
496
+ }
497
+ var init_labelOf = __esm({
498
+ "src/core/scene/kinds/labelOf.ts"() {
499
+ }
500
+ });
501
+
502
+ // src/core/scene/kinds/segment3d.ts
503
+ var def2;
504
+ var init_segment3d = __esm({
505
+ "src/core/scene/kinds/segment3d.ts"() {
506
+ init_registry();
507
+ init_labelOf();
508
+ def2 = {
509
+ type: "segment3d",
510
+ schemaVersion: 1,
511
+ migrate: {},
512
+ validate: (a) => {
513
+ if (!a?.p1 || !a?.p2) throw new Error("segment3d: p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
514
+ },
515
+ dependsOn: (a) => [a.p1, a.p2],
516
+ measure: (obj, state) => {
517
+ const p1 = state.objects[obj.attrs.p1];
518
+ const p2 = state.objects[obj.attrs.p2];
519
+ if (!p1 || !p2) return null;
520
+ const c1 = p1.attrs.constraint;
521
+ const c2 = p2.attrs.constraint;
522
+ if (c1?.kind !== "free" || c2?.kind !== "free") return null;
523
+ const dx = (c2.x ?? 0) - (c1.x ?? 0);
524
+ const dy = (c2.y ?? 0) - (c1.y ?? 0);
525
+ const dz = (c2.z ?? 0) - (c1.z ?? 0);
526
+ return [{ label: "length", value: Math.hypot(dx, dy, dz) }];
527
+ },
528
+ describe: (obj, state) => `\u0110o\u1EA1n th\u1EB3ng ${labelOf(obj.attrs.p1, state)}${labelOf(obj.attrs.p2, state)}`,
529
+ render: (obj, ctx) => {
530
+ const view = ctx.jxg;
531
+ const pA = ctx.resolveRef(obj.attrs.p1);
532
+ const pB = ctx.resolveRef(obj.attrs.p2);
533
+ return view.create("line3d", [pA, pB], {
534
+ straightFirst: false,
535
+ straightLast: false,
536
+ strokeColor: obj.attrs.color ?? "#0f172a",
537
+ strokeWidth: 2,
538
+ visible: obj.visible
539
+ });
540
+ }
541
+ };
542
+ registerKind(def2);
543
+ }
544
+ });
545
+
546
+ // src/core/scene/kinds/line3d.ts
547
+ var init_line3d = __esm({
548
+ "src/core/scene/kinds/line3d.ts"() {
549
+ init_registry();
550
+ init_labelOf();
551
+ registerKind({
552
+ type: "line3d",
553
+ schemaVersion: 1,
554
+ migrate: {},
555
+ validate: (a) => {
556
+ if (!a?.p1 || !a?.p2) throw new Error("line3d: p1/p2 required");
557
+ },
558
+ dependsOn: (a) => [a.p1, a.p2],
559
+ describe: (obj, state) => `\u0110\u01B0\u1EDDng ${obj.label} qua ${labelOf(obj.attrs.p1, state)}, ${labelOf(obj.attrs.p2, state)}`,
560
+ render: (obj, ctx) => {
561
+ const view = ctx.jxg;
562
+ const pA = ctx.resolveRef(obj.attrs.p1);
563
+ const pB = ctx.resolveRef(obj.attrs.p2);
564
+ return view.create("line3d", [pA, pB], {
565
+ straightFirst: true,
566
+ straightLast: true,
567
+ strokeColor: obj.attrs.color ?? "#0f172a",
568
+ strokeWidth: 2,
569
+ visible: obj.visible
570
+ });
571
+ }
572
+ });
573
+ }
574
+ });
575
+
576
+ // src/core/scene/kinds/ray3d.ts
577
+ var init_ray3d = __esm({
578
+ "src/core/scene/kinds/ray3d.ts"() {
579
+ init_registry();
580
+ init_labelOf();
581
+ registerKind({
582
+ type: "ray3d",
583
+ schemaVersion: 1,
584
+ migrate: {},
585
+ validate: (a) => {
586
+ if (!a?.origin || !a?.through) throw new Error("ray3d: origin/through required");
587
+ },
588
+ dependsOn: (a) => [a.origin, a.through],
589
+ describe: (obj, state) => `Tia ${obj.label} t\u1EEB ${labelOf(obj.attrs.origin, state)} qua ${labelOf(obj.attrs.through, state)}`,
590
+ render: (obj, ctx) => {
591
+ const view = ctx.jxg;
592
+ const pOrigin = ctx.resolveRef(obj.attrs.origin);
593
+ const pThrough = ctx.resolveRef(obj.attrs.through);
594
+ return view.create("line3d", [pOrigin, pThrough], {
595
+ straightFirst: false,
596
+ straightLast: true,
597
+ strokeColor: obj.attrs.color ?? "#0f172a",
598
+ strokeWidth: 2,
599
+ visible: obj.visible
600
+ });
601
+ }
602
+ });
603
+ }
604
+ });
605
+
606
+ // src/core/scene/kinds/vector3d.ts
607
+ var init_vector3d = __esm({
608
+ "src/core/scene/kinds/vector3d.ts"() {
609
+ init_registry();
610
+ init_labelOf();
611
+ registerKind({
612
+ type: "vector3d",
613
+ schemaVersion: 1,
614
+ migrate: {},
615
+ validate: (a) => {
616
+ if (!a?.from || !a?.to) throw new Error("vector3d: from/to required");
617
+ },
618
+ dependsOn: (a) => [a.from, a.to],
619
+ describe: (obj, state) => `V\xE9c-t\u01A1 ${obj.label}: ${labelOf(obj.attrs.from, state)} \u2192 ${labelOf(obj.attrs.to, state)}`,
620
+ render: (obj, ctx) => {
621
+ const view = ctx.jxg;
622
+ const pFrom = ctx.resolveRef(obj.attrs.from);
623
+ const pTo = ctx.resolveRef(obj.attrs.to);
624
+ return view.create("line3d", [pFrom, pTo], {
625
+ straightFirst: false,
626
+ straightLast: false,
627
+ lastArrow: { type: 1 },
628
+ strokeColor: obj.attrs.color ?? "#0f172a",
629
+ strokeWidth: 2,
630
+ visible: obj.visible
631
+ });
632
+ }
633
+ });
634
+ }
635
+ });
636
+
637
+ // src/core/scene/kinds/plane3d.ts
638
+ var init_plane3d = __esm({
639
+ "src/core/scene/kinds/plane3d.ts"() {
640
+ init_registry();
641
+ init_labelOf();
642
+ registerKind({
643
+ type: "plane3d",
644
+ schemaVersion: 1,
645
+ migrate: {},
646
+ validate: (a) => {
647
+ if (!a?.p1 || !a?.p2 || !a?.p3) throw new Error("plane3d: c\u1EA7n 3 \u0111i\u1EC3m");
648
+ },
649
+ dependsOn: (a) => [a.p1, a.p2, a.p3],
650
+ describe: (obj, state) => `M\u1EB7t ${obj.label} qua ${labelOf(obj.attrs.p1, state)}, ${labelOf(obj.attrs.p2, state)}, ${labelOf(obj.attrs.p3, state)}`,
651
+ render: (obj, ctx) => {
652
+ const view = ctx.jxg;
653
+ return view.create("plane3d", [
654
+ ctx.resolveRef(obj.attrs.p1),
655
+ ctx.resolveRef(obj.attrs.p2),
656
+ ctx.resolveRef(obj.attrs.p3)
657
+ ], {
658
+ fillOpacity: 0.15,
659
+ fillColor: obj.attrs.color ?? "#60a5fa",
660
+ strokeColor: obj.attrs.color ?? "#60a5fa",
661
+ visible: obj.visible
662
+ });
663
+ }
664
+ });
665
+ }
666
+ });
667
+
668
+ // src/core/scene/kinds/polygon3d.ts
669
+ var init_polygon3d = __esm({
670
+ "src/core/scene/kinds/polygon3d.ts"() {
671
+ init_registry();
672
+ registerKind({
673
+ type: "polygon3d",
674
+ schemaVersion: 1,
675
+ migrate: {},
676
+ validate: (a) => {
677
+ if (!a?.vertices || a.vertices.length < 3) throw new Error("polygon3d: c\u1EA7n \u22653 vertices");
678
+ },
679
+ dependsOn: (a) => [...a.vertices],
680
+ describe: (obj) => `\u0110a gi\xE1c ${obj.label} (${obj.attrs.vertices.length} \u0111\u1EC9nh)`,
681
+ render: (obj, ctx) => {
682
+ const view = ctx.jxg;
683
+ const refs = obj.attrs.vertices.map((id) => ctx.resolveRef(id));
684
+ return view.create("polygon3d", [refs], {
685
+ fillOpacity: 0.3,
686
+ fillColor: obj.attrs.color ?? "#60a5fa",
687
+ visible: obj.visible
688
+ });
689
+ }
690
+ });
691
+ }
692
+ });
693
+
694
+ // src/core/scene/kinds/sphere3d.ts
695
+ var init_sphere3d = __esm({
696
+ "src/core/scene/kinds/sphere3d.ts"() {
697
+ init_registry();
698
+ init_labelOf();
699
+ registerKind({
700
+ type: "sphere3d",
701
+ schemaVersion: 1,
702
+ migrate: {},
703
+ validate: (a) => {
704
+ if (!a?.center || !a?.surfacePoint) throw new Error("sphere3d: center/surfacePoint required");
705
+ },
706
+ dependsOn: (a) => [a.center, a.surfacePoint],
707
+ describe: (obj, state) => `M\u1EB7t c\u1EA7u ${obj.label} t\xE2m ${labelOf(obj.attrs.center, state)}`,
708
+ render: (obj, ctx) => {
709
+ const view = ctx.jxg;
710
+ return view.create("sphere3d", [
711
+ ctx.resolveRef(obj.attrs.center),
712
+ ctx.resolveRef(obj.attrs.surfacePoint)
713
+ ], {
714
+ fillOpacity: 0.25,
715
+ fillColor: obj.attrs.color ?? "#60a5fa",
716
+ visible: obj.visible
717
+ });
718
+ }
719
+ });
720
+ }
721
+ });
722
+
723
+ // src/core/scene/kinds/polyhedron3d.ts
724
+ var FLAVOR_LABEL;
725
+ var init_polyhedron3d = __esm({
726
+ "src/core/scene/kinds/polyhedron3d.ts"() {
727
+ init_registry();
728
+ FLAVOR_LABEL = {
729
+ pyramid: "ch\xF3p",
730
+ prism: "l\u0103ng tr\u1EE5",
731
+ tetrahedron: "t\u1EE9 di\u1EC7n",
732
+ cube: "l\u1EADp ph\u01B0\u01A1ng"
733
+ };
734
+ registerKind({
735
+ type: "polyhedron3d",
736
+ schemaVersion: 1,
737
+ migrate: {},
738
+ validate: (a) => {
739
+ if (!a?.vertices || a.vertices.length < 4) throw new Error("polyhedron3d: c\u1EA7n \u22654 vertices");
740
+ if (!a?.faces || a.faces.length < 4) throw new Error("polyhedron3d: c\u1EA7n \u22654 faces");
741
+ },
742
+ dependsOn: (a) => [...a.vertices],
743
+ describe: (obj) => `Kh\u1ED1i ${FLAVOR_LABEL[obj.attrs.flavor]} ${obj.label}`,
744
+ render: (obj, ctx) => {
745
+ const view = ctx.jxg;
746
+ const verts = obj.attrs.vertices.map((id) => ctx.resolveRef(id));
747
+ const faces = obj.attrs.faces.map(
748
+ (faceIndices, fi) => view.create("polygon3d", [faceIndices.map((i) => verts[i])], {
749
+ id: `${obj.id}.face${faceIndices.join("-")}.${fi}`,
750
+ fillOpacity: 0.25,
751
+ fillColor: obj.attrs.color ?? "#fbbf24",
752
+ strokeColor: "#0066cc",
753
+ strokeWidth: 1.5,
754
+ visible: obj.visible
755
+ })
756
+ );
757
+ return { faces };
758
+ }
759
+ });
760
+ }
761
+ });
762
+
763
+ // src/core/scene/kinds/cylinder3d.ts
764
+ var CURVED_SEGMENTS;
765
+ var init_cylinder3d = __esm({
766
+ "src/core/scene/kinds/cylinder3d.ts"() {
767
+ init_registry();
768
+ CURVED_SEGMENTS = 16;
769
+ registerKind({
770
+ type: "cylinder3d",
771
+ schemaVersion: 1,
772
+ migrate: {},
773
+ validate: (a) => {
774
+ if (!a?.baseCenter || !a?.topCenter) throw new Error("cylinder3d: baseCenter/topCenter required");
775
+ if (!(a.radius > 0)) throw new Error("cylinder3d: radius > 0");
776
+ },
777
+ dependsOn: (a) => [a.baseCenter, a.topCenter],
778
+ describe: (obj) => `Tr\u1EE5 ${obj.label} R=${obj.attrs.radius.toFixed(2)}`,
779
+ render: (obj, ctx) => {
780
+ const view = ctx.jxg;
781
+ const a = ctx.resolveRef(obj.attrs.baseCenter);
782
+ const b = ctx.resolveRef(obj.attrs.topCenter);
783
+ const r = obj.attrs.radius;
784
+ const ax = a.X?.() ?? 0, ay = a.Y?.() ?? 0, az = a.Z?.() ?? 0;
785
+ const bx = b.X?.() ?? 0, by = b.Y?.() ?? 0, bz = b.Z?.() ?? 0;
786
+ const baseRing = [];
787
+ const topRing = [];
788
+ for (let i = 0; i < CURVED_SEGMENTS; i++) {
789
+ const theta = i / CURVED_SEGMENTS * Math.PI * 2;
790
+ const dx = r * Math.cos(theta);
791
+ const dy = r * Math.sin(theta);
792
+ baseRing.push([ax + dx, ay + dy, az]);
793
+ topRing.push([bx + dx, by + dy, bz]);
794
+ }
795
+ const vertices = [...baseRing, ...topRing];
796
+ const faces = [];
797
+ faces.push(baseRing.map((_, i) => i));
798
+ faces.push(topRing.map((_, i) => CURVED_SEGMENTS + i));
799
+ for (let i = 0; i < CURVED_SEGMENTS; i++) {
800
+ const next = (i + 1) % CURVED_SEGMENTS;
801
+ faces.push([i, next, CURVED_SEGMENTS + next, CURVED_SEGMENTS + i]);
802
+ }
803
+ const vertJxgs = vertices.map(
804
+ (v, i) => view.create("point3d", v, {
805
+ id: `${obj.id}.v${i}`,
806
+ visible: false,
807
+ fixed: true,
808
+ withLabel: false
809
+ })
810
+ );
811
+ const faceJxgs = faces.map(
812
+ (face, fi) => view.create("polygon3d", [face.map((idx) => vertJxgs[idx])], {
813
+ id: `${obj.id}.face${fi}`,
814
+ fillOpacity: 0.25,
815
+ fillColor: obj.attrs.color ?? "#f97316",
816
+ strokeColor: "#0066cc",
817
+ strokeWidth: 1.5,
818
+ visible: obj.visible
819
+ })
820
+ );
821
+ return { _verts: vertJxgs, faces: faceJxgs };
822
+ }
823
+ });
824
+ }
825
+ });
826
+
827
+ // src/core/scene/kinds/cone3d.ts
828
+ var CURVED_SEGMENTS2;
829
+ var init_cone3d = __esm({
830
+ "src/core/scene/kinds/cone3d.ts"() {
831
+ init_registry();
832
+ CURVED_SEGMENTS2 = 16;
833
+ registerKind({
834
+ type: "cone3d",
835
+ schemaVersion: 1,
836
+ migrate: {},
837
+ validate: (a) => {
838
+ if (!a?.baseCenter || !a?.apex) throw new Error("cone3d: baseCenter/apex required");
839
+ if (!(a.radius > 0)) throw new Error("cone3d: radius > 0");
840
+ },
841
+ dependsOn: (a) => [a.baseCenter, a.apex],
842
+ describe: (obj) => `N\xF3n ${obj.label} R=${obj.attrs.radius.toFixed(2)}`,
843
+ render: (obj, ctx) => {
844
+ const view = ctx.jxg;
845
+ const base = ctx.resolveRef(obj.attrs.baseCenter);
846
+ const apexPt = ctx.resolveRef(obj.attrs.apex);
847
+ const r = obj.attrs.radius;
848
+ const bx = base.X?.() ?? 0, by = base.Y?.() ?? 0, bz = base.Z?.() ?? 0;
849
+ const apexCoords = [
850
+ apexPt.X?.() ?? 0,
851
+ apexPt.Y?.() ?? 0,
852
+ apexPt.Z?.() ?? 0
853
+ ];
854
+ const baseRing = [];
855
+ for (let i = 0; i < CURVED_SEGMENTS2; i++) {
856
+ const theta = i / CURVED_SEGMENTS2 * Math.PI * 2;
857
+ baseRing.push([bx + r * Math.cos(theta), by + r * Math.sin(theta), bz]);
858
+ }
859
+ const apexIdx = baseRing.length;
860
+ const vertices = [...baseRing, apexCoords];
861
+ const faces = [baseRing.map((_, i) => i)];
862
+ for (let i = 0; i < CURVED_SEGMENTS2; i++) {
863
+ faces.push([i, (i + 1) % CURVED_SEGMENTS2, apexIdx]);
864
+ }
865
+ const vertJxgs = vertices.map(
866
+ (v, i) => view.create("point3d", v, {
867
+ id: `${obj.id}.v${i}`,
868
+ visible: false,
869
+ fixed: true,
870
+ withLabel: false
871
+ })
872
+ );
873
+ const faceJxgs = faces.map(
874
+ (face, fi) => view.create("polygon3d", [face.map((idx) => vertJxgs[idx])], {
875
+ id: `${obj.id}.face${fi}`,
876
+ fillOpacity: 0.25,
877
+ fillColor: obj.attrs.color ?? "#f59e0b",
878
+ strokeColor: "#0066cc",
879
+ strokeWidth: 1.5,
880
+ visible: obj.visible
881
+ })
882
+ );
883
+ return { _verts: vertJxgs, faces: faceJxgs };
884
+ }
885
+ });
886
+ }
887
+ });
888
+
889
+ // src/core/scene/kinds/2d-constraint.ts
890
+ function transformRefs(t) {
891
+ switch (t.kind) {
892
+ case "translate":
893
+ return [];
894
+ case "rotate":
895
+ case "reflectPoint":
896
+ case "dilate":
897
+ return [t.center];
898
+ case "reflectLine":
899
+ return [t.line];
900
+ }
901
+ }
902
+ function constraintRefs2D(c) {
903
+ switch (c.kind) {
904
+ case "onLine":
905
+ return [c.lineId];
906
+ case "onSegment":
907
+ return [c.segmentId];
908
+ case "onCircle":
909
+ return [c.circleId];
910
+ case "onPolygon":
911
+ return [c.polygonId];
912
+ case "midpoint":
913
+ return [c.p1, c.p2];
914
+ case "transformed":
915
+ return [c.source, ...transformRefs(c.transform)];
916
+ case "perpFoot":
917
+ return [c.from, c.onLine];
918
+ case "circumcenter":
919
+ return [c.vertices[0], c.vertices[1], c.vertices[2]];
920
+ case "incenter":
921
+ return [c.vertices[0], c.vertices[1], c.vertices[2]];
922
+ case "centroid":
923
+ return [c.vertices[0], c.vertices[1], c.vertices[2]];
924
+ case "orthocenter":
925
+ return [c.vertices[0], c.vertices[1], c.vertices[2]];
926
+ default:
927
+ return [];
928
+ }
929
+ }
930
+ var init_d_constraint2 = __esm({
931
+ "src/core/scene/kinds/2d-constraint.ts"() {
932
+ }
933
+ });
934
+
935
+ // src/core/scene/kinds/point.ts
936
+ function buildJxgTransforms(board, ctx, t) {
937
+ switch (t.kind) {
938
+ case "translate":
939
+ return [board.create("transform", [t.dx, t.dy], { type: "translate" })];
940
+ case "rotate": {
941
+ const c = ctx.resolveRef(t.center);
942
+ return [board.create("transform", [t.angleRad, c], { type: "rotate" })];
943
+ }
944
+ case "reflectPoint": {
945
+ const c = ctx.resolveRef(t.center);
946
+ return [board.create("transform", [Math.PI, c], { type: "rotate" })];
947
+ }
948
+ case "reflectLine": {
949
+ const l = ctx.resolveRef(t.line);
950
+ return [board.create("transform", [l], { type: "reflect" })];
951
+ }
952
+ case "dilate": {
953
+ const c = ctx.resolveRef(t.center);
954
+ return [
955
+ board.create("transform", [() => -c.X(), () => -c.Y()], { type: "translate" }),
956
+ board.create("transform", [t.k, t.k], { type: "scale" }),
957
+ board.create("transform", [() => c.X(), () => c.Y()], { type: "translate" })
958
+ ];
959
+ }
960
+ }
961
+ }
962
+ var def3;
963
+ var init_point = __esm({
964
+ "src/core/scene/kinds/point.ts"() {
965
+ init_registry();
966
+ init_d_constraint2();
967
+ def3 = {
968
+ type: "point",
969
+ schemaVersion: 1,
970
+ migrate: {},
971
+ validate: (a) => {
972
+ if (!a || !a.constraint || !a.constraint.kind) {
973
+ throw new Error("point: constraint required");
974
+ }
975
+ const c = a.constraint;
976
+ if (c.kind === "perpFoot") {
977
+ if (!c.from || !c.onLine) {
978
+ throw new Error("point.perpFoot: from v\xE0 onLine b\u1EAFt bu\u1ED9c");
979
+ }
980
+ }
981
+ if (c.kind === "circumcenter") {
982
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
983
+ throw new Error("point.circumcenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
984
+ }
985
+ if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
986
+ throw new Error("point.circumcenter: 3 vertex id ph\u1EA3i non-empty");
987
+ }
988
+ }
989
+ if (c.kind === "incenter") {
990
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
991
+ throw new Error("point.incenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
992
+ }
993
+ if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
994
+ throw new Error("point.incenter: 3 vertex id ph\u1EA3i non-empty");
995
+ }
996
+ }
997
+ if (c.kind === "centroid") {
998
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
999
+ throw new Error("point.centroid: vertices ph\u1EA3i l\xE0 tuple 3 id");
1000
+ }
1001
+ if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
1002
+ throw new Error("point.centroid: 3 vertex id ph\u1EA3i non-empty");
1003
+ }
1004
+ }
1005
+ if (c.kind === "orthocenter") {
1006
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
1007
+ throw new Error("point.orthocenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
1008
+ }
1009
+ if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
1010
+ throw new Error("point.orthocenter: 3 vertex id ph\u1EA3i non-empty");
1011
+ }
1012
+ }
1013
+ },
1014
+ dependsOn: (a) => constraintRefs2D(a.constraint),
1015
+ measure: (obj) => {
1016
+ const c = obj.attrs.constraint;
1017
+ if (c.kind === "free") {
1018
+ return [
1019
+ { label: "x", value: c.x },
1020
+ { label: "y", value: c.y }
1021
+ ];
1022
+ }
1023
+ return null;
1024
+ },
1025
+ describe: (obj, state) => {
1026
+ const c = obj.attrs.constraint;
1027
+ if (c.kind === "free") return `\u0110i\u1EC3m ${obj.label}`;
1028
+ if (c.kind === "onAxis") return `${obj.label} tr\xEAn tr\u1EE5c ${c.axis}`;
1029
+ if (c.kind === "onLine") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${state?.objects[c.lineId]?.label ?? c.lineId}`;
1030
+ if (c.kind === "onSegment") return `${obj.label} tr\xEAn \u0111o\u1EA1n ${state?.objects[c.segmentId]?.label ?? c.segmentId}`;
1031
+ if (c.kind === "onCircle") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng tr\xF2n ${state?.objects[c.circleId]?.label ?? c.circleId}`;
1032
+ if (c.kind === "onPolygon") return `${obj.label} tr\xEAn \u0111a gi\xE1c ${state?.objects[c.polygonId]?.label ?? c.polygonId}`;
1033
+ if (c.kind === "midpoint") {
1034
+ const l1 = state?.objects[c.p1]?.label ?? c.p1;
1035
+ const l2 = state?.objects[c.p2]?.label ?? c.p2;
1036
+ return `${obj.label} = trung \u0111i\u1EC3m ${l1}${l2}`;
1037
+ }
1038
+ if (c.kind === "transformed") {
1039
+ const t = c.transform;
1040
+ const labelRef = (id) => state?.objects[id]?.label ?? id;
1041
+ const op = t.kind === "translate" ? `t\u1ECBnh ti\u1EBFn (${t.dx.toFixed(2)}, ${t.dy.toFixed(2)})` : t.kind === "rotate" ? `quay ${(t.angleRad * 180 / Math.PI).toFixed(0)}\xB0 quanh ${labelRef(t.center)}` : t.kind === "reflectLine" ? `\u0111\u1ED1i x\u1EE9ng qua ${labelRef(t.line)}` : t.kind === "reflectPoint" ? `\u0111\u1ED1i x\u1EE9ng qua \u0111i\u1EC3m ${labelRef(t.center)}` : t.kind === "dilate" ? `v\u1ECB t\u1EF1 k=${t.k} quanh ${labelRef(t.center)}` : "";
1042
+ return `${obj.label} = \u1EA3nh c\u1EE7a ${labelRef(c.source)} (${op})`;
1043
+ }
1044
+ if (c.kind === "perpFoot") {
1045
+ const fromLabel = state?.objects[c.from]?.label ?? c.from;
1046
+ const lineLabel = state?.objects[c.onLine]?.label ?? c.onLine;
1047
+ return `${obj.label} = ch\xE2n \u27C2 t\u1EEB ${fromLabel} xu\u1ED1ng ${lineLabel}`;
1048
+ }
1049
+ if (c.kind === "circumcenter") {
1050
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
1051
+ return `${obj.label} = t\xE2m ngo\u1EA1i ti\u1EBFp \u0394${labels}`;
1052
+ }
1053
+ if (c.kind === "incenter") {
1054
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
1055
+ return `${obj.label} = t\xE2m n\u1ED9i ti\u1EBFp \u0394${labels}`;
1056
+ }
1057
+ if (c.kind === "centroid") {
1058
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
1059
+ return `${obj.label} = tr\u1ECDng t\xE2m \u0394${labels}`;
1060
+ }
1061
+ if (c.kind === "orthocenter") {
1062
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
1063
+ return `${obj.label} = tr\u1EF1c t\xE2m \u0394${labels}`;
1064
+ }
1065
+ return `\u0110i\u1EC3m ${obj.label}`;
1066
+ },
1067
+ render: (obj, ctx) => {
1068
+ const board = ctx.jxg;
1069
+ const c = obj.attrs.constraint;
1070
+ const opts = {
1071
+ name: obj.label,
1072
+ withLabel: obj.attrs.showLabel ?? true,
1073
+ visible: obj.visible,
1074
+ fixed: obj.locked,
1075
+ strokeColor: obj.attrs.color ?? "#1e40af",
1076
+ fillColor: obj.attrs.color ?? "#1e40af",
1077
+ face: obj.attrs.face ?? "o",
1078
+ size: obj.attrs.size ?? 4
1079
+ };
1080
+ if (c.kind === "free") return board.create("point", [c.x, c.y], opts);
1081
+ if (c.kind === "onAxis") {
1082
+ const coords = c.axis === "x" ? [c.t, 0] : [0, c.t];
1083
+ return board.create("point", coords, opts);
1084
+ }
1085
+ if (c.kind === "onLine") {
1086
+ const line = ctx.resolveRef(c.lineId);
1087
+ return board.create("glider", [c.t, c.t, line], opts);
1088
+ }
1089
+ if (c.kind === "onSegment") {
1090
+ const seg = ctx.resolveRef(c.segmentId);
1091
+ return board.create("glider", [c.t, c.t, seg], opts);
1092
+ }
1093
+ if (c.kind === "onCircle") {
1094
+ const circle = ctx.resolveRef(c.circleId);
1095
+ return board.create("glider", [Math.cos(c.theta), Math.sin(c.theta), circle], opts);
1096
+ }
1097
+ if (c.kind === "onPolygon") {
1098
+ const poly = ctx.resolveRef(c.polygonId);
1099
+ return board.create("glider", [c.u, c.v, poly], opts);
1100
+ }
1101
+ if (c.kind === "midpoint") {
1102
+ const p1 = ctx.resolveRef(c.p1);
1103
+ const p2 = ctx.resolveRef(c.p2);
1104
+ return board.create("midpoint", [p1, p2], opts);
1105
+ }
1106
+ if (c.kind === "transformed") {
1107
+ const src = ctx.resolveRef(c.source);
1108
+ const transforms = buildJxgTransforms(board, ctx, c.transform);
1109
+ const parent = transforms.length === 1 ? transforms[0] : transforms;
1110
+ const pt = board.create("point", [src, parent], opts);
1111
+ pt._helpers = transforms;
1112
+ return pt;
1113
+ }
1114
+ if (c.kind === "perpFoot") {
1115
+ const from = ctx.resolveRef(c.from);
1116
+ const onLine = ctx.resolveRef(c.onLine);
1117
+ return board.create("perpendicularpoint", [onLine, from], opts);
1118
+ }
1119
+ if (c.kind === "circumcenter") {
1120
+ const a = ctx.resolveRef(c.vertices[0]);
1121
+ const b = ctx.resolveRef(c.vertices[1]);
1122
+ const c3 = ctx.resolveRef(c.vertices[2]);
1123
+ return board.create("circumcenter", [a, b, c3], opts);
1124
+ }
1125
+ if (c.kind === "incenter") {
1126
+ const a = ctx.resolveRef(c.vertices[0]);
1127
+ const b = ctx.resolveRef(c.vertices[1]);
1128
+ const c3 = ctx.resolveRef(c.vertices[2]);
1129
+ return board.create("incenter", [a, b, c3], opts);
1130
+ }
1131
+ if (c.kind === "centroid") {
1132
+ const a = ctx.resolveRef(c.vertices[0]);
1133
+ const b = ctx.resolveRef(c.vertices[1]);
1134
+ const c3 = ctx.resolveRef(c.vertices[2]);
1135
+ return board.create("point", [
1136
+ () => (a.X() + b.X() + c3.X()) / 3,
1137
+ () => (a.Y() + b.Y() + c3.Y()) / 3
1138
+ ], opts);
1139
+ }
1140
+ if (c.kind === "orthocenter") {
1141
+ const a = ctx.resolveRef(c.vertices[0]);
1142
+ const b = ctx.resolveRef(c.vertices[1]);
1143
+ const c3 = ctx.resolveRef(c.vertices[2]);
1144
+ const hide = { visible: false, withLabel: false, fixed: true, name: "" };
1145
+ const lineBC = board.create("line", [b, c3], hide);
1146
+ const altA = board.create("perpendicular", [lineBC, a], hide);
1147
+ const lineAC = board.create("line", [a, c3], hide);
1148
+ const altB = board.create("perpendicular", [lineAC, b], hide);
1149
+ const ortho = board.create("intersection", [altA, altB, 0], opts);
1150
+ ortho._helpers = [lineBC, altA, lineAC, altB];
1151
+ return ortho;
1152
+ }
1153
+ return board.create("point", [0, 0], opts);
1154
+ },
1155
+ /**
1156
+ * Free → Free update giữ nguyên JxgObj identity (gọi setPositionDirectly +
1157
+ * setAttribute) để các object phụ thuộc (line/segment/...) không bị stale
1158
+ * parent ref. Đổi constraint kind → throw để renderer fallback recreate.
1159
+ *
1160
+ * Đây cũng là endpoint cho drag-sync dispatch trong JxgRenderer: khi user
1161
+ * kéo điểm, listener dispatch UPDATE_ATTRS → update hook chạy, vị trí đã
1162
+ * đúng sẵn nên setPositionDirectly là no-op nhưng vẫn cần để sync các attrs
1163
+ * khác (label/color/...).
1164
+ */
1165
+ update: (obj, prev, ctx, existing) => {
1166
+ const c = obj.attrs.constraint;
1167
+ const oldC = prev.attrs.constraint;
1168
+ if (c.kind === "free" && oldC.kind === "free") {
1169
+ const el = existing;
1170
+ if (typeof el.setPositionDirectly === "function") {
1171
+ try {
1172
+ el.setPositionDirectly(1, [c.x, c.y]);
1173
+ } catch {
1174
+ }
1175
+ }
1176
+ if (typeof el.setAttribute === "function") {
1177
+ try {
1178
+ el.setAttribute({
1179
+ name: obj.label,
1180
+ withLabel: obj.attrs.showLabel ?? true,
1181
+ visible: obj.visible,
1182
+ fixed: obj.locked,
1183
+ strokeColor: obj.attrs.color ?? "#1e40af",
1184
+ fillColor: obj.attrs.color ?? "#1e40af",
1185
+ face: obj.attrs.face ?? "o",
1186
+ size: obj.attrs.size ?? 4
1187
+ });
1188
+ } catch {
1189
+ }
1190
+ }
1191
+ return;
1192
+ }
1193
+ throw new Error("point: constraint kind changed \u2014 recreate");
1194
+ }
1195
+ };
1196
+ registerKind(def3);
1197
+ }
1198
+ });
1199
+
1200
+ // src/core/scene/kinds/segment.ts
1201
+ var def4;
1202
+ var init_segment = __esm({
1203
+ "src/core/scene/kinds/segment.ts"() {
1204
+ init_registry();
1205
+ init_labelOf();
1206
+ def4 = {
1207
+ type: "segment",
1208
+ schemaVersion: 1,
1209
+ migrate: {},
1210
+ validate: (a) => {
1211
+ if (!a?.p1 || !a?.p2) throw new Error("segment: p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
1212
+ },
1213
+ dependsOn: (a) => [a.p1, a.p2],
1214
+ measure: (obj, state) => {
1215
+ const p1 = state.objects[obj.attrs.p1];
1216
+ const p2 = state.objects[obj.attrs.p2];
1217
+ if (!p1 || !p2) return null;
1218
+ const c1 = p1.attrs.constraint;
1219
+ const c2 = p2.attrs.constraint;
1220
+ if (c1?.kind !== "free" || c2?.kind !== "free") return null;
1221
+ const dx = (c2.x ?? 0) - (c1.x ?? 0);
1222
+ const dy = (c2.y ?? 0) - (c1.y ?? 0);
1223
+ return [{ label: "length", value: Math.hypot(dx, dy) }];
1224
+ },
1225
+ describe: (obj, state) => `\u0110o\u1EA1n th\u1EB3ng ${labelOf(obj.attrs.p1, state)}${labelOf(obj.attrs.p2, state)}`,
1226
+ render: (obj, ctx) => {
1227
+ const board = ctx.jxg;
1228
+ const p1 = ctx.resolveRef(obj.attrs.p1);
1229
+ const p2 = ctx.resolveRef(obj.attrs.p2);
1230
+ return board.create("segment", [p1, p2], {
1231
+ name: obj.label,
1232
+ withLabel: obj.attrs.showLabel ?? false,
1233
+ strokeColor: obj.attrs.color ?? "#0f172a",
1234
+ strokeWidth: obj.attrs.width ?? 2,
1235
+ dash: obj.attrs.dash ?? 0,
1236
+ visible: obj.visible,
1237
+ fixed: obj.locked
1238
+ });
1239
+ }
1240
+ };
1241
+ registerKind(def4);
1242
+ }
1243
+ });
1244
+
1245
+ // src/core/scene/kinds/line.ts
1246
+ function stripBorderSuffix(id) {
1247
+ const m = /^(.+):border:\d+$/.exec(id);
1248
+ return m ? m[1] : id;
1249
+ }
1250
+ function constructionRefs(c) {
1251
+ switch (c.kind) {
1252
+ case "perpendicular":
1253
+ case "parallel":
1254
+ return [c.throughPoint, stripBorderSuffix(c.toLine)];
1255
+ case "perpBisector":
1256
+ return [c.p1, c.p2];
1257
+ case "angleBisector":
1258
+ return [c.p1, c.vertex, c.p2];
1259
+ case "angleBisectorLines":
1260
+ return [stripBorderSuffix(c.line1), stripBorderSuffix(c.line2)];
1261
+ case "tangent":
1262
+ return [c.throughPoint, c.toCircle];
1263
+ }
1264
+ }
1265
+ var def5;
1266
+ var init_line = __esm({
1267
+ "src/core/scene/kinds/line.ts"() {
1268
+ init_registry();
1269
+ init_labelOf();
1270
+ def5 = {
1271
+ type: "line",
1272
+ schemaVersion: 1,
1273
+ migrate: {},
1274
+ validate: (a) => {
1275
+ if (a?.construction) return;
1276
+ if (!a?.p1 || !a?.p2) throw new Error("line: p1 v\xE0 p2 b\u1EAFt bu\u1ED9c (ho\u1EB7c construction)");
1277
+ },
1278
+ dependsOn: (a) => a.construction ? constructionRefs(a.construction) : [a.p1, a.p2],
1279
+ describe: (obj, state) => {
1280
+ const L = (id) => labelOf(id, state);
1281
+ const c = obj.attrs.construction;
1282
+ if (!c) return `\u0110\u01B0\u1EDDng th\u1EB3ng ${L(obj.attrs.p1)}${L(obj.attrs.p2)}`;
1283
+ switch (c.kind) {
1284
+ case "perpendicular":
1285
+ return `${obj.label} \u27C2 ${L(c.toLine)} qua ${L(c.throughPoint)}`;
1286
+ case "parallel":
1287
+ return `${obj.label} \u2225 ${L(c.toLine)} qua ${L(c.throughPoint)}`;
1288
+ case "perpBisector":
1289
+ return `${obj.label}: trung tr\u1EF1c ${L(c.p1)}${L(c.p2)}`;
1290
+ case "angleBisector":
1291
+ return `${obj.label}: ph\xE2n gi\xE1c g\xF3c ${L(c.p1)}${L(c.vertex)}${L(c.p2)}`;
1292
+ case "angleBisectorLines":
1293
+ return `${obj.label}: ph\xE2n gi\xE1c ${L(c.line1)} & ${L(c.line2)} (${c.branch === 0 ? "1" : "2"})`;
1294
+ case "tangent":
1295
+ return `${obj.label}: ti\u1EBFp tuy\u1EBFn ${L(c.toCircle)} qua ${L(c.throughPoint)}`;
1296
+ }
1297
+ },
1298
+ render: (obj, ctx) => {
1299
+ const board = ctx.jxg;
1300
+ const baseOpts = {
1301
+ name: obj.label,
1302
+ withLabel: obj.attrs.showLabel ?? false,
1303
+ strokeColor: obj.attrs.color ?? "#0f172a",
1304
+ strokeWidth: obj.attrs.width ?? 2,
1305
+ dash: obj.attrs.dash ?? 0,
1306
+ visible: obj.visible,
1307
+ fixed: obj.locked
1308
+ };
1309
+ const c = obj.attrs.construction;
1310
+ if (!c) {
1311
+ const p1 = ctx.resolveRef(obj.attrs.p1);
1312
+ const p2 = ctx.resolveRef(obj.attrs.p2);
1313
+ return board.create("line", [p1, p2], {
1314
+ ...baseOpts,
1315
+ straightFirst: true,
1316
+ straightLast: true
1317
+ });
1318
+ }
1319
+ switch (c.kind) {
1320
+ case "perpendicular": {
1321
+ const through = ctx.resolveRef(c.throughPoint);
1322
+ const toLine = ctx.resolveRef(c.toLine);
1323
+ return board.create("perpendicular", [toLine, through], baseOpts);
1324
+ }
1325
+ case "parallel": {
1326
+ const through = ctx.resolveRef(c.throughPoint);
1327
+ const toLine = ctx.resolveRef(c.toLine);
1328
+ return board.create("parallel", [toLine, through], baseOpts);
1329
+ }
1330
+ case "perpBisector": {
1331
+ const p1 = ctx.resolveRef(c.p1);
1332
+ const p2 = ctx.resolveRef(c.p2);
1333
+ const mid = board.create("midpoint", [p1, p2], {
1334
+ visible: false,
1335
+ withLabel: false,
1336
+ fixed: true,
1337
+ name: ""
1338
+ });
1339
+ const helperLine = board.create("line", [p1, p2], {
1340
+ visible: false,
1341
+ withLabel: false,
1342
+ fixed: true,
1343
+ name: "",
1344
+ straightFirst: true,
1345
+ straightLast: true
1346
+ });
1347
+ const bisector = board.create("perpendicular", [helperLine, mid], baseOpts);
1348
+ bisector._helpers = [mid, helperLine];
1349
+ return bisector;
1350
+ }
1351
+ case "angleBisector": {
1352
+ const p1 = ctx.resolveRef(c.p1);
1353
+ const vertex = ctx.resolveRef(c.vertex);
1354
+ const p2 = ctx.resolveRef(c.p2);
1355
+ return board.create("bisector", [p1, vertex, p2], baseOpts);
1356
+ }
1357
+ case "angleBisectorLines": {
1358
+ const line1Jxg = ctx.resolveRef(c.line1);
1359
+ const line2Jxg = ctx.resolveRef(c.line2);
1360
+ const comp = board.create("bisectorlines", [line1Jxg, line2Jxg], {
1361
+ line1: { visible: false, withLabel: false, fixed: true, name: "" },
1362
+ line2: { visible: false, withLabel: false, fixed: true, name: "" }
1363
+ });
1364
+ const selected = c.branch === 0 ? comp.line1 : comp.line2;
1365
+ const other = c.branch === 0 ? comp.line2 : comp.line1;
1366
+ selected.setAttribute({
1367
+ ...baseOpts,
1368
+ visible: obj.visible,
1369
+ fixed: obj.locked
1370
+ });
1371
+ selected._helpers = [other];
1372
+ return selected;
1373
+ }
1374
+ case "tangent": {
1375
+ const through = ctx.resolveRef(c.throughPoint);
1376
+ const toCircle = ctx.resolveRef(c.toCircle);
1377
+ const branch = c.branch ?? "on";
1378
+ if (branch === "on") {
1379
+ const glider = board.create("glider", [through.X(), through.Y(), toCircle], {
1380
+ visible: false,
1381
+ withLabel: false,
1382
+ fixed: true,
1383
+ name: ""
1384
+ });
1385
+ const tangent2 = board.create("tangent", [glider], baseOpts);
1386
+ tangent2._helpers = [glider];
1387
+ return tangent2;
1388
+ }
1389
+ const center = toCircle.center;
1390
+ const mid = board.create("midpoint", [center, through], {
1391
+ visible: false,
1392
+ withLabel: false,
1393
+ fixed: true,
1394
+ name: ""
1395
+ });
1396
+ const thales = board.create("circle", [mid, through], {
1397
+ visible: false,
1398
+ withLabel: false,
1399
+ fixed: true,
1400
+ strokeOpacity: 0,
1401
+ fillOpacity: 0
1402
+ });
1403
+ const touch = board.create("intersection", [thales, toCircle, branch], {
1404
+ visible: false,
1405
+ withLabel: false,
1406
+ fixed: true,
1407
+ name: ""
1408
+ });
1409
+ const tangent = board.create("line", [through, touch], {
1410
+ ...baseOpts,
1411
+ straightFirst: true,
1412
+ straightLast: true
1413
+ });
1414
+ tangent._helpers = [mid, thales, touch];
1415
+ return tangent;
1416
+ }
1417
+ }
1418
+ }
1419
+ };
1420
+ registerKind(def5);
1421
+ }
1422
+ });
1423
+
1424
+ // src/core/scene/kinds/ray.ts
1425
+ var def6;
1426
+ var init_ray = __esm({
1427
+ "src/core/scene/kinds/ray.ts"() {
1428
+ init_registry();
1429
+ init_labelOf();
1430
+ def6 = {
1431
+ type: "ray",
1432
+ schemaVersion: 1,
1433
+ migrate: {},
1434
+ validate: (a) => {
1435
+ if (!a?.origin || !a?.through) throw new Error("ray: origin v\xE0 through b\u1EAFt bu\u1ED9c");
1436
+ },
1437
+ dependsOn: (a) => [a.origin, a.through],
1438
+ describe: (obj, state) => `Tia ${labelOf(obj.attrs.origin, state)}${labelOf(obj.attrs.through, state)}`,
1439
+ render: (obj, ctx) => {
1440
+ const board = ctx.jxg;
1441
+ const o = ctx.resolveRef(obj.attrs.origin);
1442
+ const t = ctx.resolveRef(obj.attrs.through);
1443
+ return board.create("line", [o, t], {
1444
+ name: obj.label,
1445
+ straightFirst: false,
1446
+ straightLast: true,
1447
+ strokeColor: obj.attrs.color ?? "#0f172a",
1448
+ strokeWidth: obj.attrs.width ?? 2,
1449
+ dash: obj.attrs.dash ?? 0,
1450
+ visible: obj.visible,
1451
+ fixed: obj.locked
1452
+ });
1453
+ }
1454
+ };
1455
+ registerKind(def6);
1456
+ }
1457
+ });
1458
+
1459
+ // src/core/scene/kinds/vector.ts
1460
+ var def7;
1461
+ var init_vector = __esm({
1462
+ "src/core/scene/kinds/vector.ts"() {
1463
+ init_registry();
1464
+ init_labelOf();
1465
+ def7 = {
1466
+ type: "vector",
1467
+ schemaVersion: 1,
1468
+ migrate: {},
1469
+ validate: (a) => {
1470
+ if (!a?.from || !a?.to) throw new Error("vector: from v\xE0 to b\u1EAFt bu\u1ED9c");
1471
+ },
1472
+ dependsOn: (a) => [a.from, a.to],
1473
+ describe: (obj, state) => `Vector ${labelOf(obj.attrs.from, state)}${labelOf(obj.attrs.to, state)}`,
1474
+ render: (obj, ctx) => {
1475
+ const board = ctx.jxg;
1476
+ const f = ctx.resolveRef(obj.attrs.from);
1477
+ const t = ctx.resolveRef(obj.attrs.to);
1478
+ return board.create("arrow", [f, t], {
1479
+ name: obj.label,
1480
+ strokeColor: obj.attrs.color ?? "#0f172a",
1481
+ strokeWidth: obj.attrs.width ?? 2,
1482
+ visible: obj.visible,
1483
+ fixed: obj.locked
1484
+ });
1485
+ }
1486
+ };
1487
+ registerKind(def7);
1488
+ }
1489
+ });
1490
+
1491
+ // src/core/scene/kinds/circle.ts
1492
+ function constructionRefs2(c) {
1493
+ switch (c.kind) {
1494
+ case "circumscribed":
1495
+ return [c.p1, c.p2, c.p3];
1496
+ }
1497
+ }
1498
+ var def8;
1499
+ var init_circle = __esm({
1500
+ "src/core/scene/kinds/circle.ts"() {
1501
+ init_registry();
1502
+ init_labelOf();
1503
+ def8 = {
1504
+ type: "circle",
1505
+ schemaVersion: 1,
1506
+ migrate: {},
1507
+ validate: (a) => {
1508
+ if (a?.construction) return;
1509
+ if (!a?.center || !a?.surfacePoint) {
1510
+ throw new Error("circle: center v\xE0 surfacePoint b\u1EAFt bu\u1ED9c (ho\u1EB7c construction)");
1511
+ }
1512
+ },
1513
+ dependsOn: (a) => a.construction ? constructionRefs2(a.construction) : [a.center, a.surfacePoint],
1514
+ measure: (obj, state) => {
1515
+ if (obj.attrs.construction) return null;
1516
+ const center = obj.attrs.center ? state.objects[obj.attrs.center] : void 0;
1517
+ const surface = obj.attrs.surfacePoint ? state.objects[obj.attrs.surfacePoint] : void 0;
1518
+ if (!center || !surface) return null;
1519
+ const c1 = center.attrs.constraint;
1520
+ const c2 = surface.attrs.constraint;
1521
+ if (c1?.kind !== "free" || c2?.kind !== "free") return null;
1522
+ const dx = (c2.x ?? 0) - (c1.x ?? 0);
1523
+ const dy = (c2.y ?? 0) - (c1.y ?? 0);
1524
+ return [{ label: "r", value: Math.hypot(dx, dy) }];
1525
+ },
1526
+ describe: (obj, state) => {
1527
+ const L = (id) => labelOf(id, state);
1528
+ const c = obj.attrs.construction;
1529
+ if (c?.kind === "circumscribed") {
1530
+ return `\u0110\u01B0\u1EDDng tr\xF2n \u0111i qua ${L(c.p1)}${L(c.p2)}${L(c.p3)}`;
1531
+ }
1532
+ return `\u0110\u01B0\u1EDDng tr\xF2n t\xE2m ${L(obj.attrs.center)} b\xE1n k\xEDnh ${L(obj.attrs.center)}${L(obj.attrs.surfacePoint)}`;
1533
+ },
1534
+ render: (obj, ctx) => {
1535
+ const board = ctx.jxg;
1536
+ const baseOpts = {
1537
+ name: obj.label,
1538
+ withLabel: obj.attrs.showLabel ?? false,
1539
+ strokeColor: obj.attrs.color ?? "#0f172a",
1540
+ strokeWidth: obj.attrs.width ?? 2,
1541
+ dash: obj.attrs.dash ?? 0,
1542
+ fillColor: "none",
1543
+ visible: obj.visible,
1544
+ fixed: obj.locked
1545
+ };
1546
+ const c = obj.attrs.construction;
1547
+ if (c?.kind === "circumscribed") {
1548
+ const p1 = ctx.resolveRef(c.p1);
1549
+ const p2 = ctx.resolveRef(c.p2);
1550
+ const p3 = ctx.resolveRef(c.p3);
1551
+ return board.create("circumcircle", [p1, p2, p3], baseOpts);
1552
+ }
1553
+ const center = ctx.resolveRef(obj.attrs.center);
1554
+ const surface = ctx.resolveRef(obj.attrs.surfacePoint);
1555
+ return board.create("circle", [center, surface], baseOpts);
1556
+ }
1557
+ };
1558
+ registerKind(def8);
1559
+ }
1560
+ });
1561
+
1562
+ // src/core/scene/kinds/arc.ts
1563
+ function constructionRefs3(c) {
1564
+ switch (c.kind) {
1565
+ case "semicircle":
1566
+ return [c.p1, c.p2];
1567
+ case "byCenter":
1568
+ return [c.center, c.p1, c.p2];
1569
+ case "by3Points":
1570
+ return [c.p1, c.p2, c.p3];
1571
+ }
1572
+ }
1573
+ var def9;
1574
+ var init_arc = __esm({
1575
+ "src/core/scene/kinds/arc.ts"() {
1576
+ init_registry();
1577
+ init_labelOf();
1578
+ def9 = {
1579
+ type: "arc",
1580
+ schemaVersion: 1,
1581
+ migrate: {},
1582
+ validate: (a) => {
1583
+ const c = a?.construction;
1584
+ if (!c) throw new Error("arc: construction b\u1EAFt bu\u1ED9c");
1585
+ if (c.kind === "semicircle") {
1586
+ if (!c.p1 || !c.p2) throw new Error("arc.semicircle: p1, p2 b\u1EAFt bu\u1ED9c");
1587
+ } else if (c.kind === "byCenter") {
1588
+ if (!c.center || !c.p1 || !c.p2) throw new Error("arc.byCenter: center, p1, p2 b\u1EAFt bu\u1ED9c");
1589
+ } else if (c.kind === "by3Points") {
1590
+ if (!c.p1 || !c.p2 || !c.p3) throw new Error("arc.by3Points: p1, p2, p3 b\u1EAFt bu\u1ED9c");
1591
+ }
1592
+ },
1593
+ dependsOn: (a) => constructionRefs3(a.construction),
1594
+ describe: (obj, state) => {
1595
+ const L = (id) => labelOf(id, state);
1596
+ const c = obj.attrs.construction;
1597
+ switch (c.kind) {
1598
+ case "semicircle":
1599
+ return `N\u1EEDa \u0111\u01B0\u1EDDng tr\xF2n \u0111\u01B0\u1EDDng k\xEDnh ${L(c.p1)}${L(c.p2)}`;
1600
+ case "byCenter":
1601
+ return `Cung tr\xF2n t\xE2m ${L(c.center)} t\u1EEB ${L(c.p1)} \u0111\u1EBFn ${L(c.p2)}`;
1602
+ case "by3Points":
1603
+ return `Cung tr\xF2n qua ${L(c.p1)}${L(c.p2)}${L(c.p3)}`;
1604
+ }
1605
+ },
1606
+ render: (obj, ctx) => {
1607
+ const board = ctx.jxg;
1608
+ const baseOpts = {
1609
+ name: obj.label,
1610
+ withLabel: obj.attrs.showLabel ?? false,
1611
+ strokeColor: obj.attrs.color ?? "#0f172a",
1612
+ strokeWidth: obj.attrs.width ?? 2,
1613
+ dash: obj.attrs.dash ?? 0,
1614
+ fillColor: "none",
1615
+ visible: obj.visible,
1616
+ fixed: obj.locked
1617
+ };
1618
+ const c = obj.attrs.construction;
1619
+ if (c.kind === "semicircle") {
1620
+ const p1 = ctx.resolveRef(c.p1);
1621
+ const p2 = ctx.resolveRef(c.p2);
1622
+ return board.create("semicircle", [p1, p2], baseOpts);
1623
+ }
1624
+ if (c.kind === "byCenter") {
1625
+ const O = ctx.resolveRef(c.center);
1626
+ const A2 = ctx.resolveRef(c.p1);
1627
+ const B2 = ctx.resolveRef(c.p2);
1628
+ return board.create("arc", [O, A2, B2], baseOpts);
1629
+ }
1630
+ const A = ctx.resolveRef(c.p1);
1631
+ const B = ctx.resolveRef(c.p2);
1632
+ const C = ctx.resolveRef(c.p3);
1633
+ return board.create("circumcirclearc", [A, B, C], baseOpts);
1634
+ }
1635
+ };
1636
+ registerKind(def9);
1637
+ }
1638
+ });
1639
+
1640
+ // src/core/scene/kinds/sector.ts
1641
+ var def10;
1642
+ var init_sector = __esm({
1643
+ "src/core/scene/kinds/sector.ts"() {
1644
+ init_registry();
1645
+ init_labelOf();
1646
+ def10 = {
1647
+ type: "sector",
1648
+ schemaVersion: 1,
1649
+ migrate: {},
1650
+ validate: (a) => {
1651
+ const c = a?.construction;
1652
+ if (!c) throw new Error("sector: construction b\u1EAFt bu\u1ED9c");
1653
+ if (c.kind === "byCenter") {
1654
+ if (!c.center || !c.p1 || !c.p2) {
1655
+ throw new Error("sector.byCenter: center, p1, p2 b\u1EAFt bu\u1ED9c");
1656
+ }
1657
+ }
1658
+ },
1659
+ dependsOn: (a) => {
1660
+ const c = a.construction;
1661
+ return [c.center, c.p1, c.p2];
1662
+ },
1663
+ describe: (obj, state) => {
1664
+ const L = (id) => labelOf(id, state);
1665
+ const c = obj.attrs.construction;
1666
+ return `H\xECnh qu\u1EA1t t\xE2m ${L(c.center)} t\u1EEB ${L(c.p1)} \u0111\u1EBFn ${L(c.p2)}`;
1667
+ },
1668
+ render: (obj, ctx) => {
1669
+ const board = ctx.jxg;
1670
+ const c = obj.attrs.construction;
1671
+ const O = ctx.resolveRef(c.center);
1672
+ const A = ctx.resolveRef(c.p1);
1673
+ const B = ctx.resolveRef(c.p2);
1674
+ return board.create("sector", [O, A, B], {
1675
+ name: obj.label,
1676
+ withLabel: obj.attrs.showLabel ?? false,
1677
+ strokeColor: obj.attrs.color ?? "#0f172a",
1678
+ strokeWidth: obj.attrs.width ?? 2,
1679
+ fillColor: obj.attrs.fillColor ?? "#f59e0b",
1680
+ fillOpacity: obj.attrs.fillOpacity ?? 0.18,
1681
+ visible: obj.visible,
1682
+ fixed: obj.locked
1683
+ });
1684
+ }
1685
+ };
1686
+ registerKind(def10);
1687
+ }
1688
+ });
1689
+
1690
+ // src/core/scene/kinds/polygon.ts
1691
+ function regularPolygonName(n) {
1692
+ if (n === 3) return "Tam gi\xE1c \u0111\u1EC1u";
1693
+ if (n === 4) return "H\xECnh vu\xF4ng";
1694
+ if (n === 5) return "Ng\u0169 gi\xE1c \u0111\u1EC1u";
1695
+ if (n === 6) return "L\u1EE5c gi\xE1c \u0111\u1EC1u";
1696
+ return `${n}-gi\xE1c \u0111\u1EC1u`;
1697
+ }
1698
+ function regularVertexLabels(p1Label, p2Label, n) {
1699
+ const A = "A".charCodeAt(0);
1700
+ const Z = "Z".charCodeAt(0);
1701
+ if (p1Label.length === 1 && p2Label.length === 1) {
1702
+ const c1 = p1Label.charCodeAt(0);
1703
+ const c2 = p2Label.charCodeAt(0);
1704
+ if (c1 >= A && c1 <= Z && c2 === c1 + 1 && c1 + n - 1 <= Z) {
1705
+ let out = "";
1706
+ for (let i = 0; i < n; i++) out += String.fromCharCode(c1 + i);
1707
+ return out;
1708
+ }
1709
+ }
1710
+ return `${p1Label}${p2Label}\u2026`;
1711
+ }
1712
+ var def11;
1713
+ var init_polygon = __esm({
1714
+ "src/core/scene/kinds/polygon.ts"() {
1715
+ init_registry();
1716
+ init_labelOf();
1717
+ def11 = {
1718
+ type: "polygon",
1719
+ schemaVersion: 1,
1720
+ migrate: {},
1721
+ validate: (a) => {
1722
+ if (a?.construction) {
1723
+ if (a.construction.kind === "regular") {
1724
+ if (!a.construction.p1 || !a.construction.p2) {
1725
+ throw new Error("polygon (regular): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
1726
+ }
1727
+ if (!Number.isFinite(a.construction.n) || a.construction.n < 3) {
1728
+ throw new Error("polygon (regular): n \u2265 3");
1729
+ }
1730
+ }
1731
+ return;
1732
+ }
1733
+ if (!Array.isArray(a?.vertices) || a.vertices.length < 3) {
1734
+ throw new Error("polygon: c\u1EA7n \xEDt nh\u1EA5t 3 \u0111\u1EC9nh");
1735
+ }
1736
+ },
1737
+ dependsOn: (a) => {
1738
+ if (a.construction?.kind === "regular") return [a.construction.p1, a.construction.p2];
1739
+ return [...a.vertices ?? []];
1740
+ },
1741
+ describe: (obj, state) => {
1742
+ if (obj.attrs.construction?.kind === "regular") {
1743
+ const c = obj.attrs.construction;
1744
+ const labels = regularVertexLabels(labelOf(c.p1, state), labelOf(c.p2, state), c.n);
1745
+ return `${regularPolygonName(c.n)} ${labels}`;
1746
+ }
1747
+ return `\u0110a gi\xE1c ${(obj.attrs.vertices ?? []).map((id) => labelOf(id, state)).join("")}`;
1748
+ },
1749
+ render: (obj, ctx) => {
1750
+ const board = ctx.jxg;
1751
+ const label = obj.label;
1752
+ const showValue = obj.attrs.showValue ?? false;
1753
+ if (obj.attrs.construction?.kind === "regular") {
1754
+ const c = obj.attrs.construction;
1755
+ const p1 = ctx.resolveRef(c.p1);
1756
+ const p2 = ctx.resolveRef(c.p2);
1757
+ return board.create("regularpolygon", [p1, p2, c.n], {
1758
+ name: label,
1759
+ withLabel: obj.attrs.showLabel ?? false,
1760
+ borders: {
1761
+ strokeColor: obj.attrs.color ?? "#0f172a",
1762
+ strokeWidth: obj.attrs.width ?? 2
1763
+ },
1764
+ fillColor: obj.attrs.color ?? "#60a5fa",
1765
+ fillOpacity: obj.attrs.fillOpacity ?? 0.15,
1766
+ visible: obj.visible,
1767
+ fixed: obj.locked
1768
+ });
1769
+ }
1770
+ const verts = (obj.attrs.vertices ?? []).map((id) => ctx.resolveRef(id));
1771
+ const poly = board.create("polygon", verts, {
1772
+ name: showValue ? function() {
1773
+ const a = typeof this.Area === "function" ? this.Area() : 0;
1774
+ const prefix = obj.attrs.showLabel ?? true ? `${label}: ` : "";
1775
+ return `${prefix}S = ${Math.abs(a).toFixed(2)}`;
1776
+ } : label,
1777
+ withLabel: showValue ? true : obj.attrs.showLabel ?? false,
1778
+ borders: {
1779
+ strokeColor: obj.attrs.color ?? "#0f172a",
1780
+ strokeWidth: obj.attrs.width ?? 2
1781
+ },
1782
+ fillColor: obj.attrs.color ?? "#60a5fa",
1783
+ fillOpacity: obj.attrs.fillOpacity ?? 0.15,
1784
+ visible: obj.visible,
1785
+ fixed: obj.locked
1786
+ });
1787
+ return poly;
1788
+ }
1789
+ };
1790
+ registerKind(def11);
1791
+ }
1792
+ });
1793
+
1794
+ // src/core/scene/kinds/intersection.ts
1795
+ var def12;
1796
+ var init_intersection = __esm({
1797
+ "src/core/scene/kinds/intersection.ts"() {
1798
+ init_registry();
1799
+ def12 = {
1800
+ type: "intersection",
1801
+ schemaVersion: 1,
1802
+ migrate: {},
1803
+ validate: (a) => {
1804
+ if (!a || !("kind" in a)) throw new Error("intersection: kind b\u1EAFt bu\u1ED9c");
1805
+ if (!a.ref1 || !a.ref2) throw new Error("intersection: ref1 v\xE0 ref2 b\u1EAFt bu\u1ED9c");
1806
+ if (a.kind === "lineLine") return;
1807
+ if (a.kind === "lineCircle" || a.kind === "circleCircle") {
1808
+ if (a.branch !== 0 && a.branch !== 1) {
1809
+ throw new Error(`intersection.${a.kind}: branch ph\u1EA3i l\xE0 0 ho\u1EB7c 1`);
1810
+ }
1811
+ return;
1812
+ }
1813
+ throw new Error(`intersection: kind kh\xF4ng h\u1EE3p l\u1EC7 "${a.kind}"`);
1814
+ },
1815
+ dependsOn: (a) => [a.ref1, a.ref2],
1816
+ describe: (obj) => {
1817
+ const a = obj.attrs;
1818
+ return `${obj.label} = giao ${a.ref1} \u2229 ${a.ref2}`;
1819
+ },
1820
+ render: (obj, ctx) => {
1821
+ const board = ctx.jxg;
1822
+ const a = ctx.resolveRef(obj.attrs.ref1);
1823
+ const b = ctx.resolveRef(obj.attrs.ref2);
1824
+ const opts = {
1825
+ name: obj.label,
1826
+ withLabel: true,
1827
+ strokeColor: obj.attrs.color ?? "#dc2626",
1828
+ fillColor: obj.attrs.color ?? "#dc2626",
1829
+ visible: obj.visible,
1830
+ fixed: obj.locked
1831
+ };
1832
+ if (obj.attrs.kind === "lineLine") {
1833
+ return board.create("intersection", [a, b, 0], opts);
1834
+ }
1835
+ const branch = obj.attrs.branch ?? 0;
1836
+ return board.create("intersection", [a, b, branch], opts);
1837
+ }
1838
+ };
1839
+ registerKind(def12);
1840
+ }
1841
+ });
1842
+
1843
+ // src/core/scene/kinds/angle.ts
1844
+ var def13;
1845
+ var init_angle = __esm({
1846
+ "src/core/scene/kinds/angle.ts"() {
1847
+ init_registry();
1848
+ init_labelOf();
1849
+ def13 = {
1850
+ type: "angle",
1851
+ schemaVersion: 1,
1852
+ migrate: {},
1853
+ validate: (a) => {
1854
+ if (!a?.p1 || !a?.vertex || !a?.p2) {
1855
+ throw new Error("angle: p1, vertex, p2 b\u1EAFt bu\u1ED9c");
1856
+ }
1857
+ },
1858
+ dependsOn: (a) => [a.p1, a.vertex, a.p2],
1859
+ describe: (obj, state) => `G\xF3c ${labelOf(obj.attrs.p1, state)}${labelOf(obj.attrs.vertex, state)}${labelOf(obj.attrs.p2, state)}`,
1860
+ render: (obj, ctx) => {
1861
+ const board = ctx.jxg;
1862
+ const pa = ctx.resolveRef(obj.attrs.p1);
1863
+ const pv = ctx.resolveRef(obj.attrs.vertex);
1864
+ const pc = ctx.resolveRef(obj.attrs.p2);
1865
+ let parents = [pa, pv, pc];
1866
+ try {
1867
+ const ax = pa.X() - pv.X(), ay = pa.Y() - pv.Y();
1868
+ const cx = pc.X() - pv.X(), cy = pc.Y() - pv.Y();
1869
+ if (ax * cy - ay * cx < 0) parents = [pc, pv, pa];
1870
+ } catch {
1871
+ }
1872
+ return board.create("angle", parents, {
1873
+ name: obj.label,
1874
+ withLabel: obj.attrs.showLabel ?? true,
1875
+ radius: obj.attrs.radius ?? 1,
1876
+ fillColor: obj.attrs.color ?? "#22c55e",
1877
+ fillOpacity: obj.attrs.fillOpacity ?? 0.25,
1878
+ strokeColor: obj.attrs.color ?? "#16a34a",
1879
+ strokeWidth: 1.5,
1880
+ visible: obj.visible,
1881
+ fixed: obj.locked
1882
+ });
1883
+ }
1884
+ };
1885
+ registerKind(def13);
1886
+ }
1887
+ });
1888
+
1889
+ // src/core/scene/kinds/distance.ts
1890
+ var def14;
1891
+ var init_distance = __esm({
1892
+ "src/core/scene/kinds/distance.ts"() {
1893
+ init_registry();
1894
+ init_labelOf();
1895
+ def14 = {
1896
+ type: "distance",
1897
+ schemaVersion: 1,
1898
+ migrate: {},
1899
+ validate: (a) => {
1900
+ if (!a?.p1 || !a?.p2) throw new Error("distance: p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
1901
+ },
1902
+ dependsOn: (a) => [a.p1, a.p2],
1903
+ describe: (obj, state) => `Kho\u1EA3ng c\xE1ch ${labelOf(obj.attrs.p1, state)}${labelOf(obj.attrs.p2, state)}`,
1904
+ render: (obj, ctx) => {
1905
+ const board = ctx.jxg;
1906
+ const p1 = ctx.resolveRef(obj.attrs.p1);
1907
+ const p2 = ctx.resolveRef(obj.attrs.p2);
1908
+ const prefix = obj.attrs.prefix ?? "d = ";
1909
+ const precision = obj.attrs.precision ?? 2;
1910
+ return board.create("text", [
1911
+ () => (p1.X() + p2.X()) / 2,
1912
+ () => (p1.Y() + p2.Y()) / 2,
1913
+ () => `${prefix}${Math.hypot(p1.X() - p2.X(), p1.Y() - p2.Y()).toFixed(precision)}`
1914
+ ], {
1915
+ fontSize: obj.attrs.fontSize ?? 14,
1916
+ strokeColor: obj.attrs.color ?? "#dc2626",
1917
+ anchorX: "middle",
1918
+ anchorY: "middle",
1919
+ visible: obj.visible,
1920
+ fixed: true
1921
+ });
1922
+ }
1923
+ };
1924
+ registerKind(def14);
1925
+ }
1926
+ });
1927
+
1928
+ // src/core/scene/expressions/parser.ts
1929
+ function validate(expression) {
1930
+ const trimmed = expression.trim();
1931
+ if (!trimmed) return { ok: false, error: "Bi\u1EC3u th\u1EE9c r\u1ED7ng" };
1932
+ if (UNSAFE_RE.test(trimmed)) return { ok: false, error: "Bi\u1EC3u th\u1EE9c ch\u1EE9a to\xE1n t\u1EED ho\u1EB7c identifier kh\xF4ng cho ph\xE9p" };
1933
+ const jsExpr = trimmed.replace(/\^/g, "**");
1934
+ let fm;
1935
+ FUNC_CALL_RE.lastIndex = 0;
1936
+ while ((fm = FUNC_CALL_RE.exec(jsExpr)) !== null) {
1937
+ const fnName = fm[1];
1938
+ if (!ALLOWED_FUNCTIONS.includes(fnName)) {
1939
+ return { ok: false, error: `H\xE0m kh\xF4ng h\u1EE3p l\u1EC7: ${fnName}` };
1940
+ }
1941
+ }
1942
+ const ids = /* @__PURE__ */ new Set();
1943
+ let m;
1944
+ ID_RE.lastIndex = 0;
1945
+ while ((m = ID_RE.exec(jsExpr)) !== null) ids.add(m[0]);
1946
+ for (const id of ids) {
1947
+ if (id === "x") continue;
1948
+ if (ALLOWED_CONSTANTS.includes(id)) continue;
1949
+ if (ALLOWED_FUNCTIONS.includes(id)) continue;
1950
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(id)) {
1951
+ return { ok: false, error: `Identifier kh\xF4ng h\u1EE3p l\u1EC7: ${id}` };
1952
+ }
1953
+ }
1954
+ try {
1955
+ buildFunctionBody(jsExpr, Array.from(ids).filter(
1956
+ (id) => id !== "x" && !ALLOWED_CONSTANTS.includes(id) && !ALLOWED_FUNCTIONS.includes(id)
1957
+ ));
1958
+ } catch (err) {
1959
+ return { ok: false, error: `C\xFA ph\xE1p l\u1ED7i: ${err.message}` };
1960
+ }
1961
+ return { ok: true };
1962
+ }
1963
+ function compile(expression, params) {
1964
+ const v = validate(expression);
1965
+ if (!v.ok) return v.error;
1966
+ const jsExpr = expression.trim().replace(/\^/g, "**");
1967
+ const ids = /* @__PURE__ */ new Set();
1968
+ let m;
1969
+ ID_RE.lastIndex = 0;
1970
+ while ((m = ID_RE.exec(jsExpr)) !== null) ids.add(m[0]);
1971
+ const paramNames = [];
1972
+ for (const id of ids) {
1973
+ if (id === "x") continue;
1974
+ if (ALLOWED_CONSTANTS.includes(id)) continue;
1975
+ if (ALLOWED_FUNCTIONS.includes(id)) continue;
1976
+ paramNames.push(id);
1977
+ }
1978
+ try {
1979
+ const body = buildFunctionBody(jsExpr, paramNames);
1980
+ const fn = new Function("x", ...paramNames, body);
1981
+ const args = paramNames.map((name) => params[name] ?? NaN);
1982
+ return (x) => fn(x, ...args);
1983
+ } catch (err) {
1984
+ return `Compile error: ${err.message}`;
1985
+ }
1986
+ }
1987
+ function buildFunctionBody(jsExpr, _paramNames) {
1988
+ let body = jsExpr;
1989
+ body = body.replace(/\bln\b/g, "Math.log");
1990
+ body = body.replace(/\blog\b/g, "Math.log10");
1991
+ body = body.replace(/\bpi\b/g, "(Math.PI)");
1992
+ body = body.replace(/\be\b(?!\w)/g, "(Math.E)");
1993
+ for (const fn of ALLOWED_FUNCTIONS) {
1994
+ if (fn === "log" || fn === "log10") continue;
1995
+ body = body.replace(new RegExp(`\\b${fn}\\b`, "g"), `Math.${fn}`);
1996
+ }
1997
+ return `"use strict"; return (${body});`;
1998
+ }
1999
+ var ALLOWED_CONSTANTS, ALLOWED_FUNCTIONS, ID_RE, UNSAFE_RE, FUNC_CALL_RE;
2000
+ var init_parser = __esm({
2001
+ "src/core/scene/expressions/parser.ts"() {
2002
+ ALLOWED_CONSTANTS = ["pi", "e"];
2003
+ ALLOWED_FUNCTIONS = [
2004
+ "sin",
2005
+ "cos",
2006
+ "tan",
2007
+ "asin",
2008
+ "acos",
2009
+ "atan",
2010
+ "atan2",
2011
+ "sinh",
2012
+ "cosh",
2013
+ "tanh",
2014
+ "exp",
2015
+ "log",
2016
+ "log10",
2017
+ "ln",
2018
+ "sqrt",
2019
+ "cbrt",
2020
+ "abs",
2021
+ "floor",
2022
+ "ceil",
2023
+ "round",
2024
+ "min",
2025
+ "max",
2026
+ "pow"
2027
+ ];
2028
+ ID_RE = /[A-Za-z_][A-Za-z0-9_]*/g;
2029
+ UNSAFE_RE = /[=;{}]|\beval\b|\bnew\b|\breturn\b|\bthis\b|\bwindow\b|\bdocument\b|\bglobal\b|\bprocess\b/;
2030
+ FUNC_CALL_RE = /([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
2031
+ }
2032
+ });
2033
+
2034
+ // src/core/scene/kinds/function2d.ts
2035
+ var def15;
2036
+ var init_function2d = __esm({
2037
+ "src/core/scene/kinds/function2d.ts"() {
2038
+ init_registry();
2039
+ init_parser();
2040
+ def15 = {
2041
+ type: "function2d",
2042
+ schemaVersion: 1,
2043
+ migrate: {},
2044
+ validate: (a) => {
2045
+ if (!a) throw new Error("function2d: attrs b\u1EAFt bu\u1ED9c");
2046
+ if (typeof a.expression !== "string" || !a.expression.trim()) {
2047
+ throw new Error("function2d: expression r\u1ED7ng");
2048
+ }
2049
+ const v = validate(a.expression);
2050
+ if (!v.ok) throw new Error(`function2d: expression invalid \u2014 ${v.error}`);
2051
+ if (typeof a.color !== "string") throw new Error("function2d: color b\u1EAFt bu\u1ED9c");
2052
+ if (typeof a.visible !== "boolean") throw new Error("function2d: visible b\u1EAFt bu\u1ED9c");
2053
+ if (a.domain) {
2054
+ if (a.domain.min >= a.domain.max) {
2055
+ throw new Error("function2d: domain min ph\u1EA3i < max");
2056
+ }
2057
+ }
2058
+ },
2059
+ dependsOn: () => [],
2060
+ describe: (obj) => `${obj.label}(x) = ${obj.attrs.expression}`,
2061
+ render: (obj, ctx) => {
2062
+ const board = ctx.jxg;
2063
+ if (!obj.visible || !obj.attrs.visible) return null;
2064
+ const fn = compile(obj.attrs.expression, ctx.paramMap ?? {});
2065
+ if (typeof fn !== "function") return null;
2066
+ const view = ctx.defaults.view;
2067
+ const xMin = obj.attrs.domain?.min ?? view?.xMin ?? -10;
2068
+ const xMax = obj.attrs.domain?.max ?? view?.xMax ?? 10;
2069
+ return board.create("functiongraph", [fn, xMin, xMax], {
2070
+ strokeColor: obj.attrs.color,
2071
+ strokeWidth: 2,
2072
+ name: obj.label,
2073
+ withLabel: false,
2074
+ highlight: false,
2075
+ fixed: true
2076
+ });
2077
+ }
2078
+ };
2079
+ registerKind(def15);
2080
+ }
2081
+ });
2082
+
2083
+ // src/core/scene/kinds/parameter.ts
2084
+ var def16;
2085
+ var init_parameter = __esm({
2086
+ "src/core/scene/kinds/parameter.ts"() {
2087
+ init_registry();
2088
+ def16 = {
2089
+ type: "parameter",
2090
+ schemaVersion: 1,
2091
+ migrate: {},
2092
+ validate: (a) => {
2093
+ if (!a) throw new Error("parameter: attrs b\u1EAFt bu\u1ED9c");
2094
+ if (typeof a.value !== "number" || typeof a.min !== "number" || typeof a.max !== "number") {
2095
+ throw new Error("parameter: value/min/max ph\u1EA3i l\xE0 number");
2096
+ }
2097
+ if (a.min >= a.max) throw new Error("parameter: min ph\u1EA3i < max");
2098
+ if (a.value < a.min || a.value > a.max) throw new Error("parameter: value ngo\xE0i [min, max]");
2099
+ if (typeof a.step !== "number" || a.step <= 0) throw new Error("parameter: step ph\u1EA3i > 0");
2100
+ },
2101
+ dependsOn: () => [],
2102
+ describe: (obj) => `${obj.label} = ${obj.attrs.value}`,
2103
+ render: () => null
2104
+ // Không render lên board
2105
+ };
2106
+ registerKind(def16);
2107
+ }
2108
+ });
2109
+
2110
+ // src/core/scene/kinds/pointOnCurve.ts
2111
+ var def17;
2112
+ var init_pointOnCurve = __esm({
2113
+ "src/core/scene/kinds/pointOnCurve.ts"() {
2114
+ init_registry();
2115
+ def17 = {
2116
+ type: "pointOnCurve",
2117
+ schemaVersion: 1,
2118
+ migrate: {},
2119
+ validate: (a) => {
2120
+ if (!a || typeof a.functionId !== "string" || !a.functionId) {
2121
+ throw new Error("pointOnCurve: functionId b\u1EAFt bu\u1ED9c");
2122
+ }
2123
+ if (typeof a.x !== "number" || !Number.isFinite(a.x)) {
2124
+ throw new Error("pointOnCurve: x ph\u1EA3i l\xE0 finite number");
2125
+ }
2126
+ },
2127
+ dependsOn: (a) => [a.functionId],
2128
+ describe: (obj) => `${obj.label} tr\xEAn ${obj.attrs.functionId} t\u1EA1i x=${obj.attrs.x.toFixed(3)}`,
2129
+ render: (obj, ctx) => {
2130
+ const board = ctx.jxg;
2131
+ const curve = ctx.resolveRef(obj.attrs.functionId);
2132
+ if (!curve) return null;
2133
+ return board.create("glider", [obj.attrs.x, 0, curve], {
2134
+ name: obj.label,
2135
+ size: 3,
2136
+ withLabel: obj.label !== "",
2137
+ fillColor: "#000",
2138
+ strokeColor: "#000"
2139
+ });
2140
+ }
2141
+ };
2142
+ registerKind(def17);
2143
+ }
2144
+ });
2145
+
2146
+ // src/core/scene/kinds/tangent2d.ts
2147
+ var def18;
2148
+ var init_tangent2d = __esm({
2149
+ "src/core/scene/kinds/tangent2d.ts"() {
2150
+ init_registry();
2151
+ def18 = {
2152
+ type: "tangent2d",
2153
+ schemaVersion: 1,
2154
+ migrate: {},
2155
+ validate: (a) => {
2156
+ if (!a || typeof a.pointId !== "string" || !a.pointId) {
2157
+ throw new Error("tangent2d: pointId b\u1EAFt bu\u1ED9c");
2158
+ }
2159
+ },
2160
+ dependsOn: (a) => [a.pointId],
2161
+ describe: (obj) => `Ti\u1EBFp tuy\u1EBFn t\u1EA1i ${obj.attrs.pointId}`,
2162
+ render: (obj, ctx) => {
2163
+ const board = ctx.jxg;
2164
+ const pt = ctx.resolveRef(obj.attrs.pointId);
2165
+ if (!pt) return null;
2166
+ return board.create("tangent", [pt], {
2167
+ strokeColor: "#65a30d",
2168
+ strokeWidth: 1.5,
2169
+ dash: 2,
2170
+ withLabel: false
2171
+ });
2172
+ }
2173
+ };
2174
+ registerKind(def18);
2175
+ }
2176
+ });
2177
+
2178
+ // src/core/scene/expressions/evaluator.ts
2179
+ function scanRoots(fn, xMin, xMax, samples = DEFAULT_SAMPLES) {
2180
+ if (!Number.isFinite(xMin) || !Number.isFinite(xMax) || xMin >= xMax) return [];
2181
+ const out = [];
2182
+ const step = (xMax - xMin) / samples;
2183
+ let prev = fn(xMin);
2184
+ for (let i = 1; i <= samples; i++) {
2185
+ const x = xMin + i * step;
2186
+ const curr = fn(x);
2187
+ if (!Number.isFinite(prev) || !Number.isFinite(curr)) {
2188
+ prev = curr;
2189
+ continue;
2190
+ }
2191
+ if (prev * curr < 0) {
2192
+ const root = bisect(fn, x - step, x);
2193
+ if (Number.isFinite(root)) out.push(root);
2194
+ } else if (prev !== 0 && curr === 0) {
2195
+ out.push(x);
2196
+ }
2197
+ prev = curr;
2198
+ }
2199
+ return out;
2200
+ }
2201
+ function bisect(fn, lo, hi) {
2202
+ for (let i = 0; i < 50; i++) {
2203
+ const mid = (lo + hi) / 2;
2204
+ const fmid = fn(mid);
2205
+ if (!Number.isFinite(fmid)) break;
2206
+ if (Math.abs(fmid) < 1e-10) return mid;
2207
+ const flo = fn(lo);
2208
+ if (!Number.isFinite(flo)) break;
2209
+ if (flo * fmid <= 0) hi = mid;
2210
+ else lo = mid;
2211
+ }
2212
+ return (lo + hi) / 2;
2213
+ }
2214
+ function scanExtrema(fn, xMin, xMax, samples = DEFAULT_SAMPLES) {
2215
+ if (!Number.isFinite(xMin) || !Number.isFinite(xMax) || xMin >= xMax) return [];
2216
+ const out = [];
2217
+ const step = (xMax - xMin) / samples;
2218
+ const xs = [];
2219
+ const ys = [];
2220
+ for (let i = 0; i <= samples + 1; i++) {
2221
+ const x = xMin + (i - 1) * step;
2222
+ xs.push(x);
2223
+ ys.push(fn(x));
2224
+ }
2225
+ let prevSign = 0;
2226
+ let prevSignIdx = 1;
2227
+ for (let i = 1; i <= samples; i++) {
2228
+ const d = ys[i + 1] - ys[i - 1];
2229
+ if (!Number.isFinite(d)) continue;
2230
+ const sign = d > 0 ? 1 : d < 0 ? -1 : 0;
2231
+ if (sign === 0) continue;
2232
+ if (prevSign !== 0 && sign !== prevSign) {
2233
+ const type = prevSign > 0 ? "max" : "min";
2234
+ const midI = Math.round((prevSignIdx + i) / 2);
2235
+ out.push({ x: xs[midI], y: ys[midI], type });
2236
+ }
2237
+ prevSign = sign;
2238
+ prevSignIdx = i;
2239
+ }
2240
+ return out;
2241
+ }
2242
+ var DEFAULT_SAMPLES;
2243
+ var init_evaluator = __esm({
2244
+ "src/core/scene/expressions/evaluator.ts"() {
2245
+ DEFAULT_SAMPLES = 1e3;
2246
+ }
2247
+ });
2248
+
2249
+ // src/core/scene/kinds/extremum2d.ts
2250
+ var def19;
2251
+ var init_extremum2d = __esm({
2252
+ "src/core/scene/kinds/extremum2d.ts"() {
2253
+ init_registry();
2254
+ init_evaluator();
2255
+ init_parser();
2256
+ def19 = {
2257
+ type: "extremum2d",
2258
+ schemaVersion: 1,
2259
+ migrate: {},
2260
+ validate: (a) => {
2261
+ if (!a || typeof a.functionId !== "string" || !a.functionId) {
2262
+ throw new Error("extremum2d: functionId b\u1EAFt bu\u1ED9c");
2263
+ }
2264
+ if (!a.interval || a.interval.min >= a.interval.max) {
2265
+ throw new Error("extremum2d: interval min ph\u1EA3i < max");
2266
+ }
2267
+ if (a.mode !== "max" && a.mode !== "min") {
2268
+ throw new Error('extremum2d: mode ph\u1EA3i l\xE0 "max" ho\u1EB7c "min"');
2269
+ }
2270
+ },
2271
+ dependsOn: (a) => [a.functionId],
2272
+ describe: (obj) => `${obj.attrs.mode === "max" ? "C\u1EF1c \u0111\u1EA1i" : "C\u1EF1c ti\u1EC3u"} c\u1EE7a ${obj.attrs.functionId} trong [${obj.attrs.interval.min}, ${obj.attrs.interval.max}]`,
2273
+ render: (obj, ctx) => {
2274
+ const board = ctx.jxg;
2275
+ const expr = ctx.defaults._functionExpr?.[obj.attrs.functionId];
2276
+ if (!expr) return null;
2277
+ const fn = compile(expr, ctx.paramMap ?? {});
2278
+ if (typeof fn !== "function") return null;
2279
+ const extrema = scanExtrema(fn, obj.attrs.interval.min, obj.attrs.interval.max).filter((e) => e.type === obj.attrs.mode);
2280
+ return extrema.map((e) => board.create("point", [e.x, e.y], {
2281
+ name: obj.label,
2282
+ size: 3,
2283
+ fillColor: "#dc2626",
2284
+ strokeColor: "#dc2626",
2285
+ withLabel: obj.label !== ""
2286
+ }));
2287
+ }
2288
+ };
2289
+ registerKind(def19);
2290
+ }
2291
+ });
2292
+
2293
+ // src/core/scene/kinds/root2d.ts
2294
+ var def20;
2295
+ var init_root2d = __esm({
2296
+ "src/core/scene/kinds/root2d.ts"() {
2297
+ init_registry();
2298
+ init_evaluator();
2299
+ init_parser();
2300
+ def20 = {
2301
+ type: "root2d",
2302
+ schemaVersion: 1,
2303
+ migrate: {},
2304
+ validate: (a) => {
2305
+ if (!a || typeof a.functionId !== "string" || !a.functionId) {
2306
+ throw new Error("root2d: functionId b\u1EAFt bu\u1ED9c");
2307
+ }
2308
+ if (!a.interval || a.interval.min >= a.interval.max) {
2309
+ throw new Error("root2d: interval min ph\u1EA3i < max");
2310
+ }
2311
+ },
2312
+ dependsOn: (a) => [a.functionId],
2313
+ describe: (obj) => `Nghi\u1EC7m c\u1EE7a ${obj.attrs.functionId} trong [${obj.attrs.interval.min}, ${obj.attrs.interval.max}]`,
2314
+ render: (obj, ctx) => {
2315
+ const board = ctx.jxg;
2316
+ const expr = ctx.defaults._functionExpr?.[obj.attrs.functionId];
2317
+ if (!expr) return null;
2318
+ const fn = compile(expr, ctx.paramMap ?? {});
2319
+ if (typeof fn !== "function") return null;
2320
+ const roots = scanRoots(fn, obj.attrs.interval.min, obj.attrs.interval.max);
2321
+ return roots.map((x) => board.create("point", [x, 0], {
2322
+ name: obj.label,
2323
+ size: 3,
2324
+ fillColor: "#dc2626",
2325
+ strokeColor: "#dc2626",
2326
+ withLabel: obj.label !== ""
2327
+ }));
2328
+ }
2329
+ };
2330
+ registerKind(def20);
2331
+ }
2332
+ });
2333
+
2334
+ // src/core/scene/kinds/slope2d.ts
2335
+ var def21;
2336
+ var init_slope2d = __esm({
2337
+ "src/core/scene/kinds/slope2d.ts"() {
2338
+ init_registry();
2339
+ def21 = {
2340
+ type: "slope2d",
2341
+ schemaVersion: 1,
2342
+ migrate: {},
2343
+ validate: (a) => {
2344
+ if (!a || typeof a.pointId !== "string" || !a.pointId) {
2345
+ throw new Error("slope2d: pointId b\u1EAFt bu\u1ED9c");
2346
+ }
2347
+ },
2348
+ dependsOn: (a) => [a.pointId],
2349
+ describe: (obj) => `Slope t\u1EA1i ${obj.attrs.pointId}`,
2350
+ render: (obj, ctx) => {
2351
+ const board = ctx.jxg;
2352
+ const pt = ctx.resolveRef(obj.attrs.pointId);
2353
+ if (!pt) return null;
2354
+ return board.create("slopetriangle", [pt], {
2355
+ name: obj.label,
2356
+ withLabel: true,
2357
+ fillColor: "#9333ea",
2358
+ strokeColor: "#9333ea",
2359
+ fillOpacity: 0.2
2360
+ });
2361
+ }
2362
+ };
2363
+ registerKind(def21);
2364
+ }
2365
+ });
2366
+
2367
+ // src/core/scene/kinds/index.ts
2368
+ var init_kinds = __esm({
2369
+ "src/core/scene/kinds/index.ts"() {
2370
+ init_point3d();
2371
+ init_segment3d();
2372
+ init_line3d();
2373
+ init_ray3d();
2374
+ init_vector3d();
2375
+ init_plane3d();
2376
+ init_polygon3d();
2377
+ init_sphere3d();
2378
+ init_polyhedron3d();
2379
+ init_cylinder3d();
2380
+ init_cone3d();
2381
+ init_point();
2382
+ init_segment();
2383
+ init_line();
2384
+ init_ray();
2385
+ init_vector();
2386
+ init_circle();
2387
+ init_arc();
2388
+ init_sector();
2389
+ init_polygon();
2390
+ init_intersection();
2391
+ init_angle();
2392
+ init_distance();
2393
+ init_function2d();
2394
+ init_parameter();
2395
+ init_pointOnCurve();
2396
+ init_tangent2d();
2397
+ init_extremum2d();
2398
+ init_root2d();
2399
+ init_slope2d();
2400
+ }
2401
+ });
2402
+
368
2403
  // src/core/scene/index.ts
369
2404
  var init_scene = __esm({
370
2405
  "src/core/scene/index.ts"() {
@@ -372,6 +2407,7 @@ var init_scene = __esm({
372
2407
  init_store();
373
2408
  init_selectors();
374
2409
  init_hooks();
2410
+ init_kinds();
375
2411
  }
376
2412
  });
377
2413
 
@@ -474,8 +2510,8 @@ var init_JxgRenderer3D = __esm({
474
2510
  }
475
2511
  create(obj) {
476
2512
  try {
477
- const def = getKind(obj.kind);
478
- const el = def.render(obj, this.ctx());
2513
+ const def22 = getKind(obj.kind);
2514
+ const el = def22.render(obj, this.ctx());
479
2515
  this.elements.set(obj.id, el);
480
2516
  } catch (err) {
481
2517
  console.warn(`[scene/render] kh\xF4ng render \u0111\u01B0\u1EE3c ${obj.kind} id="${obj.id}":`, err);
@@ -531,16 +2567,16 @@ var init_JxgRenderer3D = __esm({
531
2567
  if (Object.is(old, cur)) {
532
2568
  continue;
533
2569
  }
534
- let def;
2570
+ let def22;
535
2571
  try {
536
- def = getKind(cur.kind);
2572
+ def22 = getKind(cur.kind);
537
2573
  } catch {
538
2574
  continue;
539
2575
  }
540
2576
  const existing = this.elements.get(id);
541
- if (def.update && existing !== void 0) {
2577
+ if (def22.update && existing !== void 0) {
542
2578
  try {
543
- def.update(cur, old, this.ctx(), existing);
2579
+ def22.update(cur, old, this.ctx(), existing);
544
2580
  continue;
545
2581
  } catch (err) {
546
2582
  console.warn(`[scene/render] update fail, recreate id="${id}":`, err);
@@ -1020,7 +3056,7 @@ function buildPoint(args, store) {
1020
3056
  return addPoint(store, c);
1021
3057
  }
1022
3058
  var buildPointOnObject;
1023
- var init_point = __esm({
3059
+ var init_point2 = __esm({
1024
3060
  "src/stamps/geometry-3d/editor/tools/handlers/point.ts"() {
1025
3061
  init_ensurePoint();
1026
3062
  buildPointOnObject = buildPoint;
@@ -1075,7 +3111,7 @@ function buildVector(args, store) {
1075
3111
  if (!from || !to || from === to) return null;
1076
3112
  return addDerived(store, "vector3d", "v", { from, to });
1077
3113
  }
1078
- var init_segment = __esm({
3114
+ var init_segment2 = __esm({
1079
3115
  "src/stamps/geometry-3d/editor/tools/handlers/segment.ts"() {
1080
3116
  init_scene();
1081
3117
  init_ensurePoint();
@@ -1102,7 +3138,7 @@ function buildPolygon(args, store) {
1102
3138
  store.dispatch({ type: "ADD", payload: { obj } });
1103
3139
  return id;
1104
3140
  }
1105
- var init_polygon = __esm({
3141
+ var init_polygon2 = __esm({
1106
3142
  "src/stamps/geometry-3d/editor/tools/handlers/polygon.ts"() {
1107
3143
  init_scene();
1108
3144
  init_ensurePoint();
@@ -1618,9 +3654,9 @@ var init_cone = __esm({
1618
3654
  var stubBuild, ALL_SURFACES, OBJECT_ONLY, NO_SURFACE, TOOLS;
1619
3655
  var init_spec = __esm({
1620
3656
  "src/stamps/geometry-3d/editor/tools/spec.ts"() {
1621
- init_point();
1622
- init_segment();
1623
- init_polygon();
3657
+ init_point2();
3658
+ init_segment2();
3659
+ init_polygon2();
1624
3660
  init_plane();
1625
3661
  init_pyramid();
1626
3662
  init_prism();