@xom11/whiteboard 0.30.0 → 0.31.0

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 (88) hide show
  1. package/dist/ai.d.mts +226 -32
  2. package/dist/ai.d.ts +226 -32
  3. package/dist/ai.js +5126 -351
  4. package/dist/ai.js.map +1 -1
  5. package/dist/ai.mjs +4970 -352
  6. package/dist/ai.mjs.map +1 -1
  7. package/dist/catalog.json +5 -5
  8. package/dist/{chunk-V3YJ6JFL.mjs → chunk-44JY2AKC.mjs} +3 -3
  9. package/dist/{chunk-V3YJ6JFL.mjs.map → chunk-44JY2AKC.mjs.map} +1 -1
  10. package/dist/{chunk-XVVLT6B3.mjs → chunk-BMYC2ILT.mjs} +4 -4
  11. package/dist/{chunk-XVVLT6B3.mjs.map → chunk-BMYC2ILT.mjs.map} +1 -1
  12. package/dist/{chunk-PPKHCRRE.mjs → chunk-C76SOFXF.mjs} +3 -3
  13. package/dist/{chunk-PPKHCRRE.mjs.map → chunk-C76SOFXF.mjs.map} +1 -1
  14. package/dist/{chunk-IHUFOV7L.mjs → chunk-CH6SFONH.mjs} +15 -3
  15. package/dist/chunk-CH6SFONH.mjs.map +1 -0
  16. package/dist/{chunk-SF3U7ZF4.mjs → chunk-DWIEVCGK.mjs} +180 -15
  17. package/dist/chunk-DWIEVCGK.mjs.map +1 -0
  18. package/dist/{chunk-SZDAS7LK.mjs → chunk-IE2GGHNF.mjs} +131 -81
  19. package/dist/chunk-IE2GGHNF.mjs.map +1 -0
  20. package/dist/{chunk-ZTQBUKLJ.mjs → chunk-JJ4FPCBE.mjs} +142 -22
  21. package/dist/chunk-JJ4FPCBE.mjs.map +1 -0
  22. package/dist/{chunk-QRUAEXLR.mjs → chunk-K5BS2H56.mjs} +5 -5
  23. package/dist/{chunk-QRUAEXLR.mjs.map → chunk-K5BS2H56.mjs.map} +1 -1
  24. package/dist/{chunk-BNBOIDO5.mjs → chunk-K7VJU7LQ.mjs} +3 -3
  25. package/dist/{chunk-BNBOIDO5.mjs.map → chunk-K7VJU7LQ.mjs.map} +1 -1
  26. package/dist/{chunk-H22OZYTW.mjs → chunk-KOXOC2FI.mjs} +48 -39
  27. package/dist/chunk-KOXOC2FI.mjs.map +1 -0
  28. package/dist/{chunk-CXHNVYMD.mjs → chunk-KWDBVLST.mjs} +5 -5
  29. package/dist/{chunk-CXHNVYMD.mjs.map → chunk-KWDBVLST.mjs.map} +1 -1
  30. package/dist/{chunk-OQIQNKPQ.mjs → chunk-LTLLQUMN.mjs} +4 -4
  31. package/dist/{chunk-OQIQNKPQ.mjs.map → chunk-LTLLQUMN.mjs.map} +1 -1
  32. package/dist/{chunk-QGNU34T7.mjs → chunk-QLQ4MJNO.mjs} +10 -4
  33. package/dist/chunk-QLQ4MJNO.mjs.map +1 -0
  34. package/dist/{chunk-BU5KLO3P.mjs → chunk-T3N4BSJV.mjs} +4 -4
  35. package/dist/{chunk-BU5KLO3P.mjs.map → chunk-T3N4BSJV.mjs.map} +1 -1
  36. package/dist/{chunk-5JM35CXV.mjs → chunk-TMRFSOM7.mjs} +4 -4
  37. package/dist/{chunk-5JM35CXV.mjs.map → chunk-TMRFSOM7.mjs.map} +1 -1
  38. package/dist/geometry-2d.d.mts +1 -1
  39. package/dist/geometry-2d.d.ts +1 -1
  40. package/dist/geometry-2d.js +449 -172
  41. package/dist/geometry-2d.js.map +1 -1
  42. package/dist/geometry-2d.mjs +5 -5
  43. package/dist/geometry-3d.d.mts +1 -1
  44. package/dist/geometry-3d.d.ts +1 -1
  45. package/dist/geometry-3d.js +172 -22
  46. package/dist/geometry-3d.js.map +1 -1
  47. package/dist/geometry-3d.mjs +4 -4
  48. package/dist/graph-2d.d.mts +1 -1
  49. package/dist/graph-2d.d.ts +1 -1
  50. package/dist/graph-2d.js +307 -100
  51. package/dist/graph-2d.js.map +1 -1
  52. package/dist/graph-2d.mjs +7 -7
  53. package/dist/{host-HOSJHQ5H.mjs → host-4FIUNIDQ.mjs} +13 -12
  54. package/dist/host-4FIUNIDQ.mjs.map +1 -0
  55. package/dist/{host-2ISGVO7O.mjs → host-4ZB4XD4S.mjs} +9 -8
  56. package/dist/host-4ZB4XD4S.mjs.map +1 -0
  57. package/dist/{host-ZQCDAT6O.mjs → host-H2IGOKJU.mjs} +3 -3
  58. package/dist/{host-ZQCDAT6O.mjs.map → host-H2IGOKJU.mjs.map} +1 -1
  59. package/dist/{host-3UFGFMJ2.mjs → host-KMWP7KBT.mjs} +90 -43
  60. package/dist/host-KMWP7KBT.mjs.map +1 -0
  61. package/dist/index.d.mts +2 -2
  62. package/dist/index.d.ts +2 -2
  63. package/dist/index.js +453 -174
  64. package/dist/index.js.map +1 -1
  65. package/dist/index.mjs +21 -21
  66. package/dist/latex.d.mts +1 -1
  67. package/dist/latex.d.ts +1 -1
  68. package/dist/latex.js +8 -2
  69. package/dist/latex.js.map +1 -1
  70. package/dist/latex.mjs +1 -1
  71. package/dist/render-NMS7OAV6.mjs +10 -0
  72. package/dist/{render-ZX2O2IK7.mjs.map → render-NMS7OAV6.mjs.map} +1 -1
  73. package/dist/serialize-PGHQZEPV.mjs +9 -0
  74. package/dist/{serialize-N4G6RFBB.mjs.map → serialize-PGHQZEPV.mjs.map} +1 -1
  75. package/dist/{types-C3FjpoUi.d.ts → types-tePd94vW.d.mts} +8 -0
  76. package/dist/{types-C3FjpoUi.d.mts → types-tePd94vW.d.ts} +8 -0
  77. package/package.json +1 -1
  78. package/dist/chunk-H22OZYTW.mjs.map +0 -1
  79. package/dist/chunk-IHUFOV7L.mjs.map +0 -1
  80. package/dist/chunk-QGNU34T7.mjs.map +0 -1
  81. package/dist/chunk-SF3U7ZF4.mjs.map +0 -1
  82. package/dist/chunk-SZDAS7LK.mjs.map +0 -1
  83. package/dist/chunk-ZTQBUKLJ.mjs.map +0 -1
  84. package/dist/host-2ISGVO7O.mjs.map +0 -1
  85. package/dist/host-3UFGFMJ2.mjs.map +0 -1
  86. package/dist/host-HOSJHQ5H.mjs.map +0 -1
  87. package/dist/render-ZX2O2IK7.mjs +0 -10
  88. package/dist/serialize-N4G6RFBB.mjs +0 -9
