@astryxdesign/core 0.7.7-canary.f9ac85f → 0.7.7

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 (198) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/dist/AppShell/useCloseMobileDrawerOnActivate.d.ts +13 -0
  3. package/dist/AppShell/useCloseMobileDrawerOnActivate.d.ts.map +1 -0
  4. package/dist/AppShell/useCloseMobileDrawerOnActivate.js +45 -0
  5. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  6. package/dist/BottomSheet/BottomSheet.js +79 -9
  7. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  8. package/dist/BottomSheet/BottomSheetPanel.js +1 -1
  9. package/dist/BottomSheet/bottomSheetStack.d.ts.map +1 -1
  10. package/dist/BottomSheet/bottomSheetStack.js +5 -4
  11. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +2 -1
  12. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  13. package/dist/Breadcrumbs/BreadcrumbItem.js +7 -3
  14. package/dist/Button/Button.d.ts.map +1 -1
  15. package/dist/Button/Button.js +30 -2
  16. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  17. package/dist/CheckboxList/CheckboxList.js +7 -1
  18. package/dist/CheckboxList/CheckboxListContext.d.ts +6 -0
  19. package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
  20. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  21. package/dist/CheckboxList/CheckboxListItem.js +10 -2
  22. package/dist/DateInput/DateInput.d.ts.map +1 -1
  23. package/dist/DateInput/DateInput.js +5 -2
  24. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  25. package/dist/DateInput/MonthScroller.js +13 -27
  26. package/dist/DateInput/Wheel.d.ts.map +1 -1
  27. package/dist/DateInput/Wheel.js +22 -41
  28. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  29. package/dist/DateTimeInput/DateTimeInput.js +5 -2
  30. package/dist/Dialog/Dialog.d.ts.map +1 -1
  31. package/dist/Dialog/Dialog.js +4 -3
  32. package/dist/Drawer/Drawer.js +0 -1
  33. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
  34. package/dist/Drawer/useDrawerDialogPresence.js +5 -1
  35. package/dist/Field/PanelSearchInput.d.ts +9 -1
  36. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  37. package/dist/Field/PanelSearchInput.js +3 -1
  38. package/dist/InfoTip/InfoTip.d.ts +2 -2
  39. package/dist/InfoTip/InfoTip.js +4 -4
  40. package/dist/Layer/layerAnimations.stylex.d.ts +7 -8
  41. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  42. package/dist/Layer/layerAnimations.stylex.js +25 -20
  43. package/dist/Layer/usePressPin.d.ts +3 -5
  44. package/dist/Layer/usePressPin.d.ts.map +1 -1
  45. package/dist/Layer/usePressPin.js +4 -6
  46. package/dist/MobileNav/MobileNav.js +0 -1
  47. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  48. package/dist/MultiSelector/hooks.js +2 -4
  49. package/dist/Outline/Outline.d.ts +2 -4
  50. package/dist/Outline/Outline.d.ts.map +1 -1
  51. package/dist/Outline/Outline.js +4 -52
  52. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  53. package/dist/Outline/useOutlineFromMarkdown.js +1 -0
  54. package/dist/Outline/useScrollSpy.js +3 -3
  55. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  56. package/dist/PowerSearch/PowerSearch.js +134 -179
  57. package/dist/RadioList/RadioList.d.ts.map +1 -1
  58. package/dist/RadioList/RadioList.js +5 -0
  59. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  60. package/dist/Resizable/ResizeHandle.js +118 -260
  61. package/dist/Resizable/resizeGesture.d.ts +91 -0
  62. package/dist/Resizable/resizeGesture.d.ts.map +1 -0
  63. package/dist/Resizable/resizeGesture.js +251 -0
  64. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  65. package/dist/SideNav/SideNavItem.js +3 -9
  66. package/dist/Token/Token.d.ts +2 -2
  67. package/dist/Token/Token.d.ts.map +1 -1
  68. package/dist/Token/Token.js +12 -4
  69. package/dist/Token/TokenNamingContext.d.ts +14 -0
  70. package/dist/Token/TokenNamingContext.d.ts.map +1 -0
  71. package/dist/Token/TokenNamingContext.js +23 -0
  72. package/dist/Tokenizer/Tokenizer.d.ts +46 -59
  73. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  74. package/dist/Tokenizer/Tokenizer.js +35 -58
  75. package/dist/Tokenizer/TokenizerBottomSheet.d.ts +25 -28
  76. package/dist/Tokenizer/TokenizerBottomSheet.d.ts.map +1 -1
  77. package/dist/Tokenizer/TokenizerBottomSheet.js +157 -54
  78. package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts.map +1 -1
  79. package/dist/Tokenizer/TokenizerItemRemovableContext.js +5 -3
  80. package/dist/Tooltip/Tooltip.d.ts +25 -30
  81. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  82. package/dist/Tooltip/Tooltip.js +24 -5
  83. package/dist/Tooltip/index.d.ts +1 -1
  84. package/dist/Tooltip/index.d.ts.map +1 -1
  85. package/dist/Tooltip/useTooltip.d.ts +28 -29
  86. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  87. package/dist/Tooltip/useTooltip.js +20 -8
  88. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  89. package/dist/TopNav/TopNavItem.js +4 -7
  90. package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
  91. package/dist/TopNav/TopNavMegaMenuItem.js +4 -7
  92. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  93. package/dist/TopNav/TopNavMenu.js +4 -7
  94. package/dist/astryx.css +5 -5
  95. package/dist/gestures/GestureHost.d.ts +27 -1
  96. package/dist/gestures/GestureHost.d.ts.map +1 -1
  97. package/dist/gestures/GestureHost.js +90 -8
  98. package/dist/hooks/useHighlightedOptionScroll.d.ts +11 -1
  99. package/dist/hooks/useHighlightedOptionScroll.d.ts.map +1 -1
  100. package/dist/hooks/useHighlightedOptionScroll.js +103 -5
  101. package/dist/hooks/useMenuPress.d.ts.map +1 -1
  102. package/dist/hooks/useMenuPress.js +4 -2
  103. package/dist/i18n/generated-english/PowerSearch/PowerSearch.generated.d.ts.map +1 -1
  104. package/dist/i18n/generated-english/PowerSearch/PowerSearch.generated.js +1 -0
  105. package/dist/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.d.ts.map +1 -1
  106. package/dist/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.js +7 -1
  107. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  108. package/dist/i18n/generated-locales/en.generated.js +1 -0
  109. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  110. package/dist/i18n/generated-locales/pseudo.generated.js +1 -0
  111. package/dist/internal/SheetConfirmation.d.ts +11 -0
  112. package/dist/internal/SheetConfirmation.d.ts.map +1 -0
  113. package/dist/internal/SheetConfirmation.js +92 -0
  114. package/dist/internal/sheetDraft.d.ts +35 -0
  115. package/dist/internal/sheetDraft.d.ts.map +1 -0
  116. package/dist/internal/sheetDraft.js +65 -0
  117. package/dist/internal/touch-sheet.d.ts +21 -0
  118. package/dist/internal/touch-sheet.d.ts.map +1 -0
  119. package/dist/internal/touch-sheet.js +19 -0
  120. package/dist/internal/useDismissSearchOnTouchDrag.d.ts +24 -0
  121. package/dist/internal/useDismissSearchOnTouchDrag.d.ts.map +1 -0
  122. package/dist/internal/useDismissSearchOnTouchDrag.js +56 -0
  123. package/dist/motion/reducedMotion.d.ts +24 -0
  124. package/dist/motion/reducedMotion.d.ts.map +1 -1
  125. package/dist/motion/reducedMotion.js +78 -1
  126. package/dist/utils/clickGuard.d.ts +8 -1
  127. package/dist/utils/clickGuard.d.ts.map +1 -1
  128. package/dist/utils/clickGuard.js +24 -1
  129. package/locales/en.json +4 -0
  130. package/locales/pseudo.json +3 -0
  131. package/package.json +9 -9
  132. package/src/AppShell/useCloseMobileDrawerOnActivate.ts +52 -0
  133. package/src/BottomSheet/BottomSheet.tsx +95 -10
  134. package/src/BottomSheet/BottomSheetPanel.tsx +7 -2
  135. package/src/BottomSheet/bottomSheetStack.ts +8 -4
  136. package/src/Breadcrumbs/BreadcrumbItem.tsx +13 -3
  137. package/src/Button/Button.tsx +37 -1
  138. package/src/CheckboxList/CheckboxList.doc.mjs +4 -2
  139. package/src/CheckboxList/CheckboxList.tsx +6 -0
  140. package/src/CheckboxList/CheckboxListContext.tsx +6 -0
  141. package/src/CheckboxList/CheckboxListItem.tsx +11 -2
  142. package/src/DateInput/DateInput.tsx +8 -2
  143. package/src/DateInput/MonthScroller.tsx +18 -22
  144. package/src/DateInput/Wheel.tsx +30 -32
  145. package/src/DateTimeInput/DateTimeInput.tsx +8 -2
  146. package/src/Dialog/Dialog.tsx +19 -4
  147. package/src/Drawer/Drawer.tsx +1 -1
  148. package/src/Drawer/useDrawerDialogPresence.ts +5 -1
  149. package/src/Field/PanelSearchInput.tsx +11 -0
  150. package/src/InfoTip/InfoTip.doc.mjs +3 -3
  151. package/src/InfoTip/InfoTip.tsx +5 -5
  152. package/src/Layer/layerAnimations.stylex.ts +24 -16
  153. package/src/Layer/usePressPin.ts +5 -11
  154. package/src/MobileNav/MobileNav.tsx +1 -1
  155. package/src/MultiSelector/hooks.ts +2 -4
  156. package/src/Outline/Outline.doc.mjs +177 -51
  157. package/src/Outline/Outline.tsx +5 -57
  158. package/src/Outline/useOutlineFromMarkdown.doc.mjs +157 -0
  159. package/src/Outline/useOutlineFromMarkdown.ts +1 -0
  160. package/src/Outline/useScrollSpy.ts +3 -3
  161. package/src/PowerSearch/PowerSearch.doc.mjs +3 -3
  162. package/src/PowerSearch/PowerSearch.tsx +196 -183
  163. package/src/RadioList/RadioList.doc.mjs +4 -2
  164. package/src/RadioList/RadioList.tsx +4 -0
  165. package/src/Resizable/ResizeHandle.tsx +133 -328
  166. package/src/Resizable/resizeGesture.ts +368 -0
  167. package/src/SideNav/SideNavItem.tsx +3 -7
  168. package/src/Token/Token.tsx +13 -4
  169. package/src/Token/TokenNamingContext.ts +28 -0
  170. package/src/Tokenizer/Tokenizer.doc.mjs +16 -0
  171. package/src/Tokenizer/Tokenizer.tsx +81 -86
  172. package/src/Tokenizer/TokenizerBottomSheet.tsx +219 -93
  173. package/src/Tokenizer/TokenizerItemRemovableContext.ts +5 -3
  174. package/src/Tooltip/Tooltip.doc.mjs +123 -54
  175. package/src/Tooltip/Tooltip.tsx +36 -39
  176. package/src/Tooltip/index.ts +0 -1
  177. package/src/Tooltip/useTooltip.doc.mjs +147 -33
  178. package/src/Tooltip/useTooltip.tsx +39 -32
  179. package/src/TopNav/TopNavItem.tsx +6 -5
  180. package/src/TopNav/TopNavMegaMenuItem.tsx +6 -5
  181. package/src/TopNav/TopNavMenu.tsx +6 -5
  182. package/src/gestures/GestureHost.ts +105 -10
  183. package/src/hooks/useHighlightedOptionScroll.ts +113 -9
  184. package/src/hooks/useMenuPress.ts +8 -2
  185. package/src/i18n/generated-english/PowerSearch/PowerSearch.generated.ts +1 -0
  186. package/src/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.ts +7 -1
  187. package/src/i18n/generated-locales/en.generated.ts +1 -0
  188. package/src/i18n/generated-locales/pseudo.generated.ts +1 -0
  189. package/src/internal/SheetConfirmation.tsx +101 -0
  190. package/src/internal/sheetDraft.ts +80 -0
  191. package/src/internal/touch-sheet.ts +21 -0
  192. package/src/internal/useDismissSearchOnTouchDrag.ts +69 -0
  193. package/src/motion/reducedMotion.ts +79 -1
  194. package/src/utils/clickGuard.ts +37 -1
  195. package/dist/internal/tokenizer-presentation.d.ts +0 -21
  196. package/dist/internal/tokenizer-presentation.d.ts.map +0 -1
  197. package/dist/internal/tokenizer-presentation.js +0 -21
  198. package/src/internal/tokenizer-presentation.ts +0 -25
