@xom11/whiteboard 0.9.1 → 0.10.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.
@@ -116,7 +116,17 @@ function deserializeIntoBoard(board, serialized, options = {}) {
116
116
  const palette = options.palette ?? paletteFor(false);
117
117
  const idMap = /* @__PURE__ */ new Map();
118
118
  const resolve = (a) => {
119
- if (typeof a === "string" && idMap.has(a)) return idMap.get(a);
119
+ if (typeof a === "string") {
120
+ if (idMap.has(a)) return idMap.get(a);
121
+ const m = /^(.+):border:(\d+)$/.exec(a);
122
+ if (m) {
123
+ const poly = idMap.get(m[1]);
124
+ const idx = parseInt(m[2], 10);
125
+ if (poly && Array.isArray(poly.borders) && poly.borders[idx]) {
126
+ return poly.borders[idx];
127
+ }
128
+ }
129
+ }
120
130
  if (Array.isArray(a)) return a.map(resolve);
121
131
  return a;
122
132
  };
@@ -164,6 +174,29 @@ var init_safeJsx = __esm({
164
174
  });
165
175
 
166
176
  // src/stamps/geometry-2d/render.ts
177
+ function containerDimsForBbox(bbox) {
178
+ const [xmin, ymax, xmax, ymin] = bbox;
179
+ const w = Math.abs(xmax - xmin);
180
+ const h = Math.abs(ymax - ymin);
181
+ if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {
182
+ return { width: FALLBACK_W, height: FALLBACK_H };
183
+ }
184
+ let width = w * PIXELS_PER_UNIT;
185
+ let height = h * PIXELS_PER_UNIT;
186
+ const maxAxis = Math.max(width, height);
187
+ if (maxAxis > MAX_DIM) {
188
+ const ratio = MAX_DIM / maxAxis;
189
+ width *= ratio;
190
+ height *= ratio;
191
+ }
192
+ const minAxis = Math.min(width, height);
193
+ if (minAxis < MIN_DIM) {
194
+ const ratio = MIN_DIM / minAxis;
195
+ width *= ratio;
196
+ height *= ratio;
197
+ }
198
+ return { width: Math.round(width), height: Math.round(height) };
199
+ }
167
200
  async function renderGeometrySvgFromState(jsonState) {
168
201
  const parsed = JSON.parse(jsonState);
169
202
  const palette = paletteFor(false);
@@ -186,10 +219,11 @@ async function renderGeometrySvgFromState(jsonState) {
186
219
  opts.grid.strokeColor = palette.grid;
187
220
  }
188
221
  });
222
+ const { width, height } = containerDimsForBbox(parsed.bbox);
189
223
  const container = document.createElement("div");
190
224
  const containerId = "jxg_offscreen_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
191
225
  container.id = containerId;
192
- container.style.cssText = "position:absolute;top:-99999px;left:-99999px;width:400px;height:300px;visibility:hidden;pointer-events:none;";
226
+ container.style.cssText = `position:absolute;top:-99999px;left:-99999px;width:${width}px;height:${height}px;visibility:hidden;pointer-events:none;`;
193
227
  document.body.appendChild(container);
194
228
  let board = null;
195
229
  try {
@@ -199,7 +233,7 @@ async function renderGeometrySvgFromState(jsonState) {
199
233
  grid: !!parsed.showGrid,
200
234
  showCopyright: false,
201
235
  showNavigation: false,
202
- keepAspectRatio: false
236
+ keepAspectRatio: true
203
237
  });
204
238
  deserializeIntoBoard(board, parsed, { palette });
205
239
  board.update();
@@ -211,12 +245,18 @@ async function renderGeometrySvgFromState(jsonState) {
211
245
  if (container.parentNode) container.parentNode.removeChild(container);
212
246
  }
213
247
  }
248
+ var PIXELS_PER_UNIT, MIN_DIM, MAX_DIM, FALLBACK_W, FALLBACK_H;
214
249
  var init_render = __esm({
215
250
  "src/stamps/geometry-2d/render.ts"() {
216
251
  init_renderInline();
217
252
  init_serialize();
218
253
  init_theme();
219
254
  init_safeJsx();
255
+ PIXELS_PER_UNIT = 20;
256
+ MIN_DIM = 100;
257
+ MAX_DIM = 1200;
258
+ FALLBACK_W = 400;
259
+ FALLBACK_H = 300;
220
260
  }
221
261
  });
222
262
 
@@ -313,8 +353,12 @@ function letterForGroup(g) {
313
353
  }