@@ -1,10 +1,10 @@
1
1
  "use client";
2
- export { geometry3dStamp } from './chunk-5JM35CXV.mjs';
3
- import './chunk-BU5KLO3P.mjs';
2
+ export { geometry3dStamp } from './chunk-TMRFSOM7.mjs';
3
+ import './chunk-T3N4BSJV.mjs';
4
4
  import './chunk-R5FL6S7L.mjs';
5
5
  import './chunk-ICR4CVOE.mjs';
6
- import './chunk-ZTQBUKLJ.mjs';
7
- import './chunk-IHUFOV7L.mjs';
6
+ import './chunk-JJ4FPCBE.mjs';
7
+ import './chunk-CH6SFONH.mjs';
8
8
  import './chunk-73Q7ADVL.mjs';
9
9
  import './chunk-B4NJJZFR.mjs';
10
10
  import './chunk-5UTGXHLJ.mjs';
@@ -1,4 +1,4 @@
1
- import { B as BaseStampCustomData, S as StampType } from './types-C3FjpoUi.mjs';
1
+ import { B as BaseStampCustomData, S as StampType } from './types-tePd94vW.mjs';
2
2
  import 'react';
3
3
  import '@excalidraw/excalidraw/element/types';
4
4
 
@@ -1,4 +1,4 @@
1
- import { B as BaseStampCustomData, S as StampType } from './types-C3FjpoUi.js';
1
+ import { B as BaseStampCustomData, S as StampType } from './types-tePd94vW.js';
2
2
  import 'react';
3
3
  import '@excalidraw/excalidraw/element/types';
4
4
 
package/dist/graph-2d.js CHANGED
@@ -392,6 +392,22 @@ var init_parser = __esm({
392
392
  }
393
393
  });
394
394
 
395
+ // src/core/scene/kinds/_label.ts
396
+ function labelOpts(labelOffset, dflt) {
397
+ const offset = labelOffset ?? dflt;
398
+ return { label: { fixed: false, ...offset ? { offset } : {} } };
399
+ }
400
+ function readLabelOffset(label) {
401
+ const off = label.evalVisProp?.("offset") ?? label.visProp?.offset;
402
+ const rel = label.relativeCoords?.scrCoords;
403
+ if (!off || !rel || off.length < 2 || rel.length < 3) return null;
404
+ return [Math.round(off[0] + rel[1]), Math.round(off[1] - rel[2])];
405
+ }
406
+ var init_label = __esm({
407
+ "src/core/scene/kinds/_label.ts"() {
408
+ }
409
+ });
410
+
395
411
  // src/core/scene/render/JxgRenderer.ts
396
412
  var JxgRenderer;
