@tutti-os/workbench-surface 0.0.109 → 0.0.111

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/README.md CHANGED
@@ -43,6 +43,12 @@ When a node body or header needs host-owned business state, pass an
43
43
  `externalNodeState` and `externalWorkspaceState`. If the source exposes
44
44
  `subscribe(...)`, the host re-renders when that subscription notifies.
45
45
 
46
+ Node body context also exposes `isDragging` and `isResizing`. The Workbench
47
+ shell continues applying live frame geometry during direct manipulation, while
48
+ an expensive body adapter may use these flags to suppress frame-only renders
49
+ until the interaction settles. The adapter must still observe both interaction
50
+ transitions so the final committed frame reaches responsive body layout.
51
+
46
52
  Host-owned business instances are represented with `projectedNodes`. A
47
53
  projected node tells the workbench that a shell should currently exist for a
48
54
  host-owned instance; the workbench reconciles that presence with snapshot
package/dist/index.d.ts CHANGED
@@ -274,6 +274,8 @@ interface WorkbenchRenderNodeContext<TData = unknown> {
274
274
  node: WorkbenchNode<TData>;
275
275
  layout: WorkbenchNodeRenderFrame;
276
276
  controller: WorkbenchController<TData>;
277
+ isDragging: boolean;
278
+ isResizing: boolean;
277
279
  }
