@sprawlify/primitives 0.0.110 → 0.0.112

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 (221) hide show
  1. package/dist/{aria-hidden-BX4SWNE_.mjs → aria-hidden-C72KtklN.mjs} +1 -1
  2. package/dist/{aria-hidden-BAGrxNHt.cjs → aria-hidden-CIBHXlCy.cjs} +1 -1
  3. package/dist/aria-hidden.cjs +2 -2
  4. package/dist/aria-hidden.mjs +2 -2
  5. package/dist/{auto-resize-B1BDDnp1.cjs → auto-resize-BTTNXiml.cjs} +1 -1
  6. package/dist/{auto-resize-CN2vAzdj.mjs → auto-resize-CbNgN0OO.mjs} +1 -1
  7. package/dist/auto-resize.cjs +2 -2
  8. package/dist/auto-resize.mjs +2 -2
  9. package/dist/{core-BmIEKqtP.mjs → core-Ci9Yu6QI.mjs} +1 -1
  10. package/dist/{core-nBg9RC_y.cjs → core-DwdPztGA.cjs} +1 -1
  11. package/dist/core.cjs +2 -2
  12. package/dist/core.mjs +2 -2
  13. package/dist/{dismissable-DWfZGIA7.mjs → dismissable-B9k5K6f9.mjs} +2 -2
  14. package/dist/{dismissable-ChwcJMHk.cjs → dismissable-DxjbwsOf.cjs} +2 -2
  15. package/dist/dismissable.cjs +3 -3
  16. package/dist/dismissable.mjs +3 -3
  17. package/dist/{dom-query-s2ESLR2U.mjs → dom-query-BFuRs3l4.mjs} +1 -1
  18. package/dist/{dom-query-Bd63cRVF.cjs → dom-query-BUO7rGsg.cjs} +1 -1
  19. package/dist/dom-query.cjs +1 -1
  20. package/dist/dom-query.mjs +1 -1
  21. package/dist/{focus-trap-fucc6OU_.cjs → focus-trap-BewqTQFt.cjs} +1 -1
  22. package/dist/{focus-trap-DzBvBlTH.mjs → focus-trap-Do8IUXYh.mjs} +1 -1
  23. package/dist/focus-trap.cjs +2 -2
  24. package/dist/focus-trap.mjs +2 -2
  25. package/dist/{focus-visible-D5qBaAOn.mjs → focus-visible-BuLf8M9F.mjs} +10 -9
  26. package/dist/{focus-visible-75XTctXf.cjs → focus-visible-Co_9bW09.cjs} +15 -8
  27. package/dist/focus-visible.cjs +3 -2
  28. package/dist/focus-visible.d.cts +2 -1
  29. package/dist/focus-visible.d.mts +2 -1
  30. package/dist/focus-visible.mjs +3 -3
  31. package/dist/{i18n-utils-DB-lUfrr.cjs → i18n-utils-BRLqoCq8.cjs} +1 -1
  32. package/dist/{i18n-utils-oWZyUib_.mjs → i18n-utils-DZs1CPj8.mjs} +1 -1
  33. package/dist/i18n-utils.cjs +2 -2
  34. package/dist/i18n-utils.mjs +2 -2
  35. package/dist/{interact-outside-2qkUnl4N.mjs → interact-outside-Ba50N1a5.mjs} +1 -1
  36. package/dist/{interact-outside-Cx4J0EBf.cjs → interact-outside-Bg-QSXqp.cjs} +1 -1
  37. package/dist/interact-outside.cjs +2 -2
  38. package/dist/interact-outside.mjs +2 -2
  39. package/dist/machines/accordion/index.cjs +2 -2
  40. package/dist/machines/accordion/index.d.cts +1 -1
  41. package/dist/machines/accordion/index.d.mts +1 -1
  42. package/dist/machines/accordion/index.mjs +2 -2
  43. package/dist/machines/angle-slider/index.cjs +2 -2
  44. package/dist/machines/angle-slider/index.d.cts +1 -1
  45. package/dist/machines/angle-slider/index.d.mts +1 -1
  46. package/dist/machines/angle-slider/index.mjs +2 -2
  47. package/dist/machines/aspect-ratio/index.cjs +2 -2
  48. package/dist/machines/aspect-ratio/index.mjs +2 -2
  49. package/dist/machines/async-list/index.cjs +2 -2
  50. package/dist/machines/async-list/index.mjs +2 -2
  51. package/dist/machines/avatar/index.cjs +2 -2
  52. package/dist/machines/avatar/index.mjs +2 -2
  53. package/dist/machines/carousel/index.cjs +3 -3
  54. package/dist/machines/carousel/index.d.cts +1 -1
  55. package/dist/machines/carousel/index.d.mts +1 -1
  56. package/dist/machines/carousel/index.mjs +3 -3
  57. package/dist/machines/cascade-select/index.cjs +12 -7
  58. package/dist/machines/cascade-select/index.d.cts +2 -2
  59. package/dist/machines/cascade-select/index.d.mts +2 -2
  60. package/dist/machines/cascade-select/index.mjs +12 -7
  61. package/dist/machines/checkbox/index.cjs +3 -3
  62. package/dist/machines/checkbox/index.d.cts +1 -1
  63. package/dist/machines/checkbox/index.d.mts +1 -1
  64. package/dist/machines/checkbox/index.mjs +3 -3
  65. package/dist/machines/clipboard/index.cjs +2 -2
  66. package/dist/machines/clipboard/index.d.cts +1 -1
  67. package/dist/machines/clipboard/index.d.mts +1 -1
  68. package/dist/machines/clipboard/index.mjs +2 -2
  69. package/dist/machines/collapsible/index.cjs +2 -2
  70. package/dist/machines/collapsible/index.d.cts +1 -1
  71. package/dist/machines/collapsible/index.d.mts +1 -1
  72. package/dist/machines/collapsible/index.mjs +2 -2
  73. package/dist/machines/color-picker/index.cjs +5 -5
  74. package/dist/machines/color-picker/index.d.cts +1 -1
  75. package/dist/machines/color-picker/index.d.mts +1 -1
  76. package/dist/machines/color-picker/index.mjs +5 -5
  77. package/dist/machines/combobox/index.cjs +14 -8
  78. package/dist/machines/combobox/index.d.cts +1 -1
  79. package/dist/machines/combobox/index.d.mts +1 -1
  80. package/dist/machines/combobox/index.mjs +14 -8
  81. package/dist/machines/date-picker/index.cjs +5 -5
  82. package/dist/machines/date-picker/index.d.cts +1 -1
  83. package/dist/machines/date-picker/index.d.mts +1 -1
  84. package/dist/machines/date-picker/index.mjs +5 -5
  85. package/dist/machines/dialog/index.cjs +7 -7
  86. package/dist/machines/dialog/index.d.cts +1 -1
  87. package/dist/machines/dialog/index.d.mts +1 -1
  88. package/dist/machines/dialog/index.mjs +7 -7
  89. package/dist/machines/drawer/index.cjs +1574 -307
  90. package/dist/machines/drawer/index.d.cts +217 -35
  91. package/dist/machines/drawer/index.d.mts +217 -35
  92. package/dist/machines/drawer/index.mjs +1574 -309
  93. package/dist/machines/dropdown-menu/index.cjs +12 -7
  94. package/dist/machines/dropdown-menu/index.d.cts +1 -1
  95. package/dist/machines/dropdown-menu/index.d.mts +1 -1
  96. package/dist/machines/dropdown-menu/index.mjs +12 -7
  97. package/dist/machines/editable/index.cjs +3 -3
  98. package/dist/machines/editable/index.mjs +3 -3
  99. package/dist/machines/file-upload/index.cjs +3 -3
  100. package/dist/machines/file-upload/index.d.cts +1 -1
  101. package/dist/machines/file-upload/index.d.mts +1 -1
  102. package/dist/machines/file-upload/index.mjs +3 -3
  103. package/dist/machines/floating-panel/index.cjs +2 -2
  104. package/dist/machines/floating-panel/index.d.cts +1 -1
  105. package/dist/machines/floating-panel/index.d.mts +1 -1
  106. package/dist/machines/floating-panel/index.mjs +2 -2
  107. package/dist/machines/hover-card/index.cjs +5 -5
  108. package/dist/machines/hover-card/index.mjs +5 -5
  109. package/dist/machines/image-cropper/index.cjs +2 -2
  110. package/dist/machines/image-cropper/index.d.cts +1 -1
  111. package/dist/machines/image-cropper/index.d.mts +1 -1
  112. package/dist/machines/image-cropper/index.mjs +2 -2
  113. package/dist/machines/listbox/index.cjs +3 -3
  114. package/dist/machines/listbox/index.d.cts +1 -1
  115. package/dist/machines/listbox/index.d.mts +1 -1
  116. package/dist/machines/listbox/index.mjs +3 -3
  117. package/dist/machines/marquee/index.cjs +2 -2
  118. package/dist/machines/marquee/index.d.cts +3 -3
  119. package/dist/machines/marquee/index.d.mts +3 -3
  120. package/dist/machines/marquee/index.mjs +2 -2
  121. package/dist/machines/navigation-menu/index.cjs +4 -4
  122. package/dist/machines/navigation-menu/index.d.cts +1 -1
  123. package/dist/machines/navigation-menu/index.d.mts +1 -1
  124. package/dist/machines/navigation-menu/index.mjs +4 -4
  125. package/dist/machines/number-input/index.cjs +2 -2
  126. package/dist/machines/number-input/index.mjs +2 -2
  127. package/dist/machines/pagination/index.cjs +2 -2
  128. package/dist/machines/pagination/index.d.cts +1 -1
  129. package/dist/machines/pagination/index.d.mts +1 -1
  130. package/dist/machines/pagination/index.mjs +2 -2
  131. package/dist/machines/password-input/index.cjs +2 -2
  132. package/dist/machines/password-input/index.d.cts +1 -1
  133. package/dist/machines/password-input/index.d.mts +1 -1
  134. package/dist/machines/password-input/index.mjs +2 -2
  135. package/dist/machines/pin-input/index.cjs +2 -2
  136. package/dist/machines/pin-input/index.mjs +2 -2
  137. package/dist/machines/popover/index.cjs +8 -8
  138. package/dist/machines/popover/index.d.cts +1 -1
  139. package/dist/machines/popover/index.d.mts +1 -1
  140. package/dist/machines/popover/index.mjs +8 -8
  141. package/dist/machines/presence/index.cjs +2 -2
  142. package/dist/machines/presence/index.mjs +2 -2
  143. package/dist/machines/progress/index.cjs +2 -2
  144. package/dist/machines/progress/index.d.cts +1 -1
  145. package/dist/machines/progress/index.d.mts +1 -1
  146. package/dist/machines/progress/index.mjs +2 -2
  147. package/dist/machines/qr-code/index.cjs +2 -2
  148. package/dist/machines/qr-code/index.mjs +2 -2
  149. package/dist/machines/radio-group/index.cjs +3 -3
  150. package/dist/machines/radio-group/index.d.cts +1 -1
  151. package/dist/machines/radio-group/index.d.mts +1 -1
  152. package/dist/machines/radio-group/index.mjs +3 -3
  153. package/dist/machines/rating-group/index.cjs +2 -2
  154. package/dist/machines/rating-group/index.mjs +2 -2
  155. package/dist/machines/scroll-area/index.cjs +2 -2
  156. package/dist/machines/scroll-area/index.d.cts +1 -1
  157. package/dist/machines/scroll-area/index.d.mts +1 -1
  158. package/dist/machines/scroll-area/index.mjs +2 -2
  159. package/dist/machines/select/index.cjs +13 -8
  160. package/dist/machines/select/index.d.cts +1 -1
  161. package/dist/machines/select/index.d.mts +1 -1
  162. package/dist/machines/select/index.mjs +13 -8
  163. package/dist/machines/separator/index.cjs +2 -2
  164. package/dist/machines/separator/index.mjs +2 -2
  165. package/dist/machines/signature-pad/index.cjs +2 -2
  166. package/dist/machines/signature-pad/index.mjs +2 -2
  167. package/dist/machines/slider/index.cjs +2 -2
  168. package/dist/machines/slider/index.d.cts +1 -1
  169. package/dist/machines/slider/index.d.mts +1 -1
  170. package/dist/machines/slider/index.mjs +2 -2
  171. package/dist/machines/splitter/index.cjs +2 -2
  172. package/dist/machines/splitter/index.d.cts +1 -1
  173. package/dist/machines/splitter/index.d.mts +1 -1
  174. package/dist/machines/splitter/index.mjs +2 -2
  175. package/dist/machines/steps/index.cjs +2 -2
  176. package/dist/machines/steps/index.d.cts +1 -1
  177. package/dist/machines/steps/index.d.mts +1 -1
  178. package/dist/machines/steps/index.mjs +2 -2
  179. package/dist/machines/switch/index.cjs +3 -3
  180. package/dist/machines/switch/index.mjs +3 -3
  181. package/dist/machines/tabs/index.cjs +2 -2
  182. package/dist/machines/tabs/index.d.cts +1 -1
  183. package/dist/machines/tabs/index.d.mts +1 -1
  184. package/dist/machines/tabs/index.mjs +2 -2
  185. package/dist/machines/tags-input/index.cjs +4 -4
  186. package/dist/machines/tags-input/index.d.cts +1 -1
  187. package/dist/machines/tags-input/index.d.mts +1 -1
  188. package/dist/machines/tags-input/index.mjs +4 -4
  189. package/dist/machines/timer/index.cjs +2 -2
  190. package/dist/machines/timer/index.d.cts +1 -1
  191. package/dist/machines/timer/index.d.mts +1 -1
  192. package/dist/machines/timer/index.mjs +2 -2
  193. package/dist/machines/toast/index.cjs +4 -4
  194. package/dist/machines/toast/index.d.cts +3 -3
  195. package/dist/machines/toast/index.d.mts +3 -3
  196. package/dist/machines/toast/index.mjs +4 -4
  197. package/dist/machines/toggle/index.cjs +2 -2
  198. package/dist/machines/toggle/index.mjs +2 -2
  199. package/dist/machines/toggle-group/index.cjs +2 -2
  200. package/dist/machines/toggle-group/index.mjs +2 -2
  201. package/dist/machines/tooltip/index.cjs +9 -9
  202. package/dist/machines/tooltip/index.mjs +10 -10
  203. package/dist/machines/tour/index.cjs +6 -6
  204. package/dist/machines/tour/index.d.cts +1 -1
  205. package/dist/machines/tour/index.d.mts +1 -1
  206. package/dist/machines/tour/index.mjs +6 -6
  207. package/dist/machines/tree-view/index.cjs +2 -2
  208. package/dist/machines/tree-view/index.d.cts +1 -1
  209. package/dist/machines/tree-view/index.d.mts +1 -1
  210. package/dist/machines/tree-view/index.mjs +2 -2
  211. package/dist/{popper-BDy37WA0.mjs → popper-BlgbmdAn.mjs} +1 -1
  212. package/dist/{popper-Cx3KHzeT.cjs → popper-viCrafLC.cjs} +1 -1
  213. package/dist/popper.cjs +2 -2
  214. package/dist/popper.mjs +2 -2
  215. package/dist/{remove-scroll-D9FAOapi.cjs → remove-scroll-D4CMJmU2.cjs} +1 -1
  216. package/dist/{remove-scroll-HcSiTbd5.mjs → remove-scroll-D55GZoBb.mjs} +1 -1
  217. package/dist/{scroll-snap-DfSwN3As.mjs → scroll-snap-CdneVP31.mjs} +1 -1
  218. package/dist/{scroll-snap-Dp_2adEa.cjs → scroll-snap-vZ2q6dcN.cjs} +1 -1
  219. package/dist/scroll-snap.cjs +2 -2
  220. package/dist/scroll-snap.mjs +2 -2
  221. package/package.json +1 -1