@@ -22,31 +22,27 @@
22
22
  * (a value below the minimum is invalid per WCAG 4.1.2) and a localized
23
23
  * "Collapsed" aria-valuetext announces the real state.
24
24
  *
25
- * Dragging takes pointer capture on the grab zone, so the rest of the gesture
26
- * is delivered to that element instead of being hit-tested against whatever
27
- * the cursor happens to be over. A resizable region that contains an embedded
28
- * frame otherwise loses the drag the moment the cursor crosses the frame: the
29
- * events go to the guest document, and a listener on the host window never
30
- * hears them again. Captured events are delivered to the capturing element,
31
- * so the move/up/cancel handlers live on the grab zone (as in Slider), not on
32
- * window.
25
+ * The handle's input (the pointer drag, the keys, the double-click) is
26
+ * resizeGesture.ts. ResizeHandle forwards its React events to it, over the
27
+ * region its `resizable` prop describes. A drag takes pointer capture on the grab zone.
28
+ * The gesture's move, up and cancel events then go there, as in Slider, even
29
+ * over an embedded frame.
33
30
  */
34
- import { useCallback, useEffect, useRef, useState } from 'react';
31
+ import { useEffect, useRef, useState } from 'react';
35
32
  import * as stylex from '@stylexjs/stylex';
