@almadar/ui 5.62.0 → 5.63.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 (35) hide show
  1. package/dist/avl/index.cjs +490 -147
  2. package/dist/avl/index.js +490 -147
  3. package/dist/components/core/atoms/Box.d.ts +2 -0
  4. package/dist/components/core/molecules/CalendarGrid.d.ts +9 -1
  5. package/dist/components/core/molecules/PositionedCanvas.d.ts +14 -1
  6. package/dist/components/core/molecules/ReplyTree.d.ts +13 -1
  7. package/dist/components/game/molecules/three/index.cjs +64 -0
  8. package/dist/components/game/molecules/three/index.js +64 -0
  9. package/dist/components/game/organisms/hooks/useCamera.d.ts +16 -0
  10. package/dist/components/index.cjs +490 -148
  11. package/dist/components/index.js +490 -148
  12. package/dist/components/marketing/organisms/CaseStudyOrganism.d.ts +10 -1
  13. package/dist/components/marketing/organisms/FeatureGridOrganism.d.ts +10 -1
  14. package/dist/components/marketing/organisms/HeroOrganism.d.ts +24 -1
  15. package/dist/components/marketing/organisms/PricingOrganism.d.ts +14 -1
  16. package/dist/components/marketing/organisms/ShowcaseOrganism.d.ts +14 -1
  17. package/dist/components/marketing/organisms/StatsOrganism.d.ts +6 -1
  18. package/dist/components/marketing/organisms/StepFlowOrganism.d.ts +9 -1
  19. package/dist/components/marketing/organisms/TeamOrganism.d.ts +10 -1
  20. package/dist/docs/index.cjs +64 -0
  21. package/dist/docs/index.d.cts +2 -0
  22. package/dist/docs/index.js +64 -0
  23. package/dist/hooks/index.cjs +148 -0
  24. package/dist/hooks/index.d.ts +2 -0
  25. package/dist/hooks/index.js +147 -1
  26. package/dist/hooks/useCanvasGestures.d.ts +44 -0
  27. package/dist/hooks/useTapReveal.d.ts +32 -0
  28. package/dist/marketing/index.cjs +59 -0
  29. package/dist/marketing/index.d.cts +2 -0
  30. package/dist/marketing/index.js +59 -0
  31. package/dist/providers/index.cjs +490 -147
  32. package/dist/providers/index.js +490 -147
  33. package/dist/runtime/index.cjs +490 -147
  34. package/dist/runtime/index.js +490 -147
  35. package/package.json +2 -2
@@ -293,12 +293,60 @@ var init_cn = __esm({
293
293
  "lib/cn.ts"() {
294
294
  }
295
295
  });
296
+ function useTapReveal(options = {}) {
297
+ const { onReveal, onDismiss, refs, enabled = true } = options;
298
+ const [revealed, setRevealed] = React85.useState(false);
299
+ const onRevealRef = React85.useRef(onReveal);
300
+ const onDismissRef = React85.useRef(onDismiss);
301
+ onRevealRef.current = onReveal;
302
+ onDismissRef.current = onDismiss;
303
+ const reveal = React85.useCallback(() => {
304
+ setRevealed((wasRevealed) => {
305
+ if (!wasRevealed) onRevealRef.current?.();
306
+ return true;
307
+ });
308
+ }, []);
309
+ const dismiss = React85.useCallback(() => {
310
+ setRevealed((wasRevealed) => {
311
+ if (wasRevealed) onDismissRef.current?.();
312
+ return false;
313
+ });
314
+ }, []);
315
+ const onPointerDown = React85.useCallback(
316
+ (e) => {
317
+ if (!enabled || e.pointerType === "mouse") return;
318
+ if (revealed) dismiss();
319
+ else reveal();
320
+ },
321
+ [enabled, revealed, reveal, dismiss]
322
+ );
323
+ React85.useEffect(() => {
324
+ if (!revealed || typeof document === "undefined") return;
325
+ const onDocDown = (ev) => {
326
+ const target = ev.target;
327
+ if (!(target instanceof Node)) return;
328
+ const inside = (refs ?? []).some((r) => r.current?.contains(target));
329
+ if (!inside) dismiss();
330
+ };
331
+ const id = setTimeout(() => document.addEventListener("pointerdown", onDocDown), 0);
332
+ return () => {
333
+ clearTimeout(id);
334
+ document.removeEventListener("pointerdown", onDocDown);
335
+ };
336
+ }, [revealed, refs, dismiss]);
337
+ return { revealed, triggerProps: { onPointerDown }, reveal, dismiss };
338
+ }
339
+ var init_useTapReveal = __esm({
340
+ "hooks/useTapReveal.ts"() {
341
+ }
342
+ });
296
343
  var paddingStyles, paddingXStyles, paddingYStyles, marginStyles, marginXStyles, marginYStyles, bgStyles, roundedStyles, shadowStyles, displayStyles, overflowStyles, positionStyles, Box;
297
344
  var init_Box = __esm({
298
345
  "components/core/atoms/Box.tsx"() {
299
346
  "use client";
300
347
  init_cn();
301
348
  init_useEventBus();
349
+ init_useTapReveal();
302
350
  paddingStyles = {
303
351
  none: "p-0",
304
352
  xs: "p-1",
@@ -424,10 +472,12 @@ var init_Box = __esm({
424
472
  action,
425
473
  actionPayload,
426
474
  hoverEvent,
475
+ tapReveal = true,
427
476
  maxWidth,
428
477
  onClick,
429
478
  onMouseEnter,
430
479
  onMouseLeave,
480
+ onPointerDown,
431
481
  ...rest
432
482
  }, ref) => {
433
483
  const eventBus = useEventBus();
@@ -450,6 +500,19 @@ var init_Box = __esm({
450
500
  }
451
501
  onMouseLeave?.(e);
452
502
  }, [hoverEvent, eventBus, onMouseLeave]);
503
+ const { triggerProps } = useTapReveal({
504
+ enabled: tapReveal && !!hoverEvent,
505
+ onReveal: React85.useCallback(() => {
506
+ if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: true });
507
+ }, [hoverEvent, eventBus]),
508
+ onDismiss: React85.useCallback(() => {
509
+ if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: false });
510
+ }, [hoverEvent, eventBus])
511
+ });
512
+ const handlePointerDown = React85.useCallback((e) => {
513
+ if (hoverEvent && tapReveal) triggerProps.onPointerDown(e);
514
+ onPointerDown?.(e);
515
+ }, [hoverEvent, tapReveal, triggerProps, onPointerDown]);
453
516
  const isClickable = action || onClick;