@@ -1,47 +1,848 @@
1
1
  import { t as createAnatomy } from "../../create-anatomy-Dr0evdYy.mjs";
2
- import { St as isLeftClick, W as raf, ht as getEventTarget, m as resizeObserverBorderBox, pt as getEventPoint, ut as addDomEvent } from "../../dom-query-s2ESLR2U.mjs";
2
+ import { $t as contains, D as disableTextSelection, G as getInitialFocus, Qt as getComputedStyle$1, St as isLeftClick, W as raf, fn as isHTMLElement, ht as getEventTarget, m as resizeObserverBorderBox, pn as isInputElement, pt as getEventPoint, un as isEditableElement, ut as addDomEvent } from "../../dom-query-BFuRs3l4.mjs";
3
3
  import { t as _defineProperty } from "../../defineProperty-D5oW_HU_.mjs";
4
- import { t as ariaHidden } from "../../aria-hidden-BX4SWNE_.mjs";
5
- import { H as toPx, u as createSplitProps } from "../../utils-VVoZ_v29.mjs";
6
- import { a as createMachine } from "../../core-BmIEKqtP.mjs";
7
- import "../../interact-outside-2qkUnl4N.mjs";
8
- import { n as trackDismissableElement } from "../../dismissable-DWfZGIA7.mjs";
9
- import { t as trapFocus } from "../../focus-trap-DzBvBlTH.mjs";
4
+ import { t as ariaHidden } from "../../aria-hidden-C72KtklN.mjs";
5
+ import { H as toPx, g as clampValue, l as compact, u as createSplitProps } from "../../utils-VVoZ_v29.mjs";
6
+ import { a as createMachine, i as createGuards } from "../../core-Ci9Yu6QI.mjs";
7
+ import "../../interact-outside-Ba50N1a5.mjs";
8
+ import { n as trackDismissableElement } from "../../dismissable-B9k5K6f9.mjs";
9
+ import { t as trapFocus } from "../../focus-trap-Do8IUXYh.mjs";
10
10
  import { t as createProps } from "../../create-props-DFW8DUsC.mjs";
11
- import { t as preventBodyScroll } from "../../remove-scroll-HcSiTbd5.mjs";
11
+ import { t as preventBodyScroll } from "../../remove-scroll-D55GZoBb.mjs";
12
12
  //#region src/machines/drawer/drawer.anatomy.ts
13
- const anatomy = createAnatomy("drawer").parts("content", "title", "trigger", "backdrop", "grabber", "grabberIndicator", "closeTrigger");
13
+ const anatomy = createAnatomy("drawer").parts("positioner", "content", "title", "description", "trigger", "backdrop", "grabber", "grabberIndicator", "closeTrigger", "swipeArea");
14
14
  const parts = anatomy.build();
15
15
  //#endregion
16
16
  //#region src/machines/drawer/drawer.dom.ts
17
17
  const getContentId = (ctx) => ctx.ids?.content ?? `drawer:${ctx.id}:content`;
18
+ const getPositionerId = (ctx) => ctx.ids?.positioner ?? `drawer:${ctx.id}:positioner`;
18
19
  const getTitleId = (ctx) => ctx.ids?.title ?? `drawer:${ctx.id}:title`;
20
+ const getDescriptionId = (ctx) => ctx.ids?.description ?? `drawer:${ctx.id}:description`;
19
21
  const getTriggerId = (ctx) => ctx.ids?.trigger ?? `drawer:${ctx.id}:trigger`;
20
22
  const getBackdropId = (ctx) => ctx.ids?.backdrop ?? `drawer:${ctx.id}:backdrop`;
21
23
  const getGrabberId = (ctx) => ctx.ids?.grabber ?? `drawer:${ctx.id}:grabber`;
22
24
  const getGrabberIndicatorId = (ctx) => ctx.ids?.grabberIndicator ?? `drawer:${ctx.id}:grabber-indicator`;
23
25
  const getCloseTriggerId = (ctx) => ctx.ids?.closeTrigger ?? `drawer:${ctx.id}:close-trigger`;
26
+ const getSwipeAreaId = (ctx) => ctx.ids?.swipeArea ?? `drawer:${ctx.id}:swipe-area`;
24
27
  const getContentEl = (ctx) => ctx.getById(getContentId(ctx));
28
+ const getTitleEl = (ctx) => ctx.getById(getTitleId(ctx));
29
+ const getDescriptionEl = (ctx) => ctx.getById(getDescriptionId(ctx));
25
30
  const getTriggerEl = (ctx) => ctx.getById(getTriggerId(ctx));
31
+ const getBackdropEl = (ctx) => ctx.getById(getBackdropId(ctx));
26
32
  const getCloseTriggerEl = (ctx) => ctx.getById(getCloseTriggerId(ctx));