397
413
  var init_JxgRenderer = __esm({
@@ -399,6 +415,7 @@ var init_JxgRenderer = __esm({
399
415
  init_registry();
400
416
  init_types2d();
401
417
  init_parser();
418
+ init_label();
402
419
  JxgRenderer = class {
403
420
  constructor(store, board, options = {}) {
404
421
  this.elements = /* @__PURE__ */ new Map();
@@ -467,6 +484,7 @@ var init_JxgRenderer = __esm({
467
484
  this.elements.set(obj.id, el);
468
485
  this.attachFreePointDragSync(obj, el);
469
486
  this.attachGliderDragSync(obj, el);
487
+ this.attachLabelDragSync(obj, el);
470
488
  } catch (err) {
471
489
  console.warn(`[scene/render/2d] kh\xF4ng render \u0111\u01B0\u1EE3c ${obj.kind} id="${obj.id}":`, err);
472
490
  }
@@ -581,6 +599,54 @@ var init_JxgRenderer = __esm({
581
599
  });
582
600
  });
583
601
  }
602
+ /**
603
+ * Cho phép kéo NHÃN của bất kỳ object nào có label (point/line/segment/
604
+ * circle...). Khi user kéo nhãn, JSXGraph cộng dồn vào label.relativeCoords
605
+ * (drag-delta screen px) nhưng KHÔNG đổi attribute offset → vị trí cuối =
606
+ * offset + relativeCoords. Ta gộp lại thành offset thuần (readLabelOffset),
607
+ * zero relativeCoords để khỏi double-count khi update-hook/recreate áp lại
608
+ * offset, rồi dispatch UPDATE_ATTRS { labelOffset }. Right-click → reset.
609
+ */
610
+ attachLabelDragSync(obj, el) {
611
+ const label = el?.label;
612
+ if (!label || typeof label.on !== "function") return;
613
+ const sceneId = obj.id;
614
+ label.on("up", () => {
615
+ if (this.disposed) return;
616
+ const off = readLabelOffset(label);
617
+ if (!off) return;
618
+ const cur = this.store.getState().objects[sceneId];
619
+ if (!cur) return;
620
+ const prev = cur.attrs.labelOffset;
621
+ if (prev && prev[0] === off[0] && prev[1] === off[1]) return;
622
+ try {
623
+ label.setAttribute({ offset: off });
624
+ if (label.relativeCoords?.scrCoords) {
625
+ label.relativeCoords.scrCoords[1] = 0;
626
+ label.relativeCoords.scrCoords[2] = 0;
627
+ }
628
+ } catch {
629
+ }
630
+ this.store.dispatch({
631
+ type: "UPDATE_ATTRS",
632
+ payload: { id: sceneId, patch: { labelOffset: off } }
633
+ });
634
+ });
635
+ const node = label.rendNode;
636
+ if (node?.addEventListener) {
637
+ node.addEventListener("contextmenu", (ev) => {
638
+ if (this.disposed) return;
639
+ ev.preventDefault();
640
+ const c = this.store.getState().objects[sceneId];
641
+ if (!c) return;
642
+ if (c.attrs.labelOffset === void 0) return;
643
+ this.store.dispatch({
644
+ type: "UPDATE_ATTRS",
645
+ payload: { id: sceneId, patch: { labelOffset: void 0 } }
646
+ });
647
+ });
648
+ }
649
+ }
584
650
  remove(id) {
585
651
  this.removeHalo(id);
586
652
  this.selectedIds.delete(id);
@@ -717,88 +783,89 @@ var init_JxgRenderer = __esm({
717
783
  layer: 4,
718
784
  needsRegularUpdate: true
719
785
  };
786
+ const elementClass = el.elementClass;
787
+ const elType = el.elType;
788
+ const isPointLike = elementClass === 1 || elType === "point" || elType === "glider" || elType === "intersection";
789
+ const isPolygonLike = Array.isArray(el.vertices) && el.vertices.length >= 3;
790
+ const isCircleLike = elementClass === 3 || elType === "circle" || elType === "circumcircle" || elType === "incircle";
791
+ const isSegment = elType === "segment";
792
+ const isLineLike = elementClass === 2 || elType === "line" || elType === "arrow" || elType === "ray" || elType === "vector" || elType === "tangent" || elType === "normal" || elType === "parallel" || elType === "perpendicular" || elType === "bisector";
720
793
  const halos = [];
721
794
  try {
722
- switch (el.elType) {
723
- case "point":
724
- case "glider":
725
- case "intersection": {
726
- const baseSize = el.getAttribute?.("size") ?? 4;
727
- const halo = board.create("point", [
728
- () => el.X?.() ?? 0,
729
- () => el.Y?.() ?? 0
730
- ], {
731
- ...haloBase,
732
- size: baseSize + 6,
733
- face: "o",
734
- strokeWidth: 2,
735
- strokeOpacity: 0.75,
736
- fillOpacity: 0.25
737
- });
738
- halos.push(halo);
739
- break;
740
- }
741
- case "segment": {
742
- if (el.point1 && el.point2) {
743
- const halo = board.create("segment", [el.point1, el.point2], {
744
- ...haloBase,
745
- strokeWidth: 9,
746
- straightFirst: false,
747
- straightLast: false
748
- });
749
- halos.push(halo);
750
- }
751
- break;
752
- }
753
- case "line":
754
- case "arrow":
755
- case "ray":
756
- case "vector":
757
- case "tangent":
758
- case "normal":
759
- case "parallel":
760
- case "perpendicular":
761
- case "bisector": {
762
- if (el.point1 && el.point2) {
763
- const halo = board.create("line", [el.point1, el.point2], {
764
- ...haloBase,
765
- strokeWidth: 9
766
- });
767
- halos.push(halo);
768
- }
769
- break;
770
- }
771
- case "circle": {
772
- if (el.center && typeof el.Radius === "function") {
773
- const halo = board.create("circle", [el.center, () => el.Radius?.() ?? 0], {
774
- ...haloBase,
775
- strokeWidth: 9,
776
- fillOpacity: 0
777
- });
778
- halos.push(halo);
779
- }
780
- break;
781
- }
782
- case "polygon": {
783
- if (Array.isArray(el.vertices) && el.vertices.length >= 3) {
784
- const last = el.vertices.length - 1;
785
- const verts = el.vertices[last] === el.vertices[0] ? el.vertices.slice(0, last) : el.vertices.slice();
786
- const halo = board.create("polygon", verts, {
787
- ...haloBase,
788
- fillOpacity: 0.2,
789
- borders: {
790
- strokeColor: SEL_STROKE,
791
- strokeWidth: 7,
792
- strokeOpacity: 0.55,
793
- highlight: false
794
- }
795
- });
796
- halos.push(halo);
795
+ if (isPointLike) {
796
+ const baseSize = el.getAttribute?.("size") ?? 4;
797
+ const halo = board.create("point", [
798
+ () => el.X?.() ?? 0,
799
+ () => el.Y?.() ?? 0
800
+ ], {
801
+ ...haloBase,
802
+ size: baseSize + 6,
803
+ face: "o",
804
+ strokeWidth: 2,
805
+ strokeOpacity: 0.75,
806
+ fillOpacity: 0.25
807
+ });
808
+ halos.push(halo);
809
+ } else if (isPolygonLike) {
810
+ const verts = el.vertices;
811
+ const last = verts.length - 1;
812
+ const trimmed = verts[last] === verts[0] ? verts.slice(0, last) : verts.slice();
813
+ const halo = board.create("polygon", trimmed, {
814
+ ...haloBase,
815
+ fillOpacity: 0.2,
816
+ borders: {
817
+ strokeColor: SEL_STROKE,
818
+ strokeWidth: 7,
819
+ strokeOpacity: 0.55,
820
+ highlight: false
797
821
  }
798
- break;
822
+ });
823
+ halos.push(halo);
824
+ } else if (isCircleLike && el.center && typeof el.Radius === "function") {
825
+ const halo = board.create("circle", [el.center, () => el.Radius?.() ?? 0], {
826
+ ...haloBase,
827
+ strokeWidth: 9,
828
+ fillOpacity: 0
829
+ });
830
+ halos.push(halo);
831
+ } else if (isSegment && el.point1 && el.point2) {
832
+ const halo = board.create("segment", [el.point1, el.point2], {
833
+ ...haloBase,
834
+ strokeWidth: 9,
835
+ straightFirst: false,
836
+ straightLast: false
837
+ });
838
+ halos.push(halo);
839
+ } else if (isLineLike && el.point1 && el.point2) {
840
+ const halo = board.create("line", [el.point1, el.point2], {
841
+ ...haloBase,
842
+ strokeWidth: 9
843
+ });
844
+ halos.push(halo);
845
+ } else if (el.center && (el.radiuspoint ?? el.radiusPoint) && (el.anglepoint ?? el.anglePoint) && (elType === "arc" || elType === "semicircle" || elType === "circumcirclearc" || elType === "sector" || elType === "angle")) {
846
+ const rp = el.radiuspoint ?? el.radiusPoint;
847
+ const ap = el.anglepoint ?? el.anglePoint;
848
+ if (elType === "sector") {
849
+ halos.push(board.create("sector", [el.center, rp, ap], {
850
+ ...haloBase,
851
+ strokeWidth: 7,
852
+ fillOpacity: 0.2
853
+ }));
854
+ } else if (elType === "angle") {
855
+ halos.push(board.create("angle", [rp, el.center, ap], {
856
+ ...haloBase,
857
+ strokeWidth: 7,
858
+ fillOpacity: 0.2,
859
+ radius: el.getAttribute?.("radius") ?? 1
860
+ }));
861
+ } else {
862
+ halos.push(board.create("arc", [el.center, rp, ap], {
863
+ ...haloBase,
864
+ strokeWidth: 9,
865
+ fillColor: "none",
866
+ fillOpacity: 0
867
+ }));
799
868
  }
800
- default:
801
- break;
802
869
  }
803
870
  } catch (err) {
804
871
  console.warn("[scene/render/2d] halo create fail:", err);
@@ -1647,8 +1714,12 @@ function constraintRefs2D(c) {
1647
1714
  return [c.line, c.circle, c.other];
1648
1715
  case "tangencyPoint":
1649
1716
  return [c.circle, c.onLine];
1650
- case "arcMidpoint":
1651
- return [c.circle, c.a, c.b, c.notContaining];
1717
+ case "arcMidpoint": {
1718
+ const containment = c.notContaining ?? c.containing;
1719
+ return containment ? [c.circle, c.a, c.b, containment] : [c.circle, c.a, c.b];
1720
+ }
1721
+ case "mixtilinearPoint":
1722
+ return [c.vertices[0], c.vertices[1], c.vertices[2]];
1652
1723
  case "pointAtDistance": {
1653
1724
  const d = c.distance;
1654
1725
  const extra = d.kind === "circleRadius" ? [d.circle] : d.kind === "segmentLength" ? [d.p1, d.p2] : [];
@@ -1935,7 +2006,7 @@ function dist(p, q) {
1935
2006
  function sideOf(a, b, p) {
1936
2007
  return (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
1937
2008
  }
1938
- function arcMidpoint(center, radius, a, b, notContaining) {
2009
+ function arcMidpoint(center, radius, a, b, reference, sameSide = false) {
1939
2010
  const mcx = (a[0] + b[0]) / 2;
1940
2011
  const mcy = (a[1] + b[1]) / 2;
1941
2012
  let ux = mcx - center[0];
@@ -1950,12 +2021,18 @@ function arcMidpoint(center, radius, a, b, notContaining) {
1950
2021
  uy /= len;
1951
2022
  const cand1 = [center[0] + radius * ux, center[1] + radius * uy];
1952
2023
  const cand2 = [center[0] - radius * ux, center[1] - radius * uy];
2024
+ if (!reference) return cand1[1] >= cand2[1] ? cand1 : cand2;
2025
+ const notContaining = reference;
1953
2026
  const sN = sideOf(a, b, notContaining);
1954
2027
  if (Math.abs(sN) < 1e-9) {
1955
- return dist(cand1, notContaining) >= dist(cand2, notContaining) ? cand1 : cand2;
2028
+ const far = dist(cand1, notContaining) >= dist(cand2, notContaining) ? cand1 : cand2;
2029
+ const near = far === cand1 ? cand2 : cand1;
2030
+ return sameSide ? near : far;
1956
2031
  }
1957
2032
  const s1 = sideOf(a, b, cand1);
1958
- return s1 * sN < 0 ? cand1 : cand2;
2033
+ const opp = s1 * sN < 0 ? cand1 : cand2;
2034
+ const same = opp === cand1 ? cand2 : cand1;
2035
+ return sameSide ? same : opp;
1959
2036
  }
1960
2037
  function excenter(vertices, oppositeIndex) {
1961
2038
  const [A, B, C] = vertices;
@@ -1971,6 +2048,35 @@ function excenter(vertices, oppositeIndex) {
1971
2048
  (w[0] * A[1] + w[1] * B[1] + w[2] * C[1]) / sum
1972
2049
  ];
1973
2050
  }
2051
+ function circumcenterXY(a, b, c) {
2052
+ const ax = a[0], ay = a[1], bx = b[0], by = b[1], cx = c[0], cy = c[1];
2053
+ const d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
2054
+ if (Math.abs(d) < 1e-12) return [(ax + bx + cx) / 3, (ay + by + cy) / 3];
2055
+ const ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d;
2056
+ const uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d;
2057
+ return [ux, uy];
2058
+ }
2059
+ function mixtilinearPoint(a, b, c, which) {
2060
+ const O = circumcenterXY(a, b, c);
2061
+ const R = Math.hypot(O[0] - a[0], O[1] - a[1]);
2062
+ const ux1 = (b[0] - a[0]) / (Math.hypot(b[0] - a[0], b[1] - a[1]) || 1);
2063
+ const uy1 = (b[1] - a[1]) / (Math.hypot(b[0] - a[0], b[1] - a[1]) || 1);
2064
+ const ux2 = (c[0] - a[0]) / (Math.hypot(c[0] - a[0], c[1] - a[1]) || 1);
2065
+ const uy2 = (c[1] - a[1]) / (Math.hypot(c[0] - a[0], c[1] - a[1]) || 1);
2066
+ let bx = ux1 + ux2, by = uy1 + uy2;
2067
+ const bl = Math.hypot(bx, by) || 1;
2068
+ bx /= bl;
2069
+ by /= bl;
2070
+ const cosA = ux1 * ux2 + uy1 * uy2;
2071
+ const sinHalf = Math.sqrt(Math.max(0, (1 - cosA) / 2));
2072
+ const cos2Half = Math.max(1e-9, (1 + cosA) / 2);
2073
+ const dotAO = bx * (O[0] - a[0]) + by * (O[1] - a[1]);
2074
+ const d = 2 * (dotAO - R * sinHalf) / cos2Half;
2075
+ const K = [a[0] + d * bx, a[1] + d * by];
2076
+ if (which === "center") return K;
2077
+ const kl = Math.hypot(K[0] - O[0], K[1] - O[1]) || 1;
2078
+ return [O[0] + R * (K[0] - O[0]) / kl, O[1] + R * (K[1] - O[1]) / kl];
2079
+ }
1974
2080
  function pointAtDistanceCoord(from, through, d) {
1975
2081
  const dx = through[0] - from[0];
1976
2082
  const dy = through[1] - from[1];
@@ -1998,29 +2104,38 @@ var init_arcMidpoint = __esm({
1998
2104
  arcMidpointConstraint = definePointConstraint({
1999
2105
  kind: "arcMidpoint",
2000
2106
  validate: (c) => {
2001
- if (!c.circle || !c.a || !c.b || !c.notContaining) {
2002
- throw new Error("point.arcMidpoint: circle, a, b, notContaining b\u1EAFt bu\u1ED9c");
2107
+ if (!c.circle || !c.a || !c.b) {
2108
+ throw new Error("point.arcMidpoint: circle, a, b b\u1EAFt bu\u1ED9c");
2109
+ }
2110
+ if (c.notContaining && c.containing) {
2111
+ throw new Error("point.arcMidpoint: kh\xF4ng th\u1EC3 v\u1EEBa notContaining v\u1EEBa containing");
2003
2112
  }
2004
2113
  },
2005
2114
  describe: (obj, state, c) => {
2006
2115
  const al = state?.objects[c.a]?.label ?? c.a;
2007
2116
  const bl = state?.objects[c.b]?.label ?? c.b;
2008
- const nl = state?.objects[c.notContaining]?.label ?? c.notContaining;
2009
- return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl} (kh\xF4ng ch\u1EE9a ${nl})`;
2117
+ const ref = c.containing ?? c.notContaining;
2118
+ if (!ref) return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl}`;
2119
+ const rl = state?.objects[ref]?.label ?? ref;
2120
+ const rel = c.containing ? "ch\u1EE9a" : "kh\xF4ng ch\u1EE9a";
2121
+ return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl} (${rel} ${rl})`;
2010
2122
  },
2011
2123
  render: (obj, ctx, c, opts) => {
2012
2124
  const board = ctx.jxg;
2013
2125
  const circle = ctx.resolveRef(c.circle);
2014
2126
  const A = ctx.resolveRef(c.a);
2015
2127
  const B = ctx.resolveRef(c.b);
2016
- const N = ctx.resolveRef(c.notContaining);
2128
+ const refName = c.containing ?? c.notContaining;
2129
+ const ref = refName ? ctx.resolveRef(refName) : void 0;
2130
+ const sameSide = !!c.containing;
2017
2131
  const O = circle?.center ?? circle?.midpoint ?? circle;
2018
2132
  const am = () => arcMidpoint(
2019
2133
  [O.X(), O.Y()],
2020
2134
  circle.Radius(),
2021
2135
  [A.X(), A.Y()],
2022
2136
  [B.X(), B.Y()],
2023
- [N.X(), N.Y()]
2137
+ ref ? [ref.X(), ref.Y()] : void 0,
2138
+ sameSide
2024
2139
  );
2025
2140
  return board.create("point", [() => am()[0], () => am()[1]], opts);
2026
2141
  }
@@ -2070,6 +2185,43 @@ var init_excenter = __esm({
2070
2185
  }
2071
2186
  });
2072
2187
 
2188
+ // src/core/scene/kinds/point-constraints/mixtilinearPoint.ts
2189
+ var mixtilinearPointConstraint;
2190
+ var init_mixtilinearPoint = __esm({
2191
+ "src/core/scene/kinds/point-constraints/mixtilinearPoint.ts"() {
2192
+ init_pointConstructions();
2193
+ init_types3();
2194
+ mixtilinearPointConstraint = definePointConstraint({
2195
+ kind: "mixtilinearPoint",
2196
+ validate: (c) => {
2197
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3 || !c.vertices.every(Boolean)) {
2198
+ throw new Error("point.mixtilinearPoint: vertices ph\u1EA3i l\xE0 tuple 3 id non-empty");
2199
+ }
2200
+ if (c.which !== "center" && c.which !== "touch") {
2201
+ throw new Error("point.mixtilinearPoint: which ph\u1EA3i 'center' | 'touch'");
2202
+ }
2203
+ },
2204
+ describe: (obj, state, c) => {
2205
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
2206
+ return c.which === "center" ? `${obj.label} = t\xE2m \u0111\u01B0\u1EDDng tr\xF2n mixtilinear \u0394${labels}` : `${obj.label} = ti\u1EBFp \u0111i\u1EC3m mixtilinear \u0394${labels} v\u1EDBi (O)`;
2207
+ },
2208
+ render: (obj, ctx, c, opts) => {
2209
+ const board = ctx.jxg;
2210
+ const a = ctx.resolveRef(c.vertices[0]);
2211
+ const b = ctx.resolveRef(c.vertices[1]);
2212
+ const d = ctx.resolveRef(c.vertices[2]);
2213
+ const mp = () => mixtilinearPoint(
2214
+ [a.X(), a.Y()],
2215
+ [b.X(), b.Y()],
2216
+ [d.X(), d.Y()],
2217
+ c.which
2218
+ );
2219
+ return board.create("point", [() => mp()[0], () => mp()[1]], opts);
2220
+ }
2221
+ });
2222
+ }
2223
+ });
2224
+
2073
2225
  // src/core/scene/kinds/point-constraints/shared.ts
2074
2226
  function buildJxgTransforms(board, ctx, t) {
2075
2227
  switch (t.kind) {
@@ -2121,11 +2273,13 @@ function buildPointOpts(obj) {
2121
2273
  strokeColor: obj.attrs.color ?? "#1e40af",
2122
2274
  fillColor: obj.attrs.color ?? "#1e40af",
2123
2275
  face: obj.attrs.face ?? "o",
2124
- size: obj.attrs.size ?? 4
2276
+ size: obj.attrs.size ?? 4,
2277
+ ...labelOpts(obj.attrs.labelOffset, [10, 10])
2125
2278
  };
2126
2279
  }
2127
2280
  var init_shared = __esm({
2128
2281
  "src/core/scene/kinds/point-constraints/shared.ts"() {
2282
+ init_label();
2129
2283
  }
2130
2284
  });
2131
2285
 
@@ -2434,6 +2588,7 @@ var init_registry2 = __esm({
2434
2588
  init_centroid();
2435
2589
  init_arcMidpoint();
2436
2590
  init_excenter();
2591
+ init_mixtilinearPoint();
2437
2592
  init_pointAtDistance();
2438
2593
  init_circleIntersection();
2439
2594
  init_circleSecondIntersection();
@@ -2459,6 +2614,7 @@ var init_registry2 = __esm({
2459
2614
  centroidConstraint,
2460
2615
  arcMidpointConstraint,
2461
2616
  excenterConstraint,
2617
+ mixtilinearPointConstraint,
2462
2618
  pointAtDistanceConstraint,
2463
2619
  circleIntersectionConstraint,
2464
2620
  circleSecondIntersectionConstraint,
@@ -2483,6 +2639,7 @@ var init_point = __esm({
2483
2639
  init_d_constraint2();
2484
2640
  init_registry2();
2485
2641
  init_shared();
2642
+ init_label();
2486
2643
  def3 = {
2487
2644
  type: "point",
2488
2645
  schemaVersion: 1,
@@ -2550,7 +2707,8 @@ var init_point = __esm({
2550
2707
  strokeColor: obj.attrs.color ?? "#1e40af",
2551
2708
  fillColor: obj.attrs.color ?? "#1e40af",
2552
2709
  face: obj.attrs.face ?? "o",
2553
- size: obj.attrs.size ?? 4
2710
+ size: obj.attrs.size ?? 4,
2711
+ ...labelOpts(obj.attrs.labelOffset, [10, 10])
2554
2712
  });
2555
2713
  } catch {
2556
2714
  }
@@ -2570,6 +2728,7 @@ var init_segment = __esm({
2570
2728
  "src/core/scene/kinds/segment.ts"() {
2571
2729
  init_registry();
2572
2730
  init_labelOf();
2731
+ init_label();
2573
2732
  def4 = {
2574
2733
  type: "segment",
2575
2734
  schemaVersion: 1,
@@ -2601,7 +2760,8 @@ var init_segment = __esm({
2601
2760
  strokeWidth: obj.attrs.width ?? 2,
2602
2761
  dash: obj.attrs.dash ?? 0,
2603
2762
  visible: obj.visible,
2604
- fixed: obj.locked
2763
+ fixed: obj.locked,
2764
+ ...labelOpts(obj.attrs.labelOffset)
2605
2765
  });
2606
2766
  }
2607
2767
  };
@@ -2638,6 +2798,7 @@ var init_line = __esm({
2638
2798
  "src/core/scene/kinds/line.ts"() {
2639
2799
  init_registry();
2640
2800
  init_labelOf();
2801
+ init_label();
2641
2802
  init_pointConstructions();
2642
2803
  def5 = {
2643
2804
  type: "line",
@@ -2680,7 +2841,8 @@ var init_line = __esm({
2680
2841
  strokeWidth: obj.attrs.width ?? 2,
2681
2842
  dash: obj.attrs.dash ?? 0,
2682
2843
  visible: obj.visible,
2683
- fixed: obj.locked
2844
+ fixed: obj.locked,
2845
+ ...labelOpts(obj.attrs.labelOffset)
2684
2846
  };
2685
2847
  const c = obj.attrs.construction;
2686
2848
  if (!c) {
@@ -2936,6 +3098,7 @@ var init_circle = __esm({
2936
3098
  "src/core/scene/kinds/circle.ts"() {
2937
3099
  init_registry();
2938
3100
  init_labelOf();
3101
+ init_label();
2939
3102
  init_pointConstructions();
2940
3103
  def8 = {
2941
3104
  type: "circle",
@@ -2997,15 +3160,17 @@ var init_circle = __esm({
2997
3160
  const board = ctx.jxg;
2998
3161
  const isCenterLabel = (l) => /^[A-Z]['′]?\d*$/u.test(l);
2999
3162
  const isCenter = isCenterLabel(obj.label);
3163
+ const isRenamedCircle = /_c$/.test(obj.label);
3000
3164
  const baseOpts = {
3001
3165
  name: obj.label,
3002
- withLabel: isCenter ? obj.attrs.showLabel ?? false : true,
3166
+ withLabel: isCenter ? obj.attrs.showLabel ?? false : isRenamedCircle ? false : true,
3003
3167
  strokeColor: obj.attrs.color ?? "#0f172a",
3004
3168
  strokeWidth: obj.attrs.width ?? 2,
3005
3169
  dash: obj.attrs.dash ?? 0,
3006
3170
  fillColor: "none",
3007
3171
  visible: obj.visible,
3008
- fixed: obj.locked
3172
+ fixed: obj.locked,
3173
+ ...labelOpts(obj.attrs.labelOffset)
3009
3174
  };
3010
3175
  const c = asConstruction(obj.attrs);
3011
3176
  if (c?.kind === "circumscribed") {
@@ -3490,8 +3655,11 @@ var init_intersection = __esm({
3490
3655
  const opts = {
3491
3656
  name: obj.label,
3492
3657
  withLabel: true,
3493
- strokeColor: obj.attrs.color ?? "#dc2626",
3494
- fillColor: obj.attrs.color ?? "#dc2626",
3658
+ // Cùng màu xanh với mọi điểm khác (buildPointOpts dùng '#1e40af').
3659
+ // Trước đây điểm giao tô đỏ '#dc2626' → tách biệt thị giác nhưng gây
3660
+ // khó hiểu ("vì sao điểm này khác màu?"). Giữ chung một màu.
3661
+ strokeColor: obj.attrs.color ?? "#1e40af",
3662
+ fillColor: obj.attrs.color ?? "#1e40af",
3495
3663
  visible: obj.visible,
3496
3664
  fixed: obj.locked
3497
3665
  };
@@ -3500,6 +3668,38 @@ var init_intersection = __esm({
3500
3668
  }
3501
3669
  const branch = obj.attrs.branch ?? 0;
3502
3670
  return board.create("intersection", [a, b, branch], opts);
3671
+ },
3672
+ /**
3673
+ * Cập nhật TẠI CHỖ các thuộc tính "trang trí" (tên/màu/ẩn-hiện/khoá) qua
3674
+ * setAttribute — giữ nguyên JxgObj identity nên các object phụ thuộc điểm
3675
+ * giao (đường thẳng qua nó, …) KHÔNG bị stale parent ref. Mô phỏng update
3676
+ * hook của point.ts.
3677
+ *
3678
+ * Nếu định nghĩa hình học đổi (kind/ref1/ref2/branch) thì throw → renderer
3679
+ * fallback remove + create để JSXGraph dựng lại phép giao đúng.
3680
+ */
3681
+ update: (obj, prev, ctx, existing) => {
3682
+ const a = obj.attrs;
3683
+ const p = prev.attrs;
3684
+ const branchA = a.branch;
3685
+ const branchP = p.branch;
3686
+ if (a.kind !== p.kind || a.ref1 !== p.ref1 || a.ref2 !== p.ref2 || branchA !== branchP) {
3687
+ throw new Error("intersection: \u0111\u1ECBnh ngh\u0129a h\xECnh h\u1ECDc \u0111\u1ED5i \u2014 recreate");
3688
+ }
3689
+ const el = existing;
3690
+ if (typeof el.setAttribute === "function") {
3691
+ try {
3692
+ el.setAttribute({
3693
+ name: obj.label,
3694
+ withLabel: true,
3695
+ strokeColor: a.color ?? "#1e40af",
3696
+ fillColor: a.color ?? "#1e40af",
3697
+ visible: obj.visible,
3698
+ fixed: obj.locked
3699
+ });
3700
+ } catch {
3701
+ }
3702
+ }
3503
3703
  }
3504
3704
  };
3505
3705
  registerKind(def12);
@@ -6258,11 +6458,17 @@ async function insertStampImage(api, opts) {
6258
6458
  const elements = api.getSceneElements();
6259
6459
  const editingId = opts.editingElementId ?? null;
6260
6460
  if (editingId) {
6461
+ const old = elements.find((e) => e.id === editingId) ;
6462
+ const oldLongest = old ? Math.max(old.width ?? 0, old.height ?? 0) : 0;
6463
+ const newLongest = Math.max(width, height);
6464
+ const scale = oldLongest > 0 && newLongest > 0 ? oldLongest / newLongest : 1;
6465
+ const w = width * scale;
6466
+ const h = height * scale;
6261
6467
  const updated = elements.map(
6262
- (e) => e.id === editingId ? { ...e, fileId, customData, width, height } : e
6468
+ (e) => e.id === editingId ? { ...e, fileId, customData, width: w, height: h } : e
6263
6469
  );
6264
6470
  api.updateScene({ elements: updated, appState: clearAppStateAfterInsert() });
6265
- return { fileId, width, height, elementId: editingId };
6471
+ return { fileId, width: w, height: h, elementId: editingId };
6266
6472
  }
6267
6473
  const newElement = buildStampImageElement(
6268
6474
  api,
@@ -6736,7 +6942,8 @@ var init_host = __esm({
6736
6942
  version: 2,
6737
6943
  jsonState
6738
6944
  }),
6739
- editingElementId: editingElement?.id ?? null
6945
+ editingElementId: editingElement?.id ?? null,
6946
+ preserveExistingSize: true
6740
6947
  });
6741
6948
  } catch (err) {
6742
6949
  console.error("Graph2D insert failed:", err);