314
354
  function objKind(obj) {
315
355
  if (!obj) return "other";
356
+ const ec = typeof obj.elementClass === "number" ? obj.elementClass : null;
357
+ if (ec === 1) return "point";
358
+ if (ec === 2) return "line";
359
+ if (ec === 3) return "circle";
316
360
  const e = (obj.elType || obj.type || "").toString().toLowerCase();
317
- if (e === "point" || e === "glider" || e === "midpoint") return "point";
361
+ if (e === "point" || e === "glider" || e === "midpoint" || e === "intersection" || e === "otherintersection" || e === "reflection" || e === "mirrorpoint" || e === "mirrorelement" || e === "orthogonalprojection" || e === "parallelpoint") return "point";
318
362
  if (e === "line" || e === "segment" || e === "arrow" || e === "axis" || e === "normal" || e === "parallel" || e === "perpendicular" || e === "tangent" || e === "bisector" || e === "perpendicularsegment") return "line";
319
363
  if (e === "circle" || e === "circumcircle") return "circle";
320
364
  return "other";
@@ -514,7 +558,7 @@ function handleDown(ctx, e) {
514
558
  if (!sc) return;
515
559
  const [sx, sy] = sc;
516
560
  const hits2 = ctx.objectsAt(e).map(ctx.promoteLabel).filter((o) => o !== ctx.axisObjsRef.current.x && o !== ctx.axisObjsRef.current.y);
517
- const obj = hits2.find((o) => objKind(o) === "point") ?? hits2[0] ?? ctx.findNearestPoint(e, 12);
561
+ const obj = hits2.find((o) => objKind(o) === "point") ?? ctx.findNearestPoint(e, 12) ?? hits2[0];
518
562
  if (obj) {
519
563
  const shift = !!(e.shiftKey || e.altKey);
520
564
  ctx.toggleSelect(obj, shift);
@@ -757,7 +801,7 @@ function handleUp(ctx, e) {
757
801
  const moved = Math.hypot(sx - start.sx, sy - start.sy);
758
802
  if (moved > 4) return;
759
803
  const hits = ctx.objectsAt(e).map(ctx.promoteLabel).filter((o) => o !== ctx.axisObjsRef.current.x && o !== ctx.axisObjsRef.current.y);
760
- const best = hits.find((o) => objKind(o) === "point") ?? hits[0] ?? ctx.findNearestPoint(e, 12);
804
+ const best = hits.find((o) => objKind(o) === "point") ?? ctx.findNearestPoint(e, 12) ?? hits[0];
761
805
  if (!best) {
762
806
  ctx.lastMoveClickRef.current = { obj: null, time: 0 };
763
807
  return;
@@ -896,8 +940,16 @@ var init_MiniBoard = __esm({
896
940
  const nextLocalId = react.useCallback(() => "j" + creationLogRef.current.length, []);
897
941
  const resolveArgs = react.useCallback((args) => {
898
942
  return args.map((a) => {
899
- if (typeof a === "string" && objMapRef.current.has(a)) {
900
- return objMapRef.current.get(a);
943
+ if (typeof a === "string") {
944
+ if (objMapRef.current.has(a)) return objMapRef.current.get(a);
945
+ const m = /^(.+):border:(\d+)$/.exec(a);
946
+ if (m) {
947
+ const poly = objMapRef.current.get(m[1]);
948
+ const idx = parseInt(m[2], 10);
949
+ if (poly && Array.isArray(poly.borders) && poly.borders[idx]) {
950
+ return poly.borders[idx];
951
+ }
952
+ }
901
953
  }
902
954
  return a;
903
955
  });
@@ -927,15 +979,27 @@ var init_MiniBoard = __esm({
927
979
  [nextLocalId, resolveArgs, pushLog]
928
980
  );
929
981
  const localIdOf = react.useCallback((obj) => {
982
+ if (!obj) return null;
930
983
  for (const [id, o] of objMapRef.current.entries()) {
931
984
  if (o === obj) return id;
932
985
  }
986
+ for (const [id, o] of objMapRef.current.entries()) {
987
+ const borders = o?.borders;
988
+ if (Array.isArray(borders)) {
989
+ const idx = borders.indexOf(obj);
990
+ if (idx >= 0) return `${id}:border:${idx}`;
991
+ }
992
+ }
933
993
  return null;
934
994
  }, []);
935
995
  const snapshotObject = react.useCallback((obj, anchorScreen) => {
936
996
  const o = obj;
937
997
  const k = objKind(o);
938
998
  if (k !== "point" && k !== "line" && k !== "circle") return null;
999
+ for (const owner of objMapRef.current.values()) {
1000
+ const borders = owner?.borders;
1001
+ if (Array.isArray(borders) && borders.indexOf(o) >= 0) return null;
1002
+ }
939
1003
  const v = o.visProp ?? {};
940
1004
  const showLabel = v.withlabel !== false;
941
1005
  const showValue = valueLabelsRef.current.has(o);
@@ -1454,7 +1518,22 @@ var init_MiniBoard = __esm({
1454
1518
  const board = boardRef.current;
1455
1519
  if (!board) return false;
1456
1520
  const idMap = objMapRef.current;
1457
- const resolved = el.args.map((a) => typeof a === "string" && idMap.has(a) ? idMap.get(a) : a);
1521
+ const resolve = (a) => {
1522
+ if (typeof a === "string") {
1523
+ if (idMap.has(a)) return idMap.get(a);
1524
+ const m = /^(.+):border:(\d+)$/.exec(a);
1525
+ if (m) {
1526
+ const poly = idMap.get(m[1]);
1527
+ const idx = parseInt(m[2], 10);
1528
+ if (poly && Array.isArray(poly.borders) && poly.borders[idx]) {
1529
+ return poly.borders[idx];
1530
+ }
1531
+ }
1532
+ }
1533
+ if (Array.isArray(a)) return a.map(resolve);
1534
+ return a;
1535
+ };
1536
+ const resolved = el.args.map(resolve);
1458
1537
  try {
1459
1538
  if (el.type === "valueLabel") {
1460
1539
  const target = resolved[0];
@@ -2968,6 +3047,7 @@ var init_EditorPanel = __esm({
2968
3047
  function GeometryEditorPanel2({ initialState, onInsert, onClose, withLeftPanel = false, onStateChange, isDark, isMobile = false, onOpenDrawer, onUndo, onRedo, canUndo, canRedo }, ref) {
2969
3048
  const handleRef = react.useRef(null);
2970
3049
  const [ready, setReady] = react.useState(false);
3050
+ const [hasContent, setHasContent] = react.useState(false);
2971
3051
  const [propsPopover, setPropsPopover] = react.useState(null);
2972
3052
  const [transformPopover, setTransformPopover] = react.useState(null);
2973
3053
  const onStateChangeRef = react.useRef(onStateChange);
@@ -2976,8 +3056,10 @@ var init_EditorPanel = __esm({
2976
3056
  }, [onStateChange]);
2977
3057
  const emitState = react.useCallback(() => {
2978
3058
  const h = handleRef.current;
3059
+ if (!h) return;
3060
+ setHasContent(h.getCreationLog().length > 0);
2979
3061
  const cb = onStateChangeRef.current;
2980
- if (!h || !cb) return;
3062
+ if (!cb) return;
2981
3063
  cb({
2982
3064
  tool: h.getTool(),
2983
3065
  showAxis: h.getShowAxis(),
@@ -3107,7 +3189,8 @@ var init_EditorPanel = __esm({
3107
3189
  {
3108
3190
  type: "button",
3109
3191
  onClick: handleInsert,
3110
- disabled: !ready,
3192
+ disabled: !ready || !hasContent,
3193
+ title: !hasContent ? "V\u1EBD \xEDt nh\u1EA5t m\u1ED9t \u0111\u1ED1i t\u01B0\u1EE3ng tr\u01B0\u1EDBc khi ch\xE8n" : void 0,
3111
3194
  "data-testid": "geometry-insert-btn-mobile",
3112
3195
  className: "rounded bg-white/15 px-3 py-1.5 text-xs font-semibold transition hover:bg-white/25 disabled:opacity-50",
3113
3196
  children: "Ch\xE8n"
@@ -3207,7 +3290,8 @@ var init_EditorPanel = __esm({
3207
3290
  "button",
3208
3291
  {
3209
3292
  onClick: handleInsert,
3210
- disabled: !ready,
3293
+ disabled: !ready || !hasContent,
3294
+ title: !hasContent ? "V\u1EBD \xEDt nh\u1EA5t m\u1ED9t \u0111\u1ED1i t\u01B0\u1EE3ng tr\u01B0\u1EDBc khi ch\xE8n" : void 0,
3211
3295
  "data-testid": "geometry-insert-btn",
3212
3296
  className: "rounded bg-emerald-600 px-3 py-1 text-xs font-medium text-white transition hover:bg-emerald-700 disabled:opacity-50",
3213
3297
  children: "Ch\xE8n"