@tutti-os/workbench-surface 0.0.47 → 0.0.49

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.js CHANGED
@@ -7178,6 +7178,59 @@ function isWorkbenchDockEntryBlocked(entry) {
7178
7178
 
7179
7179
  // src/host/dockMagnification.ts
7180
7180
  import { useCallback as useCallback8, useEffect as useEffect6, useRef as useRef5 } from "react";
7181
+
7182
+ // src/host/dockMagnificationBounds.ts
7183
+ function resolveDockMagnificationViewportBounds(viewportRect, dockPlacement) {
7184
+ return dockPlacement === "left" ? {
7185
+ crossEnd: viewportRect.right,
7186
+ crossStart: viewportRect.left,
7187
+ mainEnd: viewportRect.bottom,
7188
+ mainStart: viewportRect.top
7189
+ } : {
7190
+ crossEnd: viewportRect.bottom,
7191
+ crossStart: viewportRect.top,
7192
+ mainEnd: viewportRect.right,
7193
+ mainStart: viewportRect.left
7194
+ };
7195
+ }
7196
+ function resolveDockMagnificationVisibleHitBounds({
7197
+ dockPlacement,
7198
+ hitBounds,
7199
+ viewportRect
7200
+ }) {
7201
+ if (!hitBounds || !viewportRect) {
7202
+ return hitBounds;
7203
+ }
7204
+ const viewportBounds = resolveDockMagnificationViewportBounds(
7205
+ viewportRect,
7206
+ dockPlacement
7207
+ );
7208
+ const visibleBounds = {
7209
+ crossEnd: Math.min(hitBounds.crossEnd, viewportBounds.crossEnd),
7210
+ crossStart: Math.max(hitBounds.crossStart, viewportBounds.crossStart),
7211
+ mainEnd: Math.min(hitBounds.mainEnd, viewportBounds.mainEnd),
7212
+ mainStart: Math.max(hitBounds.mainStart, viewportBounds.mainStart)
7213
+ };
7214
+ if (visibleBounds.mainStart > visibleBounds.mainEnd || visibleBounds.crossStart > visibleBounds.crossEnd) {
7215
+ return null;
7216
+ }
7217
+ return visibleBounds;
7218
+ }
7219
+ function isDockMagnificationPointInsideHitBounds({
7220
+ clientX,
7221
+ clientY,
7222
+ dockPlacement,
7223
+ hitBounds
7224
+ }) {
7225
+ if (!hitBounds) {
7226
+ return false;
7227
+ }
7228
+ const mainAxis = dockPlacement === "left" ? clientY : clientX;
7229
+ const crossAxis = dockPlacement === "left" ? clientX : clientY;
7230
+ return mainAxis >= hitBounds.mainStart && mainAxis <= hitBounds.mainEnd && crossAxis >= hitBounds.crossStart && crossAxis <= hitBounds.crossEnd;
7231
+ }
7232
+
7233
+ // src/host/dockMagnification.ts
7181
7234
  var DOCK_ICON_BASE_SIZE = 43.2;
7182
7235
  var DOCK_ICON_PEAK_SIZE = DOCK_ICON_BASE_SIZE * 1.7;
7183
7236
  var DOCK_MAGNIFICATION_HALF_RANGE = DOCK_ICON_BASE_SIZE * 2.4;
@@ -7191,6 +7244,10 @@ var MAGNIFICATION_INFLUENCE_PADDING = 8;
7191
7244
  var DOCK_MAGNIFICATION_ENTRY_RAMP_MS = 90;
7192
7245
  var DOCK_MAGNIFICATION_CROSS_AXIS_PADDING = 8;
7193
7246
  var dockMagnificationShellBySlot = /* @__PURE__ */ new WeakMap();
7247
+ var globalPointerListenerOptions = {
7248
+ capture: true,
7249
+ passive: true
7250
+ };
7194
7251
  function mapDistanceToTargetSize(distance, baseSize = DOCK_ICON_BASE_SIZE, peakSize = DOCK_ICON_PEAK_SIZE, halfRange = DOCK_MAGNIFICATION_HALF_RANGE) {
7195
7252
  const absoluteDistance = Math.abs(distance);
7196
7253
  if (absoluteDistance >= halfRange) {
@@ -7232,18 +7289,60 @@ function resolveDockMagnificationHitBounds(slotRects, dockPlacement, crossAxisPa
7232
7289
  mainStart
7233
7290
  };
7234
7291
  }
7235
- function isDockMagnificationPointInsideHitBounds({
7236
- clientX,
7237
- clientY,
7238
- dockPlacement,
7239
- hitBounds
7292
+ function createDockMagnificationGlobalPointerTracker({
7293
+ blurTarget,
7294
+ onPointerCancel,
7295
+ onPointerMove,
7296
+ pointerTarget
7240
7297
  }) {
7241
- if (!hitBounds) {
7242
- return false;
7243
- }
7244
- const mainAxis = dockPlacement === "left" ? clientY : clientX;
7245
- const crossAxis = dockPlacement === "left" ? clientX : clientY;
7246
- return mainAxis >= hitBounds.mainStart && mainAxis <= hitBounds.mainEnd && crossAxis >= hitBounds.crossStart && crossAxis <= hitBounds.crossEnd;
7298
+ let active = false;
7299
+ const handlePointerMove = (event) => {
7300
+ const pointerEvent = event;
7301
+ onPointerMove(pointerEvent.clientX, pointerEvent.clientY);
7302
+ };
7303
+ const handlePointerCancel = () => {
7304
+ stop();
7305
+ onPointerCancel();
7306
+ };
7307
+ const start = () => {
7308
+ if (active) {
7309
+ return;
7310
+ }
7311
+ active = true;
7312
+ pointerTarget.addEventListener(
7313
+ "pointermove",
7314
+ handlePointerMove,
7315
+ globalPointerListenerOptions
7316
+ );
7317
+ pointerTarget.addEventListener(
7318
+ "pointercancel",
7319
+ handlePointerCancel,
7320
+ globalPointerListenerOptions
7321
+ );
7322
+ blurTarget?.addEventListener("blur", handlePointerCancel);
7323
+ };
7324
+ const stop = () => {
7325
+ if (!active) {
7326
+ return;
7327
+ }
7328
+ active = false;
7329
+ pointerTarget.removeEventListener(
7330
+ "pointermove",
7331
+ handlePointerMove,
7332
+ globalPointerListenerOptions
7333
+ );
7334
+ pointerTarget.removeEventListener(
7335
+ "pointercancel",
7336
+ handlePointerCancel,
7337
+ globalPointerListenerOptions
7338
+ );
7339
+ blurTarget?.removeEventListener("blur", handlePointerCancel);
7340
+ };
7341
+ return {
7342
+ isActive: () => active,
7343
+ start,
7344
+ stop
7345
+ };
7247
7346
  }
7248
7347
  function resolveDockMagnificationSlotCenter(rect, dockPlacement, baseSize = DOCK_ICON_BASE_SIZE) {
7249
7348
  return dockPlacement === "left" ? rect.top + baseSize / 2 : rect.left + baseSize / 2;
@@ -7336,6 +7435,7 @@ function clearDockSlotMagnification(slotElement, appliedStylesRef) {
7336
7435
  function useDockMagnification({
7337
7436
  dockPlacement,
7338
7437
  dockRootRef,
7438
+ dockViewportRef,
7339
7439
  slotRefs
7340
7440
  }) {
7341
7441
  const pointerAxisRef = useRef5(null);
@@ -7351,6 +7451,13 @@ function useDockMagnification({
7351
7451
  const hitBoundsRef = useRef5(null);
7352
7452
  const slotOrderRef = useRef5([]);
7353
7453
  const magnifyActiveRef = useRef5(false);
7454
+ const globalPointerTrackerRef = useRef5(null);
7455
+ const handleGlobalPointerMoveRef = useRef5(() => {
7456
+ return;
7457
+ });
7458
+ const handleGlobalPointerCancelRef = useRef5(() => {
7459
+ return;
7460
+ });
7354
7461
  const setMagnifyActive = useCallback8(
7355
7462
  (active) => {
7356
7463
  if (magnifyActiveRef.current === active) {
@@ -7377,6 +7484,7 @@ function useDockMagnification({
7377
7484
  const centers = /* @__PURE__ */ new Map();
7378
7485
  const slotRects = [];
7379
7486
  const order = [];
7487
+ const viewportRect = dockViewportRef.current?.getBoundingClientRect();
7380
7488
  for (const [anchorKey, slotElement] of slots) {
7381
7489
  order.push(anchorKey);
7382
7490
  const rect = slotElement.getBoundingClientRect();
@@ -7390,12 +7498,18 @@ function useDockMagnification({
7390
7498
  centers.set(anchorKey, center);
7391
7499
  }
7392
7500
  slotOrderRef.current = order;
7393
- hitBoundsRef.current = resolveDockMagnificationHitBounds(
7394
- slotRects,
7395
- dockPlacement
7396
- );
7501
+ hitBoundsRef.current = resolveDockMagnificationVisibleHitBounds({
7502
+ dockPlacement,
7503
+ hitBounds: resolveDockMagnificationHitBounds(slotRects, dockPlacement),
7504
+ viewportRect: viewportRect ? {
7505
+ bottom: viewportRect.bottom,
7506
+ left: viewportRect.left,
7507
+ right: viewportRect.right,
7508
+ top: viewportRect.top
7509
+ } : null
7510
+ });
7397
7511
  restCentersRef.current = centers;
7398
- }, [dockPlacement, slotRefs]);
7512
+ }, [dockPlacement, dockViewportRef, slotRefs]);
7399
7513
  const runAnimationFrame = useCallback8(
7400
7514
  (frameTime) => {
7401
7515
  if (pendingPointerAxisRef.current !== null) {
@@ -7511,6 +7625,35 @@ function useDockMagnification({
7511
7625
  }
7512
7626
  animationFrameRef.current = requestAnimationFrame(runAnimationFrame);
7513
7627
  }, [runAnimationFrame]);
7628
+ const stopGlobalPointerTracking = useCallback8(() => {
7629
+ globalPointerTrackerRef.current?.stop();
7630
+ }, []);
7631
+ const startGlobalPointerTracking = useCallback8(() => {
7632
+ if (typeof document === "undefined") {
7633
+ return;
7634
+ }
7635
+ globalPointerTrackerRef.current ??= createDockMagnificationGlobalPointerTracker({
7636
+ blurTarget: typeof window === "undefined" ? null : window,
7637
+ onPointerCancel: () => {
7638
+ handleGlobalPointerCancelRef.current();
7639
+ },
7640
+ onPointerMove: (clientX, clientY) => {
7641
+ handleGlobalPointerMoveRef.current(clientX, clientY);
7642
+ },
7643
+ pointerTarget: document
7644
+ });
7645
+ globalPointerTrackerRef.current.start();
7646
+ }, []);
7647
+ const clearTrackedPointer = useCallback8(() => {
7648
+ pendingPointerAxisRef.current = null;
7649
+ pointerAxisRef.current = null;
7650
+ entryRampStartedAtRef.current = null;
7651
+ scheduleAnimation();
7652
+ }, [scheduleAnimation]);
7653
+ const handleGlobalPointerCancel = useCallback8(() => {
7654
+ stopGlobalPointerTracking();
7655
+ clearTrackedPointer();
7656
+ }, [clearTrackedPointer, stopGlobalPointerTracking]);
7514
7657
  const handlePointerMove = useCallback8(
7515
7658
  (clientX, clientY) => {
7516
7659
  if (restCentersRef.current === null) {
@@ -7522,20 +7665,29 @@ function useDockMagnification({
7522
7665
  dockPlacement,
7523
7666
  hitBounds: hitBoundsRef.current
7524
7667
  })) {
7525
- pendingPointerAxisRef.current = null;
7526
- pointerAxisRef.current = null;
7527
- entryRampStartedAtRef.current = null;
7528
- scheduleAnimation();
7668
+ stopGlobalPointerTracking();
7669
+ clearTrackedPointer();
7529
7670
  return;
7530
7671
  }
7531
7672
  pendingPointerAxisRef.current = dockPlacement === "left" ? clientY : clientX;
7532
7673
  if (!magnifyActiveRef.current) {
7533
7674
  setMagnifyActive(true);
7534
7675
  }
7676
+ startGlobalPointerTracking();
7535
7677
  scheduleAnimation();
7536
7678
  },
7537
- [captureRestCenters, dockPlacement, scheduleAnimation, setMagnifyActive]
7679
+ [
7680
+ captureRestCenters,
7681
+ clearTrackedPointer,
7682
+ dockPlacement,
7683
+ scheduleAnimation,
7684
+ setMagnifyActive,
7685
+ startGlobalPointerTracking,
7686
+ stopGlobalPointerTracking
7687
+ ]
7538
7688
  );
7689
+ handleGlobalPointerMoveRef.current = handlePointerMove;
7690
+ handleGlobalPointerCancelRef.current = handleGlobalPointerCancel;
7539
7691
  const handlePointerLeave = useCallback8(() => {
7540
7692
  pendingPointerAxisRef.current = null;
7541
7693
  pointerAxisRef.current = null;
@@ -7558,13 +7710,15 @@ function useDockMagnification({
7558
7710
  [slotRefs]
7559
7711
  );
7560
7712
  const pauseMagnification = useCallback8(() => {
7713
+ stopGlobalPointerTracking();
7561
7714
  stopAnimation();
7562
7715
  pendingPointerAxisRef.current = null;
7563
7716
  pointerAxisRef.current = null;
7564
7717
  entryRampStartedAtRef.current = null;
7565
7718
  lastFrameTimeRef.current = null;
7566
- }, [stopAnimation]);
7719
+ }, [stopAnimation, stopGlobalPointerTracking]);
7567
7720
  const resetMagnification = useCallback8(() => {
7721
+ stopGlobalPointerTracking();
7568
7722
  stopAnimation();
7569
7723
  for (const slotElement of slotRefs.current.values()) {
7570
7724
  clearDockSlotMagnification(slotElement, appliedStylesRef.current);
@@ -7578,7 +7732,7 @@ function useDockMagnification({
7578
7732
  pendingPointerAxisRef.current = null;
7579
7733
  entryRampStartedAtRef.current = null;
7580
7734
  setMagnifyActive(false);
7581
- }, [setMagnifyActive, slotRefs, stopAnimation]);
7735
+ }, [setMagnifyActive, slotRefs, stopAnimation, stopGlobalPointerTracking]);
7582
7736
  useEffect6(
7583
7737
  () => () => {
7584
7738
  resetMagnification();
@@ -9316,6 +9470,7 @@ function WorkbenchHostDock({
9316
9470
  } = useDockMagnification({
9317
9471
  dockPlacement,
9318
9472
  dockRootRef: dockMeasureRef,
9473
+ dockViewportRef: dockItemsRef,
9319
9474
  slotRefs
9320
9475
  });
9321
9476
  const clearSlotMagnificationRef = useRef8(() => {
@@ -10161,6 +10316,7 @@ function WorkbenchHostDock({
10161
10316
  "aria-disabled": clickResolution.kind === "blocked" ? true : void 0,
10162
10317
  className: "desktop-dock__btn",
10163
10318
  "data-interactive": clickResolution.kind === "blocked" ? "false" : "true",
10319
+ "data-dock-hover-panel-trigger": hasHoverPanel ? "true" : void 0,
10164
10320
  type: "button",
10165
10321
  onPointerDown: (event) => {
10166
10322
  if (event.button !== 0) {
@@ -11359,9 +11515,40 @@ function WorkbenchHostDockHoverPanel({
11359
11515
  setPendingActionKeys,
11360
11516
  state
11361
11517
  }) {
11518
+ const lastPointerActionAtRef = useRef8(/* @__PURE__ */ new Map());
11362
11519
  if (!entry) {
11363
11520
  return null;
11364
11521
  }
11522
+ const hoverPanelEntry = entry;
11523
+ const runHoverAction = (actionKey, actionId, disabled) => {
11524
+ if (disabled || pendingActionKeys.has(actionKey)) {
11525
+ return;
11526
+ }
11527
+ setPendingActionKeys((current) => {
11528
+ const next = new Set(current);
11529
+ next.add(actionKey);
11530
+ return next;
11531
+ });
11532
+ void (async () => {
11533
+ try {
11534
+ await onDockEntryAction?.({
11535
+ actionId,
11536
+ entryId: hoverPanelEntry.id,
11537
+ host
11538
+ });
11539
+ } catch {
11540
+ } finally {
11541
+ setPendingActionKeys((current) => {
11542
+ if (!current.has(actionKey)) {
11543
+ return current;
11544
+ }
11545
+ const next = new Set(current);
11546
+ next.delete(actionKey);
11547
+ return next;
11548
+ });
11549
+ }
11550
+ })();
11551
+ };
11365
11552
  return /* @__PURE__ */ jsxs8(
11366
11553
  "div",
11367
11554
  {
@@ -11394,36 +11581,34 @@ function WorkbenchHostDockHoverPanel({
11394
11581
  className: "desktop-dock__hover-action",
11395
11582
  disabled: action.disabled || isLocallyPending,
11396
11583
  type: "button",
11584
+ onPointerDown: (event) => {
11585
+ if (event.button !== 0) {
11586
+ return;
11587
+ }
11588
+ event.preventDefault();
11589
+ event.stopPropagation();
11590
+ lastPointerActionAtRef.current.set(
11591
+ actionKey,
11592
+ performance.now()
11593
+ );
11594
+ runHoverAction(
11595
+ actionKey,
11596
+ action.id,
11597
+ action.disabled === true
11598
+ );
11599
+ },
11397
11600
  onClick: (event) => {
11398
11601
  event.preventDefault();
11399
11602
  event.stopPropagation();
11400
- if (action.disabled === true || pendingActionKeys.has(actionKey)) {
11603
+ const lastPointerAt = lastPointerActionAtRef.current.get(actionKey);
11604
+ if (lastPointerAt !== void 0 && performance.now() - lastPointerAt < 700) {
11401
11605
  return;
11402
11606
  }
11403
- setPendingActionKeys((current) => {
11404
- const next = new Set(current);
11405
- next.add(actionKey);
11406
- return next;
11407
- });
11408
- void (async () => {
11409
- try {
11410
- await onDockEntryAction?.({
11411
- actionId: action.id,
11412
- entryId: entry.id,
11413
- host
11414
- });
11415
- } catch {
11416
- } finally {
11417
- setPendingActionKeys((current) => {
11418
- if (!current.has(actionKey)) {
11419
- return current;
11420
- }
11421
- const next = new Set(current);
11422
- next.delete(actionKey);
11423
- return next;
11424
- });
11425
- }
11426
- })();
11607
+ runHoverAction(
11608
+ actionKey,
11609
+ action.id,
11610
+ action.disabled === true
11611
+ );
11427
11612
  },
11428
11613
  children: isPending ? action.pendingLabel ?? action.label : action.label
11429
11614
  },