@project-skymap/library 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -849,7 +849,8 @@ function createEngine({
849
849
  onSelect,
850
850
  onHover,
851
851
  onArrangementChange,
852
- onFovChange
852
+ onFovChange,
853
+ onLongPress
853
854
  }) {
854
855
  let hoveredBookId = null;
855
856
  let focusedBookId = null;
@@ -919,13 +920,20 @@ function createEngine({
919
920
  pinchStartDistance: 0,
920
921
  pinchStartFov: ENGINE_CONFIG.defaultFov,
921
922
  pinchCenterX: 0,
922
- pinchCenterY: 0
923
+ pinchCenterY: 0,
924
+ // Double-tap detection
925
+ lastTapTime: 0,
926
+ lastTapX: 0,
927
+ lastTapY: 0,
928
+ // Long-press detection
929
+ longPressTimer: null,
930
+ longPressTriggered: false
923
931
  };
924
932
  const mouseNDC = new THREE5__namespace.Vector2();
925
933
  let isMouseInWindow = false;
926
934
  let isTouchDevice = false;
927
935
  let edgeHoverStart = 0;
928
- let handlers = { onSelect, onHover, onArrangementChange, onFovChange };
936
+ let handlers = { onSelect, onHover, onArrangementChange, onFovChange, onLongPress };
929
937
  let currentConfig;
930
938
  const constellationLayer = new ConstellationArtworkLayer(scene);
931
939
  function mix(a, b, t) {
@@ -2418,6 +2426,11 @@ function createEngine({
2418
2426
  function onTouchStart(e) {
2419
2427
  e.preventDefault();
2420
2428
  isTouchDevice = true;
2429
+ if (state.longPressTimer) {
2430
+ clearTimeout(state.longPressTimer);
2431
+ state.longPressTimer = null;
2432
+ }
2433
+ state.longPressTriggered = false;
2421
2434
  const touches = e.touches;
2422
2435
  state.touchCount = touches.length;
2423
2436
  if (touches.length === 1) {
@@ -2433,6 +2446,22 @@ function createEngine({
2433
2446
  state.isDragging = true;
2434
2447
  state.velocityX = 0;
2435
2448
  state.velocityY = 0;
2449
+ state.longPressTimer = setTimeout(() => {
2450
+ if (!state.touchMoved && state.touchCount === 1) {
2451
+ state.longPressTriggered = true;
2452
+ const rect = renderer.domElement.getBoundingClientRect();
2453
+ const mX = state.touchStartX - rect.left;
2454
+ const mY = state.touchStartY - rect.top;
2455
+ mouseNDC.x = mX / rect.width * 2 - 1;
2456
+ mouseNDC.y = -(mY / rect.height) * 2 + 1;
2457
+ const syntheticEvent = {
2458
+ clientX: state.touchStartX,
2459
+ clientY: state.touchStartY
2460
+ };
2461
+ const hit = pick(syntheticEvent);
2462
+ handlers.onLongPress?.(hit?.node ?? null, state.touchStartX, state.touchStartY);
2463
+ }
2464
+ }, ENGINE_CONFIG.longPressDelay);
2436
2465
  } else if (touches.length === 2) {
2437
2466
  const t0 = touches[0];
2438
2467
  const t1 = touches[1];
@@ -2459,6 +2488,10 @@ function createEngine({
2459
2488
  const totalDy = touch.clientY - state.touchStartY;
2460
2489
  if (Math.sqrt(totalDx * totalDx + totalDy * totalDy) > ENGINE_CONFIG.tapMaxDistance) {
2461
2490
  state.touchMoved = true;
2491
+ if (state.longPressTimer) {
2492
+ clearTimeout(state.longPressTimer);
2493
+ state.longPressTimer = null;
2494
+ }
2462
2495
  }
2463
2496
  const speedScale = state.fov / ENGINE_CONFIG.defaultFov;
2464
2497
  const rotLock = Math.max(0, Math.min(1, (state.fov - 100) / (ENGINE_CONFIG.maxFov - 100)));
@@ -2493,11 +2526,21 @@ function createEngine({
2493
2526
  }
2494
2527
  function onTouchEnd(e) {
2495
2528
  e.preventDefault();
2529
+ if (state.longPressTimer) {
2530
+ clearTimeout(state.longPressTimer);
2531
+ state.longPressTimer = null;
2532
+ }
2496
2533
  const remainingTouches = e.touches.length;
2497
2534
  if (remainingTouches === 0) {
2498
- const duration = performance.now() - state.touchStartTime;
2499
- const wasTap = !state.touchMoved && duration < ENGINE_CONFIG.tapMaxDuration;
2535
+ const now = performance.now();
2536
+ const duration = now - state.touchStartTime;
2537
+ const wasTap = !state.touchMoved && duration < ENGINE_CONFIG.tapMaxDuration && !state.longPressTriggered;
2500
2538
  if (wasTap) {
2539
+ const timeSinceLastTap = now - state.lastTapTime;
2540
+ const distFromLastTap = Math.sqrt(
2541
+ Math.pow(state.touchStartX - state.lastTapX, 2) + Math.pow(state.touchStartY - state.lastTapY, 2)
2542
+ );
2543
+ const isDoubleTap = timeSinceLastTap < ENGINE_CONFIG.doubleTapMaxDelay && distFromLastTap < ENGINE_CONFIG.doubleTapMaxDistance;
2501
2544
  const rect = renderer.domElement.getBoundingClientRect();
2502
2545
  const mX = state.touchStartX - rect.left;
2503
2546
  const mY = state.touchStartY - rect.top;
@@ -2508,13 +2551,26 @@ function createEngine({
2508
2551
  clientY: state.touchStartY
2509
2552
  };
2510
2553
  const hit = pick(syntheticEvent);
2511
- if (hit) {
2512
- handlers.onSelect?.(hit.node);
2513
- constellationLayer.setFocused(hit.node.id);
2514
- if (hit.node.level === 2) setFocusedBook(hit.node.id);
2515
- else if (hit.node.level === 3 && hit.node.parent) setFocusedBook(hit.node.parent);
2554
+ if (isDoubleTap) {
2555
+ if (hit) {
2556
+ flyTo(hit.node.id, ENGINE_CONFIG.minFov);
2557
+ handlers.onSelect?.(hit.node);
2558
+ }
2559
+ state.lastTapTime = 0;
2560
+ state.lastTapX = 0;
2561
+ state.lastTapY = 0;
2516
2562
  } else {
2517
- setFocusedBook(null);
2563
+ if (hit) {
2564
+ handlers.onSelect?.(hit.node);
2565
+ constellationLayer.setFocused(hit.node.id);
2566
+ if (hit.node.level === 2) setFocusedBook(hit.node.id);
2567
+ else if (hit.node.level === 3 && hit.node.parent) setFocusedBook(hit.node.parent);
2568
+ } else {
2569
+ setFocusedBook(null);
2570
+ }
2571
+ state.lastTapTime = now;
2572
+ state.lastTapX = state.touchStartX;
2573
+ state.lastTapY = state.touchStartY;
2518
2574
  }
2519
2575
  }
2520
2576
  state.isDragging = false;
@@ -2533,6 +2589,10 @@ function createEngine({
2533
2589
  }
2534
2590
  function onTouchCancel(e) {
2535
2591
  e.preventDefault();
2592
+ if (state.longPressTimer) {
2593
+ clearTimeout(state.longPressTimer);
2594
+ state.longPressTimer = null;
2595
+ }
2536
2596
  state.isDragging = false;
2537
2597
  state.dragMode = "none";
2538
2598
  state.touchCount = 0;
@@ -2918,8 +2978,14 @@ var init_createEngine = __esm({
2918
2978
  // Snappier than mouse (0.92)
2919
2979
  tapMaxDuration: 300,
2920
2980
  // ms
2921
- tapMaxDistance: 10
2981
+ tapMaxDistance: 10,
2922
2982
  // px
2983
+ doubleTapMaxDelay: 300,
2984
+ // ms between taps
2985
+ doubleTapMaxDistance: 30,
2986
+ // px between tap locations
2987
+ longPressDelay: 500
2988
+ // ms to trigger long-press
2923
2989
  };
2924
2990
  ORDER_REVEAL_CONFIG = {
2925
2991
  globalDim: 0.85,
@@ -2930,7 +2996,7 @@ var init_createEngine = __esm({
2930
2996
  }
2931
2997
  });
2932
2998
  var StarMap = react.forwardRef(
2933
- ({ config, className, onSelect, onHover, onArrangementChange, onFovChange }, ref) => {
2999
+ ({ config, className, onSelect, onHover, onArrangementChange, onFovChange, onLongPress }, ref) => {
2934
3000
  const containerRef = react.useRef(null);
2935
3001
  const engineRef = react.useRef(null);
2936
3002
  react.useImperativeHandle(ref, () => ({
@@ -2953,7 +3019,8 @@ var StarMap = react.forwardRef(
2953
3019
  onSelect,
2954
3020
  onHover,
2955
3021
  onArrangementChange,
2956
- onFovChange
3022
+ onFovChange,
3023
+ onLongPress
2957
3024
  });
2958
3025
  engineRef.current.setConfig(config);
2959
3026
  engineRef.current.start();
@@ -2969,8 +3036,8 @@ var StarMap = react.forwardRef(
2969
3036
  engineRef.current?.setConfig?.(config);
2970
3037
  }, [config]);
2971
3038
  react.useEffect(() => {
2972
- engineRef.current?.setHandlers?.({ onSelect, onHover, onArrangementChange, onFovChange });
2973
- }, [onSelect, onHover, onArrangementChange, onFovChange]);
3039
+ engineRef.current?.setHandlers?.({ onSelect, onHover, onArrangementChange, onFovChange, onLongPress });
3040
+ }, [onSelect, onHover, onArrangementChange, onFovChange, onLongPress]);
2974
3041
  return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className, style: { width: "100%", height: "100%" } });
2975
3042
  }
2976
3043
  );