@adaptabletools/adaptable 23.0.9 → 23.0.11

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.
@@ -5,6 +5,7 @@ import useDraggable from '../utils/useDraggable';
5
5
  import { isBrowserDocumentAvailable } from '../../View/UIHelper';
6
6
  import { createPortal } from 'react-dom';
7
7
  import { useStacking } from './useStacking';
8
+ import { clamp } from '../../Utilities/Extensions/NumberExtensions';
8
9
  let portalElement;
9
10
  const ensurePortalElement = () => {
10
11
  if (!isBrowserDocumentAvailable()) {
@@ -17,7 +18,55 @@ const ensurePortalElement = () => {
17
18
  portalElement.classList.add('ab-cmp-modal-window');
18
19
  document.body.appendChild(portalElement);
19
20
  };
20
- const normalizeTopLeft = (position) => (position < 0 ? 0 : position);
21
+ const normalizeTopLeft = (position, edgeOffset) => position < edgeOffset ? edgeOffset : position;
22
+ export const WINDOW_MODAL_VIEWPORT_EDGE_OFFSET = 10;
23
+ const getViewportSize = () => {
24
+ if (!isBrowserDocumentAvailable()) {
25
+ return { width: Infinity, height: Infinity };
26
+ }
27
+ return { width: window.innerWidth, height: window.innerHeight };
28
+ };
29
+ const parsePxConstraint = (value) => {
30
+ if (!value) {
31
+ return undefined;
32
+ }
33
+ const match = /^(-?\d+(?:\.\d+)?)px$/.exec(value.trim());
34
+ if (!match) {
35
+ return undefined;
36
+ }
37
+ const parsed = Number(match[1]);
38
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
39
+ };
40
+ const readDomSizeConstraints = (element) => {
41
+ if (!element || !isBrowserDocumentAvailable()) {
42
+ return {};
43
+ }
44
+ const computed = window.getComputedStyle(element);
45
+ return {
46
+ minWidth: parsePxConstraint(computed.minWidth),
47
+ minHeight: parsePxConstraint(computed.minHeight),
48
+ maxWidth: parsePxConstraint(computed.maxWidth),
49
+ maxHeight: parsePxConstraint(computed.maxHeight),
50
+ };
51
+ };
52
+ export const clampWindowModalToViewport = (position, size, minWidth = 0, minHeight = 0, edgeOffset = WINDOW_MODAL_VIEWPORT_EDGE_OFFSET) => {
53
+ const { width: viewportWidth, height: viewportHeight } = getViewportSize();
54
+ const width = clamp(size.width, minWidth, Math.max(minWidth, viewportWidth - 2 * edgeOffset));
55
+ const height = clamp(size.height, minHeight, Math.max(minHeight, viewportHeight - 2 * edgeOffset));
56
+ const x = clamp(position.x, edgeOffset, Math.max(edgeOffset, viewportWidth - edgeOffset - width));
57
+ const y = clamp(position.y, edgeOffset, Math.max(edgeOffset, viewportHeight - edgeOffset - height));
58
+ return {
59
+ position: { x, y },
60
+ size: { width, height },
61
+ };
62
+ };
63
+ export const clampWindowModalPositionToViewport = (position, size, edgeOffset = WINDOW_MODAL_VIEWPORT_EDGE_OFFSET) => {
64
+ const { width: viewportWidth, height: viewportHeight } = getViewportSize();
65
+ return {
66
+ x: clamp(position.x, edgeOffset, Math.max(edgeOffset, viewportWidth - edgeOffset - size.width)),
67
+ y: clamp(position.y, edgeOffset, Math.max(edgeOffset, viewportHeight - edgeOffset - size.height)),
68
+ };
69
+ };
21
70
  const getResizePositionDelta = (direction, delta) => {
22
71
  const positionDelta = { x: 0, y: 0 };
23
72
  const left = -delta.width;
@@ -38,16 +87,126 @@ const getResizePositionDelta = (direction, delta) => {
38
87
  }
39
88
  return null;
40
89
  };
90
+ const resizeDirectionAffects = (direction) => ({
91
+ left: ['left', 'topLeft', 'bottomLeft'].includes(direction),
92
+ top: ['top', 'topLeft', 'topRight'].includes(direction),
93
+ right: ['right', 'topRight', 'bottomRight'].includes(direction),
94
+ bottom: ['bottom', 'bottomLeft', 'bottomRight'].includes(direction),
95
+ });
96
+ const getClampedResizeState = (direction, delta, basePosition, baseSize, minWidth, minHeight, maxWidth, maxHeight, edgeOffset = WINDOW_MODAL_VIEWPORT_EDGE_OFFSET) => {
97
+ const { width: viewportWidth, height: viewportHeight } = getViewportSize();
98
+ const affects = resizeDirectionAffects(direction);
99
+ let deltaWidth = delta.width;
100
+ let deltaHeight = delta.height;
101
+ if (affects.left) {
102
+ deltaWidth = Math.min(deltaWidth, Math.max(0, basePosition.x - edgeOffset));
103
+ }
104
+ if (affects.top) {
105
+ deltaHeight = Math.min(deltaHeight, Math.max(0, basePosition.y - edgeOffset));
106
+ }
107
+ if (affects.right) {
108
+ deltaWidth = Math.min(deltaWidth, viewportWidth - edgeOffset - basePosition.x - baseSize.width);
109
+ }
110
+ if (affects.bottom) {
111
+ deltaHeight = Math.min(deltaHeight, viewportHeight - edgeOffset - basePosition.y - baseSize.height);
112
+ }
113
+ if (affects.left || affects.right) {
114
+ const maxDeltaWidth = maxWidth != null ? maxWidth - baseSize.width : Infinity;
115
+ const minDeltaWidth = minWidth - baseSize.width;
116
+ if (minDeltaWidth <= maxDeltaWidth) {
117
+ deltaWidth = clamp(deltaWidth, minDeltaWidth, maxDeltaWidth);
118
+ }
119
+ }
120
+ if (affects.top || affects.bottom) {
121
+ const maxDeltaHeight = maxHeight != null ? maxHeight - baseSize.height : Infinity;
122
+ const minDeltaHeight = minHeight - baseSize.height;
123
+ if (minDeltaHeight <= maxDeltaHeight) {
124
+ deltaHeight = clamp(deltaHeight, minDeltaHeight, maxDeltaHeight);
125
+ }
126
+ }
127
+ const adjustedDelta = { width: deltaWidth, height: deltaHeight };
128
+ const positionDelta = getResizePositionDelta(direction, adjustedDelta);
129
+ const newPosition = {
130
+ x: basePosition.x + (positionDelta?.x ?? 0),
131
+ y: basePosition.y + (positionDelta?.y ?? 0),
132
+ };
133
+ const newSize = {
134
+ width: baseSize.width + deltaWidth,
135
+ height: baseSize.height + deltaHeight,
136
+ };
137
+ const clamped = clampWindowModalToViewport(newPosition, newSize, minWidth, minHeight, edgeOffset);
138
+ return {
139
+ ...clamped,
140
+ positionDelta: positionDelta
141
+ ? {
142
+ x: clamped.position.x - basePosition.x,
143
+ y: clamped.position.y - basePosition.y,
144
+ }
145
+ : null,
146
+ };
147
+ };
148
+ const RESIZE_HANDLE_CLASS_PREFIX = 'ab-WindowModal__resize-handle';
149
+ const RESIZE_EDGE_HANDLES = ['left', 'right', 'top', 'bottom'];
150
+ const RESIZE_HANDLE_CLASSES = {
151
+ left: `${RESIZE_HANDLE_CLASS_PREFIX}--left`,
152
+ right: `${RESIZE_HANDLE_CLASS_PREFIX}--right`,
153
+ top: `${RESIZE_HANDLE_CLASS_PREFIX}--top`,
154
+ bottom: `${RESIZE_HANDLE_CLASS_PREFIX}--bottom`,
155
+ };
156
+ const AT_CONSTRAINT_EPSILON = 0.5;
157
+ const GROW_CURSOR = {
158
+ left: 'w-resize',
159
+ right: 'e-resize',
160
+ top: 'n-resize',
161
+ bottom: 's-resize',
162
+ };
163
+ const SHRINK_CURSOR = {
164
+ left: 'e-resize',
165
+ right: 'w-resize',
166
+ top: 's-resize',
167
+ bottom: 'n-resize',
168
+ };
169
+ const getEdgeResizeCursor = (edge, size, constraints) => {
170
+ const isHorizontal = edge === 'left' || edge === 'right';
171
+ const value = isHorizontal ? size.width : size.height;
172
+ const min = isHorizontal ? constraints.minWidth : constraints.minHeight;
173
+ const max = isHorizontal ? constraints.maxWidth : constraints.maxHeight;
174
+ const atMax = max != null && value >= max - AT_CONSTRAINT_EPSILON;
175
+ const atMin = value <= min + AT_CONSTRAINT_EPSILON;
176
+ if (atMax && !atMin) {
177
+ return SHRINK_CURSOR[edge];
178
+ }
179
+ if (atMin && !atMax) {
180
+ return GROW_CURSOR[edge];
181
+ }
182
+ return isHorizontal ? 'ew-resize' : 'ns-resize';
183
+ };
184
+ const updateResizeHandleCursors = (container, size, constraints) => {
185
+ if (!container) {
186
+ return;
187
+ }
188
+ RESIZE_EDGE_HANDLES.forEach((edge) => {
189
+ const handle = container.querySelector(`.${RESIZE_HANDLE_CLASSES[edge]}`);
190
+ if (handle) {
191
+ handle.style.cursor = getEdgeResizeCursor(edge, size, constraints);
192
+ }
193
+ });
194
+ };
41
195
  export const WindowModal = (props) => {
42
196
  ensurePortalElement();
43
197
  const positionDeltaRef = React.useRef(null);
198
+ const lastClampedRef = React.useRef(null);
44
199
  const normalizedPosition = {
45
- x: normalizeTopLeft(props.position?.x),
46
- y: normalizeTopLeft(props.position?.y),
200
+ x: normalizeTopLeft(props.position?.x ?? 0, WINDOW_MODAL_VIEWPORT_EDGE_OFFSET),
201
+ y: normalizeTopLeft(props.position?.y ?? 0, WINDOW_MODAL_VIEWPORT_EDGE_OFFSET),
47
202
  };
48
203
  const positionRef = React.useRef(normalizedPosition);
204
+ const sizeRef = React.useRef(props.size);
49
205
  const stacking = useStacking();
50
206
  positionRef.current = normalizedPosition;
207
+ sizeRef.current = props.size;
208
+ const minWidth = props.minWidth ?? 0;
209
+ const minHeight = props.minHeight ?? 0;
51
210
  const style = {
52
211
  zIndex: stacking.zIndex,
53
212
  position: 'absolute',
@@ -59,41 +218,83 @@ export const WindowModal = (props) => {
59
218
  x: positionRef.current.x + dx,
60
219
  y: positionRef.current.y + dy,
61
220
  };
62
- props.onChange({
63
- position: newPosition,
64
- size: props.size,
65
- });
221
+ const size = sizeRef.current;
222
+ const position = clampWindowModalPositionToViewport(newPosition, size, WINDOW_MODAL_VIEWPORT_EDGE_OFFSET);
223
+ props.onChange({ position, size });
66
224
  };
67
225
  const { targetRef, applyTransform } = useDraggable({
68
226
  handleSelector: props.handleSelector,
69
227
  onDrop: handleDrop,
228
+ edgeOffset: WINDOW_MODAL_VIEWPORT_EDGE_OFFSET,
70
229
  });
71
- const handleResizeStop = (event, direction, elementRef, delta) => {
72
- let newPosition = normalizedPosition;
73
- if (positionDeltaRef.current) {
74
- newPosition = {
75
- x: normalizedPosition.x + positionDeltaRef.current.x,
76
- y: normalizedPosition.y + positionDeltaRef.current.y,
77
- };
78
- positionDeltaRef.current = null;
79
- applyTransform(0, 0);
80
- }
81
- const newSize = {
82
- width: props.size.width + delta.width,
83
- height: props.size.height + delta.height,
230
+ const constraintsRef = React.useRef({ minWidth, minHeight });
231
+ const getContentElement = () => {
232
+ const resizableEl = targetRef.current?.firstElementChild;
233
+ return resizableEl?.firstElementChild ?? null;
234
+ };
235
+ const computeEffectiveConstraints = () => {
236
+ const dom = readDomSizeConstraints(getContentElement());
237
+ return {
238
+ minWidth: Math.max(minWidth, dom.minWidth ?? 0),
239
+ minHeight: Math.max(minHeight, dom.minHeight ?? 0),
240
+ maxWidth: dom.maxWidth,
241
+ maxHeight: dom.maxHeight,
84
242
  };
85
- props.onChange({
86
- position: newPosition,
87
- size: newSize,
88
- });
89
243
  };
90
- const handleResize = React.useCallback((event, direction, elementRef, delta) => {
91
- const positionDelta = getResizePositionDelta(direction, delta);
92
- if (positionDelta) {
93
- positionDeltaRef.current = positionDelta;
94
- applyTransform(positionDelta.x, positionDelta.y);
244
+ const handleResizeStart = () => {
245
+ constraintsRef.current = computeEffectiveConstraints();
246
+ updateResizeHandleCursors(targetRef.current, sizeRef.current, constraintsRef.current);
247
+ };
248
+ const handleResize = (_event, direction, elementRef, delta) => {
249
+ const constraints = constraintsRef.current;
250
+ const clamped = getClampedResizeState(direction, delta, positionRef.current, sizeRef.current, constraints.minWidth, constraints.minHeight, constraints.maxWidth, constraints.maxHeight);
251
+ lastClampedRef.current = clamped;
252
+ if (clamped.positionDelta) {
253
+ positionDeltaRef.current = clamped.positionDelta;
254
+ applyTransform(clamped.positionDelta.x, clamped.positionDelta.y);
255
+ }
256
+ elementRef.style.width = `${clamped.size.width}px`;
257
+ elementRef.style.height = `${clamped.size.height}px`;
258
+ updateResizeHandleCursors(targetRef.current, clamped.size, constraints);
259
+ if (isBrowserDocumentAvailable() &&
260
+ RESIZE_EDGE_HANDLES.includes(direction)) {
261
+ document.body.style.cursor = getEdgeResizeCursor(direction, clamped.size, constraints);
95
262
  }
96
- }, []);
263
+ };
264
+ React.useEffect(() => {
265
+ updateResizeHandleCursors(targetRef.current, sizeRef.current, computeEffectiveConstraints());
266
+ }, [props.size.width, props.size.height, minWidth, minHeight]);
267
+ const handleResizeStop = () => {
268
+ applyTransform(0, 0);
269
+ positionDeltaRef.current = null;
270
+ if (isBrowserDocumentAvailable()) {
271
+ document.body.style.cursor = '';
272
+ }
273
+ const clamped = lastClampedRef.current ??
274
+ clampWindowModalToViewport(positionRef.current, sizeRef.current, minWidth, minHeight, WINDOW_MODAL_VIEWPORT_EDGE_OFFSET);
275
+ lastClampedRef.current = null;
276
+ props.onChange(clamped);
277
+ };
278
+ const onChangeRef = React.useRef(props.onChange);
279
+ onChangeRef.current = props.onChange;
280
+ React.useEffect(() => {
281
+ if (!isBrowserDocumentAvailable()) {
282
+ return;
283
+ }
284
+ const handleViewportResize = () => {
285
+ const current = positionRef.current;
286
+ const currentSize = sizeRef.current;
287
+ const clamped = clampWindowModalToViewport(current, currentSize, minWidth, minHeight, WINDOW_MODAL_VIEWPORT_EDGE_OFFSET);
288
+ if (clamped.position.x !== current.x ||
289
+ clamped.position.y !== current.y ||
290
+ clamped.size.width !== currentSize.width ||
291
+ clamped.size.height !== currentSize.height) {
292
+ onChangeRef.current(clamped);
293
+ }
294
+ };
295
+ window.addEventListener('resize', handleViewportResize);
296
+ return () => window.removeEventListener('resize', handleViewportResize);
297
+ }, [minWidth, minHeight]);
97
298
  const ResizableCmp = Resizable;
98
299
  const handleMouseDown = (event) => {
99
300
  const node = targetRef.current;
@@ -102,7 +303,7 @@ export const WindowModal = (props) => {
102
303
  }
103
304
  stacking.bringInFront();
104
305
  };
105
- return createPortal(_jsx("div", { style: style, ref: targetRef, onMouseDown: handleMouseDown, children: _jsx(ResizableCmp, { minWidth: props.minWidth, minHeight: props.minHeight, onResizeStop: handleResizeStop, onResize: handleResize, bounds: "window", defaultSize: {
306
+ return createPortal(_jsx("div", { style: style, ref: targetRef, onMouseDown: handleMouseDown, children: _jsx(ResizableCmp, { minWidth: minWidth, minHeight: minHeight, onResizeStart: handleResizeStart, onResizeStop: handleResizeStop, onResize: handleResize, handleClasses: RESIZE_HANDLE_CLASSES, size: {
106
307
  width: props.size.width,
107
308
  height: props.size.height,
108
309
  }, children: props.children }) }), portalElement);
@@ -47,7 +47,7 @@ function ComboboxContent({ className, side = 'bottom', sideOffset = 6, align = '
47
47
  const resizableStyle = resizable
48
48
  ? { resize: 'both' }
49
49
  : undefined;
50
- return (_jsx(ComboboxPrimitive.Portal, { children: _jsx(ComboboxPrimitive.Positioner, { side: side, sideOffset: sideOffset, align: align, alignOffset: alignOffset, anchor: anchor, className: `twa:isolate ${POPUP_Z_INDEX}`, children: _jsx(ComboboxPrimitive.Popup, { ref: popupRef, "data-slot": "combobox-content", "data-chips": !!anchor, "data-resizable": resizable || undefined, style: { ...resizableStyle, ...style }, className: cn('twa:group/combobox-content twa:relative twa:origin-(--transform-origin) twa:overflow-hidden twa:rounded-input twa:bg-popover twa:text-popover-foreground twa:shadow-md twa:ring-1 twa:ring-foreground/10 twa:duration-100 twa:data-[side=bottom]:slide-in-from-top-2 twa:data-[side=inline-end]:slide-in-from-left-2 twa:data-[side=inline-start]:slide-in-from-right-2 twa:data-[side=left]:slide-in-from-right-2 twa:data-[side=right]:slide-in-from-left-2 twa:data-[side=top]:slide-in-from-bottom-2 twa:*:data-[slot=input-group]:m-1 twa:*:data-[slot=input-group]:mb-0 twa:*:data-[slot=input-group]:h-8 twa:*:data-[slot=input-group]:border-input/30 twa:*:data-[slot=input-group]:bg-input/30 twa:*:data-[slot=input-group]:shadow-none twa:data-open:animate-in twa:data-open:fade-in-0 twa:data-open:zoom-in-95 twa:data-closed:animate-out twa:data-closed:fade-out-0 twa:data-closed:zoom-out-95', 'twa:max-h-[min(var(--available-height),80vh)]', 'twa:max-w-[min(var(--available-width),80vw)]', 'twa:min-w-[min(calc(var(--anchor-width)+--spacing(7)),var(--available-width),80vw)]', resizable ? 'twa:flex twa:flex-col twa:min-h-24' : 'twa:w-(--anchor-width)', className), ...props }) }) }));
50
+ return (_jsx(ComboboxPrimitive.Portal, { children: _jsx(ComboboxPrimitive.Positioner, { side: side, sideOffset: sideOffset, align: align, alignOffset: alignOffset, anchor: anchor, className: `twa:isolate ${POPUP_Z_INDEX}`, children: _jsx(ComboboxPrimitive.Popup, { ref: popupRef, "data-slot": "combobox-content", "data-chips": !!anchor, "data-resizable": resizable || undefined, style: { ...resizableStyle, ...style }, className: cn('twa:group/combobox-content twa:relative twa:origin-(--transform-origin) twa:overflow-hidden twa:rounded-input twa:bg-popover twa:text-popover-foreground twa:shadow-md twa:ring-1 twa:ring-foreground/10 twa:duration-100 twa:data-[side=bottom]:slide-in-from-top-2 twa:data-[side=inline-end]:slide-in-from-left-2 twa:data-[side=inline-start]:slide-in-from-right-2 twa:data-[side=left]:slide-in-from-right-2 twa:data-[side=right]:slide-in-from-left-2 twa:data-[side=top]:slide-in-from-bottom-2 twa:*:data-[slot=input-group]:m-1 twa:*:data-[slot=input-group]:mb-0 twa:*:data-[slot=input-group]:h-8 twa:*:data-[slot=input-group]:border-input/30 twa:*:data-[slot=input-group]:bg-input/30 twa:*:data-[slot=input-group]:shadow-none twa:data-open:animate-in twa:data-open:fade-in-0 twa:data-open:zoom-in-95 twa:data-closed:animate-out twa:data-closed:fade-out-0 twa:data-closed:zoom-out-95', 'twa:max-h-[min(var(--available-height,80vh),80vh)]', 'twa:max-w-[min(var(--available-width,80vw),80vw)]', 'twa:min-w-[min(calc(var(--anchor-width)+--spacing(7)),var(--available-width),80vw)]', resizable ? 'twa:flex twa:flex-col twa:min-h-24' : 'twa:w-(--anchor-width)', className), ...props }) }) }));
51
51
  }
52
52
  const ComboboxList = React.forwardRef(function ComboboxList({ className, ...props }, ref) {
53
53
  return (_jsx(ComboboxPrimitive.List, { "data-slot": "combobox-list", className: cn('twa:no-scrollbar twa:max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] twa:scroll-py-1 twa:overflow-y-auto twa:overscroll-contain twa:p-1 twa:data-empty:p-0', RESIZABLE_LIST_CLASS, className), ref: ref, ...props }));
@@ -1,8 +1,10 @@
1
- export default function useDraggable({ onMove, onDrop, getBoundingRect, handleSelector, }: {
1
+ export default function useDraggable({ onMove, onDrop, getBoundingRect, handleSelector, edgeOffset, }: {
2
2
  onMove?: (event: MouseEvent) => void;
3
3
  onDrop?: (dx: number, dy: number) => void;
4
4
  getBoundingRect?: () => DOMRect;
5
5
  handleSelector?: string;
6
+ /** Minimum distance to keep from the constrain region edges while dragging. */
7
+ edgeOffset?: number;
6
8
  }): {
7
9
  handleRef: (newNode: HTMLElement) => void;
8
10
  targetRef: import("react").RefObject<HTMLElement>;
@@ -2,29 +2,34 @@ import { useRef, useCallback, useEffect } from 'react';
2
2
  function clamp(num, min, max) {
3
3
  return num <= min ? min : num >= max ? max : num;
4
4
  }
5
- export default function useDraggable({ onMove, onDrop, getBoundingRect = () => document.body.getBoundingClientRect(), handleSelector, }) {
5
+ const getTranslationBounds = (targetRect, boundingRect) => {
6
+ const left = boundingRect.x - targetRect.x;
7
+ const right = left + boundingRect.width - targetRect.width;
8
+ const top = boundingRect.y - targetRect.y;
9
+ const bottom = top + boundingRect.height - targetRect.height;
10
+ return { left, right, top, bottom };
11
+ };
12
+ const applyEdgeOffsetToBoundingRect = (boundingRect, edgeOffset) => {
13
+ if (!edgeOffset) {
14
+ return boundingRect;
15
+ }
16
+ return new DOMRect(boundingRect.x + edgeOffset, boundingRect.y + edgeOffset, boundingRect.width - 2 * edgeOffset, boundingRect.height - 2 * edgeOffset);
17
+ };
18
+ export default function useDraggable({ onMove, onDrop, getBoundingRect = () => document.body.getBoundingClientRect(), handleSelector, edgeOffset = 0, }) {
6
19
  const startRef = useRef(null);
7
20
  const handleRef = useRef(null);
8
21
  const targetRef = useRef(null);
9
- const handleRefCallback = useCallback((newNode) => {
10
- const oldNode = handleRef.current;
11
- if (oldNode) {
12
- oldNode.removeEventListener('mousedown', handleMouseDown);
13
- }
14
- if (newNode) {
15
- newNode.addEventListener('mousedown', handleMouseDown);
16
- boundInitialPosition();
17
- }
18
- handleRef.current = newNode;
19
- }, []);
20
- useEffect(() => {
21
- if (handleSelector) {
22
- setTimeout(() => {
23
- const node = targetRef?.current?.querySelector(handleSelector);
24
- node && handleRefCallback(node);
25
- }, 16);
26
- }
27
- }, [handleSelector]);
22
+ const onMoveRef = useRef(onMove);
23
+ const onDropRef = useRef(onDrop);
24
+ const getBoundingRectRef = useRef(getBoundingRect);
25
+ const edgeOffsetRef = useRef(edgeOffset);
26
+ onMoveRef.current = onMove;
27
+ onDropRef.current = onDrop;
28
+ getBoundingRectRef.current = getBoundingRect;
29
+ edgeOffsetRef.current = edgeOffset;
30
+ const getEffectiveBoundingRect = () => {
31
+ return applyEdgeOffsetToBoundingRect(getBoundingRectRef.current(), edgeOffsetRef.current);
32
+ };
28
33
  const applyTransform = (dx, dy) => {
29
34
  if (!targetRef.current)
30
35
  return;
@@ -39,12 +44,19 @@ export default function useDraggable({ onMove, onDrop, getBoundingRect = () => d
39
44
  dy: clamp(event.pageY - pageY, bounds.top, bounds.bottom),
40
45
  };
41
46
  };
42
- const getTranslationBounds = (targetRect, boundingRect) => {
43
- const left = boundingRect.x - targetRect.x;
44
- const right = left + boundingRect.width - targetRect.width;
45
- const top = boundingRect.y - targetRect.y;
46
- const bottom = top + boundingRect.height - targetRect.height;
47
- return { left, right, top, bottom };
47
+ const handleMouseUp = (event) => {
48
+ event.preventDefault();
49
+ document.removeEventListener('mouseup', handleMouseUp);
50
+ document.removeEventListener('mousemove', handleMouseMove);
51
+ const { dx, dy } = getTranslation(event);
52
+ applyTransform(0, 0);
53
+ onDropRef.current?.(dx, dy);
54
+ };
55
+ const handleMouseMove = (event) => {
56
+ event.preventDefault();
57
+ const { dx, dy } = getTranslation(event);
58
+ applyTransform(dx, dy);
59
+ onMoveRef.current?.(event);
48
60
  };
49
61
  const handleMouseDown = (event) => {
50
62
  if (!targetRef.current)
@@ -53,37 +65,48 @@ export default function useDraggable({ onMove, onDrop, getBoundingRect = () => d
53
65
  document.addEventListener('mouseup', handleMouseUp);
54
66
  document.addEventListener('mousemove', handleMouseMove);
55
67
  const targetRect = targetRef.current.getBoundingClientRect();
56
- const boundingRect = getBoundingRect();
68
+ const boundingRect = getEffectiveBoundingRect();
57
69
  startRef.current = {
58
70
  pageX: event.pageX,
59
71
  pageY: event.pageY,
60
72
  bounds: getTranslationBounds(targetRect, boundingRect),
61
73
  };
62
74
  };
63
- const handleMouseUp = (event) => {
64
- event.preventDefault();
65
- document.removeEventListener('mouseup', handleMouseUp);
66
- document.removeEventListener('mousemove', handleMouseMove);
67
- const { dx, dy } = getTranslation(event);
68
- applyTransform(0, 0);
69
- onDrop && onDrop(dx, dy);
70
- };
71
- const handleMouseMove = (event) => {
72
- event.preventDefault();
73
- const { dx, dy } = getTranslation(event);
74
- applyTransform(dx, dy);
75
- onMove && onMove(event);
76
- };
77
75
  const boundInitialPosition = () => {
78
76
  if (!targetRef.current)
79
77
  return;
80
78
  const targetRect = targetRef.current.getBoundingClientRect();
81
- const boundingRect = getBoundingRect();
79
+ const boundingRect = getEffectiveBoundingRect();
82
80
  const bounds = getTranslationBounds(targetRect, boundingRect);
83
81
  const dx = clamp(0, bounds.left, bounds.right);
84
82
  const dy = clamp(0, bounds.top, bounds.bottom);
85
- if (onDrop && (dx !== 0 || dy !== 0))
86
- onDrop(dx, dy);
83
+ if (onDropRef.current && (dx !== 0 || dy !== 0)) {
84
+ onDropRef.current(dx, dy);
85
+ }
87
86
  };
87
+ const handleMouseDownRef = useRef(handleMouseDown);
88
+ handleMouseDownRef.current = handleMouseDown;
89
+ const stableHandleMouseDown = useCallback((event) => {
90
+ handleMouseDownRef.current(event);
91
+ }, []);
92
+ const handleRefCallback = useCallback((newNode) => {
93
+ const oldNode = handleRef.current;
94
+ if (oldNode) {
95
+ oldNode.removeEventListener('mousedown', stableHandleMouseDown);
96
+ }
97
+ if (newNode) {
98
+ newNode.addEventListener('mousedown', stableHandleMouseDown);
99
+ boundInitialPosition();
100
+ }
101
+ handleRef.current = newNode;
102
+ }, [stableHandleMouseDown]);
103
+ useEffect(() => {
104
+ if (handleSelector) {
105
+ setTimeout(() => {
106
+ const node = targetRef?.current?.querySelector(handleSelector);
107
+ node && handleRefCallback(node);
108
+ }, 16);
109
+ }
110
+ }, [handleSelector, handleRefCallback]);
88
111
  return { handleRef: handleRefCallback, targetRef, applyTransform };
89
112
  }
package/src/env.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export default {
2
2
  NEXT_PUBLIC_INFINITE_TABLE_LICENSE_KEY: "StartDate=2021-06-29|EndDate=2030-01-01|Owner=Adaptable|Type=distribution|TS=1624971462479|C=137829811,1004007071,2756196225,1839832928,3994409405,636616862" || '',
3
- PUBLISH_TIMESTAMP: 1783674285163 || Date.now(),
4
- VERSION: "23.0.9" || '--current-version--',
3
+ PUBLISH_TIMESTAMP: 1784037600986 || Date.now(),
4
+ VERSION: "23.0.11" || '--current-version--',
5
5
  };