33
+ const getSwipeAreaEl = (ctx) => ctx.getById(getSwipeAreaId(ctx));
34
+ //#endregion
35
+ //#region src/machines/drawer/utils/snap-point.ts
36
+ function resolveSnapPointValue(snapPoint, viewportSize, rootFontSize) {
37
+ if (!Number.isFinite(viewportSize) || viewportSize <= 0) return null;
38
+ if (typeof snapPoint === "number") {
39
+ if (!Number.isFinite(snapPoint)) return null;
40
+ if (snapPoint <= 1) return clampValue(snapPoint, 0, 1) * viewportSize;
41
+ return snapPoint;
42
+ }
43
+ const trimmed = snapPoint.trim();
44
+ if (trimmed.endsWith("px")) {
45
+ const value = Number.parseFloat(trimmed);
46
+ return Number.isFinite(value) ? value : null;
47
+ }
48
+ if (trimmed.endsWith("rem")) {
49
+ const value = Number.parseFloat(trimmed);
50
+ return Number.isFinite(value) ? value * rootFontSize : null;
51
+ }
52
+ return null;
53
+ }
54
+ function resolveSnapPoint(snapPoint, options) {
55
+ const { contentSize, viewportSize, rootFontSize } = options;
56
+ const maxSize = Math.min(contentSize, viewportSize);
57
+ if (!Number.isFinite(maxSize) || maxSize <= 0) return null;
58
+ const resolvedSize = resolveSnapPointValue(snapPoint, viewportSize, rootFontSize);
59
+ if (resolvedSize === null || !Number.isFinite(resolvedSize)) return null;
60
+ const height = clampValue(resolvedSize, 0, maxSize);
61
+ return {
62
+ value: snapPoint,
63
+ height,
64
+ offset: Math.max(0, contentSize - height)
65
+ };
66
+ }
67
+ const HEIGHT_DEDUP_EPSILON_PX = 1;
68
+ function dedupeSnapPoints(points) {
69
+ if (points.length <= 1) return points;
70
+ const deduped = [];
71
+ const seenHeights = [];
72
+ for (let index = points.length - 1; index >= 0; index -= 1) {
73
+ const point = points[index];
74
+ if (seenHeights.some((height) => Math.abs(height - point.height) <= HEIGHT_DEDUP_EPSILON_PX)) continue;
75
+ seenHeights.push(point.height);
76
+ deduped.push(point);
77
+ }
78
+ deduped.reverse();
79
+ return deduped;
80
+ }
81
+ function findClosestSnapPoint(offset, snapPoints) {
82
+ return snapPoints.reduce((acc, curr) => {
83
+ const closestDiff = Math.abs(offset - acc.offset);
84
+ return Math.abs(offset - curr.offset) < closestDiff ? curr : acc;
85
+ });
86
+ }
87
+ //#endregion
88
+ //#region src/machines/drawer/utils/session.ts
89
+ const VELOCITY_WINDOW_MS = 100;
90
+ const MAX_RELEASE_VELOCITY_AGE_MS = 80;
91
+ const MIN_GESTURE_DURATION_MS = 50;
92
+ const MIN_VELOCITY_SAMPLES = 2;
93
+ const SAMPLE_BUFFER_COMPACT_THRESHOLD = 8;
94
+ const DEFERRED_DRAG_MIN_MAIN_AXIS_PX = 6;
95
+ const DEFERRED_DRAG_MAIN_OVER_CROSS_RATIO = 1.35;
96
+ function isVerticalSwipeDirection(direction) {
97
+ return direction === "down" || direction === "up";
98
+ }
99
+ function isNegativeSwipeDirection(direction) {
100
+ return direction === "up" || direction === "left";
101
+ }
102
+ var SwipeSession = class {
103
+ constructor() {
104
+ _defineProperty(this, "startPoint", null);
105
+ _defineProperty(this, "velocity", null);
106
+ _defineProperty(this, "samples", []);
107
+ _defineProperty(this, "sampleStartIndex", 0);
108
+ _defineProperty(this, "gestureStartAxis", null);
109
+ _defineProperty(this, "gestureStartTime", null);
110
+ _defineProperty(this, "gestureSign", 1);
111
+ _defineProperty(this, "pendingSwipe", null);
112
+ }
113
+ setStartPoint(point) {
114
+ this.startPoint = point;
115
+ }
116
+ clearStartPoint() {
117
+ this.startPoint = null;
118
+ }
119
+ getStartPoint() {
120
+ return this.startPoint;
121
+ }
122
+ getGestureAxis(direction) {
123
+ return direction === "left" || direction === "right" ? "x" : "y";
124
+ }
125
+ getGestureSign(direction) {
126
+ return isNegativeSwipeDirection(direction) ? -1 : 1;
127
+ }
128
+ getAxisValue(point, axis) {
129
+ return point[axis];
130
+ }
131
+ getMainAxisDisplacement(point, axis, sign) {
132
+ if (!this.startPoint) return 0;
133
+ return (this.getAxisValue(this.startPoint, axis) - this.getAxisValue(point, axis)) * sign;
134
+ }
135
+ getCrossAxisDisplacement(point, axis) {
136
+ if (!this.startPoint) return 0;
137
+ const crossAxis = axis === "x" ? "y" : "x";
138
+ const startAxis = this.getAxisValue(this.startPoint, crossAxis);
139
+ return this.getAxisValue(point, crossAxis) - startAxis;
140
+ }
141
+ track(point, axis, sign) {
142
+ const axisValue = this.getAxisValue(point, axis);
143
+ const now = performance.now();
144
+ if (this.gestureStartAxis === null) {
145
+ this.gestureStartAxis = axisValue;
146
+ this.gestureStartTime = now;
147
+ this.gestureSign = sign;
148
+ }
149
+ this.samples.push({
150
+ axis: axisValue,
151
+ time: now
152
+ });
153
+ const cutoff = now - VELOCITY_WINDOW_MS;
154
+ while (this.sampleStartIndex < this.samples.length && this.samples[this.sampleStartIndex].time < cutoff) this.sampleStartIndex += 1;
155
+ if (this.sampleStartIndex >= SAMPLE_BUFFER_COMPACT_THRESHOLD) {
156
+ this.samples = this.samples.slice(this.sampleStartIndex);
157
+ this.sampleStartIndex = 0;
158
+ }
159
+ if (this.samples.length - this.sampleStartIndex < MIN_VELOCITY_SAMPLES) {
160
+ this.velocity = 0;
161
+ return;
162
+ }
163
+ const oldest = this.samples[this.sampleStartIndex];
164
+ const newest = this.samples[this.samples.length - 1];
165
+ const dt = newest.time - oldest.time;
166
+ if (dt <= 0) {
167
+ this.velocity = 0;
168
+ return;
169
+ }
170
+ const velocity = (newest.axis - oldest.axis) * sign / dt * 1e3;
171
+ this.velocity = Number.isFinite(velocity) ? velocity : 0;
172
+ }
173
+ getReleaseVelocity() {
174
+ const now = performance.now();
175
+ if (this.samples.length - this.sampleStartIndex >= MIN_VELOCITY_SAMPLES) {
176
+ if (now - this.samples[this.samples.length - 1].time <= MAX_RELEASE_VELOCITY_AGE_MS) return this.velocity ?? 0;
177
+ }
178
+ if (this.gestureStartAxis !== null && this.gestureStartTime !== null) {
179
+ const lastSample = this.samples[this.samples.length - 1];
180
+ if (lastSample) {
181
+ const dt = Math.max(lastSample.time - this.gestureStartTime, MIN_GESTURE_DURATION_MS);
182
+ const velocity = (lastSample.axis - this.gestureStartAxis) * this.gestureSign / dt * 1e3;
183
+ return Number.isFinite(velocity) ? velocity : 0;
184
+ }
185
+ }
186
+ return this.velocity ?? 0;
187
+ }
188
+ clearVelocityTracking() {
189
+ this.samples = [];
190
+ this.sampleStartIndex = 0;
191
+ this.velocity = null;
192
+ this.gestureStartAxis = null;
193
+ this.gestureStartTime = null;
194
+ this.gestureSign = 1;
195
+ }
196
+ clear() {
197
+ this.cancelDeferredSwipe();
198
+ this.clearStartPoint();
199
+ this.clearVelocityTracking();
200
+ }
201
+ startDeferredSwipe(options) {
202
+ const { getWin, pointerId, startPoint, swipeDirection, onCommit, canCommit, onCancel } = options;
203
+ this.cancelDeferredSwipe();
204
+ const win = getWin();
205
+ const vertical = isVerticalSwipeDirection(swipeDirection);
206
+ const onMove = (event) => {
207
+ if (event.pointerId !== pointerId) return;
208
+ const dx = event.clientX - startPoint.x;
209
+ const dy = event.clientY - startPoint.y;
210
+ const mainDelta = vertical ? dy : dx;
211
+ const crossDelta = vertical ? dx : dy;
212
+ const absMain = Math.abs(mainDelta);
213
+ if (absMain >= DEFERRED_DRAG_MIN_MAIN_AXIS_PX && absMain >= Math.abs(crossDelta) * DEFERRED_DRAG_MAIN_OVER_CROSS_RATIO) {
214
+ if (!canCommit || canCommit()) onCommit(startPoint);
215
+ this.cancelDeferredSwipe();
216
+ }
217
+ };
218
+ const onEnd = (event) => {
219
+ if (event.pointerId !== pointerId) return;
220
+ onCancel?.();
221
+ this.cancelDeferredSwipe();
222
+ };
223
+ this.pendingSwipe = {
224
+ pointerId,
225
+ startPoint,
226
+ cleanups: [
227
+ addDomEvent(win, "pointermove", onMove, { capture: true }),
228
+ addDomEvent(win, "pointerup", onEnd, { capture: true }),
229
+ addDomEvent(win, "pointercancel", onEnd, { capture: true }),
230
+ addDomEvent(win, "lostpointercapture", onEnd, { capture: true })
231
+ ]
232
+ };
233
+ }
234
+ cancelDeferredSwipe() {
235
+ if (!this.pendingSwipe) return;
236
+ this.pendingSwipe.cleanups.forEach((cleanup) => cleanup());
237
+ this.pendingSwipe = null;
238
+ }
239
+ bind(options) {
240
+ const { getDoc, getSelectionTarget, swipeDirection, onStart, onMove, onEnd, onCancel, preventDefault, cancelOnInterrupt } = options;
241
+ const doc = getDoc();
242
+ let usingTouchEvents = false;
243
+ let restoreSelection;
244
+ const axis = this.getGestureAxis(swipeDirection);
245
+ const sign = this.getGestureSign(swipeDirection);
246
+ const trackPoint = (point) => {
247
+ this.track(point, axis, sign);
248
+ };
249
+ const startSelectionGuard = () => {
250
+ restoreSelection ?? (restoreSelection = disableTextSelection({
251
+ doc,
252
+ target: getSelectionTarget?.()
253
+ }));
254
+ };
255
+ const stopSelectionGuard = () => {
256
+ restoreSelection?.();
257
+ restoreSelection = void 0;
258
+ };
259
+ function onPointerMove(event) {
260
+ if (event.pointerType === "touch" && usingTouchEvents) return;
261
+ const point = getEventPoint(event);
262
+ const target = getEventTarget(event);
263
+ startSelectionGuard();
264
+ trackPoint(point);
265
+ onMove({
266
+ point,
267
+ target,
268
+ event,
269
+ pointerType: event.pointerType,
270
+ axis,
271
+ swipeDirection
272
+ });
273
+ }
274
+ function onPointerUp(event) {
275
+ if (event.pointerType === "touch" && usingTouchEvents) {
276
+ usingTouchEvents = false;
277
+ return;
278
+ }
279
+ stopSelectionGuard();
280
+ onEnd({
281
+ point: getEventPoint(event),
282
+ swipeDirection
283
+ });
284
+ }
285
+ function onPointerCancel(event) {
286
+ if (event.pointerType === "touch" && usingTouchEvents) {
287
+ usingTouchEvents = false;
288
+ return;
289
+ }
290
+ stopSelectionGuard();
291
+ onCancel();
292
+ }
293
+ function onTouchStartEvent(event) {
294
+ if (!event.touches[0]) return;
295
+ usingTouchEvents = true;
296
+ const point = getEventPoint(event);
297
+ const target = getEventTarget(event);
298
+ onStart?.({
299
+ point,
300
+ target,
301
+ event,
302
+ pointerType: "touch",
303
+ axis,
304
+ swipeDirection
305
+ });
306
+ }
307
+ function onTouchMoveEvent(event) {
308
+ if (!event.touches[0]) return;
309
+ usingTouchEvents = true;
310
+ const point = getEventPoint(event);
311
+ const details = {
312
+ point,
313
+ target: getEventTarget(event),
314
+ event,
315
+ pointerType: "touch",
316
+ axis,
317
+ swipeDirection
318
+ };
319
+ if (preventDefault?.(details) && event.cancelable) event.preventDefault();
320
+ startSelectionGuard();
321
+ trackPoint(point);
322
+ onMove(details);
323
+ }
324
+ function onTouchEnd(event) {
325
+ if (event.touches.length !== 0) return;
326
+ stopSelectionGuard();
327
+ onEnd({
328
+ point: getEventPoint(event),
329
+ swipeDirection
330
+ });
331
+ }
332
+ function onTouchCancel() {
333
+ stopSelectionGuard();
334
+ onCancel();
335
+ }
336
+ function onVisibilityChange() {
337
+ if (doc.visibilityState !== "hidden") return;
338
+ if (cancelOnInterrupt?.({
339
+ reason: "visibility-hidden",
340
+ event: doc,
341
+ target: null,
342
+ pointerType: null
343
+ }) === false) return;
344
+ stopSelectionGuard();
345
+ onCancel();
346
+ }
347
+ function onLostPointerCapture(event) {
348
+ if (event.pointerType === "touch") return;
349
+ const target = getEventTarget(event);
350
+ if (cancelOnInterrupt?.({
351
+ reason: "lost-pointer-capture",
352
+ event,
353
+ target,
354
+ pointerType: event.pointerType
355
+ }) === false) return;
356
+ onCancel();
357
+ }
358
+ const cleanups = [
359
+ addDomEvent(doc, "pointermove", onPointerMove),
360
+ addDomEvent(doc, "pointerup", onPointerUp),
361
+ addDomEvent(doc, "pointercancel", onPointerCancel),
362
+ addDomEvent(doc, "touchstart", onTouchStartEvent, {
363
+ capture: true,
364
+ passive: false
365
+ }),
366
+ addDomEvent(doc, "touchmove", onTouchMoveEvent, {
367
+ capture: true,
368
+ passive: false
369
+ }),
370
+ addDomEvent(doc, "touchend", onTouchEnd, { capture: true }),
371
+ addDomEvent(doc, "touchcancel", onTouchCancel, { capture: true }),
372
+ addDomEvent(doc, "visibilitychange", onVisibilityChange),
373
+ addDomEvent(doc, "lostpointercapture", onLostPointerCapture, true)
374
+ ];
375
+ return () => {
376
+ stopSelectionGuard();
377
+ cleanups.forEach((cleanup) => cleanup());
378
+ };
379
+ }
380
+ };
381
+ //#endregion
382
+ //#region src/machines/drawer/utils/drawer-session.ts
383
+ const RELEASE_DISPLACEMENT_TRUST_PX = 24;
384
+ const OPEN_SWIPE_HIDDEN_VISIBLE_RATIO = .22;
385
+ const OPEN_SWIPE_HIDDEN_VELOCITY_MULTIPLIER = 1.25;
386
+ const OPEN_SWIPE_REVEALED_VISIBLE_RATIO = .5;
387
+ const OPEN_SWIPE_REVEALED_OPPOSING_MAX_ABS_VELOCITY = 650;
388
+ const DRAG_START_THRESHOLD = .3;
389
+ const CROSS_AXIS_BIAS = .58;
390
+ const SCROLL_SLACK_GATE = .5;
391
+ const SCROLL_SLACK_EPSILON = 1;
392
+ const SEQUENTIAL_THRESHOLD = 24;
393
+ const SNAP_VELOCITY_THRESHOLD = 400;
394
+ const SNAP_VELOCITY_MULTIPLIER = .4;
395
+ const MAX_SNAP_VELOCITY = 4e3;
396
+ const SWIPE_STRENGTH_MAX_DURATION_MS = 360;
397
+ const SWIPE_STRENGTH_MIN_SCALAR = .1;
398
+ const SWIPE_STRENGTH_MAX_SCALAR = 1;
399
+ const SWIPE_AREA_OPEN_INTENT_MIN_PX = 5;
400
+ const NO_DRAG_DATA_ATTR = "data-no-drag";
401
+ const NO_DRAG_SELECTOR = `[${NO_DRAG_DATA_ATTR}]`;
402
+ var DrawerSwipeSession = class {
403
+ constructor(options) {
404
+ _defineProperty(this, "session", new SwipeSession());
405
+ _defineProperty(this, "dragOffset", null);
406
+ _defineProperty(this, "preventDragOnScroll", void 0);
407
+ this.preventDragOnScroll = options.preventDragOnScroll;
408
+ }
409
+ contentPointerDown(options) {
410
+ const { event, getDoc, getContentEl, getWin, swipeDirection, canCommit, onCommit } = options;
411
+ if (shouldIgnorePointerDownForDrag(event)) return;
412
+ if (isTextSelectionInDrawer(getDoc(), getContentEl())) return;
413
+ if (!canCommit()) return;
414
+ const point = getEventPoint(event);
415
+ if (!(event.pointerType === "mouse" || event.pointerType === "pen")) {
416
+ onCommit(point);
417
+ return;
418
+ }
419
+ this.session.startDeferredSwipe({
420
+ getWin,
421
+ pointerId: event.pointerId,
422
+ startPoint: point,
423
+ swipeDirection,
424
+ onCommit,
425
+ canCommit
426
+ });
427
+ }
428
+ grabberPointerDown(options) {
429
+ const { event, point, canCommit, onCommit } = options;
430
+ if (shouldIgnorePointerDownForDrag(event)) return;
431
+ this.session.cancelDeferredSwipe();
432
+ if (!canCommit()) return;
433
+ onCommit(point);
434
+ }
435
+ adjustReleaseVelocityAgainstDisplacement(velocity, displacementFromSnap) {
436
+ const displacementSign = Math.sign(displacementFromSnap);
437
+ const velocitySign = Math.sign(velocity);
438
+ if (displacementSign !== 0 && Math.abs(displacementFromSnap) >= RELEASE_DISPLACEMENT_TRUST_PX && velocitySign !== 0 && velocitySign !== displacementSign) return 0;
439
+ return velocity;
440
+ }
441
+ adjustReleaseVelocityForOpenSwipe(velocity, visibleRatio, swipeVelocityThreshold) {
442
+ if (visibleRatio < OPEN_SWIPE_HIDDEN_VISIBLE_RATIO && velocity < 0 && Math.abs(velocity) < swipeVelocityThreshold * OPEN_SWIPE_HIDDEN_VELOCITY_MULTIPLIER) return 0;
443
+ if (visibleRatio > OPEN_SWIPE_REVEALED_VISIBLE_RATIO && velocity > 0 && Math.abs(velocity) < OPEN_SWIPE_REVEALED_OPPOSING_MAX_ABS_VELOCITY) return 0;
444
+ return velocity;
445
+ }
446
+ beginSwipe(point) {
447
+ this.session.setStartPoint(point);
448
+ }
449
+ clearSwipeStart() {
450
+ this.session.clearStartPoint();
451
+ }
452
+ getSwipeStart() {
453
+ return this.session.getStartPoint();
454
+ }
455
+ getDragOffset() {
456
+ return this.dragOffset;
457
+ }
458
+ resetDragOffset() {
459
+ this.dragOffset = null;
460
+ }
461
+ resetVelocity() {
462
+ this.session.clearVelocityTracking();
463
+ }
464
+ reset() {
465
+ this.dragOffset = null;
466
+ this.session.clear();
467
+ }
468
+ setDragOffset(point, resolvedActiveSnapPointOffset, direction) {
469
+ if (!this.session.getStartPoint()) {
470
+ this.dragOffset = null;
471
+ return;
472
+ }
473
+ const axis = this.session.getGestureAxis(direction);
474
+ const sign = this.session.getGestureSign(direction);
475
+ let delta = this.session.getMainAxisDisplacement(point, axis, sign) - resolvedActiveSnapPointOffset;
476
+ if (delta > 0) delta = Math.sqrt(delta);
477
+ this.dragOffset = -delta;
478
+ }
479
+ setSwipeOpenOffset(point, contentSize, direction) {
480
+ if (!this.session.getStartPoint()) {
481
+ this.dragOffset = null;
482
+ return;
483
+ }
484
+ const axis = this.session.getGestureAxis(direction);
485
+ const sign = this.session.getGestureSign(direction);
486
+ const openDisplacement = this.session.getMainAxisDisplacement(point, axis, sign);
487
+ let dragOffset = contentSize - Math.max(0, openDisplacement);
488
+ if (dragOffset < 0) dragOffset = -Math.sqrt(Math.abs(dragOffset));
489
+ this.dragOffset = dragOffset;
490
+ }
491
+ canStartDrag(point, target, container, preventDragOnScroll, direction) {
492
+ if (!isHTMLElement(target)) return false;
493
+ if (isDragExemptElement(target)) return false;
494
+ if (!this.session.getStartPoint() || !container) return false;
495
+ if (!preventDragOnScroll) return true;
496
+ const axis = this.session.getGestureAxis(direction);
497
+ const sign = this.session.getGestureSign(direction);
498
+ const delta = this.session.getMainAxisDisplacement(point, axis, sign);
499
+ if (Math.abs(delta) < DRAG_START_THRESHOLD) return false;
500
+ if (Math.abs(this.session.getCrossAxisDisplacement(point, axis)) > Math.abs(delta) * CROSS_AXIS_BIAS) {
501
+ const crossScroll = getScrollInfo(target, container, isVerticalSwipeDirection(direction) ? "right" : "down");
502
+ if (crossScroll.availableForwardScroll > SCROLL_SLACK_GATE || crossScroll.availableBackwardScroll > SCROLL_SLACK_GATE) return false;
503
+ }
504
+ const { availableForwardScroll, availableBackwardScroll } = getScrollInfo(target, container, direction);
505
+ if (delta > 0 && availableForwardScroll > SCROLL_SLACK_GATE || delta < 0 && availableBackwardScroll > SCROLL_SLACK_GATE) return false;
506
+ return true;
507
+ }
508
+ resolveSnapPointOnRelease(snapPoints, snapPoint, snapToSequentialPoints, contentSize) {
509
+ const dragOffset = this.dragOffset;
510
+ if (dragOffset === null) return snapPoints[0]?.value ?? 1;
511
+ const releaseVelocity = this.session.getReleaseVelocity();
512
+ if (snapToSequentialPoints && snapPoint) {
513
+ const ordered = [...snapPoints].sort((a, b) => a.offset - b.offset);
514
+ let currentIndex = 0;
515
+ let closestDist = Math.abs(snapPoint.offset - ordered[0].offset);
516
+ for (let i = 1; i < ordered.length; i++) {
517
+ const dist = Math.abs(snapPoint.offset - ordered[i].offset);
518
+ if (dist < closestDist) {
519
+ closestDist = dist;
520
+ currentIndex = i;
521
+ }
522
+ }
523
+ const currentPoint = ordered[currentIndex];
524
+ const delta = dragOffset - currentPoint.offset;
525
+ const dragDirection = Math.sign(delta);
526
+ const velocityAdjusted = this.adjustReleaseVelocityAgainstDisplacement(releaseVelocity, delta);
527
+ const velocityDirection = Math.sign(velocityAdjusted);
528
+ let targetSnapPoint = currentPoint;
529
+ let effectiveTargetOffset = dragOffset;
530
+ if (dragDirection !== 0 && velocityDirection === dragDirection && Math.abs(velocityAdjusted) >= SNAP_VELOCITY_THRESHOLD) {
531
+ const adjacentIndex = Math.min(Math.max(currentIndex + dragDirection, 0), ordered.length - 1);
532
+ if (adjacentIndex !== currentIndex) {
533
+ targetSnapPoint = ordered[adjacentIndex];
534
+ effectiveTargetOffset = targetSnapPoint.offset;
535
+ } else if (dragDirection > 0) return null;
536
+ } else if (delta > SEQUENTIAL_THRESHOLD) {
537
+ const nextPoint = ordered[Math.min(currentIndex + 1, ordered.length - 1)];
538
+ if (nextPoint) {
539
+ targetSnapPoint = nextPoint;
540
+ effectiveTargetOffset = nextPoint.offset;
541
+ }
542
+ } else if (delta < -SEQUENTIAL_THRESHOLD) {
543
+ const prevPoint = ordered[Math.max(currentIndex - 1, 0)];
544
+ if (prevPoint) {
545
+ targetSnapPoint = prevPoint;
546
+ effectiveTargetOffset = prevPoint.offset;
547
+ }
548
+ }
549
+ if (Math.abs(effectiveTargetOffset - contentSize) < Math.abs(effectiveTargetOffset - targetSnapPoint.offset)) return null;
550
+ return targetSnapPoint.value;
551
+ }
552
+ const snapRestOffset = snapPoint?.offset ?? 0;
553
+ const velocity = this.adjustReleaseVelocityAgainstDisplacement(releaseVelocity, dragOffset - snapRestOffset);
554
+ let targetOffset = dragOffset;
555
+ if (Math.abs(velocity) >= SNAP_VELOCITY_THRESHOLD) {
556
+ const clamped = clampValue(velocity, -MAX_SNAP_VELOCITY, MAX_SNAP_VELOCITY);
557
+ targetOffset += clamped * SNAP_VELOCITY_MULTIPLIER;
558
+ targetOffset = Math.max(0, targetOffset);
559
+ }
560
+ return findClosestSnapPoint(targetOffset, snapPoints).value;
561
+ }
562
+ shouldOpenOnRelease(contentSize, swipeVelocityThreshold, openThreshold) {
563
+ const dragOffset = this.dragOffset;
564
+ if (dragOffset === null || contentSize === null) return false;
565
+ const visibleSize = contentSize - dragOffset;
566
+ const visibleRatio = visibleSize / contentSize;
567
+ const velocity = this.adjustReleaseVelocityForOpenSwipe(this.session.getReleaseVelocity(), visibleRatio, swipeVelocityThreshold);
568
+ return velocity < 0 && Math.abs(velocity) >= swipeVelocityThreshold || visibleSize >= contentSize * openThreshold;
569
+ }
570
+ shouldDismissOnRelease(contentSize, snapPoints, resolvedSnapOffset) {
571
+ const dragOffset = this.dragOffset;
572
+ if (dragOffset === null || contentSize === null) return false;
573
+ const velocity = this.adjustReleaseVelocityAgainstDisplacement(this.session.getReleaseVelocity(), dragOffset - resolvedSnapOffset);
574
+ if (contentSize - dragOffset <= 0) return true;
575
+ let targetOffset = dragOffset;
576
+ if (Math.abs(velocity) >= SNAP_VELOCITY_THRESHOLD) {
577
+ const clamped = clampValue(velocity, -MAX_SNAP_VELOCITY, MAX_SNAP_VELOCITY);
578
+ targetOffset += clamped * SNAP_VELOCITY_MULTIPLIER;
579
+ targetOffset = Math.max(0, targetOffset);
580
+ }
581
+ const closeDistance = Math.abs(targetOffset - contentSize);
582
+ const closest = findClosestSnapPoint(targetOffset, snapPoints);
583
+ return closeDistance < Math.abs(targetOffset - closest.offset);
584
+ }
585
+ getSwipeStrength(targetOffset, resolvedSnapOffset = null) {
586
+ const dragOffset = this.dragOffset;
587
+ if (dragOffset === null) return SWIPE_STRENGTH_MAX_SCALAR;
588
+ let velocity = this.session.getReleaseVelocity();
589
+ if (resolvedSnapOffset != null) velocity = this.adjustReleaseVelocityAgainstDisplacement(velocity, dragOffset - resolvedSnapOffset);
590
+ const distance = Math.abs(dragOffset - targetOffset);
591
+ const absVelocity = Math.abs(velocity);
592
+ if (absVelocity <= 0 || distance <= 0) return SWIPE_STRENGTH_MAX_SCALAR;
593
+ return SWIPE_STRENGTH_MIN_SCALAR + clampValue(distance / absVelocity * 1e3 / SWIPE_STRENGTH_MAX_DURATION_MS, 0, 1) * (SWIPE_STRENGTH_MAX_SCALAR - SWIPE_STRENGTH_MIN_SCALAR);
594
+ }
595
+ bindDragTracking(options) {
596
+ const { getDoc, getContentEl, getSwipeAreaEl, swipeDirection, onMove, onEnd, onCancel } = options;
597
+ const preventDragOnScroll = this.preventDragOnScroll;
598
+ const isVertical = isVerticalSwipeDirection(swipeDirection);
599
+ let lastAxis = 0;
600
+ return this.session.bind({
601
+ getDoc,
602
+ getSelectionTarget: getContentEl,
603
+ swipeDirection,
604
+ onMove,
605
+ onEnd,
606
+ onCancel,
607
+ cancelOnInterrupt: ({ reason, target }) => {
608
+ if (reason !== "lost-pointer-capture") return true;
609
+ return isWithinDrawerInteractionSurface(target, getContentEl(), getSwipeAreaEl());
610
+ },
611
+ onStart({ pointerType, point }) {
612
+ if (pointerType !== "touch") return;
613
+ lastAxis = isVertical ? point.y : point.x;
614
+ },
615
+ preventDefault({ event, pointerType, point, target }) {
616
+ if (pointerType !== "touch") return false;
617
+ const contentEl = getContentEl();
618
+ const resolvedTarget = target ?? event.target;
619
+ if (!preventDragOnScroll()) return false;
620
+ if (!contentEl || !resolvedTarget || isDragExemptElement(resolvedTarget)) return false;
621
+ const scrollParent = findClosestScrollableAncestorOnSwipeAxis(resolvedTarget, contentEl, swipeDirection);
622
+ if (scrollParent) {
623
+ const currentAxis = isVertical ? point.y : point.x;
624
+ const shouldPrevent = shouldPreventTouchScroll({
625
+ scrollParent,
626
+ swipeDirection,
627
+ lastMainAxis: lastAxis,
628
+ currentMainAxis: currentAxis
629
+ });
630
+ lastAxis = currentAxis;
631
+ return shouldPrevent;
632
+ }
633
+ lastAxis = isVertical ? point.y : point.x;
634
+ return false;
635
+ }
636
+ });
637
+ }
638
+ bindSwipeOpenTracking(options) {
639
+ const { getDoc, getContentEl, getSwipeAreaEl, swipeDirection, onMove, onEnd, onCancel } = options;
640
+ return this.session.bind({
641
+ getDoc,
642
+ getSelectionTarget: getSwipeAreaEl,
643
+ swipeDirection,
644
+ onMove({ point }) {
645
+ onMove({ point });
646
+ },
647
+ onEnd,
648
+ onCancel,
649
+ cancelOnInterrupt: ({ reason, target }) => {
650
+ if (reason !== "lost-pointer-capture") return true;
651
+ return isWithinDrawerInteractionSurface(target, getContentEl(), getSwipeAreaEl());
652
+ }
653
+ });
654
+ }
655
+ };
656
+ function isWithinDrawerInteractionSurface(target, contentEl, swipeAreaEl) {
657
+ if (!target) return false;
658
+ return contains(contentEl, target) || contains(swipeAreaEl, target);
659
+ }
660
+ const oppositeSwipeDirection = {
661
+ up: "down",
662
+ down: "up",
663
+ start: "end",
664
+ end: "start"
665
+ };
666
+ function resolveSwipeDirection(direction, dir) {
667
+ if (direction === "start") return dir === "rtl" ? "right" : "left";
668
+ if (direction === "end") return dir === "rtl" ? "left" : "right";
669
+ return direction;
670
+ }
671
+ function getSwipeDirectionSize(rect, direction) {
672
+ return isVerticalSwipeDirection(direction) ? rect.height : rect.width;
673
+ }
674
+ function resolveSwipeProgress(contentSize, dragOffset, snapPointOffset) {
675
+ if (!contentSize || contentSize <= 0) return 0;
676
+ return clampValue(1 - (dragOffset ?? snapPointOffset) / contentSize, 0, 1);
677
+ }
678
+ function hasOpeningSwipeIntent(start, current, direction) {
679
+ const axis = isVerticalSwipeDirection(direction) ? "y" : "x";
680
+ const sign = isNegativeSwipeDirection(direction) ? -1 : 1;
681
+ return (start[axis] - current[axis]) * sign > SWIPE_AREA_OPEN_INTENT_MIN_PX;
682
+ }
683
+ function overflowAllowsScroll(overflow) {
684
+ return overflow === "auto" || overflow === "scroll" || overflow === "overlay";
685
+ }
686
+ function canScrollAlongY(el) {
687
+ if (!overflowAllowsScroll(getComputedStyle$1(el).overflowY)) return false;
688
+ return el.scrollHeight > el.clientHeight + SCROLL_SLACK_EPSILON;
689
+ }
690
+ function canScrollAlongX(el) {
691
+ if (!overflowAllowsScroll(getComputedStyle$1(el).overflowX)) return false;
692
+ return el.scrollWidth > el.clientWidth + SCROLL_SLACK_EPSILON;
693
+ }
694
+ function canScrollOnSwipeAxis(el, direction) {
695
+ return isVerticalSwipeDirection(direction) ? canScrollAlongY(el) : canScrollAlongX(el);
696
+ }
697
+ function findClosestScrollableAncestorOnSwipeAxis(target, container, direction) {
698
+ if (!container) return null;
699
+ let el = target;
700
+ while (el && el !== container) {
701
+ if (canScrollOnSwipeAxis(el, direction)) return el;
702
+ el = el.parentElement;
703
+ }
704
+ return null;
705
+ }
706
+ function getScrollInfo(target, container, direction) {
707
+ let availableForwardScroll = 0;
708
+ let availableBackwardScroll = 0;
709
+ if (!container) return {
710
+ availableForwardScroll,
711
+ availableBackwardScroll
712
+ };
713
+ const vertical = isVerticalSwipeDirection(direction);
714
+ let element = target;
715
+ while (element) {
716
+ if (vertical ? canScrollAlongY(element) : canScrollAlongX(element)) {
717
+ const clientSize = vertical ? element.clientHeight : element.clientWidth;
718
+ const scrollPos = vertical ? element.scrollTop : element.scrollLeft;
719
+ const scrolled = (vertical ? element.scrollHeight : element.scrollWidth) - scrollPos - clientSize;
720
+ availableForwardScroll += scrolled;
721
+ availableBackwardScroll += scrollPos;
722
+ }
723
+ if (element === container || element === element.ownerDocument.documentElement) break;
724
+ element = element.parentElement;
725
+ }
726
+ return {
727
+ availableForwardScroll,
728
+ availableBackwardScroll
729
+ };
730
+ }
731
+ function shouldPreventTouchScroll(options) {
732
+ const { scrollParent, swipeDirection, lastMainAxis, currentMainAxis } = options;
733
+ const vertical = isVerticalSwipeDirection(swipeDirection);
734
+ const movingPositive = currentMainAxis > lastMainAxis;
735
+ if (vertical) {
736
+ const scrollPos = scrollParent.scrollTop;
737
+ const maxScroll = Math.max(0, scrollParent.scrollHeight - scrollParent.clientHeight);
738
+ if (swipeDirection === "down") return scrollPos <= SCROLL_SLACK_EPSILON && movingPositive;
739
+ if (swipeDirection === "up") return scrollPos >= maxScroll - SCROLL_SLACK_EPSILON && !movingPositive;
740
+ } else {
741
+ const scrollPos = scrollParent.scrollLeft;
742
+ const maxScroll = Math.max(0, scrollParent.scrollWidth - scrollParent.clientWidth);
743
+ if (swipeDirection === "right") return scrollPos <= SCROLL_SLACK_EPSILON && movingPositive;
744
+ if (swipeDirection === "left") return scrollPos >= maxScroll - SCROLL_SLACK_EPSILON && !movingPositive;
745
+ }
746
+ return false;
747
+ }
748
+ function isDragExemptElement(el) {
749
+ if (!isHTMLElement(el)) return false;
750
+ if (el.closest(NO_DRAG_SELECTOR)) return true;
751
+ let node = el;
752
+ while (node) {
753
+ if (isEditableElement(node)) return true;
754
+ node = node.parentElement;
755
+ }
756
+ const input = el.closest("input");
757
+ if (isInputElement(input)) {
758
+ const type = input.type;
759
+ if (type === "range" || type === "file") return true;
760
+ }
761
+ return false;
762
+ }
763
+ function isTextSelectionInDrawer(doc, contentEl) {
764
+ if (!contentEl) return false;
765
+ const selection = doc.getSelection();
766
+ if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return false;
767
+ try {
768
+ const range = selection.getRangeAt(0);
769
+ if (contains(contentEl, range.commonAncestorContainer)) return true;
770
+ if (contains(contentEl, selection.anchorNode)) return true;
771
+ if (contains(contentEl, selection.focusNode)) return true;
772
+ if (typeof range.intersectsNode === "function" && range.intersectsNode(contentEl)) return true;
773
+ } catch {
774
+ return false;
775
+ }
776
+ return false;
777
+ }
778
+ function isDragExemptFromComposedPath(event) {
779
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [];
780
+ for (const node of path) if (isDragExemptElement(node)) return true;
781
+ return isDragExemptElement(event.target);
782
+ }
783
+ function shouldIgnorePointerDownForDrag(event) {
784
+ if (!isLeftClick(event)) return true;
785
+ const target = getEventTarget(event);
786
+ if (target?.hasAttribute(NO_DRAG_DATA_ATTR) || target?.closest(NO_DRAG_SELECTOR)) return true;
787
+ return isDragExemptFromComposedPath(event);
788
+ }
27
789
  //#endregion