454
517
  return React85__namespace.default.createElement(
455
518
  Component2,
@@ -477,6 +540,7 @@ var init_Box = __esm({
477
540
  onClick: isClickable ? handleClick : void 0,
478
541
  onMouseEnter: hoverEvent || onMouseEnter ? handleMouseEnter : void 0,
479
542
  onMouseLeave: hoverEvent || onMouseLeave ? handleMouseLeave : void 0,
543
+ onPointerDown: hoverEvent && tapReveal || onPointerDown ? handlePointerDown : void 0,
480
544
  style: maxWidth ? { maxWidth, ...rest.style } : rest.style,
481
545
  ...rest
482
546
  },
@@ -4887,6 +4951,11 @@ var init_TextHighlight = __esm({
4887
4951
  if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: false, annotationId });
4888
4952
  onMouseLeave?.();
4889
4953
  },
4954
+ onPointerDown: (e) => {
4955
+ if (e.pointerType === "mouse") return;
4956
+ if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: true, annotationId });
4957
+ onMouseEnter?.();
4958
+ },
4890
4959
  role: "button",
4891
4960
  tabIndex: 0,
4892
4961
  onKeyDown: (e) => {
@@ -5062,6 +5131,7 @@ var init_LawReferenceTooltip = __esm({
5062
5131
  init_Typography();
5063
5132
  init_Divider();
5064
5133
  init_cn();
5134
+ init_useTapReveal();
5065
5135
  positionStyles2 = {
5066
5136
  top: "bottom-full left-1/2 -translate-x-1/2 mb-2",
5067
5137
  bottom: "top-full left-1/2 -translate-x-1/2 mt-2",
@@ -5083,6 +5153,7 @@ var init_LawReferenceTooltip = __esm({
5083
5153
  const { t } = hooks.useTranslate();
5084
5154
  const [isVisible, setIsVisible] = React85__namespace.default.useState(false);
5085
5155
  const timeoutRef = React85__namespace.default.useRef(null);
5156
+ const triggerRef = React85__namespace.default.useRef(null);
5086
5157
  const handleMouseEnter = () => {
5087
5158
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
5088
5159
  timeoutRef.current = setTimeout(() => setIsVisible(true), 200);
@@ -5091,6 +5162,8 @@ var init_LawReferenceTooltip = __esm({
5091
5162
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
5092
5163
  setIsVisible(false);
5093
5164
  };
5165
+ const { revealed, triggerProps } = useTapReveal({ refs: [triggerRef] });
5166
+ const open = isVisible || revealed;
5094
5167
  React85__namespace.default.useEffect(() => {
5095
5168
  return () => {
5096
5169
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
@@ -5099,6 +5172,7 @@ var init_LawReferenceTooltip = __esm({
5099
5172
  return /* @__PURE__ */ jsxRuntime.jsxs(
5100
5173
  Box,
5101
5174
  {
5175
+ ref: triggerRef,
5102
5176
  as: "span",
5103
5177
  position: "relative",
5104
5178
  display: "inline-block",
@@ -5107,9 +5181,10 @@ var init_LawReferenceTooltip = __esm({
5107
5181
  onMouseLeave: handleMouseLeave,
5108
5182
  onFocus: handleMouseEnter,
5109
5183
  onBlur: handleMouseLeave,
5184
+ onPointerDown: triggerProps.onPointerDown,
5110
5185
  children: [
5111
5186
  children,
5112
- isVisible && /* @__PURE__ */ jsxRuntime.jsxs(
5187
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
5113
5188
  Box,
5114
5189
  {
5115
5190
  padding: "sm",
@@ -10869,6 +10944,36 @@ function useCamera() {
10869
10944
  cameraRef.current.zoom = Math.max(0.5, Math.min(3, cameraRef.current.zoom * zoomDelta));
10870
10945
  drawFn?.();
10871
10946
  }, []);
10947
+ const handlePointerDown = React85.useCallback((e) => {
10948
+ handleMouseDown(e);
10949
+ }, [handleMouseDown]);
10950
+ const handlePointerUp = React85.useCallback(() => {
10951
+ handleMouseUp();
10952
+ }, [handleMouseUp]);
10953
+ const handlePointerMove = React85.useCallback((e, drawFn) => {
10954
+ return handleMouseMove(e, drawFn);
10955
+ }, [handleMouseMove]);
10956
+ const panBy = React85.useCallback((dx, dy, drawFn) => {
10957
+ cameraRef.current.x -= dx;
10958
+ cameraRef.current.y -= dy;
10959
+ targetCameraRef.current = null;
10960
+ drawFn?.();
10961
+ }, []);
10962
+ const zoomAtPoint = React85.useCallback((factor, centerX, centerY, viewportSize, drawFn) => {
10963
+ const cam = cameraRef.current;
10964
+ const oldZoom = cam.zoom;
10965
+ const newZoom = Math.max(0.5, Math.min(3, oldZoom * factor));
10966
+ if (newZoom === oldZoom) {
10967
+ drawFn?.();
10968
+ return;
10969
+ }
10970
+ const inv = 1 / oldZoom - 1 / newZoom;
10971
+ cam.x += (centerX - viewportSize.width / 2) * inv;
10972
+ cam.y += (centerY - viewportSize.height / 2) * inv;
10973
+ cam.zoom = newZoom;
10974
+ targetCameraRef.current = null;
10975
+ drawFn?.();
10976
+ }, []);
10872
10977
  const screenToWorld = React85.useCallback((clientX, clientY, canvas, viewportSize) => {
10873
10978
  const rect = canvas.getBoundingClientRect();
10874
10979
  const screenX = clientX - rect.left;
@@ -10900,6 +11005,11 @@ function useCamera() {
10900
11005
  handleMouseMove,
10901
11006
  handleMouseLeave,
10902
11007
  handleWheel,
11008
+ handlePointerDown,
11009
+ handlePointerUp,
11010
+ handlePointerMove,
11011
+ panBy,
11012
+ zoomAtPoint,
10903
11013
  screenToWorld,
10904
11014
  lerpToTarget
10905
11015
  };
@@ -10909,6 +11019,113 @@ var init_useCamera = __esm({
10909
11019
  "use client";
10910
11020
  }
10911
11021
  });
11022
+ function localPoint(canvas, clientX, clientY) {
11023
+ if (!canvas) return { x: clientX, y: clientY };
11024
+ const rect = canvas.getBoundingClientRect();
11025
+ return { x: clientX - rect.left, y: clientY - rect.top };
11026
+ }
11027
+ function useCanvasGestures(options) {
11028
+ const {
11029
+ canvasRef,
11030
+ enabled = true,
11031
+ wheelStep = 1.1,
11032
+ onPointerDown,
11033
+ onPointerMove,
11034
+ onPointerUp,
11035
+ onZoom,
11036
+ onPanDelta,
11037
+ onMultiTouchStart
11038
+ } = options;
11039
+ const pointers = React85.useRef(/* @__PURE__ */ new Map());
11040
+ const pinch = React85.useRef(null);
11041
+ const computePinch = React85.useCallback(() => {
11042
+ const pts = [...pointers.current.values()];
11043
+ const dx = pts[1].x - pts[0].x;
11044
+ const dy = pts[1].y - pts[0].y;
11045
+ return {
11046
+ distance: Math.hypot(dx, dy) || 1,
11047
+ centroid: { x: (pts[0].x + pts[1].x) / 2, y: (pts[0].y + pts[1].y) / 2 }
11048
+ };
11049
+ }, []);
11050
+ const handlePointerDown = React85.useCallback(
11051
+ (e) => {
11052
+ if (!enabled) return;
11053
+ e.currentTarget.setPointerCapture(e.pointerId);
11054
+ pointers.current.set(e.pointerId, localPoint(canvasRef.current, e.clientX, e.clientY));
11055
+ if (pointers.current.size === 2) {
11056
+ onMultiTouchStart?.();
11057
+ pinch.current = computePinch();
11058
+ } else if (pointers.current.size === 1) {
11059
+ onPointerDown?.(e);
11060
+ }
11061
+ },
11062
+ [enabled, canvasRef, onMultiTouchStart, computePinch, onPointerDown]
11063
+ );
11064
+ const handlePointerMove = React85.useCallback(
11065
+ (e) => {
11066
+ if (!enabled) return;
11067
+ if (pointers.current.has(e.pointerId)) {
11068
+ pointers.current.set(e.pointerId, localPoint(canvasRef.current, e.clientX, e.clientY));
11069
+ }
11070
+ if (pointers.current.size >= 2 && pinch.current) {
11071
+ const next = computePinch();
11072
+ const prev = pinch.current;
11073
+ if (next.distance !== prev.distance) {
11074
+ onZoom?.(next.distance / prev.distance, next.centroid.x, next.centroid.y);
11075
+ }
11076
+ onPanDelta?.(next.centroid.x - prev.centroid.x, next.centroid.y - prev.centroid.y);
11077
+ pinch.current = next;
11078
+ return;
11079
+ }
11080
+ onPointerMove?.(e);
11081
+ },
11082
+ [enabled, canvasRef, computePinch, onZoom, onPanDelta, onPointerMove]
11083
+ );
11084
+ const endPointer = React85.useCallback(
11085
+ (e, fireUp) => {
11086
+ const wasMulti = pointers.current.size >= 2;
11087
+ pointers.current.delete(e.pointerId);
11088
+ if (pointers.current.size < 2) pinch.current = null;
11089
+ if (wasMulti && pointers.current.size === 1) return;
11090
+ if (pointers.current.size === 0 && fireUp) onPointerUp?.(e);
11091
+ },
11092
+ [onPointerUp]
11093
+ );
11094
+ const handlePointerUp = React85.useCallback(
11095
+ (e) => {
11096
+ if (!enabled) return;
11097
+ endPointer(e, true);
11098
+ },
11099
+ [enabled, endPointer]
11100
+ );
11101
+ const handlePointerCancel = React85.useCallback(
11102
+ (e) => {
11103
+ if (!enabled) return;
11104
+ endPointer(e, false);
11105
+ },
11106
+ [enabled, endPointer]
11107
+ );
11108
+ const handleWheel = React85.useCallback(
11109
+ (e) => {
11110
+ if (!enabled) return;
11111
+ e.preventDefault();
11112
+ const { x, y } = localPoint(canvasRef.current, e.clientX, e.clientY);
11113
+ onZoom?.(e.deltaY < 0 ? wheelStep : 1 / wheelStep, x, y);
11114
+ },
11115
+ [enabled, canvasRef, wheelStep, onZoom]
11116
+ );
11117
+ return {
11118
+ onPointerDown: handlePointerDown,
11119
+ onPointerMove: handlePointerMove,
11120
+ onPointerUp: handlePointerUp,
11121
+ onPointerCancel: handlePointerCancel,
11122
+ onWheel: handleWheel
11123
+ };
11124
+ }
11125
+ var init_useCanvasGestures = __esm({
11126
+ "hooks/useCanvasGestures.ts"() {
11127
+ }
11128
+ });
10912
11129
  function unitAtlasUrl(unit) {
10913
11130
  if (unit.spriteSheet) return unit.spriteSheet;
10914
11131
  const sprite = unit.sprite;
@@ -11287,11 +11504,12 @@ function IsometricCanvas({
11287
11504
  cameraRef,
11288
11505
  targetCameraRef,
11289
11506
  dragDistance,
11290
- handleMouseDown,
11291
- handleMouseUp,
11292
- handleMouseMove,
11293
11507
  handleMouseLeave,
11294
- handleWheel,
11508
+ handlePointerDown,
11509
+ handlePointerUp,
11510
+ handlePointerMove,
11511
+ panBy,
11512
+ zoomAtPoint,
11295
11513
  screenToWorld,
11296
11514
  lerpToTarget
11297
11515
  } = useCamera();
@@ -11726,11 +11944,16 @@ function IsometricCanvas({
11726
11944
  cancelAnimationFrame(rafIdRef.current);
11727
11945
  };
11728
11946
  }, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, pendingCount, atlasPending, lerpToTarget, targetCameraRef]);
11729
- const handleMouseMoveWithCamera = React85.useCallback((e) => {
11730
- if (enableCamera) {
11731
- const wasPanning = handleMouseMove(e, () => draw(animTimeRef.current));
11732
- if (wasPanning) return;
11733
- }
11947
+ const singlePointerActiveRef = React85.useRef(false);
11948
+ const handleCanvasPointerDown = React85.useCallback((e) => {
11949
+ singlePointerActiveRef.current = true;
11950
+ if (enableCamera) handlePointerDown(e);
11951
+ }, [enableCamera, handlePointerDown]);
11952
+ const handleCanvasPointerMove = React85.useCallback((e) => {
11953
+ if (enableCamera) handlePointerMove(e, () => draw(animTimeRef.current));
11954
+ }, [enableCamera, handlePointerMove, draw]);
11955
+ const handleCanvasHover = React85.useCallback((e) => {
11956
+ if (singlePointerActiveRef.current) return;
11734
11957
  if (!onTileHover && !tileHoverEvent || !canvasRef.current) return;
11735
11958
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11736
11959
  const adjustedX = world.x - scaledTileWidth / 2;
@@ -11741,26 +11964,10 @@ function IsometricCanvas({
11741
11964
  if (tileHoverEvent) eventBus.emit(`UI:${tileHoverEvent}`, { x: isoPos.x, y: isoPos.y });
11742
11965
  onTileHover?.(isoPos.x, isoPos.y);
11743
11966
  }
11744
- }, [enableCamera, handleMouseMove, draw, onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11745
- const handleMouseLeaveWithCamera = React85.useCallback(() => {
11746
- handleMouseLeave();
11747
- if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
11748
- onTileLeave?.();
11749
- }, [handleMouseLeave, onTileLeave, tileLeaveEvent, eventBus]);
11750
- const handleWheelWithCamera = React85.useCallback((e) => {
11751
- if (enableCamera) {
11752
- handleWheel(e, () => draw(animTimeRef.current));
11753
- }
11754
- }, [enableCamera, handleWheel, draw]);
11755
- React85.useEffect(() => {
11756
- const canvas = canvasRef.current;
11757
- if (!canvas) return;
11758
- canvas.addEventListener("wheel", handleWheelWithCamera, { passive: false });
11759
- return () => {
11760
- canvas.removeEventListener("wheel", handleWheelWithCamera);
11761
- };
11762
- }, [handleWheelWithCamera]);
11763
- const handleClick = React85.useCallback((e) => {
11967
+ }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11968
+ const handleCanvasPointerUp = React85.useCallback((e) => {
11969
+ singlePointerActiveRef.current = false;
11970
+ if (enableCamera) handlePointerUp();
11764
11971
  if (dragDistance() > 5) return;
11765
11972
  if (!canvasRef.current) return;
11766
11973
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
@@ -11778,7 +11985,32 @@ function IsometricCanvas({
11778
11985
  onTileClick?.(isoPos.x, isoPos.y);
11779
11986
  }
11780
11987
  }
11781
- }, [dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
11988
+ }, [enableCamera, handlePointerUp, dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
11989
+ const handleCanvasPointerLeave = React85.useCallback(() => {
11990
+ handleMouseLeave();
11991
+ if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
11992
+ onTileLeave?.();
11993
+ }, [handleMouseLeave, onTileLeave, tileLeaveEvent, eventBus]);
11994
+ const applyZoom = React85.useCallback((factor, centerX, centerY) => {
11995
+ if (enableCamera) zoomAtPoint(factor, centerX, centerY, viewportSize, () => draw(animTimeRef.current));
11996
+ }, [enableCamera, zoomAtPoint, viewportSize, draw]);
11997
+ const applyPanDelta = React85.useCallback((dx, dy) => {
11998
+ if (enableCamera) panBy(dx, dy, () => draw(animTimeRef.current));
11999
+ }, [enableCamera, panBy, draw]);
12000
+ const cancelSinglePointer = React85.useCallback(() => {
12001
+ singlePointerActiveRef.current = false;
12002
+ if (enableCamera) handlePointerUp();
12003
+ }, [enableCamera, handlePointerUp]);
12004
+ const gestureHandlers = useCanvasGestures({
12005
+ canvasRef,
12006
+ enabled: enableCamera || !!onTileHover || !!tileHoverEvent || !!onTileClick || !!tileClickEvent || !!onUnitClick || !!unitClickEvent,
12007
+ onPointerDown: handleCanvasPointerDown,
12008
+ onPointerMove: handleCanvasPointerMove,
12009
+ onPointerUp: handleCanvasPointerUp,
12010
+ onZoom: applyZoom,
12011
+ onPanDelta: applyPanDelta,
12012
+ onMultiTouchStart: cancelSinglePointer
12013
+ });
11782
12014
  if (error) {
11783
12015
  return /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { title: t("canvas.errorTitle"), message: error.message, className });
11784
12016
  }
@@ -11810,13 +12042,17 @@ function IsometricCanvas({
11810
12042
  {
11811
12043
  ref: canvasRef,
11812
12044
  "data-testid": "game-canvas",
11813
- onClick: handleClick,
11814
- onMouseDown: enableCamera ? handleMouseDown : void 0,
11815
- onMouseMove: handleMouseMoveWithCamera,
11816
- onMouseUp: enableCamera ? handleMouseUp : void 0,
11817
- onMouseLeave: handleMouseLeaveWithCamera,
12045
+ onPointerDown: gestureHandlers.onPointerDown,
12046
+ onPointerMove: (e) => {
12047
+ gestureHandlers.onPointerMove(e);
12048
+ handleCanvasHover(e);
12049
+ },
12050
+ onPointerUp: gestureHandlers.onPointerUp,
12051
+ onPointerCancel: gestureHandlers.onPointerCancel,
12052
+ onPointerLeave: handleCanvasPointerLeave,
12053
+ onWheel: gestureHandlers.onWheel,
11818
12054
  onContextMenu: (e) => e.preventDefault(),
11819
- className: "cursor-pointer",
12055
+ className: "cursor-pointer touch-none",
11820
12056
  style: {
11821
12057
  width: viewportSize.width,
11822
12058
  height: viewportSize.height
@@ -11870,6 +12106,7 @@ var init_IsometricCanvas = __esm({
11870
12106
  init_ErrorState();
11871
12107
  init_useImageCache();
11872
12108
  init_useCamera();
12109
+ init_useCanvasGestures();
11873
12110
  init_useUnitSpriteAtlas();
11874
12111
  init_verificationRegistry();
11875
12112
  init_isometric();
@@ -14985,6 +15222,10 @@ var init_StateMachineView = __esm({
14985
15222
  const handleMouseLeave2 = () => {
14986
15223
  onHover(null, 0, 0);
14987
15224
  };
15225
+ const handlePointerDown2 = (e) => {
15226
+ if (e.pointerType === "mouse") return;
15227
+ handleMouseEnter2();
15228
+ };
14988
15229
  return /* @__PURE__ */ jsxRuntime.jsxs(
14989
15230
  "g",
14990
15231
  {
@@ -14994,6 +15235,7 @@ var init_StateMachineView = __esm({
14994
15235
  onClick: () => onClick?.(bundle),
14995
15236
  onMouseEnter: handleMouseEnter2,
14996
15237
  onMouseLeave: handleMouseLeave2,
15238
+ onPointerDown: handlePointerDown2,
14997
15239
  style: { pointerEvents: "auto" },
14998
15240
  children: [
14999
15241
  /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -15111,6 +15353,10 @@ var init_StateMachineView = __esm({
15111
15353
  const handleMouseLeave = React85.useCallback(() => {
15112
15354
  onHover(null, 0, 0);
15113
15355
  }, [onHover]);
15356
+ const handlePointerDown = React85.useCallback((e) => {
15357
+ if (e.pointerType === "mouse") return;
15358
+ handleMouseEnter();
15359
+ }, [handleMouseEnter]);
15114
15360
  const uniqueMarkerId = `arrow-${bundle.id}`;
15115
15361
  const hasDetails = bundle.labels.some((l) => l.hasDetails);
15116
15362
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -15122,6 +15368,7 @@ var init_StateMachineView = __esm({
15122
15368
  onClick: () => onClick?.(bundle),
15123
15369
  onMouseEnter: handleMouseEnter,
15124
15370
  onMouseLeave: handleMouseLeave,
15371
+ onPointerDown: handlePointerDown,
15125
15372
  style: { pointerEvents: "auto" },
15126
15373
  children: [
15127
15374
  /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -23620,10 +23867,84 @@ function SubMenu({
23620
23867
  );
23621
23868
  return typeof document !== "undefined" ? reactDom.createPortal(panel, document.body) : panel;
23622
23869
  }
23870
+ function MenuItemRow({
23871
+ item,
23872
+ itemId,
23873
+ hasSubMenu,
23874
+ isDanger,
23875
+ direction,
23876
+ isSubMenuOpen,
23877
+ activeSubMenuRef,
23878
+ eventBus,
23879
+ onItemClick,
23880
+ openSubMenu
23881
+ }) {
23882
+ const rowRef = React85.useRef(null);
23883
+ const { triggerProps } = useTapReveal({
23884
+ enabled: hasSubMenu,
23885
+ onReveal: () => openSubMenu(itemId, rowRef.current),
23886
+ refs: [rowRef]
23887
+ });
23888
+ return /* @__PURE__ */ jsxRuntime.jsxs(Box, { children: [
23889
+ /* @__PURE__ */ jsxRuntime.jsx(
23890
+ Box,
23891
+ {
23892
+ ref: rowRef,
23893
+ as: "button",
23894
+ onClick: () => onItemClick({ ...item, id: itemId }, itemId),
23895
+ "aria-disabled": item.disabled || void 0,
23896
+ onMouseEnter: (e) => {
23897
+ if (hasSubMenu) openSubMenu(itemId, e.currentTarget);
23898
+ },
23899
+ onPointerDown: hasSubMenu ? triggerProps.onPointerDown : void 0,
23900
+ "data-testid": item.event ? `action-${item.event}` : void 0,
23901
+ className: cn(
23902
+ "w-full flex items-center justify-between gap-3 px-4 py-2 text-start",
23903
+ "text-sm transition-colors",
23904
+ "hover:bg-muted",
23905
+ "focus:outline-none focus:bg-muted",
23906
+ "disabled:opacity-50 disabled:cursor-not-allowed",
23907
+ item.disabled && "cursor-not-allowed",
23908
+ isDanger && "text-error hover:bg-error/10"
23909
+ ),
23910
+ children: /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center gap-3 flex-1 min-w-0", children: [
23911
+ item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: item.icon, size: "sm", className: "flex-shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: item.icon, size: "sm", className: "flex-shrink-0" })),
23912
+ /* @__PURE__ */ jsxRuntime.jsx(
23913
+ Typography,
23914
+ {
23915
+ variant: "small",
23916
+ className: cn("flex-1", isDanger && "text-red-600"),
23917
+ children: item.label
23918
+ }
23919
+ ),
23920
+ item.badge !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", size: "sm", children: item.badge }),
23921
+ hasSubMenu && /* @__PURE__ */ jsxRuntime.jsx(
23922
+ Icon,
23923
+ {
23924
+ name: direction === "rtl" ? "chevron-left" : "chevron-right",
23925
+ size: "sm",
23926
+ className: "flex-shrink-0"
23927
+ }
23928
+ )
23929
+ ] })
23930
+ }
23931
+ ),
23932
+ hasSubMenu && isSubMenuOpen && item.subMenu && /* @__PURE__ */ jsxRuntime.jsx(
23933
+ SubMenu,
23934
+ {
23935
+ items: item.subMenu,
23936
+ itemRef: activeSubMenuRef,
23937
+ direction,
23938
+ eventBus
23939
+ }
23940
+ )
23941
+ ] });
23942
+ }
23623
23943
  var MENU_GAP, menuContainerStyles, Menu;
23624
23944
  var init_Menu = __esm({
23625
23945
  "components/core/molecules/Menu.tsx"() {
23626
23946
  "use client";
23947
+ init_useTapReveal();
23627
23948
  init_Box();
23628
23949
  init_Icon();
23629
23950
  init_Divider();
@@ -23678,6 +23999,10 @@ var init_Menu = __esm({
23678
23999
  setIsOpen(false);
23679
24000
  }
23680
24001
  };
24002
+ const openSubMenu = (itemId, el) => {
24003
+ setActiveSubMenu(itemId);
24004
+ setActiveSubMenuRef(el);
24005
+ };
23681
24006
  React85.useEffect(() => {
23682
24007
  if (isOpen) {
23683
24008
  updatePosition();
@@ -23721,61 +24046,22 @@ var init_Menu = __esm({
23721
24046
  if (isDivider) {
23722
24047
  return /* @__PURE__ */ jsxRuntime.jsx(Divider, { className: "my-1" }, `divider-${index}`);
23723
24048
  }
23724
- return /* @__PURE__ */ jsxRuntime.jsxs(Box, { children: [
23725
- /* @__PURE__ */ jsxRuntime.jsx(
23726
- Box,
23727
- {
23728
- as: "button",
23729
- onClick: () => handleItemClick({ ...item, id: itemId }, itemId),
23730
- "aria-disabled": item.disabled || void 0,
23731
- onMouseEnter: (e) => {
23732
- if (hasSubMenu) {
23733
- setActiveSubMenu(itemId);
23734
- setActiveSubMenuRef(e.currentTarget);
23735
- }
23736
- },
23737
- "data-testid": item.event ? `action-${item.event}` : void 0,
23738
- className: cn(
23739
- "w-full flex items-center justify-between gap-3 px-4 py-2 text-start",
23740
- "text-sm transition-colors",
23741
- "hover:bg-muted",
23742
- "focus:outline-none focus:bg-muted",
23743
- "disabled:opacity-50 disabled:cursor-not-allowed",
23744
- item.disabled && "cursor-not-allowed",
23745
- isDanger && "text-error hover:bg-error/10"
23746
- ),
23747
- children: /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center gap-3 flex-1 min-w-0", children: [
23748
- item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: item.icon, size: "sm", className: "flex-shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: item.icon, size: "sm", className: "flex-shrink-0" })),
23749
- /* @__PURE__ */ jsxRuntime.jsx(
23750
- Typography,
23751
- {
23752
- variant: "small",
23753
- className: cn("flex-1", isDanger && "text-red-600"),
23754
- children: item.label
23755
- }
23756
- ),
23757
- item.badge !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", size: "sm", children: item.badge }),
23758
- hasSubMenu && /* @__PURE__ */ jsxRuntime.jsx(
23759
- Icon,
23760
- {
23761
- name: direction === "rtl" ? "chevron-left" : "chevron-right",
23762
- size: "sm",
23763
- className: "flex-shrink-0"
23764
- }
23765
- )
23766
- ] })
23767
- }
23768
- ),
23769
- hasSubMenu && activeSubMenu === itemId && item.subMenu && /* @__PURE__ */ jsxRuntime.jsx(
23770
- SubMenu,
23771
- {
23772
- items: item.subMenu,
23773
- itemRef: activeSubMenuRef,
23774
- direction,
23775
- eventBus
23776
- }
23777
- )
23778
- ] }, itemId);
24049
+ return /* @__PURE__ */ jsxRuntime.jsx(
24050
+ MenuItemRow,
24051
+ {
24052
+ item,
24053
+ itemId,
24054
+ hasSubMenu,
24055
+ isDanger,
24056
+ direction,
24057
+ isSubMenuOpen: activeSubMenu === itemId,
24058
+ activeSubMenuRef,
24059
+ eventBus,
24060
+ onItemClick: handleItemClick,
24061
+ openSubMenu
24062
+ },
24063
+ itemId
24064
+ );
23779
24065
  });
23780
24066
  const panel = isOpen && triggerRect ? /* @__PURE__ */ jsxRuntime.jsxs(
23781
24067
  "div",
@@ -26078,6 +26364,7 @@ var init_Popover = __esm({
26078
26364
  "use client";
26079
26365
  init_Typography();
26080
26366
  init_cn();
26367
+ init_useTapReveal();
26081
26368
  arrowClasses = {
26082
26369
  top: "top-full left-1/2 -translate-x-1/2 border-t-white border-l-transparent border-r-transparent border-b-transparent",
26083
26370
  bottom: "bottom-full left-1/2 -translate-x-1/2 border-b-white border-l-transparent border-r-transparent border-t-transparent",
@@ -26148,18 +26435,32 @@ var init_Popover = __esm({
26148
26435
  document.addEventListener("mousedown", handleClickOutside);
26149
26436
  return () => document.removeEventListener("mousedown", handleClickOutside);
26150
26437
  }, [isOpen, trigger]);
26151
- const triggerProps = trigger === "click" ? {
26438
+ const { triggerProps: tapTriggerProps } = useTapReveal({
26439
+ enabled: trigger === "hover",
26440
+ onReveal: handleOpen,
26441
+ onDismiss: handleClose,
26442
+ refs: [triggerRef, popoverRef]
26443
+ });
26444
+ const handlerProps = trigger === "click" ? {
26152
26445
  onClick: handleToggle
26153
26446
  } : {
26154
26447
  onMouseEnter: handleOpen,
26155
- onMouseLeave: handleClose
26448
+ onMouseLeave: handleClose,
26449
+ onPointerDown: tapTriggerProps.onPointerDown
26156
26450
  };
26157
26451
  const childElement = React85__namespace.default.isValidElement(children) ? children : /* @__PURE__ */ jsxRuntime.jsx("span", { children });
26452
+ const childPointerDown = childElement.props.onPointerDown;
26158
26453
  const triggerElement = React85__namespace.default.cloneElement(
26159
26454
  childElement,
26160
26455
  {
26161
26456
  ref: triggerRef,
26162
- ...triggerProps
26457
+ ...handlerProps,
26458
+ ...trigger === "hover" ? {
26459
+ onPointerDown: (e) => {
26460
+ tapTriggerProps.onPointerDown(e);
26461
+ childPointerDown?.(e);
26462
+ }
26463
+ } : void 0
26163
26464
  }
26164
26465
  );
26165
26466
  const panel = isOpen && triggerRect ? /* @__PURE__ */ jsxRuntime.jsxs(
@@ -26690,6 +26991,7 @@ var init_Tooltip = __esm({
26690
26991
  "use client";
26691
26992
  init_Typography();
26692
26993
  init_cn();
26994
+ init_useTapReveal();
26693
26995
  TRIGGER_GAP2 = 8;
26694
26996
  arrowClasses2 = {
26695
26997
  top: "top-full left-1/2 -translate-x-1/2 border-t-primary border-l-transparent border-r-transparent border-b-transparent",
@@ -26734,6 +27036,11 @@ var init_Tooltip = __esm({
26734
27036
  setIsVisible(false);
26735
27037
  }, hideDelay);
26736
27038
  };
27039
+ const { triggerProps } = useTapReveal({
27040
+ onReveal: handleMouseEnter,
27041
+ onDismiss: handleMouseLeave,
27042
+ refs: [triggerRef, tooltipRef]
27043
+ });
26737
27044
  React85.useLayoutEffect(() => {
26738
27045
  if (isVisible) {
26739
27046
  updatePosition();
@@ -26746,12 +27053,17 @@ var init_Tooltip = __esm({
26746
27053
  };
26747
27054
  }, []);
26748
27055
  const triggerElement = React85__namespace.default.isValidElement(children) ? children : /* @__PURE__ */ jsxRuntime.jsx("span", { children });
27056
+ const childPointerDown = triggerElement.props.onPointerDown;
26749
27057
  const trigger = React85__namespace.default.cloneElement(triggerElement, {
26750
27058
  ref: triggerRef,
26751
27059
  onMouseEnter: handleMouseEnter,
26752
27060
  onMouseLeave: handleMouseLeave,
26753
27061
  onFocus: handleMouseEnter,
26754
- onBlur: handleMouseLeave
27062
+ onBlur: handleMouseLeave,
27063
+ onPointerDown: (e) => {
27064
+ triggerProps.onPointerDown(e);
27065
+ childPointerDown?.(e);
27066
+ }
26755
27067
  });
26756
27068
  const tooltipContent = isVisible && triggerRect ? /* @__PURE__ */ jsxRuntime.jsxs(
26757
27069
  "div",
@@ -30101,6 +30413,14 @@ var init_GraphView = __esm({
30101
30413
  },
30102
30414
  [onNodeClick]
30103
30415
  );
30416
+ const handleNodePointerDown = React85.useCallback(
30417
+ (e, node) => {
30418
+ if (e.pointerType === "mouse") return;
30419
+ handleNodeMouseEnter(node);
30420
+ handleNodeClickInternal(node);
30421
+ },
30422
+ [handleNodeMouseEnter, handleNodeClickInternal]
30423
+ );
30104
30424
  if (nodes.length === 0) {
30105
30425
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: cn("flex items-center justify-center", className), style: { width: w, height: h }, children: /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-muted-foreground text-sm", children: t("display.noGraphData") }) });
30106
30426
  }
@@ -30160,6 +30480,7 @@ var init_GraphView = __esm({
30160
30480
  onMouseEnter: () => handleNodeMouseEnter(node),
30161
30481
  onMouseLeave: handleNodeMouseLeave,
30162
30482
  onClick: () => handleNodeClickInternal(node),
30483
+ onPointerDown: (e) => handleNodePointerDown(e, node),
30163
30484
  children: [
30164
30485
  /* @__PURE__ */ jsxRuntime.jsx(
30165
30486
  "circle",
@@ -30240,13 +30561,13 @@ var init_MapView = __esm({
30240
30561
  shadowSize: [41, 41]
30241
30562
  });
30242
30563
  L.Marker.prototype.options.icon = defaultIcon;
30243
- const { useEffect: useEffect81, useRef: useRef78, useCallback: useCallback123, useState: useState110 } = React85__namespace.default;
30564
+ const { useEffect: useEffect82, useRef: useRef80, useCallback: useCallback125, useState: useState111 } = React85__namespace.default;
30244
30565
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
30245
30566
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
30246
30567
  function MapUpdater({ centerLat, centerLng, zoom }) {
30247
30568
  const map = useMap();
30248
- const prevRef = useRef78({ centerLat, centerLng, zoom });
30249
- useEffect81(() => {
30569
+ const prevRef = useRef80({ centerLat, centerLng, zoom });
30570
+ useEffect82(() => {
30250
30571
  const prev = prevRef.current;
30251
30572
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
30252
30573
  map.setView([centerLat, centerLng], zoom);
@@ -30257,7 +30578,7 @@ var init_MapView = __esm({
30257
30578
  }
30258
30579
  function MapClickHandler({ onMapClick }) {
30259
30580
  const map = useMap();
30260
- useEffect81(() => {
30581
+ useEffect82(() => {
30261
30582
  if (!onMapClick) return;
30262
30583
  const handler = (e) => {
30263
30584
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -30285,8 +30606,8 @@ var init_MapView = __esm({
30285
30606
  showAttribution = true
30286
30607
  }) {
30287
30608
  const eventBus = useEventBus3();
30288
- const [clickedPosition, setClickedPosition] = useState110(null);
30289
- const handleMapClick = useCallback123((lat, lng) => {
30609
+ const [clickedPosition, setClickedPosition] = useState111(null);
30610
+ const handleMapClick = useCallback125((lat, lng) => {
30290
30611
  if (showClickedPin) {
30291
30612
  setClickedPosition({ lat, lng });
30292
30613
  }
@@ -30295,7 +30616,7 @@ var init_MapView = __esm({
30295
30616
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
30296
30617
  }
30297
30618
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
30298
- const handleMarkerClick = useCallback123((marker) => {
30619
+ const handleMarkerClick = useCallback125((marker) => {
30299
30620
  onMarkerClick?.(marker);
30300
30621
  if (markerClickEvent) {
30301
30622
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -30636,7 +30957,7 @@ var init_StarRating = __esm({
30636
30957
  "aria-valuenow": value,
30637
30958
  tabIndex: readOnly ? void 0 : 0,
30638
30959
  onKeyDown: handleKeyDown,
30639
- onMouseLeave: () => setHoverValue(null),
30960
+ onPointerLeave: () => setHoverValue(null),
30640
30961
  children: Array.from({ length: max }, (_, i) => {
30641
30962
  const fillLevel = Math.max(0, Math.min(1, displayValue - i));
30642
30963
  const isFull = fillLevel >= 1;
@@ -30646,7 +30967,7 @@ var init_StarRating = __esm({
30646
30967
  {
30647
30968
  className: "relative inline-block",
30648
30969
  onClick: () => handleStarClick(i, false),
30649
- onMouseMove: (e) => {
30970
+ onPointerMove: (e) => {
30650
30971
  if (readOnly) return;
30651
30972
  const rect = e.currentTarget.getBoundingClientRect();
30652
30973
  const isLeftHalf = e.clientX - rect.left < rect.width / 2;
@@ -34536,7 +34857,7 @@ var init_PositionedCanvas = __esm({
34536
34857
  {
34537
34858
  ref: containerRef,
34538
34859
  "data-testid": "positioned-canvas",
34539
- className: "relative bg-background border border-border rounded-container overflow-hidden",
34860
+ className: "relative bg-background border border-border rounded-container overflow-hidden touch-none",
34540
34861
  style: { width, height },
34541
34862
  onClick: handleContainerClick,
34542
34863
  children: items.map((item) => {
@@ -38705,6 +39026,7 @@ var init_GraphCanvas = __esm({
38705
39026
  init_ErrorState();
38706
39027
  init_EmptyState();
38707
39028
  init_useEventBus();
39029
+ init_useCanvasGestures();
38708
39030
  GROUP_COLORS2 = [
38709
39031
  "var(--color-primary)",
38710
39032
  "var(--color-success)",
@@ -38742,6 +39064,10 @@ var init_GraphCanvas = __esm({
38742
39064
  const animRef = React85.useRef(0);
38743
39065
  const [zoom, setZoom] = React85.useState(1);
38744
39066
  const [offset, setOffset] = React85.useState({ x: 0, y: 0 });
39067
+ const zoomRef = React85.useRef(zoom);
39068
+ zoomRef.current = zoom;
39069
+ const offsetRef = React85.useRef(offset);
39070
+ offsetRef.current = offset;
38745
39071
  const [hoveredNode, setHoveredNode] = React85.useState(null);
38746
39072
  const nodesRef = React85.useRef([]);
38747
39073
  const [, forceUpdate] = React85.useState(0);
@@ -39001,25 +39327,27 @@ var init_GraphCanvas = __esm({
39001
39327
  setZoom(1);
39002
39328
  setOffset({ x: 0, y: 0 });
39003
39329
  }, []);
39004
- const handleWheel = React85.useCallback(
39005
- (e) => {
39006
- if (!interactive) return;
39007
- e.preventDefault();
39008
- const coords = toCoords(e);
39009
- if (!coords) return;
39010
- const oldZoom = zoom;
39011
- const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
39012
- const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
39013
- if (newZoom === oldZoom) return;
39014
- setOffset((o) => ({
39015
- x: coords.screenX - (coords.screenX - o.x) * (newZoom / oldZoom),
39016
- y: coords.screenY - (coords.screenY - o.y) * (newZoom / oldZoom)
39017
- }));
39018
- setZoom(newZoom);
39019
- },
39020
- [interactive, toCoords, zoom]
39021
- );
39022
- const handleMouseDown = React85.useCallback(
39330
+ const applyZoom = React85.useCallback((factor, cx, cy) => {
39331
+ if (!interactive) return;
39332
+ const oldZoom = zoomRef.current;
39333
+ const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
39334
+ if (newZoom === oldZoom) return;
39335
+ const o = offsetRef.current;
39336
+ setOffset({
39337
+ x: cx - (cx - o.x) * (newZoom / oldZoom),
39338
+ y: cy - (cy - o.y) * (newZoom / oldZoom)
39339
+ });
39340
+ setZoom(newZoom);
39341
+ }, [interactive]);
39342
+ const applyPanDelta = React85.useCallback((dx, dy) => {
39343
+ if (!interactive) return;
39344
+ setOffset((o) => ({ x: o.x + dx, y: o.y + dy }));
39345
+ }, [interactive]);
39346
+ const cancelSinglePointer = React85.useCallback(() => {
39347
+ interactionRef.current.mode = "none";
39348
+ interactionRef.current.dragNodeId = null;
39349
+ }, []);
39350
+ const handlePointerDown = React85.useCallback(
39023
39351
  (e) => {
39024
39352
  const coords = toCoords(e);
39025
39353
  if (!coords) return;
@@ -39041,7 +39369,7 @@ var init_GraphCanvas = __esm({
39041
39369
  },
39042
39370
  [toCoords, nodeAt, draggable, interactive, offset]
39043
39371
  );
39044
- const handleMouseMove = React85.useCallback(
39372
+ const handlePointerMove = React85.useCallback(
39045
39373
  (e) => {
39046
39374
  const state = interactionRef.current;
39047
39375
  if (state.mode === "panning") {
@@ -39070,7 +39398,7 @@ var init_GraphCanvas = __esm({
39070
39398
  },
39071
39399
  [toCoords, nodeAt]
39072
39400
  );
39073
- const handleMouseUp = React85.useCallback(
39401
+ const handlePointerUp = React85.useCallback(
39074
39402
  (e) => {
39075
39403
  const state = interactionRef.current;
39076
39404
  const moved = Math.abs(e.clientX - state.downPos.x) + Math.abs(e.clientY - state.downPos.y);
@@ -39087,11 +39415,19 @@ var init_GraphCanvas = __esm({
39087
39415
  },
39088
39416
  [toCoords, nodeAt, handleNodeClick]
39089
39417
  );
39090
- const handleMouseLeave = React85.useCallback(() => {
39091
- interactionRef.current.mode = "none";
39092
- interactionRef.current.dragNodeId = null;
39418
+ const handlePointerLeave = React85.useCallback(() => {
39093
39419
  setHoveredNode(null);
39094
39420
  }, []);
39421
+ const gestureHandlers = useCanvasGestures({
39422
+ canvasRef,
39423
+ enabled: interactive || draggable,
39424
+ onPointerDown: handlePointerDown,
39425
+ onPointerMove: handlePointerMove,
39426
+ onPointerUp: handlePointerUp,
39427
+ onZoom: applyZoom,
39428
+ onPanDelta: applyPanDelta,
39429
+ onMultiTouchStart: cancelSinglePointer
39430
+ });
39095
39431
  const handleDoubleClick = React85.useCallback(
39096
39432
  (e) => {
39097
39433
  const coords = toCoords(e);
@@ -39160,13 +39496,14 @@ var init_GraphCanvas = __esm({
39160
39496
  ref: canvasRef,
39161
39497
  width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
39162
39498
  height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
39163
- className: "w-full cursor-grab active:cursor-grabbing",
39499
+ className: "w-full cursor-grab active:cursor-grabbing touch-none",
39164
39500
  style: { height },
39165
- onWheel: handleWheel,
39166
- onMouseDown: handleMouseDown,
39167
- onMouseMove: handleMouseMove,
39168
- onMouseUp: handleMouseUp,
39169
- onMouseLeave: handleMouseLeave,
39501
+ onPointerDown: gestureHandlers.onPointerDown,
39502
+ onPointerMove: gestureHandlers.onPointerMove,
39503
+ onPointerUp: gestureHandlers.onPointerUp,
39504
+ onPointerCancel: gestureHandlers.onPointerCancel,
39505
+ onPointerLeave: handlePointerLeave,
39506
+ onWheel: gestureHandlers.onWheel,
39170
39507
  onDoubleClick: handleDoubleClick
39171
39508
  }
39172
39509
  ) }),
@@ -43638,6 +43975,9 @@ var init_GameCanvas3D2 = __esm({
43638
43975
  target: cameraTarget,
43639
43976
  enableDamping: true,
43640
43977
  dampingFactor: 0.05,
43978
+ enableZoom: true,
43979
+ enablePan: true,
43980
+ touches: { ONE: THREE3__namespace.TOUCH.ROTATE, TWO: THREE3__namespace.TOUCH.DOLLY_PAN },
43641
43981
  minDistance: 2,
43642
43982
  maxDistance: 100,
43643
43983
  maxPolarAngle: Math.PI / 2 - 0.1
@@ -48949,20 +49289,21 @@ var init_SplitPane = __esm({
48949
49289
  const [ratio, setRatio] = React85.useState(initialRatio);
48950
49290
  const containerRef = React85.useRef(null);
48951
49291
  const isDragging = React85.useRef(false);
48952
- const handleMouseDown = React85.useCallback(
49292
+ const handlePointerDown = React85.useCallback(
48953
49293
  (e) => {
48954
49294
  if (!resizable) return;
48955
49295
  e.preventDefault();
48956
49296
  isDragging.current = true;
48957
- const handleMouseMove = (e2) => {
49297
+ e.currentTarget.setPointerCapture(e.pointerId);
49298
+ const handlePointerMove = (ev) => {
48958
49299
  if (!isDragging.current || !containerRef.current) return;
48959
49300
  const rect = containerRef.current.getBoundingClientRect();
48960
49301
  let newRatio;
48961
49302
  if (direction === "horizontal") {
48962
- const x = e2.clientX - rect.left;
49303
+ const x = ev.clientX - rect.left;
48963
49304
  newRatio = x / rect.width * 100;
48964
49305
  } else {
48965
- const y = e2.clientY - rect.top;
49306
+ const y = ev.clientY - rect.top;
48966
49307
  newRatio = y / rect.height * 100;
48967
49308
  }
48968
49309
  const minRatio = minSize / (direction === "horizontal" ? rect.width : rect.height) * 100;
@@ -48970,13 +49311,15 @@ var init_SplitPane = __esm({
48970
49311
  newRatio = Math.max(minRatio, Math.min(maxRatio, newRatio));
48971
49312
  setRatio(newRatio);
48972
49313
  };
48973
- const handleMouseUp = () => {
49314
+ const handlePointerUp = () => {
48974
49315
  isDragging.current = false;
48975
- document.removeEventListener("mousemove", handleMouseMove);
48976
- document.removeEventListener("mouseup", handleMouseUp);
49316
+ document.removeEventListener("pointermove", handlePointerMove);
49317
+ document.removeEventListener("pointerup", handlePointerUp);
49318
+ document.removeEventListener("pointercancel", handlePointerUp);
48977
49319
  };
48978
- document.addEventListener("mousemove", handleMouseMove);
48979
- document.addEventListener("mouseup", handleMouseUp);
49320
+ document.addEventListener("pointermove", handlePointerMove);
49321
+ document.addEventListener("pointerup", handlePointerUp);
49322
+ document.addEventListener("pointercancel", handlePointerUp);
48980
49323
  },
48981
49324
  [direction, minSize, resizable]
48982
49325
  );
@@ -49005,9 +49348,9 @@ var init_SplitPane = __esm({
49005
49348
  resizable && /* @__PURE__ */ jsxRuntime.jsx(
49006
49349
  "div",
49007
49350
  {
49008
- onMouseDown: handleMouseDown,
49351
+ onPointerDown: handlePointerDown,
49009
49352
  className: cn(
49010
- "flex-shrink-0 bg-border transition-colors",
49353
+ "flex-shrink-0 bg-border transition-colors touch-none",
49011
49354
  isHorizontal ? "w-1 cursor-col-resize hover:w-1.5 hover:bg-muted-foreground" : "h-1 cursor-row-resize hover:h-1.5 hover:bg-muted-foreground"
49012
49355
  )
49013
49356
  }