278
280
  interface WorkbenchDockContext<TData = unknown> {
279
281
  focusedNodeId: string | null;
@@ -661,7 +663,11 @@ interface WorkbenchHostNodeBodyContext<TExternalNodeState = unknown, TExternalWo
661
663
  host: WorkbenchHostHandle;
662
664
  instanceId: string;
663
665
  instanceKey?: string | null;
666
+ /** True while the host is interactively moving this node. */
667
+ isDragging: boolean;
664
668
  isFocused: boolean;
669
+ /** True while the host is interactively resizing this node. */
670
+ isResizing: boolean;
665
671
  /** Current host presentation mode; null for the normal window layout. */
666
672
  presentationMode?: WorkbenchSurfacePresentation["mode"] | null;
667
673
  node: WorkbenchNode<WorkbenchHostNodeData>;
package/dist/index.js CHANGED
@@ -3267,6 +3267,12 @@ function WorkbenchNodeLayerItem({
3267
3267
  const isFocused = useWorkbenchSelector(
3268
3268
  (state) => selectFocusedWorkbenchNode(state)?.id === nodeID
3269
3269
  );
3270
+ const isDragging = useWorkbenchSelector(
3271
+ (state) => state.activeDragNodeId === nodeID
3272
+ );
3273
+ const isResizing = useWorkbenchSelector(
3274
+ (state) => state.activeResizeNodeId === nodeID
3275
+ );
3270
3276
  const zIndex = useWorkbenchSelector(
3271
3277
  (state) => selectWorkbenchNodeZIndex(state, nodeID)
3272
3278
  );
@@ -3297,6 +3303,8 @@ function WorkbenchNodeLayerItem({
3297
3303
  }),
3298
3304
  children: renderNode({
3299
3305
  node,
3306
+ isDragging,
3307
+ isResizing,
3300
3308
  layout: {
3301
3309
  frame: node.frame,
3302
3310
  presentation,
@@ -8659,6 +8667,77 @@ function resolveDockLabelTooltipAnchorRect(slotElement) {
8659
8667
  };
8660
8668
  }
8661
8669
 
8670
+ // src/host/dockWallpaperSampling.ts
8671
+ var dockWallpaperSampleMaxSizePx = 192;
8672
+ var dockWallpaperToneSampleCount = 7;
8673
+ var sampleByImage = /* @__PURE__ */ new WeakMap();
8674
+ function getDockWallpaperImageSample(image) {
8675
+ if (sampleByImage.has(image)) {
8676
+ return sampleByImage.get(image) ?? null;
8677
+ }
8678
+ const sample = createDockWallpaperImageSample(image);
8679
+ sampleByImage.set(image, sample);
8680
+ return sample;
8681
+ }
8682
+ function createDockWallpaperImageSample(image) {
8683
+ if (image.naturalWidth <= 0 || image.naturalHeight <= 0) {
8684
+ return null;
8685
+ }
8686
+ const canvas = document.createElement("canvas");
8687
+ const scale = dockWallpaperSampleMaxSizePx / Math.max(image.naturalWidth, image.naturalHeight);
8688
+ canvas.width = Math.max(1, Math.round(image.naturalWidth * scale));
8689
+ canvas.height = Math.max(1, Math.round(image.naturalHeight * scale));
8690
+ const context = canvas.getContext("2d", { willReadFrequently: true });
8691
+ if (!context) {
8692
+ return null;
8693
+ }
8694
+ try {
8695
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
8696
+ return {
8697
+ data: context.getImageData(0, 0, canvas.width, canvas.height).data,
8698
+ height: canvas.height,
8699
+ width: canvas.width
8700
+ };
8701
+ } catch {
8702
+ return null;
8703
+ }
8704
+ }
8705
+ function sampleDockWallpaperLuminanceAtElement({
8706
+ elementRect,
8707
+ renderedImageRect,
8708
+ sample,
8709
+ wallpaperRect
8710
+ }) {
8711
+ const isVerticalElement = elementRect.height >= elementRect.width;
8712
+ let luminanceSum = 0;
8713
+ let samples = 0;
8714
+ for (let index = 0; index < dockWallpaperToneSampleCount; index += 1) {
8715
+ const ratio = index / (dockWallpaperToneSampleCount - 1);
8716
+ const clientX = isVerticalElement ? elementRect.left + elementRect.width / 2 : elementRect.left + elementRect.width * ratio;
8717
+ const clientY = isVerticalElement ? elementRect.top + elementRect.height * ratio : elementRect.top + elementRect.height / 2;
8718
+ const imageX = (clientX - wallpaperRect.left - renderedImageRect.left) / renderedImageRect.width;
8719
+ const imageY = (clientY - wallpaperRect.top - renderedImageRect.top) / renderedImageRect.height;
8720
+ if (imageX < 0 || imageX > 1 || imageY < 0 || imageY > 1) {
8721
+ continue;
8722
+ }
8723
+ const sampleX = Math.min(
8724
+ sample.width - 1,
8725
+ Math.max(0, Math.round(imageX * (sample.width - 1)))
8726
+ );
8727
+ const sampleY = Math.min(
8728
+ sample.height - 1,
8729
+ Math.max(0, Math.round(imageY * (sample.height - 1)))
8730
+ );
8731
+ const pixelOffset = (sampleY * sample.width + sampleX) * 4;
8732
+ const red = sample.data[pixelOffset] ?? 0;
8733
+ const green = sample.data[pixelOffset + 1] ?? 0;
8734
+ const blue = sample.data[pixelOffset + 2] ?? 0;
8735
+ luminanceSum += 0.2126 * red + 0.7152 * green + 0.0722 * blue;
8736
+ samples += 1;
8737
+ }
8738
+ return samples > 0 ? luminanceSum / samples : null;
8739
+ }
8740
+
8662
8741
  // src/host/dockScrollState.ts
8663
8742
  var dockScrollOverflowEpsilonPx = 1;
8664
8743
  function resolveWorkbenchHostDockScrollState({
@@ -12858,8 +12937,8 @@ async function resolveDockWallpaperTones({
12858
12937
  if (!wallpaperImage) {
12859
12938
  return /* @__PURE__ */ new Map();
12860
12939
  }
12861
- const sampleCanvas = createDockWallpaperSampleCanvas(wallpaperImage);
12862
- if (!sampleCanvas) {
12940
+ const wallpaperSample = getDockWallpaperImageSample(wallpaperImage);
12941
+ if (!wallpaperSample) {
12863
12942
  return /* @__PURE__ */ new Map();
12864
12943
  }
12865
12944
  const wallpaperRect = wallpaperElement.getBoundingClientRect();
@@ -12877,7 +12956,7 @@ async function resolveDockWallpaperTones({
12877
12956
  const luminance = sampleDockWallpaperLuminanceAtElement({
12878
12957
  elementRect: element.getBoundingClientRect(),
12879
12958
  renderedImageRect,
12880
- sampleCanvas,
12959
+ sample: wallpaperSample,
12881
12960
  wallpaperRect
12882
12961
  });
12883
12962
  if (luminance === null) {
@@ -12910,26 +12989,6 @@ function loadDockWallpaperImage(url) {
12910
12989
  dockWallpaperImageCache.set(url, promise);
12911
12990
  return promise;
12912
12991
  }
12913
- function createDockWallpaperSampleCanvas(image) {
12914
- if (image.naturalWidth <= 0 || image.naturalHeight <= 0) {
12915
- return null;
12916
- }
12917
- const canvas = document.createElement("canvas");
12918
- const scale = dockWallpaperSampleCanvasMaxSizePx / Math.max(image.naturalWidth, image.naturalHeight);
12919
- canvas.width = Math.max(1, Math.round(image.naturalWidth * scale));
12920
- canvas.height = Math.max(1, Math.round(image.naturalHeight * scale));
12921
- const context = canvas.getContext("2d", { willReadFrequently: true });
12922
- if (!context) {
12923
- return null;
12924
- }
12925
- try {
12926
- context.drawImage(image, 0, 0, canvas.width, canvas.height);
12927
- context.getImageData(0, 0, 1, 1);
12928
- } catch {
12929
- return null;
12930
- }
12931
- return canvas;
12932
- }
12933
12992
  function resolveDockWallpaperRenderedImageRect({
12934
12993
  containerHeight,
12935
12994
  containerWidth,
@@ -12987,45 +13046,6 @@ function resolveDockWallpaperPositionOffset(value, availableSpace) {
12987
13046
  }
12988
13047
  return availableSpace / 2;
12989
13048
  }
12990
- function sampleDockWallpaperLuminanceAtElement({
12991
- elementRect,
12992
- renderedImageRect,
12993
- sampleCanvas,
12994
- wallpaperRect
12995
- }) {
12996
- const context = sampleCanvas.getContext("2d", { willReadFrequently: true });
12997
- if (!context) {
12998
- return null;
12999
- }
13000
- const isVerticalElement = elementRect.height >= elementRect.width;
13001
- let luminanceSum = 0;
13002
- let samples = 0;
13003
- for (let index = 0; index < dockWallpaperToneSampleCount; index += 1) {
13004
- const ratio = index / (dockWallpaperToneSampleCount - 1);
13005
- const clientX = isVerticalElement ? elementRect.left + elementRect.width / 2 : elementRect.left + elementRect.width * ratio;
13006
- const clientY = isVerticalElement ? elementRect.top + elementRect.height * ratio : elementRect.top + elementRect.height / 2;
13007
- const imageX = (clientX - wallpaperRect.left - renderedImageRect.left) / renderedImageRect.width;
13008
- const imageY = (clientY - wallpaperRect.top - renderedImageRect.top) / renderedImageRect.height;
13009
- if (imageX < 0 || imageX > 1 || imageY < 0 || imageY > 1) {
13010
- continue;
13011
- }
13012
- const canvasX = Math.min(
13013
- sampleCanvas.width - 1,
13014
- Math.max(0, Math.round(imageX * (sampleCanvas.width - 1)))
13015
- );
13016
- const canvasY = Math.min(
13017
- sampleCanvas.height - 1,
13018
- Math.max(0, Math.round(imageY * (sampleCanvas.height - 1)))
13019
- );
13020
- const pixel = context.getImageData(canvasX, canvasY, 1, 1).data;
13021
- const red = pixel[0] ?? 0;
13022
- const green = pixel[1] ?? 0;
13023
- const blue = pixel[2] ?? 0;
13024
- luminanceSum += 0.2126 * red + 0.7152 * green + 0.0722 * blue;
13025
- samples += 1;
13026
- }
13027
- return samples > 0 ? luminanceSum / samples : null;
13028
- }
13029
13049
  function dockWallpaperToneMapsEqual(left, right) {
13030
13050
  if (left.size !== right.size) {
13031
13051
  return false;
@@ -13102,8 +13122,6 @@ var dockHoverPanelCloseDelayMs = 160;
13102
13122
  var dockHoverPanelHitSlopPx = 12;
13103
13123
  var dockHoverPanelBridgeSlopPx = 6;
13104
13124
  var dockHoverPanelPointerRestTolerancePx = 4;
13105
- var dockWallpaperSampleCanvasMaxSizePx = 192;
13106
- var dockWallpaperToneSampleCount = 7;
13107
13125
  var dockWallpaperDarkLuminanceThreshold = 132;
13108
13126
  function rectContainsPoint(rect, clientX, clientY, slopPx = 0) {
13109
13127
  return clientX >= rect.left - slopPx && clientX <= rect.right + slopPx && clientY >= rect.top - slopPx && clientY <= rect.bottom + slopPx;
@@ -13188,7 +13206,9 @@ function createWorkbenchHostNodeBodyContext({
13188
13206
  host,
13189
13207
  instanceId: context.node.data.instanceId,
13190
13208
  instanceKey: context.node.data.instanceKey ?? null,
13209
+ isDragging: context.isDragging,
13191
13210
  isFocused: selectFocusedWorkbenchNode(host.getSnapshot())?.id === context.node.id,
13211
+ isResizing: context.isResizing,
13192
13212
  presentationMode: context.layout.presentation?.mode ?? null,
13193
13213
  node: context.node,
13194
13214
  setNodeRuntimeState(state) {