28
790
  //#region src/machines/drawer/drawer.connect.ts
791
+ const SWIPE_OPEN_HIDDEN_OFFSET = 9999;
792
+ function getSwipeOpenOffset(swipingOpen, dragOffset, contentSize) {
793
+ if (!swipingOpen || dragOffset !== null) return null;
794
+ return contentSize ?? SWIPE_OPEN_HIDDEN_OFFSET;
795
+ }
29
796
  function connect(service, normalize) {
30
- const { state, send, context, scope, prop } = service;
797
+ const { state, send, context, scope, prop, refs } = service;
31
798
  const open = state.hasTag("open");
799
+ const closed = state.matches("closed");
800
+ const swipingOpen = state.matches("swiping-open");
32
801
  const dragOffset = context.get("dragOffset");
33
802
  const dragging = dragOffset !== null;
34
- const activeSnapPoint = context.get("activeSnapPoint");
803
+ const snapPoint = context.get("snapPoint");
804
+ const swipeDirection = prop("swipeDirection");
805
+ const physicalDirection = resolveSwipeDirection(swipeDirection, prop("dir"));
806
+ const contentSize = context.get("contentSize");
807
+ const swipeStrength = context.get("swipeStrength");
35
808
  const resolvedActiveSnapPoint = context.get("resolvedActiveSnapPoint");
36
- const translate = dragOffset ?? resolvedActiveSnapPoint?.offset;
37
- function onPointerDown(event) {
38
- if (!isLeftClick(event)) return;
39
- const target = getEventTarget(event);
40
- if (target?.hasAttribute("data-no-drag") || target?.closest("[data-no-drag]")) return;
41
- if (state.matches("closing")) return;
42
- send({
43
- type: "POINTER_DOWN",
44
- point: getEventPoint(event)
809
+ const snapPointOffset = resolvedActiveSnapPoint?.offset ?? 0;
810
+ const currentOffset = getSwipeOpenOffset(swipingOpen, dragOffset, contentSize) ?? dragOffset ?? snapPointOffset;
811
+ const signedSnapPointOffset = isNegativeSwipeDirection(physicalDirection) ? -snapPointOffset : snapPointOffset;
812
+ const isActivelySwiping = dragging || swipingOpen;
813
+ const swipeMovement = dragging || swipingOpen ? currentOffset - snapPointOffset : 0;
814
+ const signedMovement = isNegativeSwipeDirection(physicalDirection) ? -swipeMovement : swipeMovement;
815
+ const swipeProgress = isActivelySwiping && contentSize && contentSize > 0 ? clampValue(Math.abs(signedMovement) / contentSize, 0, 1) : swipingOpen ? 1 : 0;
816
+ const signedCurrentOffset = isNegativeSwipeDirection(physicalDirection) ? -currentOffset : currentOffset;
817
+ const translateX = isVerticalSwipeDirection(physicalDirection) ? 0 : signedCurrentOffset;
818
+ const translateY = isVerticalSwipeDirection(physicalDirection) ? signedCurrentOffset : 0;
819
+ function onContentPointerDown(event) {
820
+ refs.get("swipeSession").contentPointerDown({
821
+ event,
822
+ getDoc: () => scope.getDoc(),
823
+ getContentEl: () => getContentEl(scope),
824
+ getWin: () => scope.getWin(),
825
+ swipeDirection: physicalDirection,
826
+ canCommit: () => state.hasTag("open") && !state.matches("closing"),
827
+ onCommit(point) {
828
+ send({
829
+ type: "POINTER_DOWN",
830
+ point
831
+ });
832
+ }
833
+ });
834
+ }
835
+ function onGrabberPointerDown(event) {
836
+ refs.get("swipeSession").grabberPointerDown({
837
+ event,
838
+ point: getEventPoint(event),
839
+ canCommit: () => state.hasTag("open") && !state.matches("closing"),
840
+ onCommit(point) {
841
+ send({
842
+ type: "POINTER_DOWN",
843
+ point
844
+ });
845
+ }
45
846
  });
46
847
  }
47
848
  return {
@@ -52,47 +853,74 @@ function connect(service, normalize) {
52
853
  send({ type: nextOpen ? "OPEN" : "CLOSE" });
53
854
  },
54
855
  snapPoints: prop("snapPoints"),
55
- activeSnapPoint,
56
- setActiveSnapPoint(snapPoint) {
57
- if (context.get("activeSnapPoint") === snapPoint) return;
856
+ swipeDirection,
857
+ snapPoint,
858
+ setSnapPoint(snapPoint) {
859
+ if (context.get("snapPoint") === snapPoint) return;
58
860
  send({
59
- type: "ACTIVE_SNAP_POINT.SET",
861
+ type: "SNAP_POINT.SET",
60
862
  snapPoint
61
863
  });
62
864
  },
63
865
  getOpenPercentage() {
64
- if (!open) return 0;
65
- const contentHeight = context.get("contentHeight");
66
- if (!contentHeight) return 0;
67
- const currentOffset = translate ?? 0;
68
- return Math.max(0, Math.min(1, 1 - currentOffset / contentHeight));
866
+ if (!open || !contentSize) return 0;
867
+ return clampValue(1 - currentOffset / contentSize, 0, 1);
69
868
  },
70
- getActiveSnapIndex() {
71
- return prop("snapPoints").indexOf(activeSnapPoint);
869
+ getSnapPointIndex() {
870
+ if (snapPoint === null) return -1;
871
+ return prop("snapPoints").indexOf(snapPoint);
72
872
  },
73
- getContentHeight() {
74
- return context.get("contentHeight");
873
+ getContentSize() {
874
+ return contentSize;
875
+ },
876
+ getPositionerProps() {
877
+ return normalize.element({
878
+ ...parts.positioner.attrs,
879
+ id: getPositionerId(scope),
880
+ dir: prop("dir"),
881
+ hidden: closed,
882
+ "data-state": open ? "open" : "closed",
883
+ "data-swipe-direction": physicalDirection,
884
+ style: compact({ pointerEvents: prop("modal") ? void 0 : "none" })
885
+ });
75
886
  },
76
887
  getContentProps(props = { draggable: true }) {
888
+ const movementX = isVerticalSwipeDirection(physicalDirection) ? 0 : signedMovement;
889
+ const movementY = isVerticalSwipeDirection(physicalDirection) ? signedMovement : 0;
890
+ const rendered = context.get("rendered");
77
891
  return normalize.element({
78
892
  ...parts.content.attrs,
79
893
  dir: prop("dir"),
80
894
  id: getContentId(scope),
81
895
  tabIndex: -1,
82
- role: "dialog",
896
+ role: prop("role"),
83
897
  "aria-modal": prop("modal"),
84
- "aria-labelledby": getTitleId(scope),
898
+ "aria-labelledby": rendered.title ? getTitleId(scope) : void 0,
899
+ "aria-describedby": rendered.description ? getDescriptionId(scope) : void 0,
85
900
  hidden: !open,
86
901
  "data-state": open ? "open" : "closed",
87
- style: {
88
- transform: "translate3d(0, var(--drawer-translate, 0), 0)",
89
- transitionDuration: dragging ? "0s" : void 0,
90
- "--drawer-translate": toPx(translate),
902
+ "data-expanded": resolvedActiveSnapPoint?.offset === 0 ? "" : void 0,
903
+ "data-swipe-direction": physicalDirection,
904
+ "data-swiping": dragging || swipingOpen ? "" : void 0,
905
+ "data-dragging": dragging ? "" : void 0,
906
+ style: compact({
907
+ pointerEvents: prop("modal") ? void 0 : "auto",
908
+ visibility: swipingOpen && dragOffset === null ? "hidden" : void 0,
909
+ transform: "translate3d(var(--drawer-translate-x, 0px), var(--drawer-translate-y, 0px), 0)",
910
+ transitionDuration: dragging || swipingOpen ? "0s" : void 0,
911
+ "--drawer-translate": toPx(translateY),
912
+ "--drawer-translate-x": toPx(translateX),
913
+ "--drawer-translate-y": toPx(translateY),
914
+ "--drawer-snap-point-offset-x": isVerticalSwipeDirection(physicalDirection) ? "0px" : toPx(signedSnapPointOffset),
915
+ "--drawer-snap-point-offset-y": isVerticalSwipeDirection(physicalDirection) ? toPx(signedSnapPointOffset) : "0px",
916
+ "--drawer-swipe-movement-x": toPx(movementX),
917
+ "--drawer-swipe-movement-y": toPx(movementY),
918
+ "--drawer-swipe-strength": `${swipeStrength}`,
91
919
  willChange: "transform"
92
- },
920
+ }),
93
921
  onPointerDown(event) {
94
922
  if (!props.draggable) return;
95
- onPointerDown(event);
923
+ onContentPointerDown(event);
96
924
  }
97
925
  });
98
926
  },
@@ -103,6 +931,13 @@ function connect(service, normalize) {
103
931
  dir: prop("dir")
104
932
  });
105
933
  },
934
+ getDescriptionProps() {
935
+ return normalize.element({
936
+ ...parts.description.attrs,
937
+ id: getDescriptionId(scope),
938
+ dir: prop("dir")
939
+ });
940
+ },
106
941
  getTriggerProps() {
107
942
  return normalize.button({
108
943
  ...parts.trigger.attrs,
@@ -117,9 +952,14 @@ function connect(service, normalize) {
117
952
  return normalize.element({
118
953
  ...parts.backdrop.attrs,
119
954
  id: getBackdropId(scope),
120
- hidden: !open,
955
+ hidden: !open || swipingOpen && dragOffset === null,
121
956
  "data-state": open ? "open" : "closed",
122
- style: { willChange: "opacity" }
957
+ "data-swiping": dragging || swipingOpen ? "" : void 0,
958
+ style: {
959
+ willChange: "opacity",
960
+ "--drawer-swipe-progress": `${swipeProgress}`,
961
+ "--drawer-swipe-strength": `${swipeStrength}`
962
+ }
123
963
  });
124
964
  },
125
965
  getGrabberProps() {
@@ -127,7 +967,7 @@ function connect(service, normalize) {
127
967
  ...parts.grabber.attrs,
128
968
  id: getGrabberId(scope),
129
969
  onPointerDown(event) {
130
- onPointerDown(event);
970
+ onGrabberPointerDown(event);
131
971
  },
132
972
  style: { touchAction: "none" }
133
973
  });
@@ -146,143 +986,121 @@ function connect(service, normalize) {
146
986
  send({ type: "CLOSE" });
147
987
  }
148
988
  });
989
+ },
990
+ getSwipeAreaProps(props = {}) {
991
+ const disabled = props.disabled ?? false;
992
+ const physicalOpenDirection = resolveSwipeDirection(props.swipeDirection ?? oppositeSwipeDirection[swipeDirection], prop("dir"));
993
+ return normalize.element({
994
+ ...parts.swipeArea.attrs,
995
+ id: getSwipeAreaId(scope),
996
+ role: "presentation",
997
+ "aria-hidden": true,
998
+ "data-state": open ? "open" : "closed",
999
+ "data-swiping": swipingOpen ? "" : void 0,
1000
+ "data-swipe-direction": physicalOpenDirection,
1001
+ "data-disabled": disabled ? "" : void 0,
1002
+ style: {
1003
+ touchAction: isVerticalSwipeDirection(physicalOpenDirection) ? "pan-x" : "pan-y",
1004
+ pointerEvents: disabled || open && !swipingOpen ? "none" : void 0
1005
+ },
1006
+ onPointerDown(event) {
1007
+ if (disabled) return;
1008
+ if (!isLeftClick(event)) return;
1009
+ if (event.pointerType === "touch") return;
1010
+ if (open && !swipingOpen) return;
1011
+ send({
1012
+ type: "SWIPE_AREA.START",
1013
+ point: getEventPoint(event)
1014
+ });
1015
+ if (event.cancelable) event.preventDefault();
1016
+ },
1017
+ onTouchStart(event) {
1018
+ if (disabled) return;
1019
+ if (open && !swipingOpen) return;
1020
+ const touch = event.touches[0];
1021
+ if (!touch) return;
1022
+ send({
1023
+ type: "SWIPE_AREA.START",
1024
+ point: {
1025
+ x: touch.clientX,
1026
+ y: touch.clientY
1027
+ }
1028
+ });
1029
+ }
1030
+ });
149
1031
  }
150
1032
  };
151
1033
  }
152
1034
  //#endregion
153
- //#region src/machines/drawer/utils/find-closest-snap-point.ts
154
- function findClosestSnapPoint(offset, snapPoints) {
155
- return snapPoints.reduce((acc, curr) => {
156
- const closestDiff = Math.abs(offset - acc.offset);
157
- return Math.abs(offset - curr.offset) < closestDiff ? curr : acc;
158
- });
159
- }
160
- //#endregion
161
- //#region src/machines/drawer/utils/get-scroll-info.ts
162
- function isScrollContainer(element) {
163
- const overflow = getComputedStyle(element).overflowY;
164
- return overflow === "auto" || overflow === "scroll";
165
- }
166
- function getScrollInfo(target, container) {
167
- let element = target;
168
- let availableScroll = 0;
169
- let availableScrollTop = 0;
170
- while (element) {
171
- const { clientHeight, scrollTop, scrollHeight } = element;
172
- const scrolled = scrollHeight - scrollTop - clientHeight;
173
- if ((scrollTop !== 0 || scrolled !== 0) && isScrollContainer(element)) {
174
- availableScroll += scrolled;
175
- availableScrollTop += scrollTop;
176
- }
177
- if (element === container || element === document.documentElement) break;
178
- element = element.parentNode;
179
- }
180
- return {
181
- availableScroll,
182
- availableScrollTop
183
- };
184
- }
185
- //#endregion
186
- //#region src/machines/drawer/utils/drag-manager.ts
187
- const DRAG_START_THRESHOLD = .3;
188
- var DragManager = class {
1035
+ //#region src/machines/drawer/drawer.registry.ts
1036
+ var DrawerRegistry = class {
189
1037
  constructor() {
190
- _defineProperty(this, "pointerStart", null);
191
- _defineProperty(this, "dragOffset", null);
192
- _defineProperty(this, "lastPoint", null);
193
- _defineProperty(this, "lastTimestamp", null);
194
- _defineProperty(this, "velocity", null);
1038
+ _defineProperty(this, "elements", /* @__PURE__ */ new Map());
1039
+ _defineProperty(this, "swipingIds", /* @__PURE__ */ new Set());
1040
+ _defineProperty(this, "swipeProgress", /* @__PURE__ */ new Map());
1041
+ _defineProperty(this, "listeners", /* @__PURE__ */ new Set());
195
1042
  }
196
- setPointerStart(point) {
197
- this.pointerStart = point;
198
- }
199
- clearPointerStart() {
200
- this.pointerStart = null;
201
- }
202
- getPointerStart() {
203
- return this.pointerStart;
204
- }
205
- setDragOffset(point, resolvedActiveSnapPointOffset) {
206
- if (!this.pointerStart) return;
207
- const currentTimestamp = (/* @__PURE__ */ new Date()).getTime();
208
- if (this.lastPoint) {
209
- const dy = point.y - this.lastPoint.y;
210
- if (this.lastTimestamp) {
211
- const dt = currentTimestamp - this.lastTimestamp;
212
- if (dt > 0) {
213
- const calculatedVelocity = dy / dt * 1e3;
214
- this.velocity = Number.isFinite(calculatedVelocity) ? calculatedVelocity : 0;
215
- }
216
- }
217
- }
218
- this.lastPoint = point;
219
- this.lastTimestamp = currentTimestamp;
220
- let delta = this.pointerStart.y - point.y - resolvedActiveSnapPointOffset;
221
- if (delta > 0) delta = 0;
222
- this.dragOffset = -delta;
1043
+ notify() {
1044
+ this.listeners.forEach((fn) => fn());
223
1045
  }
224
- getDragOffset() {
225
- return this.dragOffset;
1046
+ register(id, el) {
1047
+ this.elements.set(id, el);
1048
+ this.notify();
226
1049
  }
227
- clearDragOffset() {
228
- this.dragOffset = null;
1050
+ unregister(id) {
1051
+ this.swipingIds.delete(id);
1052
+ this.swipeProgress.delete(id);
1053
+ if (!this.elements.delete(id)) return;
1054
+ this.notify();
229
1055
  }
230
- getVelocity() {
231
- return this.velocity;
1056
+ setSwiping(id, swiping) {
1057
+ if (!(swiping ? !this.swipingIds.has(id) : this.swipingIds.has(id)) && swiping) return;
1058
+ if (swiping) this.swipingIds.add(id);
1059
+ else {
1060
+ this.swipingIds.delete(id);
1061
+ this.swipeProgress.delete(id);
1062
+ }
1063
+ this.notify();
232
1064
  }
233
- clearVelocityTracking() {
234
- this.lastPoint = null;
235
- this.lastTimestamp = null;
236
- this.velocity = null;
1065
+ setSwipeProgress(id, progress) {
1066
+ this.swipeProgress.set(id, progress);
1067
+ this.notify();
237
1068
  }
238
- clear() {
239
- this.clearPointerStart();
240
- this.clearDragOffset();
241
- this.clearVelocityTracking();
1069
+ getSwipeProgressAfter(id) {
1070
+ const keys = [...this.elements.keys()];
1071
+ const myIndex = keys.indexOf(id);
1072
+ if (myIndex === -1) return 0;
1073
+ for (let i = keys.length - 1; i > myIndex; i -= 1) if (this.swipingIds.has(keys[i])) return this.swipeProgress.get(keys[i]) ?? 0;
1074
+ return 0;
242
1075
  }
243
- shouldStartDragging(point, target, container, preventDragOnScroll) {
244
- if (!this.pointerStart || !container) return false;
245
- if (preventDragOnScroll) {
246
- const delta = this.pointerStart.y - point.y;
247
- if (Math.abs(delta) < DRAG_START_THRESHOLD) return false;
248
- const { availableScroll, availableScrollTop } = getScrollInfo(target, container);
249
- if (delta > 0 && Math.abs(availableScroll) > 1 || delta < 0 && Math.abs(availableScrollTop) > 0) return false;
250
- }
251
- return true;
1076
+ hasSwipingAfter(id) {
1077
+ const keys = [...this.elements.keys()];
1078
+ const myIndex = keys.indexOf(id);
1079
+ if (myIndex === -1) return false;
1080
+ return keys.slice(myIndex + 1).some((key) => this.swipingIds.has(key));
252
1081
  }
253
- findClosestSnapPoint(snapPoints) {
254
- if (this.dragOffset === null) return snapPoints[0]?.value ?? 1;
255
- return findClosestSnapPoint(this.dragOffset, snapPoints).value;
1082
+ getEntries() {
1083
+ return this.elements;
256
1084
  }
257
- shouldDismiss(contentHeight, snapPoints, swipeVelocityThreshold, closeThreshold) {
258
- if (this.dragOffset === null || this.velocity === null || contentHeight === null) return false;
259
- const visibleHeight = contentHeight - this.dragOffset;
260
- const smallestSnapPoint = snapPoints.reduce((acc, curr) => curr.offset > acc.offset ? curr : acc);
261
- const isFastSwipe = this.velocity > 0 && this.velocity >= swipeVelocityThreshold;
262
- const closeThresholdInPixels = contentHeight * (1 - closeThreshold);
263
- const isBelowSmallestSnapPoint = visibleHeight < contentHeight - smallestSnapPoint.offset;
264
- return isFastSwipe || visibleHeight < closeThresholdInPixels && isBelowSmallestSnapPoint || visibleHeight === 0;
1085
+ subscribe(fn) {
1086
+ this.listeners.add(fn);
1087
+ return () => {
1088
+ this.listeners.delete(fn);
1089
+ };
265
1090
  }
266
1091
  };
267
- //#endregion
268
- //#region src/machines/drawer/utils/resolve-snap-point.ts
269
- function resolveSnapPoint(snapPoint, containerHeight) {
270
- if (typeof snapPoint === "number") return {
271
- value: snapPoint,
272
- offset: containerHeight - snapPoint * containerHeight
273
- };
274
- if (typeof snapPoint === "string") return {
275
- value: snapPoint,
276
- offset: containerHeight - parseFloat(snapPoint)
277
- };
278
- throw new Error(`Invalid snap point: ${snapPoint}`);
279
- }
1092
+ const drawerRegistry = new DrawerRegistry();
280
1093
  //#endregion
281
1094
  //#region src/machines/drawer/drawer.machine.ts
1095
+ const { and } = createGuards();
1096
+ const getActiveSnapOffset = (context) => context.get("resolvedActiveSnapPoint")?.offset ?? 0;
1097
+ const hasRemSnapPoints = (snapPoints) => snapPoints.some((snapPoint) => typeof snapPoint === "string" && snapPoint.trim().endsWith("rem"));
1098
+ const DEFAULT_SNAP_POINTS = [1];
282
1099
  const machine = createMachine({
283
1100
  props({ props, scope }) {
284
1101
  const initialFocusEl = props.role === "alertdialog" ? () => getCloseTriggerEl(scope) : void 0;
285
1102
  const modal = typeof props.modal === "boolean" ? props.modal : true;
1103
+ const snapPoints = props.snapPoints ?? DEFAULT_SNAP_POINTS;
286
1104
  return {
287
1105
  modal,
288
1106
  trapFocus: modal,
@@ -290,11 +1108,14 @@ const machine = createMachine({
290
1108
  closeOnInteractOutside: true,
291
1109
  closeOnEscape: true,
292
1110
  restoreFocus: true,
1111
+ role: "dialog",
293
1112
  initialFocusEl,
294
- snapPoints: [1],
295
- defaultActiveSnapPoint: 1,
296
- swipeVelocityThreshold: 700,
297
- closeThreshold: .25,
1113
+ snapPoints,
1114
+ defaultSnapPoint: props.defaultSnapPoint ?? snapPoints[0] ?? null,
1115
+ swipeDirection: "down",
1116
+ snapToSequentialPoints: false,
1117
+ swipeVelocityThreshold: 500,
1118
+ closeThreshold: .5,
298
1119
  preventDragOnScroll: true,
299
1120
  ...props
300
1121
  };
@@ -302,88 +1123,198 @@ const machine = createMachine({
302
1123
  context({ bindable, prop }) {
303
1124
  return {
304
1125
  dragOffset: bindable(() => ({ defaultValue: null })),
305
- activeSnapPoint: bindable(() => ({
306
- defaultValue: prop("defaultActiveSnapPoint"),
307
- value: prop("activeSnapPoint"),
308
- onChange(value) {
309
- return prop("onActiveSnapPointChange")?.({ snapPoint: value });
1126
+ snapPoint: bindable(() => ({
1127
+ defaultValue: prop("defaultSnapPoint"),
1128
+ value: prop("snapPoint"),
1129
+ onChange(snapPoint) {
1130
+ return prop("onSnapPointChange")?.({ snapPoint });
310
1131
  }
311
1132
  })),
312
1133
  resolvedActiveSnapPoint: bindable(() => ({ defaultValue: null })),
313
- contentHeight: bindable(() => ({ defaultValue: null }))
1134
+ contentSize: bindable(() => ({ defaultValue: null })),
1135
+ viewportSize: bindable(() => ({ defaultValue: 0 })),
1136
+ rootFontSize: bindable(() => ({ defaultValue: 16 })),
1137
+ swipeStrength: bindable(() => ({ defaultValue: 1 })),
1138
+ rendered: bindable(() => ({ defaultValue: {
1139
+ title: true,
1140
+ description: true
1141
+ } }))
314
1142
  };
315
1143
  },
316
- refs() {
317
- return { dragManager: new DragManager() };
1144
+ refs({ prop }) {
1145
+ return { swipeSession: new DrawerSwipeSession({ preventDragOnScroll: () => prop("preventDragOnScroll") }) };
318
1146
  },
319
- computed: { resolvedSnapPoints({ context, prop }) {
320
- const contentHeight = context.get("contentHeight");
321
- if (contentHeight === null) return [];
322
- return prop("snapPoints").map((snapPoint) => resolveSnapPoint(snapPoint, contentHeight));
323
- } },
324
- watch({ track, context, prop, action }) {
325
- track([() => context.get("activeSnapPoint"), () => context.get("contentHeight")], () => {
326
- const activeSnapPoint = context.get("activeSnapPoint");
327
- const contentHeight = context.get("contentHeight");
328
- if (contentHeight === null) return;
329
- const resolvedActiveSnapPoint = resolveSnapPoint(activeSnapPoint, contentHeight);
330
- context.set("resolvedActiveSnapPoint", resolvedActiveSnapPoint);
1147
+ computed: {
1148
+ drawerId({ prop, scope }) {
1149
+ return String(prop("id") ?? scope.id);
1150
+ },
1151
+ physicalSwipeDirection({ prop }) {
1152
+ return resolveSwipeDirection(prop("swipeDirection"), prop("dir"));
1153
+ },
1154
+ resolvedSnapPoints({ context, prop }) {
1155
+ const contentSize = context.get("contentSize");
1156
+ const viewportSize = context.get("viewportSize");
1157
+ const rootFontSize = context.get("rootFontSize");
1158
+ if (contentSize === null) return [];
1159
+ return dedupeSnapPoints(prop("snapPoints").map((snapPoint) => resolveSnapPoint(snapPoint, {
1160
+ contentSize,
1161
+ viewportSize,
1162
+ rootFontSize
1163
+ })).filter((point) => point !== null));
1164
+ }
1165
+ },
1166
+ watch({ track, context, prop, action, computed }) {
1167
+ track([
1168
+ () => context.get("snapPoint"),
1169
+ () => context.get("contentSize"),
1170
+ () => context.get("viewportSize"),
1171
+ () => context.get("rootFontSize"),
1172
+ () => prop("snapPoints").join("|")
1173
+ ], () => {
1174
+ const snapPoint = context.get("snapPoint");
1175
+ const contentSize = context.get("contentSize");
1176
+ const viewportSize = context.get("viewportSize");
1177
+ const rootFontSize = context.get("rootFontSize");
1178
+ if (snapPoint === null || contentSize === null) {
1179
+ context.set("resolvedActiveSnapPoint", null);
1180
+ return;
1181
+ }
1182
+ const resolvedPoints = computed("resolvedSnapPoints");
1183
+ const matchedPoint = resolvedPoints.find((point) => Object.is(point.value, snapPoint));
1184
+ if (matchedPoint) {
1185
+ context.set("resolvedActiveSnapPoint", matchedPoint);
1186
+ return;
1187
+ }
1188
+ const resolvedActiveSnapPoint = resolveSnapPoint(snapPoint, {
1189
+ contentSize,
1190
+ viewportSize,
1191
+ rootFontSize
1192
+ });
1193
+ if (resolvedActiveSnapPoint) {
1194
+ context.set("resolvedActiveSnapPoint", resolvedActiveSnapPoint);
1195
+ return;
1196
+ }
1197
+ const fallbackPoint = resolvedPoints[0];
1198
+ if (!fallbackPoint) {
1199
+ context.set("resolvedActiveSnapPoint", null);
1200
+ return;
1201
+ }
1202
+ context.set("snapPoint", fallbackPoint.value);
1203
+ context.set("resolvedActiveSnapPoint", fallbackPoint);
331
1204
  });
332
1205
  track([() => prop("open")], () => {
333
1206
  action(["toggleVisibility"]);
334
1207
  });
1208
+ track([
1209
+ () => context.get("dragOffset"),
1210
+ () => context.get("contentSize"),
1211
+ () => getActiveSnapOffset(context)
1212
+ ], () => {
1213
+ action(["syncDrawerStack"]);
1214
+ });
335
1215
  },
336
1216
  initialState({ prop }) {
337
1217
  return prop("open") || prop("defaultOpen") ? "open" : "closed";
338
1218
  },
339
- on: { "ACTIVE_SNAP_POINT.SET": { actions: ["setActiveSnapPoint"] } },
1219
+ on: { "SNAP_POINT.SET": { actions: ["setSnapPoint"] } },
340
1220
  states: {
341
1221
  open: {
342
1222
  tags: ["open"],
1223
+ entry: [
1224
+ "checkRenderedElements",
1225
+ "setInitialFocus",
1226
+ "deferClearDragOffset"
1227
+ ],
343
1228
  effects: [
344
1229
  "trackDismissableElement",
345
1230
  "preventScroll",
346
1231
  "trapFocus",
347
1232
  "hideContentBelow",
348
1233
  "trackPointerMove",
349
- "trackContentHeight"
1234
+ "trackSizeMeasurements",
1235
+ "trackNestedDrawerMetrics",
1236
+ "trackDrawerStack"
350
1237
  ],
351
1238
  on: {
352
- "CONTROLLED.CLOSE": { target: "closed" },
1239
+ "CONTROLLED.CLOSE": {
1240
+ target: "closing",
1241
+ actions: ["clearSwipeOpenAnimation", "resetSwipeStrength"]
1242
+ },
353
1243
  POINTER_DOWN: { actions: ["setPointerStart"] },
354
1244
  POINTER_MOVE: [{
355
1245
  guard: "isDragging",
356
1246
  actions: ["setDragOffset"]
357
1247
  }, {
358
1248
  guard: "shouldStartDragging",
359
- actions: ["setDragOffset"]
1249
+ actions: ["setRegistrySwiping", "setDragOffset"]
360
1250
  }],
361
1251
  POINTER_UP: [
1252
+ {
1253
+ guard: and("shouldCloseOnSwipe", "isOpenControlled"),
1254
+ actions: [
1255
+ "clearSwipeOpenAnimation",
1256
+ "clearRegistrySwiping",
1257
+ "clearPointerStart",
1258
+ "clearDragOffset",
1259
+ "invokeOnClose"
1260
+ ]
1261
+ },
362
1262
  {
363
1263
  guard: "shouldCloseOnSwipe",
364
- target: "closing"
1264
+ target: "closing",
1265
+ actions: [
1266
+ "clearSwipeOpenAnimation",
1267
+ "clearRegistrySwiping",
1268
+ "setDismissSwipeStrength"
1269
+ ]
365
1270
  },
366
1271
  {
367
1272
  guard: "isDragging",
368
1273
  actions: [
1274
+ "clearRegistrySwiping",
1275
+ "suppressBackdropAnimation",
1276
+ "setSnapSwipeStrength",
369
1277
  "setClosestSnapPoint",
370
1278
  "clearPointerStart",
371
- "clearDragOffset"
1279
+ "clearDragOffset",
1280
+ "clearVelocityTracking"
372
1281
  ]
373
1282
  },
374
- { actions: ["clearPointerStart", "clearDragOffset"] }
1283
+ { actions: [
1284
+ "clearRegistrySwiping",
1285
+ "clearPointerStart",
1286
+ "clearDragOffset",
1287
+ "clearVelocityTracking"
1288
+ ] }
375
1289
  ],
1290
+ POINTER_CANCEL: [{
1291
+ guard: "isDragging",
1292
+ actions: [
1293
+ "clearRegistrySwiping",
1294
+ "clearPointerStart",
1295
+ "clearDragOffset",
1296
+ "clearVelocityTracking"
1297
+ ]
1298
+ }, { actions: [
1299
+ "clearRegistrySwiping",
1300
+ "clearPointerStart",
1301
+ "clearVelocityTracking"
1302
+ ] }],
376
1303
  CLOSE: [{
377
1304
  guard: "isOpenControlled",
378
- actions: ["invokeOnClose"]
1305
+ actions: ["clearSwipeOpenAnimation", "invokeOnClose"]
379
1306
  }, {
380
1307
  target: "closing",
381
- actions: ["invokeOnClose"]
1308
+ actions: [
1309
+ "clearSwipeOpenAnimation",
1310
+ "resetSwipeStrength",
1311
+ "invokeOnClose"
1312
+ ]
382
1313
  }]
383
1314
  }
384
1315
  },
385
1316
  closing: {
386
- effects: ["trackExitAnimation"],
1317
+ effects: ["trackExitAnimation", "trackDrawerStack"],
387
1318
  on: { ANIMATION_END: {
388
1319
  target: "closed",
389
1320
  actions: [
@@ -392,11 +1323,73 @@ const machine = createMachine({
392
1323
  "clearDragOffset",
393
1324
  "clearActiveSnapPoint",
394
1325
  "clearResolvedActiveSnapPoint",
395
- "clearContentHeight",
1326
+ "clearSizeMeasurements",
396
1327
  "clearVelocityTracking"
397
1328
  ]
398
1329
  } }
399
1330
  },
1331
+ "swipe-area-dragging": {
1332
+ tags: ["closed"],
1333
+ effects: ["trackSwipeOpenPointerMove"],
1334
+ on: {
1335
+ POINTER_MOVE: {
1336
+ guard: "hasSwipeIntent",
1337
+ target: "swiping-open"
1338
+ },
1339
+ POINTER_UP: {
1340
+ target: "closed",
1341
+ actions: ["clearPointerStart", "clearVelocityTracking"]
1342
+ },
1343
+ POINTER_CANCEL: {
1344
+ target: "closed",
1345
+ actions: ["clearPointerStart", "clearVelocityTracking"]
1346
+ }
1347
+ }
1348
+ },
1349
+ "swiping-open": {
1350
+ tags: ["open"],
1351
+ effects: ["trackSwipeOpenPointerMove", "trackSizeMeasurements"],
1352
+ on: {
1353
+ POINTER_MOVE: { actions: ["setSwipeOpenDragOffset"] },
1354
+ POINTER_UP: [
1355
+ {
1356
+ guard: and("shouldOpenOnSwipe", "isOpenControlled"),
1357
+ actions: ["clearPointerStart", "invokeOnOpen"]
1358
+ },
1359
+ {
1360
+ guard: "shouldOpenOnSwipe",
1361
+ target: "open",
1362
+ actions: ["clearPointerStart", "invokeOnOpen"]
1363
+ },
1364
+ {
1365
+ target: "closed",
1366
+ actions: [
1367
+ "clearPointerStart",
1368
+ "clearDragOffset",
1369
+ "clearSizeMeasurements"
1370
+ ]
1371
+ }
1372
+ ],
1373
+ POINTER_CANCEL: {
1374
+ target: "closed",
1375
+ actions: [
1376
+ "clearPointerStart",
1377
+ "clearDragOffset",
1378
+ "clearSizeMeasurements",
1379
+ "clearVelocityTracking"
1380
+ ]
1381
+ },
1382
+ "CONTROLLED.OPEN": { target: "open" },
1383
+ CLOSE: {
1384
+ target: "closed",
1385
+ actions: [
1386
+ "clearPointerStart",
1387
+ "clearDragOffset",
1388
+ "clearSizeMeasurements"
1389
+ ]
1390
+ }
1391
+ }
1392
+ },
400
1393
  closed: {
401
1394
  tags: ["closed"],
402
1395
  on: {
@@ -407,7 +1400,11 @@ const machine = createMachine({
407
1400
  }, {
408
1401
  target: "open",
409
1402
  actions: ["invokeOnOpen"]
410
- }]
1403
+ }],
1404
+ "SWIPE_AREA.START": {
1405
+ target: "swipe-area-dragging",
1406
+ actions: ["setPointerStart"]
1407
+ }
411
1408
  }
412
1409
  }
413
1410
  },
@@ -417,71 +1414,204 @@ const machine = createMachine({
417
1414
  isDragging({ context }) {
418
1415
  return context.get("dragOffset") !== null;
419
1416
  },
420
- shouldStartDragging({ prop, refs, event, scope }) {
421
- return refs.get("dragManager").shouldStartDragging(event.point, event.target, getContentEl(scope), prop("preventDragOnScroll"));
1417
+ shouldStartDragging({ computed, prop, refs, event, scope }) {
1418
+ return refs.get("swipeSession").canStartDrag(event.point, event.target, getContentEl(scope), prop("preventDragOnScroll"), computed("physicalSwipeDirection"));
422
1419
  },
423
1420
  shouldCloseOnSwipe({ prop, context, computed, refs }) {
424
- return refs.get("dragManager").shouldDismiss(context.get("contentHeight"), computed("resolvedSnapPoints"), prop("swipeVelocityThreshold"), prop("closeThreshold"));
1421
+ if (prop("snapToSequentialPoints")) return false;
1422
+ return refs.get("swipeSession").shouldDismissOnRelease(context.get("contentSize"), computed("resolvedSnapPoints"), getActiveSnapOffset(context));
1423
+ },
1424
+ hasSwipeIntent({ refs, computed, event }) {
1425
+ const start = refs.get("swipeSession").getSwipeStart();
1426
+ if (!start || !event.point) return false;
1427
+ return hasOpeningSwipeIntent(start, event.point, computed("physicalSwipeDirection"));
1428
+ },
1429
+ shouldOpenOnSwipe({ context, refs, prop }) {
1430
+ return refs.get("swipeSession").shouldOpenOnRelease(context.get("contentSize"), prop("swipeVelocityThreshold"), prop("closeThreshold"));
425
1431
  }
426
1432
  },
427
1433
  actions: {
1434
+ setInitialFocus({ prop, scope }) {
1435
+ if (prop("trapFocus")) return;
1436
+ raf(() => {
1437
+ getInitialFocus({
1438
+ root: getContentEl(scope),
1439
+ getInitialEl: prop("initialFocusEl")
1440
+ })?.focus({ preventScroll: true });
1441
+ });
1442
+ },
1443
+ checkRenderedElements({ context, scope }) {
1444
+ raf(() => {
1445
+ context.set("rendered", {
1446
+ title: !!getTitleEl(scope),
1447
+ description: !!getDescriptionEl(scope)
1448
+ });
1449
+ });
1450
+ },
1451
+ deferClearDragOffset({ context, refs, scope }) {
1452
+ if (context.get("dragOffset") === null) return;
1453
+ const contentEl = getContentEl(scope);
1454
+ const backdropEl = getBackdropEl(scope);
1455
+ if (contentEl) contentEl.style.setProperty("animation", "none", "important");
1456
+ if (backdropEl) backdropEl.style.setProperty("animation", "none", "important");
1457
+ raf(() => {
1458
+ refs.get("swipeSession").resetDragOffset();
1459
+ context.set("dragOffset", null);
1460
+ });
1461
+ },
1462
+ suppressBackdropAnimation({ scope }) {
1463
+ const backdropEl = getBackdropEl(scope);
1464
+ if (!backdropEl) return;
1465
+ backdropEl.style.setProperty("animation", "none", "important");
1466
+ },
1467
+ clearSwipeOpenAnimation({ scope }) {
1468
+ const contentEl = getContentEl(scope);
1469
+ const backdropEl = getBackdropEl(scope);
1470
+ if (contentEl) contentEl.style.removeProperty("animation");
1471
+ if (backdropEl) backdropEl.style.removeProperty("animation");
1472
+ },
428
1473
  invokeOnOpen({ prop }) {
429
1474
  prop("onOpenChange")?.({ open: true });
430
1475
  },
431
1476
  invokeOnClose({ prop }) {
432
1477
  prop("onOpenChange")?.({ open: false });
433
1478
  },
434
- setActiveSnapPoint({ context, event }) {
435
- context.set("activeSnapPoint", event.snapPoint);
1479
+ setSnapPoint({ context, event }) {
1480
+ context.set("snapPoint", event.snapPoint);
436
1481
  },
437
1482
  setPointerStart({ event, refs }) {
438
- refs.get("dragManager").setPointerStart(event.point);
1483
+ refs.get("swipeSession").beginSwipe(event.point);
439
1484
  },
440
- setDragOffset({ context, event, refs }) {
441
- const dragManager = refs.get("dragManager");
442
- dragManager.setDragOffset(event.point, context.get("resolvedActiveSnapPoint")?.offset || 0);
443
- context.set("dragOffset", dragManager.getDragOffset());
1485
+ setDragOffset({ context, event, refs, computed }) {
1486
+ const swipeSession = refs.get("swipeSession");
1487
+ const physicalSwipeDirection = event.swipeDirection ?? computed("physicalSwipeDirection");
1488
+ swipeSession.setDragOffset(event.point, getActiveSnapOffset(context), physicalSwipeDirection);
1489
+ context.set("dragOffset", swipeSession.getDragOffset());
444
1490
  },
445
- setClosestSnapPoint({ computed, context, refs }) {
1491
+ setSwipeOpenDragOffset({ context, event, refs, computed }) {
1492
+ const swipeSession = refs.get("swipeSession");
1493
+ const contentSize = context.get("contentSize");
1494
+ if (!contentSize) return;
1495
+ swipeSession.setSwipeOpenOffset(event.point, contentSize, computed("physicalSwipeDirection"));
1496
+ context.set("dragOffset", swipeSession.getDragOffset());
1497
+ },
1498
+ setClosestSnapPoint({ computed, context, refs, prop, send }) {
446
1499
  const snapPoints = computed("resolvedSnapPoints");
447
- const contentHeight = context.get("contentHeight");
448
- if (!snapPoints.length || contentHeight === null) return;
449
- const closestSnapPoint = refs.get("dragManager").findClosestSnapPoint(snapPoints);
450
- context.set("activeSnapPoint", closestSnapPoint);
451
- const resolved = resolveSnapPoint(closestSnapPoint, contentHeight);
1500
+ const contentSize = context.get("contentSize");
1501
+ const viewportSize = context.get("viewportSize");
1502
+ const rootFontSize = context.get("rootFontSize");
1503
+ if (!snapPoints.length || contentSize === null) return;
1504
+ const closestSnapPoint = refs.get("swipeSession").resolveSnapPointOnRelease(snapPoints, context.get("resolvedActiveSnapPoint"), prop("snapToSequentialPoints"), contentSize);
1505
+ if (closestSnapPoint === null) {
1506
+ send({ type: "CLOSE" });
1507
+ return;
1508
+ }
1509
+ context.set("snapPoint", closestSnapPoint);
1510
+ const resolved = resolveSnapPoint(closestSnapPoint, {
1511
+ contentSize,
1512
+ viewportSize,
1513
+ rootFontSize
1514
+ });
452
1515
  context.set("resolvedActiveSnapPoint", resolved);
453
1516
  },
454
1517
  clearDragOffset({ context, refs }) {
455
- refs.get("dragManager").clearDragOffset();
1518
+ refs.get("swipeSession").resetDragOffset();
456
1519
  context.set("dragOffset", null);
457
1520
  },
458
- clearActiveSnapPoint({ context, prop }) {
459
- context.set("activeSnapPoint", prop("defaultActiveSnapPoint"));
1521
+ clearActiveSnapPoint({ context }) {
1522
+ context.set("snapPoint", context.initial("snapPoint"));
1523
+ },
1524
+ clearSizeMeasurements({ context }) {
1525
+ context.set("contentSize", null);
1526
+ context.set("viewportSize", 0);
1527
+ context.set("rootFontSize", 16);
460
1528
  },
461
1529
  clearResolvedActiveSnapPoint({ context }) {
462
1530
  context.set("resolvedActiveSnapPoint", null);
463
1531
  },
464
1532
  clearPointerStart({ refs }) {
465
- refs.get("dragManager").clearPointerStart();
466
- },
467
- clearContentHeight({ context }) {
468
- context.set("contentHeight", null);
1533
+ refs.get("swipeSession").clearSwipeStart();
469
1534
  },
470
1535
  clearVelocityTracking({ refs }) {
471
- refs.get("dragManager").clearVelocityTracking();
1536
+ refs.get("swipeSession").resetVelocity();
1537
+ },
1538
+ setSnapSwipeStrength({ context, refs, computed, prop }) {
1539
+ const swipeSession = refs.get("swipeSession");
1540
+ const snapPoints = computed("resolvedSnapPoints");
1541
+ const contentSize = context.get("contentSize");
1542
+ const closestSnapPoint = swipeSession.resolveSnapPointOnRelease(snapPoints, context.get("resolvedActiveSnapPoint"), prop("snapToSequentialPoints"), contentSize ?? 0);
1543
+ if (closestSnapPoint === null) return;
1544
+ const viewportSize = context.get("viewportSize");
1545
+ const rootFontSize = context.get("rootFontSize");
1546
+ const resolved = resolveSnapPoint(closestSnapPoint, {
1547
+ contentSize: contentSize ?? 0,
1548
+ viewportSize,
1549
+ rootFontSize
1550
+ });
1551
+ const restOffset = getActiveSnapOffset(context);
1552
+ context.set("swipeStrength", swipeSession.getSwipeStrength(resolved?.offset ?? 0, restOffset));
1553
+ },
1554
+ setDismissSwipeStrength({ context, refs }) {
1555
+ const swipeSession = refs.get("swipeSession");
1556
+ const contentSize = context.get("contentSize");
1557
+ const restOffset = getActiveSnapOffset(context);
1558
+ context.set("swipeStrength", swipeSession.getSwipeStrength(contentSize ?? 0, restOffset));
1559
+ },
1560
+ resetSwipeStrength({ context }) {
1561
+ context.set("swipeStrength", 1);
1562
+ },
1563
+ setRegistrySwiping({ computed }) {
1564
+ drawerRegistry.setSwiping(computed("drawerId"), true);
1565
+ },
1566
+ clearRegistrySwiping({ computed }) {
1567
+ drawerRegistry.setSwiping(computed("drawerId"), false);
472
1568
  },
473
1569
  toggleVisibility({ event, send, prop }) {
474
1570
  send({
475
1571
  type: prop("open") ? "CONTROLLED.OPEN" : "CONTROLLED.CLOSE",
476
1572
  previousEvent: event
477
1573
  });
1574
+ },
1575
+ syncDrawerStack({ context, prop, computed }) {
1576
+ const contentSize = context.get("contentSize");
1577
+ if (contentSize === null) return;
1578
+ const dragOffset = context.get("dragOffset");
1579
+ const progress = resolveSwipeProgress(contentSize, dragOffset, getActiveSnapOffset(context));
1580
+ const id = computed("drawerId");
1581
+ if (dragOffset !== null) drawerRegistry.setSwipeProgress(id, progress);
1582
+ const stack = prop("stack");
1583
+ if (!stack) return;
1584
+ stack.setHeight(id, contentSize);
1585
+ stack.setSwipe(id, dragOffset !== null, progress);
478
1586
  }
479
1587
  },
480
1588
  effects: {
1589
+ trackDrawerStack({ context, prop, computed }) {
1590
+ const stack = prop("stack");
1591
+ if (!stack) return;
1592
+ const id = computed("drawerId");
1593
+ stack.register(id);
1594
+ stack.setOpen(id, true);
1595
+ const sync = () => {
1596
+ const contentSize = context.get("contentSize");
1597
+ const dragOffset = context.get("dragOffset");
1598
+ const snapPointOffset = getActiveSnapOffset(context);
1599
+ stack.setHeight(id, contentSize ?? 0);
1600
+ stack.setSwipe(id, dragOffset !== null, resolveSwipeProgress(contentSize, dragOffset, snapPointOffset));
1601
+ };
1602
+ sync();
1603
+ return () => {
1604
+ stack.setSwipe(id, false, 0);
1605
+ stack.setOpen(id, false);
1606
+ stack.unregister(id);
1607
+ };
1608
+ },
481
1609
  trackDismissableElement({ scope, prop, send }) {
482
1610
  const getContentEl$1 = () => getContentEl(scope);
483
1611
  return trackDismissableElement(getContentEl$1, {
1612
+ type: "drawer",
484
1613
  defer: true,
1614
+ pointerBlocking: prop("modal"),
485
1615
  exclude: [getTriggerEl(scope)],
486
1616
  onInteractOutside(event) {
487
1617
  prop("onInteractOutside")?.(event);
@@ -513,7 +1643,7 @@ const machine = createMachine({
513
1643
  preventScroll: true,
514
1644
  returnFocusOnDeactivate: !!prop("restoreFocus"),
515
1645
  initialFocus: prop("initialFocusEl"),
516
- setReturnFocus: (el) => prop("finalFocusEl")?.() || el,
1646
+ setReturnFocus: (el) => prop("finalFocusEl")?.() ?? getTriggerEl(scope) ?? el,
517
1647
  getShadowRoot: true
518
1648
  });
519
1649
  },
@@ -522,105 +1652,115 @@ const machine = createMachine({
522
1652
  const getElements = () => [getContentEl(scope)];
523
1653
  return ariaHidden(getElements, { defer: true });
524
1654
  },
525
- trackPointerMove({ scope, send, prop }) {
526
- let lastY = 0;
527
- function onPointerMove(event) {
528
- send({
529
- type: "POINTER_MOVE",
530
- point: getEventPoint(event),
531
- target: getEventTarget(event)
532
- });
533
- }
534
- function onPointerUp(event) {
535
- if (event.pointerType === "touch") return;
536
- send({
537
- type: "POINTER_UP",
538
- point: getEventPoint(event)
539
- });
540
- }
541
- function onTouchStart(event) {
542
- if (!event.touches[0]) return;
543
- lastY = event.touches[0].clientY;
544
- }
545
- function onTouchMove(event) {
546
- if (!event.touches[0]) return;
547
- const point = getEventPoint(event);
548
- const target = event.target;
549
- if (!prop("preventDragOnScroll")) {
1655
+ trackPointerMove({ scope, send, refs, computed }) {
1656
+ return refs.get("swipeSession").bindDragTracking({
1657
+ getDoc: () => scope.getDoc(),
1658
+ getContentEl: () => getContentEl(scope),
1659
+ getSwipeAreaEl: () => getSwipeAreaEl(scope),
1660
+ swipeDirection: computed("physicalSwipeDirection"),
1661
+ onMove(details) {
550
1662
  send({
551
1663
  type: "POINTER_MOVE",
552
- point,
553
- target
1664
+ ...details
554
1665
  });
555
- return;
1666
+ },
1667
+ onEnd(details) {
1668
+ send({
1669
+ type: "POINTER_UP",
1670
+ ...details
1671
+ });
1672
+ },
1673
+ onCancel() {
1674
+ send({ type: "POINTER_CANCEL" });
556
1675
  }
557
- const contentEl = getContentEl(scope);
558
- if (!contentEl) return;
559
- let el = target;
560
- while (el && el !== contentEl && el.scrollHeight <= el.clientHeight) el = el.parentElement;
561
- if (el && el !== contentEl) {
562
- const scrollTop = el.scrollTop;
563
- const y = event.touches[0].clientY;
564
- if (scrollTop <= 0 && y > lastY) event.preventDefault();
565
- lastY = y;
1676
+ });
1677
+ },
1678
+ trackSizeMeasurements({ context, scope, computed, prop }) {
1679
+ const contentEl = getContentEl(scope);
1680
+ if (!contentEl) return;
1681
+ const html = scope.getDoc().documentElement;
1682
+ const shouldMeasureRootFontSize = hasRemSnapPoints(prop("snapPoints"));
1683
+ const updateSize = () => {
1684
+ const direction = computed("physicalSwipeDirection");
1685
+ const rect = contentEl.getBoundingClientRect();
1686
+ const viewportSize = isVerticalSwipeDirection(direction) ? html.clientHeight : html.clientWidth;
1687
+ context.set("contentSize", getSwipeDirectionSize(rect, direction));
1688
+ context.set("viewportSize", viewportSize);
1689
+ if (shouldMeasureRootFontSize) {
1690
+ const rootFontSize = Number.parseFloat(getComputedStyle(html).fontSize);
1691
+ if (Number.isFinite(rootFontSize)) context.set("rootFontSize", rootFontSize);
566
1692
  }
567
- send({
568
- type: "POINTER_MOVE",
569
- point,
570
- target
571
- });
572
- }
573
- function onTouchEnd(event) {
574
- if (event.touches.length !== 0) return;
575
- send({
576
- type: "POINTER_UP",
577
- point: getEventPoint(event)
578
- });
579
- }
580
- const doc = scope.getDoc();
581
- const cleanups = [
582
- addDomEvent(doc, "pointermove", onPointerMove),
583
- addDomEvent(doc, "pointerup", onPointerUp),
584
- addDomEvent(doc, "touchstart", onTouchStart, { passive: false }),
585
- addDomEvent(doc, "touchmove", onTouchMove, { passive: false }),
586
- addDomEvent(doc, "touchend", onTouchEnd)
587
- ];
1693
+ };
1694
+ updateSize();
1695
+ const cleanups = [resizeObserverBorderBox.observe(contentEl, updateSize), addDomEvent(scope.getWin(), "resize", updateSize)];
588
1696
  return () => {
589
- cleanups.forEach((cleanup) => cleanup());
1697
+ cleanups.forEach((cleanup) => cleanup?.());
590
1698
  };
591
1699
  },
592
- trackContentHeight({ context, scope }) {
1700
+ trackNestedDrawerMetrics({ scope, computed }) {
593
1701
  const contentEl = getContentEl(scope);
594
1702
  if (!contentEl) return;
595
- const updateHeight = () => {
596
- const rect = contentEl.getBoundingClientRect();
597
- context.set("contentHeight", rect.height);
1703
+ const id = computed("drawerId");
1704
+ drawerRegistry.register(id, contentEl);
1705
+ const sync = () => {
1706
+ const entries = [...drawerRegistry.getEntries().entries()];
1707
+ const myIndex = entries.findIndex(([entryId]) => entryId === id);
1708
+ if (myIndex === -1) return;
1709
+ const nestedCount = entries.length - 1 - myIndex;
1710
+ const frontmostHeight = (entries[entries.length - 1]?.[1])?.getBoundingClientRect().height ?? 0;
1711
+ const myHeight = contentEl.getBoundingClientRect().height;
1712
+ contentEl.style.setProperty("--nested-drawers", `${nestedCount}`);
1713
+ contentEl.style.setProperty("--drawer-height", `${myHeight}px`);
1714
+ contentEl.style.setProperty("--drawer-frontmost-height", `${frontmostHeight}px`);
1715
+ if (nestedCount > 0 && frontmostHeight > 0) contentEl.setAttribute("data-nested-drawer-open", "");
1716
+ else if (nestedCount === 0) contentEl.removeAttribute("data-nested-drawer-open");
1717
+ if (drawerRegistry.hasSwipingAfter(id)) contentEl.setAttribute("data-nested-drawer-swiping", "");
1718
+ else contentEl.removeAttribute("data-nested-drawer-swiping");
1719
+ };
1720
+ sync();
1721
+ const cleanups = [
1722
+ drawerRegistry.subscribe(sync),
1723
+ resizeObserverBorderBox.observe(contentEl, () => drawerRegistry.notify()),
1724
+ addDomEvent(scope.getWin(), "resize", () => drawerRegistry.notify())
1725
+ ];
1726
+ return () => {
1727
+ cleanups.forEach((cleanup) => cleanup?.());
1728
+ contentEl.removeAttribute("data-nested-drawer-open");
1729
+ contentEl.removeAttribute("data-nested-drawer-swiping");
1730
+ contentEl.style.setProperty("--nested-drawers", "0");
1731
+ contentEl.style.removeProperty("--drawer-frontmost-height");
1732
+ drawerRegistry.unregister(id);
598
1733
  };
599
- updateHeight();
600
- return resizeObserverBorderBox.observe(contentEl, updateHeight);
601
1734
  },
602
- trackExitAnimation({ send, scope }) {
603
- let cleanup;
604
- const rafCleanup = raf(() => {
605
- const contentEl = getContentEl(scope);
606
- if (!contentEl) return;
607
- const animationName = getComputedStyle(contentEl).animationName;
608
- if (!animationName || animationName === "none") {
609
- send({ type: "ANIMATION_END" });
610
- return;
1735
+ trackSwipeOpenPointerMove({ scope, send, refs, computed }) {
1736
+ return refs.get("swipeSession").bindSwipeOpenTracking({
1737
+ getDoc: () => scope.getDoc(),
1738
+ getContentEl: () => getContentEl(scope),
1739
+ getSwipeAreaEl: () => getSwipeAreaEl(scope),
1740
+ swipeDirection: computed("physicalSwipeDirection"),
1741
+ onMove(details) {
1742
+ send({
1743
+ type: "POINTER_MOVE",
1744
+ ...details
1745
+ });
1746
+ },
1747
+ onEnd(details) {
1748
+ send({
1749
+ type: "POINTER_UP",
1750
+ ...details
1751
+ });
1752
+ },
1753
+ onCancel() {
1754
+ send({ type: "POINTER_CANCEL" });
611
1755
  }
612
- const onEnd = (event) => {
613
- if (getEventTarget(event) === contentEl) send({ type: "ANIMATION_END" });
614
- };
615
- contentEl.addEventListener("animationend", onEnd);
616
- cleanup = () => {
617
- contentEl.removeEventListener("animationend", onEnd);
618
- };
619
1756
  });
620
- return () => {
621
- rafCleanup();
622
- cleanup?.();
623
- };
1757
+ },
1758
+ trackExitAnimation({ send, scope }) {
1759
+ const contentEl = getContentEl(scope);
1760
+ if (!contentEl) return;
1761
+ return addDomEvent(contentEl, "exitcomplete", () => {
1762
+ send({ type: "ANIMATION_END" });
1763
+ });
624
1764
  }
625
1765
  }
626
1766
  }
@@ -638,6 +1778,8 @@ const props = createProps()([
638
1778
  "defaultOpen",
639
1779
  "getRootNode",
640
1780
  "snapPoints",
1781
+ "swipeDirection",
1782
+ "snapToSequentialPoints",
641
1783
  "swipeVelocityThreshold",
642
1784
  "closeThreshold",
643
1785
  "preventDragOnScroll",
@@ -653,10 +1795,133 @@ const props = createProps()([
653
1795
  "restoreFocus",
654
1796
  "role",
655
1797
  "trapFocus",
656
- "defaultActiveSnapPoint",
657
- "activeSnapPoint",
658
- "onActiveSnapPointChange"
1798
+ "defaultSnapPoint",
1799
+ "snapPoint",
1800
+ "onSnapPointChange",
1801
+ "stack"
659
1802
  ]);
660
1803
  const splitProps = createSplitProps(props);
661
1804
  //#endregion
662
- export { anatomy, connect, machine, props, splitProps };
1805
+ //#region src/machines/drawer/drawer.stack.ts
1806
+ function resolveSnapshot(entries) {
1807
+ let openCount = 0;
1808
+ let frontmostHeight = 0;
1809
+ let swipeProgress = 0;
1810
+ let frontmostOrder = -1;
1811
+ entries.forEach((entry) => {
1812
+ if (!entry.open) return;
1813
+ openCount += 1;
1814
+ if (entry.order < frontmostOrder) return;
1815
+ frontmostOrder = entry.order;
1816
+ frontmostHeight = entry.height > 0 ? entry.height : 0;
1817
+ swipeProgress = entry.swiping ? clampValue(entry.swipeProgress, 0, 1) : 0;
1818
+ });
1819
+ return {
1820
+ active: openCount > 0,
1821
+ openCount,
1822
+ swipeProgress,
1823
+ frontmostHeight
1824
+ };
1825
+ }
1826
+ function createStack() {
1827
+ const entries = /* @__PURE__ */ new Map();
1828
+ const listeners = /* @__PURE__ */ new Set();
1829
+ let order = 0;
1830
+ let pendingNotify = false;
1831
+ let snapshot = Object.freeze({
1832
+ active: false,
1833
+ openCount: 0,
1834
+ swipeProgress: 0,
1835
+ frontmostHeight: 0
1836
+ });
1837
+ const scheduleNotify = () => {
1838
+ if (pendingNotify) return;
1839
+ pendingNotify = true;
1840
+ queueMicrotask(() => {
1841
+ pendingNotify = false;
1842
+ listeners.forEach((listener) => listener());
1843
+ });
1844
+ };
1845
+ const notify = () => {
1846
+ const nextSnapshot = resolveSnapshot(entries);
1847
+ if (snapshot.active === nextSnapshot.active && snapshot.openCount === nextSnapshot.openCount && snapshot.swipeProgress === nextSnapshot.swipeProgress && snapshot.frontmostHeight === nextSnapshot.frontmostHeight) return;
1848
+ snapshot = Object.freeze(nextSnapshot);
1849
+ scheduleNotify();
1850
+ };
1851
+ const ensureEntry = (id) => {
1852
+ let entry = entries.get(id);
1853
+ if (!entry) {
1854
+ entry = {
1855
+ order: ++order,
1856
+ open: false,
1857
+ height: 0,
1858
+ swiping: false,
1859
+ swipeProgress: 0
1860
+ };
1861
+ entries.set(id, entry);
1862
+ }
1863
+ return entry;
1864
+ };
1865
+ return {
1866
+ getSnapshot() {
1867
+ return snapshot;
1868
+ },
1869
+ subscribe(listener) {
1870
+ listeners.add(listener);
1871
+ return () => {
1872
+ listeners.delete(listener);
1873
+ };
1874
+ },
1875
+ register(id) {
1876
+ ensureEntry(id);
1877
+ notify();
1878
+ },
1879
+ unregister(id) {
1880
+ if (!entries.delete(id)) return;
1881
+ notify();
1882
+ },
1883
+ setOpen(id, open) {
1884
+ const entry = ensureEntry(id);
1885
+ if (entry.open === open) return;
1886
+ entry.open = open;
1887
+ if (!open) {
1888
+ entry.swiping = false;
1889
+ entry.swipeProgress = 0;
1890
+ }
1891
+ notify();
1892
+ },
1893
+ setHeight(id, height) {
1894
+ const entry = ensureEntry(id);
1895
+ const nextHeight = Number.isFinite(height) && height > 0 ? height : 0;
1896
+ if (entry.height === nextHeight) return;
1897
+ entry.height = nextHeight;
1898
+ notify();
1899
+ },
1900
+ setSwipe(id, swiping, progress) {
1901
+ const entry = ensureEntry(id);
1902
+ const nextProgress = swiping ? clampValue(Number.isFinite(progress) ? progress : 0, 0, 1) : 0;
1903
+ if (entry.swiping === swiping && entry.swipeProgress === nextProgress) return;
1904
+ entry.swiping = swiping;
1905
+ entry.swipeProgress = nextProgress;
1906
+ notify();
1907
+ }
1908
+ };
1909
+ }
1910
+ function connectStack(snapshot, normalize) {
1911
+ const getIndentProps = () => {
1912
+ return normalize.element({
1913
+ "data-active": snapshot.active ? "" : void 0,
1914
+ "data-inactive": snapshot.active ? void 0 : "",
1915
+ style: {
1916
+ "--drawer-swipe-progress": `${clampValue(snapshot.swipeProgress, 0, 1)}`,
1917
+ "--drawer-frontmost-height": snapshot.frontmostHeight > 0 ? `${snapshot.frontmostHeight}px` : void 0
1918
+ }
1919
+ });
1920
+ };
1921
+ return {
1922
+ getIndentProps,
1923
+ getIndentBackgroundProps: getIndentProps
1924
+ };
1925
+ }
1926
+ //#endregion
1927
+ export { anatomy, connect, connectStack, createStack, machine, props, splitProps };