36
- import { devWarn } from "../utils/devWarning.js";
37
33
  import "../theme/tokens.stylex.js";
38
34
  import { colorVars, durationVars, easeVars, radiusVars, spacingVars } from "../theme/tokens.stylex.js";
39
35
  import { focusOutlineProps } from "../utils/focusOutline.stylex.js";
40
36
  import { mergeProps, rtlStyles } from "../utils/index.js";
41
37
  import { getCollapseControls, getEffectiveMaxPx } from "./useResizable.js";
38
+ import { createResizeGesture } from "./resizeGesture.js";
42
39
  import { themeProps } from "../utils/themeProps.js";
40
+ import { useDevWarning } from "../hooks/useDevWarning.js";
43
41
  import { useMergedRefs } from "../hooks/useMergedRefs.js";
44
42
  import { registerEnglishFallback, useComponentTranslator } from "../i18n/useComponentTranslator.js";
45
43
  import english from "../i18n/generated-english/Resizable/ResizeHandle.generated.js";
46
44
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
47
45
  registerEnglishFallback(english);
48
- const KEYBOARD_STEP = 10;
49
- const KEYBOARD_LARGE_STEP = 50;
50
46
  function resolveEffectiveSide(pillPlacement, isReversed, isCollapsed) {
51
47
  if (pillPlacement !== 'auto') {
52
48
  return pillPlacement;
@@ -79,20 +75,6 @@ function hitAreaBiasDir(effectiveSide) {
79
75
  }
80
76
  return effectiveSide === 'start' ? -1 : 1;
81
77
  }
82
-
83
- /**
84
- * Drag feedback lives on `<body>`, not on the handle: the pointer spends the
85
- * drag off the handle, over whatever the panels contain, and the resize cursor
86
- * has to hold there. Module scope so the writes stay outside the component.
87
- */
88
- function applyBodyDragStyles(isHorizontal) {
89
- document.body.style.cursor = isHorizontal ? 'col-resize' : 'row-resize';
90
- document.body.style.userSelect = 'none';
91
- }
92
- function clearBodyDragStyles() {
93
- document.body.style.cursor = '';
94
- document.body.style.userSelect = '';
95
- }
96
78
  const styles = {
97
79
  handle: {
98
80
  kVAEAm: "x1n2onr6",
@@ -266,45 +248,6 @@ const dynamicStyles = {
266
248
  "--x-transform": `translate(-50%, calc(${dir} * (100% + ${spacingVars['--spacing-1']})))` != null ? `translate(-50%, calc(${dir} * (100% + ${spacingVars['--spacing-1']})))` : undefined
267
249
  }]
268
250
  };
269
- /**
270
- * The snap point `count` points away from the region's current size in
271
- * `direction` (+1 grows, -1 shrinks). A point beyond `minSize` or `maxSize`
272
- * counts as that bound; the nearest end point serves when fewer remain, and
273
- * the current size when it is already there.
274
- */
275
- function adjacentSnapPoint(resizable, direction, count) {
276
- const points = [...new Set(resizable._snaps.map(p => Math.min(resizable._maxSizePx, Math.max(resizable._minSizePx, p))))].sort((a, b) => a - b);
277
- const size = resizable._size;
278
- const ahead = direction > 0 ? points.filter(p => p > size) : points.filter(p => p < size).reverse();
279
- if (ahead.length === 0) {
280
- return size;
281
- }
282
- return ahead[Math.min(count, ahead.length) - 1];
283
- }
284
-
285
- /**
286
- * Collapse an open pane, or restore a collapsed one to the size it had before
287
- * it collapsed (APG window splitter: Enter "restores the splitter to its
288
- * previous position"). The hook's own collapse/expand do exactly that; a
289
- * hand-built `ResizableProps` (no registered controls) falls back to a resize
290
- * gesture to the minimum, which also cannot reopen a pane whose minimum sits below its
291
- * collapse threshold.
292
- */
293
- function toggleCollapse(resizable) {
294
- const controls = getCollapseControls(resizable);
295
- if (controls) {
296
- if (resizable._isCollapsed) {
297
- controls.expand();
298
- } else {
299
- controls.collapse();
300
- }
301
- return;
302
- }
303
- resizable._onResizeStart();
304
- resizable._onResizeMove(resizable._isCollapsed ? resizable._minSizePx : -resizable._size);
305
- resizable._onResizeEnd();
306
- }
307
-
308
251
  /**
309
252
  * Draggable resize handle placed between resizable panels. Renders as a thin
310
253
  * divider line with a wider invisible hit area and optional pill grip indicator.
@@ -340,220 +283,125 @@ export function ResizeHandle({
340
283
  const t = useComponentTranslator();
341
284
  const label = labelFromProps ?? t('@astryx.resizable.handle.label');
342
285
  const handleRef = useRef(null);
343
- // The pointer that owns the in-flight drag, plus the values its deltas are
344
- // measured against. A ref so moves don't re-render, and so unmount can tell
345
- // a live drag from a finished one.
346
- const dragRef = useRef(null);
347
- const [isDragging, setIsDragging] = useState(false);
348
286
  const [isHovered, setIsHovered] = useState(false);
349
287
  const [isFocused, setIsFocused] = useState(false);
350
288
  const isHorizontal = direction === 'horizontal';
351
289
  const isOverlay = positionMode === 'overlay';
352
- const sign = isReversed ? -1 : 1;
353
- const effectiveSide = resolveEffectiveSide(pillPlacement, isReversed, resizable?._isCollapsed ?? false);
290
+
291
+ // --- The region a `resizable` prop describes ---
292
+
293
+ // The gesture reads the latest props at each input. A drag sees the region
294
+ // as it is now, not as it was at the press.
295
+ const resizableRef = useRef(resizable);
296
+ resizableRef.current = resizable;
297
+ // The region a drag in flight started on: told the gesture is over even
298
+ // after the prop has gone.
299
+ const dragRegionRef = useRef(undefined);
300
+ const configRef = useRef({
301
+ direction,
302
+ isReversed,
303
+ isDisabled
304
+ });
305
+ configRef.current = {
306
+ direction,
307
+ isReversed,
308
+ isDisabled
309
+ };
310
+ const [isDragging, setIsDragging] = useState(false);
311
+ const [regionGesture] = useState(() => {
312
+ const model = {
313
+ state() {
314
+ const region = resizableRef.current;
315
+ return {
316
+ size: region?._size ?? 0,
317
+ isCollapsed: region?._isCollapsed ?? false,
318
+ minPx: region?._minSizePx ?? 0,
319
+ maxPx: region?._maxSizePx ?? Infinity,
320
+ snaps: region?._snaps ?? [],
321
+ collapsedSize: region?._collapsedSize ?? 0,
322
+ collapsible: region?._collapsible ?? false
323
+ };
324
+ },
325
+ start: () => {
326
+ dragRegionRef.current = resizableRef.current;
327
+ resizableRef.current?._onResizeStart();
328
+ },
329
+ move: delta => resizableRef.current?._onResizeMove(delta),
330
+ end: () => {
331
+ const region = resizableRef.current ?? dragRegionRef.current;
332
+ dragRegionRef.current = undefined;
333
+ region?._onResizeEnd();
334
+ },
335
+ // Not a resize end, but the gesture is over. The region froze its state
336
+ // for the drag, so it must hear that the drag ended.
337
+ cancel: () => {
338
+ const region = resizableRef.current ?? dragRegionRef.current;
339
+ dragRegionRef.current = undefined;
340
+ region?._onResizeCancel?.();
341
+ },
342
+ // The hook's own collapse and expand restore the size before the
343
+ // collapse; a hand-built `ResizableProps` has none.
344
+ get collapse() {
345
+ const region = resizableRef.current;
346
+ return region ? getCollapseControls(region)?.collapse : undefined;
347
+ },
348
+ get expand() {
349
+ const region = resizableRef.current;
350
+ return region ? getCollapseControls(region)?.expand : undefined;
351
+ }
352
+ };
353
+ return createResizeGesture(() => handleRef.current, model, () => ({
354
+ ...configRef.current,
355
+ isDisabled: configRef.current.isDisabled || resizableRef.current == null
356
+ }), setIsDragging);
357
+ });
358
+ const gesture = resizable != null ? regionGesture : null;
359
+ const isCollapsed = resizable?._isCollapsed ?? false;
360
+ const effectiveSide = resolveEffectiveSide(pillPlacement, isReversed, isCollapsed);
354
361
  // Physical offset direction shared by the pill and the grab zone (null =
355
362
  // centered / no bias). Keeps the two positioned identically in LTR and RTL.
356
363
  const hitBiasDir = hitAreaBiasDir(effectiveSide);
357
- const getRTLMultiplier = useCallback(() => {
358
- const el = handleRef.current;
359
- if (!el) {
360
- return 1;
361
- }
362
- return getComputedStyle(el).direction === 'rtl' ? -1 : 1;
363
- }, []);
364
364
  const isInteracting = isHovered || isFocused;
365
365
 
366
- // A handle whose axis disagrees with its hook drags along one axis while the
367
- // hook measures percentage bounds on the other. `ResizableProps` is exported,
368
- // so `_direction` is optional for old hand-built objects; absent means there
369
- // is no hook axis to compare, not that the hook is horizontal.
370
- const didWarnAxisRef = useRef(false);
371
- useEffect(() => {
372
- const hookDirection = resizable?._direction;
373
- if (hookDirection != null && hookDirection !== direction && !didWarnAxisRef.current) {
374
- didWarnAxisRef.current = true;
375
- devWarn('ResizeHandle', `direction="${direction}" but its useResizable region is ` + `"${hookDirection}". They must match: the hook measures one axis ` + 'and the handle drags the other.');
376
- }
377
- }, [direction, resizable]);
378
-
379
- // --- Pointer drag ---
380
- const clearDrag = useCallback(() => {
381
- dragRef.current = null;
382
- setIsDragging(false);
383
- clearBodyDragStyles();
384
- }, []);
385
- const handlePointerDown = useCallback(e => {
386
- if (isDisabled || !resizable || dragRef.current) {
387
- return;
388
- }
389
- e.preventDefault();
390
- e.stopPropagation();
391
- // Take the pointer for the whole gesture. Without capture the browser
392
- // re-hit-tests every move, so a frame anywhere under the drag path
393
- // swallows the rest of it into the guest document.
394
- e.currentTarget.setPointerCapture?.(e.pointerId);
395
- dragRef.current = {
396
- pointerId: e.pointerId,
397
- startPos: isHorizontal ? e.clientX : e.clientY,
398
- rtl: isHorizontal ? getRTLMultiplier() : 1
399
- };
400
- setIsDragging(true);
401
- resizable._onResizeStart();
402
- applyBodyDragStyles(isHorizontal);
403
- }, [isDisabled, resizable, isHorizontal, getRTLMultiplier]);
404
- const handlePointerMove = useCallback(e => {
405
- const drag = dragRef.current;
406
- if (!drag || drag.pointerId !== e.pointerId || !resizable) {
407
- return;
408
- }
409
- const currentPos = isHorizontal ? e.clientX : e.clientY;
410
- resizable._onResizeMove((currentPos - drag.startPos) * drag.rtl * sign);
411
- }, [resizable, isHorizontal, sign]);
412
- const handlePointerUp = useCallback(e => {
413
- if (dragRef.current?.pointerId !== e.pointerId) {
414
- return;
415
- }
416
- clearDrag();
417
- resizable?._onResizeEnd();
418
- }, [clearDrag, resizable]);
366
+ // A handle whose axis disagrees with its region drags along one axis while
367
+ // the region measures percentage bounds on the other. `ResizableProps` is
368
+ // exported, so `_direction` is optional for old hand-built objects. Without
369
+ // it there is no axis to compare.
370
+ const regionDirection = resizable?._direction;
371
+ useDevWarning('ResizeHandle', `direction="${direction}" but its useResizable region is ` + `"${regionDirection}". They must match: the hook measures one axis ` + 'and the handle drags the other.', regionDirection != null && regionDirection !== direction);
419
372
 
420
- // Serves pointercancel and lostpointercapture alike: an interrupted drag
421
- // ends without signalling a resize end. lostpointercapture also fires on the
422
- // implicit release after every pointerup/pointercancel, by which point the
423
- // drag is already cleared and this is a no-op.
424
- const cancelDrag = useCallback(e => {
425
- if (dragRef.current?.pointerId !== e.pointerId) {
426
- return;
427
- }
428
- clearDrag();
429
- // Not a resize end, but still the end of the gesture: the region froze
430
- // state for the duration of the drag and has to be told it is over.
431
- resizable?._onResizeCancel?.();
432
- }, [clearDrag, resizable]);
433
-
434
- // --- Keyboard ---
435
- const handleKeyDown = useCallback(e => {
436
- if (isDisabled || !resizable) {
437
- return;
438
- }
439
- const step = e.shiftKey ? KEYBOARD_LARGE_STEP : KEYBOARD_STEP;
440
- const rtl = isHorizontal ? getRTLMultiplier() : 1;
373
+ // --- Cleanup on unmount, or when the `resizable` prop goes ---
441
374
 
442
- // The pane's grow direction on this key: ArrowRight/Down grow unless the
443
- // layout is reversed or, on the inline axis, right-to-left.
444
- const grows = key => (key === 'ArrowRight' || key === 'ArrowDown' ? 1 : key === 'ArrowLeft' || key === 'ArrowUp' ? -1 : 0) * (isHorizontal ? rtl : 1) * sign > 0;
445
- switch (e.key) {
446
- case 'ArrowRight':
447
- case 'ArrowDown':
448
- case 'ArrowLeft':
449
- case 'ArrowUp':
450
- {
451
- e.preventDefault();
452
- if (resizable._isCollapsed) {
453
- // A collapsed pane answers only the grow arrow: it opens one step
454
- // above its minimum, the value the separator announces while
455
- // collapsed, or at the collapse threshold when that is larger (APG
456
- // window splitter: the arrow moves the separator from where it is;
457
- // a bare step would stay below the threshold and leave it in
458
- // place). The shrink arrow is a no-op.
459
- if (grows(e.key)) {
460
- resizable._onResizeStart();
461
- // At least the collapse threshold, or the move would stay
462
- // collapsed when the minimum sits below it.
463
- resizable._onResizeMove(Math.max(resizable._minSizePx + step, resizable._collapsedSize));
464
- resizable._onResizeEnd();
465
- }
466
- break;
467
- }
468
- if (resizable._snaps.length > 0) {
469
- // With snap points the region can rest only on a point, so a
470
- // pixel step would snap straight back; the arrow moves to the
471
- // adjacent point in its direction instead (Shift, two points),
472
- // staying within the bounds.
473
- resizable._onResizeStart();
474
- resizable._onResizeMove(adjacentSnapPoint(resizable, grows(e.key) ? 1 : -1, e.shiftKey ? 2 : 1) - resizable._size);
475
- resizable._onResizeEnd();
476
- break;
477
- }
478
- const direction = e.key === 'ArrowRight' || e.key === 'ArrowDown' ? 1 : -1;
479
- resizable._onResizeStart();
480
- resizable._onResizeMove(direction * step * (isHorizontal ? rtl : 1) * sign);
481
- resizable._onResizeEnd();
482
- break;
483
- }
484
- case 'Home':
485
- {
486
- e.preventDefault();
487
- resizable._onResizeStart();
488
- resizable._onResizeMove(resizable._minSizePx - resizable._size);
489
- resizable._onResizeEnd();
490
- break;
491
- }
492
- case 'End':
493
- {
494
- e.preventDefault();
495
- // Only a configured maximum is the region's own limit; the reported
496
- // stand-in for an unbounded region is where the pane *could* reach,
497
- // not where End may safely move it (a recorded gap).
498
- if (resizable._maxSizePx !== Infinity) {
499
- resizable._onResizeStart();
500
- resizable._onResizeMove(resizable._maxSizePx - resizable._size);
501
- resizable._onResizeEnd();
502
- }
503
- break;
504
- }
505
- case 'Enter':
506
- {
507
- e.preventDefault();
508
- if (resizable._collapsible) {
509
- toggleCollapse(resizable);
510
- }
511
- break;
512
- }
513
- }
514
- }, [isDisabled, resizable, isHorizontal, getRTLMultiplier, sign]);
515
-
516
- // --- Double-click collapse ---
517
- const handleDoubleClick = useCallback(() => {
518
- if (isDisabled || !resizable || !resizable._collapsible) {
519
- return;
520
- }
521
- toggleCollapse(resizable);
522
- }, [isDisabled, resizable]);
523
-
524
- // --- Cleanup on unmount ---
525
- // Removing the grab zone implicitly releases its captured pointer and takes
526
- // its listeners with it, so an in-flight drag stops driving the region on
527
- // its own. The body cursor/user-select overrides are not on the element,
528
- // though, so they have to be released here or they stick after the handle
529
- // is gone — and the region, which outlives this handle, still has to hear
375
+ // Removing the grab zone releases its captured pointer and its listeners.
376
+ // A `resizable` prop that goes away takes the grab zone's handlers too. The
377
+ // body's cursor and user-select overrides are not on the element, so they
378
+ // are released here. The region outlives this handle and still has to hear
530
379
  // that its gesture is over.
531
- const cancelOnUnmountRef = useRef(undefined);
532
- cancelOnUnmountRef.current = resizable?._onResizeCancel;
380
+ const hasRegion = resizable != null;
533
381
  useEffect(() => {
382
+ if (!hasRegion) {
383
+ return;
384
+ }
534
385
  return () => {
535
- if (dragRef.current) {
536
- dragRef.current = null;
537
- clearBodyDragStyles();
538
- cancelOnUnmountRef.current?.();
539
- }
386
+ regionGesture.interrupt();
540
387
  };
541
- }, []);
388
+ }, [hasRegion, regionGesture]);
542
389
 
543
390
  // --- ARIA ---
544
391
  // When collapsed the panel's real size (0) sits below aria-valuemin, which
545
392
  // is invalid per WCAG 4.1.2. Clamp aria-valuenow to the minimum and announce
546
393
  // the true state via aria-valuetext instead; the valuetext is removed as
547
394
  // soon as the panel expands so the numeric value reads again.
548
- const isCollapsed = resizable?._isCollapsed ?? false;
549
- const ariaValueNow = resizable ? isCollapsed ? Math.max(resizable._size, resizable._minSizePx) : resizable._size : undefined;
550
- const ariaValueMin = resizable ? resizable._minSizePx : undefined;
395
+ const sizeNow = resizable?._size;
396
+ const minNow = resizable?._minSizePx;
397
+ const ariaValueNow = sizeNow == null || minNow == null ? undefined : isCollapsed ? Math.max(sizeNow, minNow) : sizeNow;
398
+ const ariaValueMin = minNow;
551
399
  // A separator with a value needs a maximum (WAI-ARIA, APG window splitter).
552
400
  // Unbounded regions report the basis they can fill; a hand-built props
553
401
  // object without it reports only a configured maximum.
554
402
  const effectiveMax = resizable ? getEffectiveMaxPx(resizable) ?? (resizable._maxSizePx !== Infinity ? resizable._maxSizePx : undefined) : undefined;
555
403
  const ariaValueMax = effectiveMax != null && Number.isFinite(effectiveMax) ? effectiveMax : undefined;
556
- const ariaValueText = resizable && isCollapsed ? t('@astryx.resizable.collapsed') : undefined;
404
+ const ariaValueText = resizable != null && isCollapsed ? t('@astryx.resizable.collapsed') : undefined;
557
405
  return /*#__PURE__*/_jsxs("div", {
558
406
  ref: useMergedRefs(ref, handleRef),
559
407
  role: "separator",
@@ -565,7 +413,11 @@ export function ResizeHandle({
565
413
  "aria-label": label,
566
414
  "aria-disabled": isDisabled || undefined,
567
415
  tabIndex: isDisabled ? -1 : 0,
568
- onDoubleClick: handleDoubleClick,
416
+ onDoubleClick: () => {
417
+ if (!isDisabled) {
418
+ gesture?.doubleClick();
419
+ }
420
+ },
569
421
  onFocus: () => setIsFocused(true),
570
422
  onBlur: () => setIsFocused(false),
571
423
  "data-resizing": isDragging || undefined,
@@ -578,7 +430,9 @@ export function ResizeHandle({
578
430
  // this accessibility-critical handler can't be clobbered by a passed prop.
579
431
  onKeyDown: e => {
580
432
  props.onKeyDown?.(e);
581
- handleKeyDown(e);
433
+ if (!isDisabled) {
434
+ gesture?.keyDown(e);
435
+ }
582
436
  },
583
437
  children: [/*#__PURE__*/_jsx("div", {
584
438
  ...stylex.props(styles.hitArea, isHorizontal ? styles.hitAreaHorizontal : styles.hitAreaVertical,
@@ -587,11 +441,15 @@ export function ResizeHandle({
587
441
  // it's offset to a panel side, reuse the pill's physical-offset
588
442
  // construction so the two stay aligned in LTR and RTL alike.
589
443
  hitBiasDir == null ? isHorizontal ? rtlStyles.centerInline('0px') : styles.hitAreaCenteredY : isHorizontal ? dynamicStyles.hitAreaOffsetX(hitBiasDir) : dynamicStyles.hitAreaOffsetY(hitBiasDir), isDisabled && styles.disabled),
590
- onPointerDown: handlePointerDown,
591
- onPointerMove: handlePointerMove,
592
- onPointerUp: handlePointerUp,
593
- onPointerCancel: cancelDrag,
594
- onLostPointerCapture: cancelDrag,
444
+ onPointerDown: e => {
445
+ if (!isDisabled) {
446
+ gesture?.pointerDown(e);
447
+ }
448
+ },
449
+ onPointerMove: e => gesture?.pointerMove(e),
450
+ onPointerUp: e => gesture?.pointerUp(e),
451
+ onPointerCancel: e => gesture?.pointerCancel(e),
452
+ onLostPointerCapture: e => gesture?.pointerCancel(e),
595
453
  onPointerEnter: () => setIsHovered(true),
596
454
  onPointerLeave: () => {
597
455
  if (!isDragging) {
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @file resizeGesture.ts
3
+ * @input A handle element, a resize model (its state, and a gesture's start,
4
+ * move, end and cancel), and the handle's axis, reversal and disabled state
5
+ * @output createResizeGesture: the window splitter's input in one place: a
6
+ * pointer drag that holds its pointer, the arrow keys, Home, End and Enter,
7
+ * and a double-click collapse; and the body's drag cursor
8
+ * @position Internal to the Resizable family. ResizeHandle forwards its React
9
+ * events to it. No React, and no DOM work on import
10
+ */
11
+ import type { ResizableDirection } from './resizableModel.js';
12
+ /** Pixels an arrow key moves the separator; Shift moves it further. */
13
+ export declare const KEYBOARD_STEP = 10;
14
+ export declare const KEYBOARD_LARGE_STEP = 50;
15
+ /** What a gesture reads from its region when an input arrives. */
16
+ export interface ResizeGestureState {
17
+ /** The region's size in px, 0 while collapsed. */
18
+ readonly size: number;
19
+ readonly isCollapsed: boolean;
20
+ readonly minPx: number;
21
+ /** The configured maximum, Infinity when the region has none. */
22
+ readonly maxPx: number;
23
+ readonly snaps: ReadonlyArray<number>;
24
+ readonly collapsedSize: number;
25
+ readonly collapsible: boolean;
26
+ }
27
+ /** The region a gesture resizes. */
28
+ export interface ResizeGestureModel {
29
+ state(): ResizeGestureState;
30
+ /** A gesture begins: the region holds its basis until it ends. */
31
+ start(): void;
32
+ /** The gesture has moved `delta` px from where it began. */
33
+ move(delta: number): void;
34
+ /** The gesture ends as completed. */
35
+ end(): void;
36
+ /** The gesture ends without completing. */
37
+ cancel(): void;
38
+ /**
39
+ * The region's own collapse and expand. They restore the size it had before
40
+ * it collapsed, as the APG window splitter's Enter does. Without them a
41
+ * toggle is a resize gesture to the minimum.
42
+ */
43
+ collapse?: () => void;
44
+ expand?: () => void;
45
+ }
46
+ /** The handle's configuration, read at each input. */
47
+ export interface ResizeGestureConfig {
48
+ readonly direction: ResizableDirection;
49
+ readonly isReversed: boolean;
50
+ readonly isDisabled: boolean;
51
+ }
52
+ /** The fields of a pointer event the gesture reads (native or React). */
53
+ export interface ResizeGesturePointerEvent {
54
+ readonly pointerId: number;
55
+ readonly clientX: number;
56
+ readonly clientY: number;
57
+ readonly currentTarget: EventTarget | null;
58
+ preventDefault(): void;
59
+ stopPropagation(): void;
60
+ }
61
+ /** The fields of a key event the gesture reads (native or React). */
62
+ export interface ResizeGestureKeyEvent {
63
+ readonly key: string;
64
+ readonly shiftKey: boolean;
65
+ preventDefault(): void;
66
+ }
67
+ export interface ResizeGesture {
68
+ pointerDown(event: ResizeGesturePointerEvent): void;
69
+ pointerMove(event: ResizeGesturePointerEvent): void;
70
+ pointerUp(event: ResizeGesturePointerEvent): void;
71
+ /** A `pointercancel` or a `lostpointercapture`. */
72
+ pointerCancel(event: ResizeGesturePointerEvent): void;
73
+ keyDown(event: ResizeGestureKeyEvent): void;
74
+ doubleClick(): void;
75
+ /** Whether a pointer drag is in flight. */
76
+ readonly isDragging: boolean;
77
+ /**
78
+ * Ends a drag in flight as cancelled, for a handle that goes away mid-drag.
79
+ * It releases the pointer and the body's drag cursor, and tells the region
80
+ * that the gesture is over.
81
+ */
82
+ interrupt(): void;
83
+ }
84
+ /**
85
+ * The window splitter's input for one handle. A drag takes pointer capture
86
+ * on the element the press landed on. The rest of the gesture then goes to
87
+ * that element. Without capture, the browser hit-tests each move, and an
88
+ * embedded frame under the pointer takes the drag.
89
+ */
90
+ export declare function createResizeGesture(getHandle: () => Element | null, model: ResizeGestureModel, getConfig: () => ResizeGestureConfig, onDraggingChange?: (isDragging: boolean) => void): ResizeGesture;
91
+ //# sourceMappingURL=resizeGesture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resizeGesture.d.ts","sourceRoot":"","sources":["../../src/Resizable/resizeGesture.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AAEzD,uEAAuE;AACvE,eAAO,MAAM,aAAa,KAAK,CAAC;AAChC,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,kEAAkE;AAClE,MAAM,WAAW,kBAAkB;IACjC,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAED,oCAAoC;AACpC,MAAM,WAAW,kBAAkB;IACjC,KAAK,IAAI,kBAAkB,CAAC;IAC5B,kEAAkE;IAClE,KAAK,IAAI,IAAI,CAAC;IACd,4DAA4D;IAC5D,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,qCAAqC;IACrC,GAAG,IAAI,IAAI,CAAC;IACZ,2CAA2C;IAC3C,MAAM,IAAI,IAAI,CAAC;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,sDAAsD;AACtD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED,yEAAyE;AACzE,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,cAAc,IAAI,IAAI,CAAC;IACvB,eAAe,IAAI,IAAI,CAAC;CACzB;AAED,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,cAAc,IAAI,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,KAAK,EAAE,yBAAyB,GAAG,IAAI,CAAC;IACpD,WAAW,CAAC,KAAK,EAAE,yBAAyB,GAAG,IAAI,CAAC;IACpD,SAAS,CAAC,KAAK,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAClD,mDAAmD;IACnD,aAAa,CAAC,KAAK,EAAE,yBAAyB,GAAG,IAAI,CAAC;IACtD,OAAO,CAAC,KAAK,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAC5C,WAAW,IAAI,IAAI,CAAC;IACpB,2CAA2C;IAC3C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B;;;;OAIG;IACH,SAAS,IAAI,IAAI,CAAC;CACnB;AAqED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,OAAO,GAAG,IAAI,EAC/B,KAAK,EAAE,kBAAkB,EACzB,SAAS,EAAE,MAAM,mBAAmB,EACpC,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,GAC/C,aAAa,CAmMf"}