@dreamtree-org/twreact-ui 1.3.11 → 2.0.0

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 (164) hide show
  1. package/README.md +40 -6
  2. package/ai-skills/dreamtree-ui.md +47 -9
  3. package/codemods/v2.cjs +198 -0
  4. package/dist/ai-schema.json +609 -33
  5. package/dist/components/blocks/AppShell.d.ts.map +1 -1
  6. package/dist/components/blocks/BottomActionBar.d.ts +6 -0
  7. package/dist/components/blocks/BottomActionBar.d.ts.map +1 -0
  8. package/dist/components/blocks/ConfirmDialog.d.ts.map +1 -1
  9. package/dist/components/blocks/PageHeader.d.ts.map +1 -1
  10. package/dist/components/core/Checkbox.d.ts.map +1 -1
  11. package/dist/components/core/DatePicker.d.ts.map +1 -1
  12. package/dist/components/core/DateRangePicker.d.ts.map +1 -1
  13. package/dist/components/core/Form.d.ts.map +1 -1
  14. package/dist/components/core/Input.d.ts.map +1 -1
  15. package/dist/components/core/Popover.d.ts.map +1 -1
  16. package/dist/components/core/ProgressBar.d.ts.map +1 -1
  17. package/dist/components/core/Rate.d.ts.map +1 -1
  18. package/dist/components/core/SearchField.d.ts +9 -0
  19. package/dist/components/core/SearchField.d.ts.map +1 -0
  20. package/dist/components/core/SegmentedControl.d.ts +8 -0
  21. package/dist/components/core/SegmentedControl.d.ts.map +1 -0
  22. package/dist/components/core/Select.d.ts.map +1 -1
  23. package/dist/components/core/SwipeableListItem.d.ts +4 -0
  24. package/dist/components/core/SwipeableListItem.d.ts.map +1 -0
  25. package/dist/components/core/SwipeableListItemGesture.d.ts +58 -0
  26. package/dist/components/core/SwipeableListItemGesture.d.ts.map +1 -0
  27. package/dist/components/core/Switch.d.ts.map +1 -1
  28. package/dist/components/core/Table/ActionMenu.d.ts +10 -3
  29. package/dist/components/core/Table/ActionMenu.d.ts.map +1 -1
  30. package/dist/components/core/Table/MobileCard.d.ts +10 -1
  31. package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
  32. package/dist/components/core/Table/MobileFilters.d.ts +8 -2
  33. package/dist/components/core/Table/MobileFilters.d.ts.map +1 -1
  34. package/dist/components/core/Table/Table.d.ts.map +1 -1
  35. package/dist/components/core/Table/TableRow.d.ts +2 -1
  36. package/dist/components/core/Table/TableRow.d.ts.map +1 -1
  37. package/dist/components/core/Tabs.d.ts +3 -3
  38. package/dist/components/core/Tabs.d.ts.map +1 -1
  39. package/dist/components/core/ThreeDotPopover.d.ts +10 -22
  40. package/dist/components/core/ThreeDotPopover.d.ts.map +1 -1
  41. package/dist/components/core/Tooltip.d.ts +2 -1
  42. package/dist/components/core/Tooltip.d.ts.map +1 -1
  43. package/dist/components/core/internal/Calendar.d.ts +77 -0
  44. package/dist/components/core/internal/Calendar.d.ts.map +1 -0
  45. package/dist/components/core/menu/ActionMenuSurface.d.ts +42 -0
  46. package/dist/components/core/menu/ActionMenuSurface.d.ts.map +1 -0
  47. package/dist/components/core/menu/ActionSheetMenu.d.ts +17 -0
  48. package/dist/components/core/menu/ActionSheetMenu.d.ts.map +1 -0
  49. package/dist/components/core/menu/MenuPopover.d.ts +21 -0
  50. package/dist/components/core/menu/MenuPopover.d.ts.map +1 -0
  51. package/dist/components/core/menu/menuItems.d.ts +21 -0
  52. package/dist/components/core/menu/menuItems.d.ts.map +1 -0
  53. package/dist/components/core/menu/menuKeyboard.d.ts +16 -0
  54. package/dist/components/core/menu/menuKeyboard.d.ts.map +1 -0
  55. package/dist/components/feedback/ActionSheet.d.ts +4 -0
  56. package/dist/components/feedback/ActionSheet.d.ts.map +1 -0
  57. package/dist/components/feedback/Alert.d.ts.map +1 -1
  58. package/dist/components/feedback/Dialog.d.ts.map +1 -1
  59. package/dist/components/feedback/Drawer.d.ts.map +1 -1
  60. package/dist/components/feedback/Sheet.d.ts +4 -0
  61. package/dist/components/feedback/Sheet.d.ts.map +1 -0
  62. package/dist/components/feedback/Toast.d.ts +4 -3
  63. package/dist/components/feedback/Toast.d.ts.map +1 -1
  64. package/dist/components/feedback/ToastItem.d.ts +47 -0
  65. package/dist/components/feedback/ToastItem.d.ts.map +1 -0
  66. package/dist/components/feedback/ToastStack.d.ts +20 -0
  67. package/dist/components/feedback/ToastStack.d.ts.map +1 -0
  68. package/dist/components/feedback/ToastSwipe.d.ts +22 -0
  69. package/dist/components/feedback/ToastSwipe.d.ts.map +1 -0
  70. package/dist/components/feedback/Tour.d.ts.map +1 -1
  71. package/dist/components/feedback/sheetCore.d.ts +30 -0
  72. package/dist/components/feedback/sheetCore.d.ts.map +1 -0
  73. package/dist/components/navigation/Navbar.d.ts.map +1 -1
  74. package/dist/components/navigation/Sidebar.d.ts +6 -0
  75. package/dist/components/navigation/Sidebar.d.ts.map +1 -1
  76. package/dist/components/rich-text/RichTextEditor.d.ts.map +1 -1
  77. package/dist/components/rich-text/RichTextToolbar.d.ts +6 -2
  78. package/dist/components/rich-text/RichTextToolbar.d.ts.map +1 -1
  79. package/dist/components/utility/PullToRefresh.d.ts +5 -0
  80. package/dist/components/utility/PullToRefresh.d.ts.map +1 -0
  81. package/dist/esm/components/blocks/AppShell.js +97 -90
  82. package/dist/esm/components/blocks/BottomActionBar.js +132 -0
  83. package/dist/esm/components/blocks/ConfirmDialog.js +40 -4
  84. package/dist/esm/components/blocks/PageHeader.js +16 -3
  85. package/dist/esm/components/core/Checkbox.js +13 -24
  86. package/dist/esm/components/core/DatePicker.js +117 -304
  87. package/dist/esm/components/core/DateRangePicker.js +260 -308
  88. package/dist/esm/components/core/Form.js +11 -2
  89. package/dist/esm/components/core/Input.js +13 -18
  90. package/dist/esm/components/core/Popover.js +59 -48
  91. package/dist/esm/components/core/ProgressBar.js +36 -64
  92. package/dist/esm/components/core/Rate.js +5 -10
  93. package/dist/esm/components/core/SearchField.js +311 -0
  94. package/dist/esm/components/core/SegmentedControl.js +189 -0
  95. package/dist/esm/components/core/Select.js +185 -190
  96. package/dist/esm/components/core/SwipeableListItem.js +286 -0
  97. package/dist/esm/components/core/SwipeableListItemGesture.js +219 -0
  98. package/dist/esm/components/core/Switch.js +11 -13
  99. package/dist/esm/components/core/Table/ActionMenu.js +45 -231
  100. package/dist/esm/components/core/Table/ColumnMenu.js +1 -1
  101. package/dist/esm/components/core/Table/MobileCard.js +66 -16
  102. package/dist/esm/components/core/Table/MobileFilters.js +181 -10
  103. package/dist/esm/components/core/Table/Table.js +149 -102
  104. package/dist/esm/components/core/Table/TableRow.js +10 -2
  105. package/dist/esm/components/core/Tabs.js +7 -7
  106. package/dist/esm/components/core/ThreeDotPopover.js +93 -99
  107. package/dist/esm/components/core/Tooltip.js +138 -145
  108. package/dist/esm/components/core/internal/Calendar.js +381 -0
  109. package/dist/esm/components/core/menu/ActionMenuSurface.js +110 -0
  110. package/dist/esm/components/core/menu/ActionSheetMenu.js +140 -0
  111. package/dist/esm/components/core/menu/MenuPopover.js +177 -0
  112. package/dist/esm/components/core/menu/menuItems.js +61 -0
  113. package/dist/esm/components/core/menu/menuKeyboard.js +54 -0
  114. package/dist/esm/components/feedback/ActionSheet.js +55 -0
  115. package/dist/esm/components/feedback/Alert.js +63 -53
  116. package/dist/esm/components/feedback/Dialog.js +141 -53
  117. package/dist/esm/components/feedback/Drawer.js +130 -132
  118. package/dist/esm/components/feedback/Sheet.js +125 -0
  119. package/dist/esm/components/feedback/Toast.js +21 -291
  120. package/dist/esm/components/feedback/ToastItem.js +348 -0
  121. package/dist/esm/components/feedback/ToastStack.js +130 -0
  122. package/dist/esm/components/feedback/ToastSwipe.js +52 -0
  123. package/dist/esm/components/feedback/Tour.js +107 -30
  124. package/dist/esm/components/feedback/sheetCore.js +494 -0
  125. package/dist/esm/components/navigation/Dropdown.js +1 -1
  126. package/dist/esm/components/navigation/Navbar.js +172 -37
  127. package/dist/esm/components/navigation/Sidebar.js +263 -111
  128. package/dist/esm/components/rich-text/RichTextEditor.js +9 -3
  129. package/dist/esm/components/rich-text/RichTextToolbar.js +29 -9
  130. package/dist/esm/components/utility/Portal.js +31 -0
  131. package/dist/esm/components/utility/PullToRefresh.js +229 -0
  132. package/dist/esm/hooks/useFloatingPosition.js +82 -0
  133. package/dist/esm/hooks/useHaptics.js +65 -0
  134. package/dist/esm/hooks/usePresence.js +58 -0
  135. package/dist/esm/hooks/usePresentation.js +21 -0
  136. package/dist/esm/hooks/useSheetDrag.js +220 -0
  137. package/dist/esm/index.js +13 -2
  138. package/dist/esm/utils/position.js +10 -63
  139. package/dist/hooks/useFloatingPosition.d.ts +8 -4
  140. package/dist/hooks/useFloatingPosition.d.ts.map +1 -1
  141. package/dist/hooks/useHaptics.d.ts +19 -0
  142. package/dist/hooks/useHaptics.d.ts.map +1 -0
  143. package/dist/hooks/useSheetDrag.d.ts +31 -0
  144. package/dist/hooks/useSheetDrag.d.ts.map +1 -0
  145. package/dist/index.css +1 -1
  146. package/dist/index.d.ts +9 -1
  147. package/dist/index.esm.css +1 -1
  148. package/dist/index.esm.js +8851 -3198
  149. package/dist/index.js +8856 -3195
  150. package/dist/rich-text/index.js +41 -11
  151. package/dist/types/blocks.d.ts +32 -2
  152. package/dist/types/blocks.d.ts.map +1 -1
  153. package/dist/types/index.d.ts +353 -19
  154. package/dist/types/index.d.ts.map +1 -1
  155. package/dist/types/rich-text.d.ts +5 -0
  156. package/dist/types/rich-text.d.ts.map +1 -1
  157. package/dist/utils/position.d.ts +0 -15
  158. package/dist/utils/position.d.ts.map +1 -1
  159. package/doc/README.md +15 -0
  160. package/mcp/catalog.snapshot.json +1398 -464
  161. package/package.json +7 -2
  162. package/dist/esm/hooks/useSwipeToClose.js +0 -66
  163. package/dist/hooks/useSwipeToClose.d.ts +0 -24
  164. package/dist/hooks/useSwipeToClose.d.ts.map +0 -1
@@ -4,6 +4,7 @@ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
4
4
  import React, { useState, useRef, useEffect } from 'react';
5
5
  import { Redo, Undo, Link, SquareCode, Quote, ListOrdered, List, Heading3, Heading2, Heading1, Code, Strikethrough, Italic, Bold } from 'lucide-react';
6
6
  import { cn } from '../../utils/cn.js';
7
+ import { TOUCH_TARGET } from '../../utils/touchTarget.js';
7
8
 
8
9
  var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || "");
9
10
  var shortcutLabel = function shortcutLabel(keys) {
@@ -285,9 +286,23 @@ function LinkForm(_ref) {
285
286
  });
286
287
  }
287
288
  var formButton = "h-8 rounded-md px-2 text-sm font-medium text-gray-700 hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 dark:text-gray-200 dark:hover:bg-gray-800";
289
+ // Keeps the editor selection when a toolbar button is pressed. `pointerdown`
290
+ // covers touch and pen (iOS drops the selection on a plain tap otherwise);
291
+ // `mousedown` stays for desktop browsers that move focus on mousedown even
292
+ // when pointerdown was cancelled.
293
+ var keepSelection = function keepSelection(e) {
294
+ return e.preventDefault();
295
+ };
296
+ /** `top` offset for a sticky toolbar: `true` → 0, a number → px, a string → any CSS length. */
297
+ function stickyTop(sticky) {
298
+ if (typeof sticky === "number") return "".concat(sticky, "px");
299
+ if (typeof sticky === "string" && sticky) return sticky;
300
+ return "0px";
301
+ }
288
302
  /**
289
303
  * A WAI-ARIA toolbar: one tab stop, arrow keys / Home / End move between the
290
- * buttons (roving tabindex), toggles report `aria-pressed`.
304
+ * buttons (roving tabindex), toggles report `aria-pressed`. With `sticky` it
305
+ * stays pinned to the top of the scrolling page while the content scrolls.
291
306
  */
292
307
  function RichTextToolbar(_ref2) {
293
308
  var editor = _ref2.editor,
@@ -296,7 +311,9 @@ function RichTextToolbar(_ref2) {
296
311
  disabled = _ref2.disabled,
297
312
  controlsId = _ref2.controlsId,
298
313
  _ref2$label = _ref2.label,
299
- label = _ref2$label === void 0 ? "Formatting" : _ref2$label;
314
+ label = _ref2$label === void 0 ? "Formatting" : _ref2$label,
315
+ _ref2$sticky = _ref2.sticky,
316
+ sticky = _ref2$sticky === void 0 ? false : _ref2$sticky;
300
317
  var _useState5 = useState(0),
301
318
  _useState6 = _slicedToArray(_useState5, 2),
302
319
  focusIndex = _useState6[0],
@@ -345,7 +362,11 @@ function RichTextToolbar(_ref2) {
345
362
  "aria-controls": controlsId,
346
363
  "aria-orientation": "horizontal",
347
364
  onKeyDown: onKeyDown,
348
- className: "flex flex-wrap items-center gap-0.5 border-b border-gray-200 bg-gray-50 px-1 py-1 dark:border-gray-700 dark:bg-gray-800/60",
365
+ "data-sticky": sticky ? "" : undefined,
366
+ style: sticky ? {
367
+ top: stickyTop(sticky)
368
+ } : undefined,
369
+ className: cn("flex flex-wrap items-center gap-0.5 border-b border-line bg-background px-1 py-1", sticky && "sticky z-sticky"),
349
370
  children: keys.map(function (key, i) {
350
371
  var item = TOOLBAR_ITEMS[key];
351
372
  var Icon = item.icon;
@@ -354,7 +375,7 @@ function RichTextToolbar(_ref2) {
354
375
  var newGroup = i > 0 && TOOLBAR_ITEMS[keys[i - 1]].group !== item.group;
355
376
  return jsxs(React.Fragment, {
356
377
  children: [newGroup && jsx("span", {
357
- className: "mx-1 h-5 w-px bg-gray-300 dark:bg-gray-600",
378
+ className: "mx-1 h-5 w-px bg-line",
358
379
  "aria-hidden": "true"
359
380
  }), jsx("button", {
360
381
  ref: function ref(node) {
@@ -371,13 +392,12 @@ function RichTextToolbar(_ref2) {
371
392
  onFocus: function onFocus() {
372
393
  return setFocusIndex(i);
373
394
  },
374
- onMouseDown: function onMouseDown(e) {
375
- return e.preventDefault();
376
- },
395
+ onPointerDown: keepSelection,
396
+ onMouseDown: keepSelection,
377
397
  onClick: function onClick() {
378
398
  return activate(key);
379
399
  },
380
- className: cn("inline-flex h-8 w-8 items-center justify-center rounded-md text-gray-600 transition-colors", "hover:bg-gray-200 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-gray-700 dark:hover:text-gray-100", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500", "disabled:cursor-not-allowed disabled:opacity-50", pressed && "bg-primary-100 text-primary-700 dark:bg-primary-900/40 dark:text-primary-300", unavailable && "opacity-40"),
400
+ className: cn("inline-flex h-8 w-8 items-center justify-center rounded-md text-muted transition-colors duration-fast motion-reduce:transition-none", TOUCH_TARGET, "hover:bg-muted/10 hover:text-foreground", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500", "disabled:cursor-not-allowed disabled:opacity-50", pressed && "bg-primary-100 text-primary-700 dark:bg-primary-900/40 dark:text-primary-300", unavailable && "opacity-40"),
381
401
  children: jsx(Icon, {
382
402
  className: "h-4 w-4",
383
403
  "aria-hidden": "true"
@@ -394,4 +414,4 @@ function RichTextToolbar(_ref2) {
394
414
  });
395
415
  }
396
416
 
397
- export { DEFAULT_TOOLBAR, RichTextToolbar, TOOLBAR_ITEMS, normalizeUrl, selectToolbarState };
417
+ export { DEFAULT_TOOLBAR, RichTextToolbar, TOOLBAR_ITEMS, normalizeUrl, selectToolbarState, stickyTop };
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+ import { useSyncExternalStore } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+
5
+ /**
6
+ * Internal (not exported). Renders `children` into `container` (default
7
+ * `document.body`). Renders nothing on the server and on the first client
8
+ * render, so hydration matches; the portal appears right after mount.
9
+ *
10
+ * @param {{ container?: Element | null, children?: React.ReactNode }} props
11
+ */
12
+ var subscribe = function subscribe() {
13
+ return function () {};
14
+ };
15
+ var getClientSnapshot = function getClientSnapshot() {
16
+ return true;
17
+ };
18
+ var getServerSnapshot = function getServerSnapshot() {
19
+ return false;
20
+ };
21
+ function Portal(_ref) {
22
+ var container = _ref.container,
23
+ children = _ref.children;
24
+ // false on the server and during hydration, true on the client after it.
25
+ var mounted = useSyncExternalStore(subscribe, getClientSnapshot, getServerSnapshot);
26
+ if (!mounted || typeof document === "undefined") return null;
27
+ var target = container || document.body;
28
+ return target ? /*#__PURE__*/createPortal(children, target) : null;
29
+ }
30
+
31
+ export { Portal as default };
@@ -0,0 +1,229 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import { forwardRef, useState, useRef, useEffect } from 'react';
5
+ import { RefreshCw } from 'lucide-react';
6
+ import { cn } from '../../utils/cn.js';
7
+ import { useLatestRef } from '../../hooks/useLatestRef.js';
8
+ import { useReducedMotion } from '../../hooks/useReducedMotion.js';
9
+
10
+ var _excluded = ["onRefresh", "threshold", "maxPull", "disabled", "refreshingLabel", "scrollRef", "className", "contentClassName", "children"];
11
+ // Touch pull-to-refresh around a scroll container (DTUI-56). The gesture only
12
+ // starts while the scroller sits at scrollTop 0 and the finger moves down, so
13
+ // normal scrolling is never intercepted. The page itself keeps the global
14
+ // `overscroll-behavior-y: none` baseline, so the browser's own pull-to-refresh
15
+ // does not fight this one.
16
+ var INDICATOR_SIZE = 40;
17
+ // Rubber-band resistance: 1:1 at first, asymptotic to `max`.
18
+ function resist(distance, max) {
19
+ if (distance <= 0 || max <= 0) return 0;
20
+ return max * (1 - 1 / (distance / max + 1));
21
+ }
22
+ var PullToRefresh = /*#__PURE__*/forwardRef(function PullToRefresh(_ref, ref) {
23
+ var onRefresh = _ref.onRefresh,
24
+ _ref$threshold = _ref.threshold,
25
+ threshold = _ref$threshold === void 0 ? 64 : _ref$threshold,
26
+ maxPull = _ref.maxPull,
27
+ _ref$disabled = _ref.disabled,
28
+ disabled = _ref$disabled === void 0 ? false : _ref$disabled,
29
+ _ref$refreshingLabel = _ref.refreshingLabel,
30
+ refreshingLabel = _ref$refreshingLabel === void 0 ? "Refreshing…" : _ref$refreshingLabel,
31
+ scrollRef = _ref.scrollRef,
32
+ className = _ref.className,
33
+ contentClassName = _ref.contentClassName,
34
+ children = _ref.children,
35
+ rest = _objectWithoutProperties(_ref, _excluded);
36
+ var limit = Math.max(threshold, maxPull !== null && maxPull !== void 0 ? maxPull : threshold * 2);
37
+ var reducedMotion = useReducedMotion();
38
+ var _useState = useState(null),
39
+ _useState2 = _slicedToArray(_useState, 2),
40
+ scrollEl = _useState2[0],
41
+ setScrollEl = _useState2[1];
42
+ var _useState3 = useState(0),
43
+ _useState4 = _slicedToArray(_useState3, 2),
44
+ pull = _useState4[0],
45
+ setPull = _useState4[1];
46
+ var _useState5 = useState(false),
47
+ _useState6 = _slicedToArray(_useState5, 2),
48
+ dragging = _useState6[0],
49
+ setDragging = _useState6[1];
50
+ var _useState7 = useState(false),
51
+ _useState8 = _slicedToArray(_useState7, 2),
52
+ refreshing = _useState8[0],
53
+ setRefreshing = _useState8[1];
54
+ var gesture = useRef(null);
55
+ var pullRef = useRef(0);
56
+ var busy = useRef(false);
57
+ var mounted = useRef(true);
58
+ var config = useLatestRef({
59
+ onRefresh: onRefresh,
60
+ threshold: threshold,
61
+ limit: limit,
62
+ disabled: disabled
63
+ });
64
+ useEffect(function () {
65
+ mounted.current = true;
66
+ return function () {
67
+ mounted.current = false;
68
+ };
69
+ }, []);
70
+ useEffect(function () {
71
+ var el = scrollRef ? scrollRef.current : scrollEl;
72
+ if (!el || disabled) return undefined;
73
+ var setDistance = function setDistance(value) {
74
+ pullRef.current = value;
75
+ setPull(value);
76
+ };
77
+ var settle = function settle() {
78
+ busy.current = false;
79
+ if (!mounted.current) return;
80
+ setRefreshing(false);
81
+ setDistance(0);
82
+ };
83
+ var startRefresh = function startRefresh() {
84
+ busy.current = true;
85
+ setRefreshing(true);
86
+ setDistance(config.current.threshold);
87
+ // A sync throw or a rejection retracts the indicator too; onRefresh owns its errors.
88
+ new Promise(function (resolve) {
89
+ var _config$current$onRef, _config$current;
90
+ return resolve((_config$current$onRef = (_config$current = config.current).onRefresh) === null || _config$current$onRef === void 0 ? void 0 : _config$current$onRef.call(_config$current));
91
+ }).then(settle, settle);
92
+ };
93
+ var begin = function begin(touch) {
94
+ gesture.current = {
95
+ x: touch.clientX,
96
+ y: touch.clientY,
97
+ axis: null
98
+ };
99
+ };
100
+ var onTouchStart = function onTouchStart(e) {
101
+ gesture.current = null;
102
+ if (busy.current || e.touches.length !== 1 || el.scrollTop > 0) return;
103
+ begin(e.touches[0]);
104
+ };
105
+ var onTouchMove = function onTouchMove(e) {
106
+ if (busy.current || e.touches.length !== 1) return;
107
+ var touch = e.touches[0];
108
+ // A scroll that returns to the top mid-gesture can still pull from there.
109
+ if (!gesture.current) {
110
+ if (el.scrollTop > 0) return;
111
+ begin(touch);
112
+ return;
113
+ }
114
+ var g = gesture.current;
115
+ var dy = touch.clientY - g.y;
116
+ if (!g.axis) {
117
+ var dx = touch.clientX - g.x;
118
+ if (Math.abs(dx) < 4 && Math.abs(dy) < 4) return;
119
+ g.axis = Math.abs(dx) > Math.abs(dy) || dy < 0 || el.scrollTop > 0 ? "none" : "pull";
120
+ if (g.axis === "pull") setDragging(true);
121
+ }
122
+ if (g.axis !== "pull") return;
123
+ if (el.scrollTop > 0) {
124
+ // The user pushed back up and scrolled the content: hand the gesture back.
125
+ g.axis = "none";
126
+ setDragging(false);
127
+ setDistance(0);
128
+ return;
129
+ }
130
+ if (dy <= 0) {
131
+ setDistance(0);
132
+ return;
133
+ }
134
+ // Only now, mid-pull at the top, keep the browser from also panning.
135
+ if (e.cancelable) e.preventDefault();
136
+ setDistance(resist(dy, config.current.limit));
137
+ };
138
+ var finish = function finish(commit) {
139
+ var g = gesture.current;
140
+ gesture.current = null;
141
+ if (!g || g.axis !== "pull") return;
142
+ setDragging(false);
143
+ if (commit && pullRef.current >= config.current.threshold) startRefresh();else setDistance(0);
144
+ };
145
+ var onTouchEnd = function onTouchEnd() {
146
+ return finish(true);
147
+ };
148
+ var onTouchCancel = function onTouchCancel() {
149
+ return finish(false);
150
+ };
151
+ // touchmove must be non-passive to stop the pan while pulling; the rest stay passive.
152
+ el.addEventListener("touchstart", onTouchStart, {
153
+ passive: true
154
+ });
155
+ el.addEventListener("touchmove", onTouchMove, {
156
+ passive: false
157
+ });
158
+ el.addEventListener("touchend", onTouchEnd, {
159
+ passive: true
160
+ });
161
+ el.addEventListener("touchcancel", onTouchCancel, {
162
+ passive: true
163
+ });
164
+ return function () {
165
+ gesture.current = null;
166
+ el.removeEventListener("touchstart", onTouchStart);
167
+ el.removeEventListener("touchmove", onTouchMove);
168
+ el.removeEventListener("touchend", onTouchEnd);
169
+ el.removeEventListener("touchcancel", onTouchCancel);
170
+ };
171
+ }, [scrollRef, scrollEl, disabled, config]);
172
+ // A pull left half-way when `disabled` flips on is dropped (a refresh in flight still shows).
173
+ var distance = disabled && !refreshing ? 0 : pull;
174
+ var progress = Math.min(1, distance / threshold);
175
+ var armed = refreshing || distance >= threshold;
176
+ var animate = !dragging && !reducedMotion;
177
+ var transition = animate ? "transition-transform duration-base ease-standard motion-reduce:transition-none" : "";
178
+ var contentStyle = distance > 0 ? {
179
+ transform: "translateY(".concat(distance, "px)")
180
+ } : undefined;
181
+ var indicatorStyle = {
182
+ transform: "translateY(".concat(distance - INDICATOR_SIZE, "px)"),
183
+ opacity: refreshing ? 1 : progress
184
+ };
185
+ var iconStyle = reducedMotion || refreshing ? undefined : {
186
+ transform: "rotate(".concat(Math.round(progress * 270), "deg)")
187
+ };
188
+ var contentProps = {
189
+ className: cn(scrollRef ? "h-full" : "h-full overflow-y-auto overscroll-y-contain", transition, contentClassName),
190
+ style: contentStyle,
191
+ "aria-busy": refreshing || undefined,
192
+ "data-ptr-content": ""
193
+ };
194
+ return jsxs("div", _objectSpread2(_objectSpread2({
195
+ ref: ref,
196
+ className: cn("relative overflow-hidden", className),
197
+ "data-state": refreshing ? "refreshing" : distance > 0 ? "pulling" : "idle"
198
+ }, rest), {}, {
199
+ children: [jsx("div", {
200
+ "aria-hidden": "true",
201
+ "data-ptr-indicator": "",
202
+ className: cn("pointer-events-none absolute inset-x-0 top-0 z-10 flex justify-center", transition),
203
+ style: indicatorStyle,
204
+ children: jsx("span", {
205
+ className: cn("flex h-10 w-10 items-center justify-center rounded-full border border-line bg-surface shadow-sm", armed ? "text-primary-600 dark:text-primary-400" : "text-muted"),
206
+ children: jsx(RefreshCw, {
207
+ className: cn("h-5 w-5", refreshing && !reducedMotion && "animate-spin motion-reduce:animate-none"),
208
+ style: iconStyle,
209
+ "aria-hidden": "true",
210
+ "data-ptr-spinner": ""
211
+ })
212
+ })
213
+ }), jsx("div", {
214
+ role: "status",
215
+ "aria-live": "polite",
216
+ className: "sr-only",
217
+ children: refreshing ? refreshingLabel : ""
218
+ }), scrollRef ? jsx("div", _objectSpread2(_objectSpread2({}, contentProps), {}, {
219
+ children: children
220
+ })) : jsx("div", _objectSpread2(_objectSpread2({
221
+ ref: setScrollEl
222
+ }, contentProps), {}, {
223
+ children: children
224
+ }))]
225
+ }));
226
+ });
227
+ PullToRefresh.displayName = "PullToRefresh";
228
+
229
+ export { PullToRefresh as default, resist };
@@ -0,0 +1,82 @@
1
+ import { offset, flip, shift, size, useFloating, autoUpdate } from '@floating-ui/react-dom';
2
+
3
+ // Internal (not exported). One positioning engine for every anchored overlay
4
+ // (Popover, Select, Tooltip, Tour, …), replacing the hand-rolled
5
+ // `utils/position.js`.
6
+ var SHIFT_PADDING = 8;
7
+ var MIN_FIT_HEIGHT = 80; // px; never squeeze a capped floating element below this
8
+ /**
9
+ * Positions a floating element next to a reference element.
10
+ *
11
+ * @param {object} [options]
12
+ * @param {import('@floating-ui/react-dom').Placement} [options.placement='bottom-start']
13
+ * @param {number} [options.offset=4] - gap in px between reference and floating.
14
+ * @param {boolean} [options.flip=true] - flip to the opposite side when out of room.
15
+ * @param {boolean} [options.shift=true] - slide along the axis to stay in view (8px padding).
16
+ * @param {boolean|'exact'} [options.matchWidth=false] - `true` sets the floating
17
+ * element's min-width to the reference's width (listboxes, comboboxes);
18
+ * `'exact'` sets its width too.
19
+ * @param {number} [options.maxHeight] - px cap on the floating element's
20
+ * height; it also shrinks to the space left on its side (min 80px).
21
+ * @param {'fixed'|'absolute'} [options.strategy='fixed']
22
+ * @param {boolean} [options.open] - while false, auto-updates are paused.
23
+ * @returns {{ refs: { setReference: Function, setFloating: Function, reference: object, floating: object },
24
+ * floatingStyles: object, placement: string, isPositioned: boolean, update: () => void }}
25
+ */
26
+ function useFloatingPosition() {
27
+ var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
28
+ _ref$placement = _ref.placement,
29
+ placement = _ref$placement === void 0 ? "bottom-start" : _ref$placement,
30
+ _ref$offset = _ref.offset,
31
+ offset$1 = _ref$offset === void 0 ? 4 : _ref$offset,
32
+ _ref$flip = _ref.flip,
33
+ flip$1 = _ref$flip === void 0 ? true : _ref$flip,
34
+ _ref$shift = _ref.shift,
35
+ shift$1 = _ref$shift === void 0 ? true : _ref$shift,
36
+ _ref$matchWidth = _ref.matchWidth,
37
+ matchWidth = _ref$matchWidth === void 0 ? false : _ref$matchWidth,
38
+ maxHeight = _ref.maxHeight,
39
+ _ref$strategy = _ref.strategy,
40
+ strategy = _ref$strategy === void 0 ? "fixed" : _ref$strategy,
41
+ open = _ref.open;
42
+ var middleware = [offset(offset$1)];
43
+ if (flip$1) middleware.push(flip({
44
+ padding: SHIFT_PADDING
45
+ }));
46
+ if (shift$1) middleware.push(shift({
47
+ padding: SHIFT_PADDING
48
+ }));
49
+ if (matchWidth || maxHeight != null) {
50
+ middleware.push(size({
51
+ padding: SHIFT_PADDING,
52
+ apply: function apply(_ref2) {
53
+ var rects = _ref2.rects,
54
+ elements = _ref2.elements,
55
+ availableHeight = _ref2.availableHeight;
56
+ var style = elements.floating.style;
57
+ if (matchWidth) style.minWidth = "".concat(rects.reference.width, "px");
58
+ if (matchWidth === "exact") style.width = "".concat(rects.reference.width, "px");
59
+ if (maxHeight != null) {
60
+ var room = Math.max(MIN_FIT_HEIGHT, availableHeight);
61
+ style.maxHeight = "".concat(Math.min(maxHeight, room), "px");
62
+ }
63
+ }
64
+ }));
65
+ }
66
+ var result = useFloating({
67
+ placement: placement,
68
+ strategy: strategy,
69
+ open: open,
70
+ middleware: middleware,
71
+ whileElementsMounted: autoUpdate
72
+ });
73
+ return {
74
+ refs: result.refs,
75
+ floatingStyles: result.floatingStyles,
76
+ placement: result.placement,
77
+ isPositioned: result.isPositioned,
78
+ update: result.update
79
+ };
80
+ }
81
+
82
+ export { useFloatingPosition };
@@ -0,0 +1,65 @@
1
+ 'use client';
2
+ import { useSyncExternalStore, useCallback } from 'react';
3
+ import { useReducedMotion } from './useReducedMotion.js';
4
+
5
+ /**
6
+ * Named vibration patterns (milliseconds; arrays alternate vibrate / pause).
7
+ * Kept short: haptics confirm an action, they never carry information alone.
8
+ */
9
+ var HAPTIC_PATTERNS = Object.freeze({
10
+ selection: 10,
11
+ success: [10, 40, 10],
12
+ warning: [20, 60, 20],
13
+ error: [30, 40, 30, 40, 30]
14
+ });
15
+ function canVibrate() {
16
+ return typeof navigator !== "undefined" && typeof navigator.vibrate === "function";
17
+ }
18
+ // Vibration support never changes during a page's life: nothing to subscribe to.
19
+ var subscribeNever = function subscribeNever() {
20
+ return function () {};
21
+ };
22
+ var getServerSupported = function getServerSupported() {
23
+ return false;
24
+ };
25
+ function resolvePattern(pattern) {
26
+ if (typeof pattern === "number") return pattern > 0 ? pattern : null;
27
+ if (Array.isArray(pattern)) return pattern.length > 0 ? pattern : null;
28
+ return Object.prototype.hasOwnProperty.call(HAPTIC_PATTERNS, pattern) ? HAPTIC_PATTERNS[pattern] : null;
29
+ }
30
+ /**
31
+ * Thin, opt-in wrapper around `navigator.vibrate` with named patterns.
32
+ *
33
+ * `trigger` is a no-op (returns `false`) when the Vibration API is missing
34
+ * (iOS Safari, desktop Safari, the server), when the user asks for reduced
35
+ * motion, when `enabled` is `false`, or for an unknown pattern name.
36
+ *
37
+ * @param options - `enabled` (default `true`) gates every trigger.
38
+ * @returns `{ trigger, supported, reducedMotion }`.
39
+ */
40
+ function useHaptics() {
41
+ var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
42
+ _ref$enabled = _ref.enabled,
43
+ enabled = _ref$enabled === void 0 ? true : _ref$enabled;
44
+ var supported = useSyncExternalStore(subscribeNever, canVibrate, getServerSupported);
45
+ var reducedMotion = useReducedMotion();
46
+ var trigger = useCallback(function () {
47
+ var pattern = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "selection";
48
+ if (!enabled || reducedMotion || !canVibrate()) return false;
49
+ var resolved = resolvePattern(pattern);
50
+ if (resolved == null) return false;
51
+ try {
52
+ return navigator.vibrate(resolved) !== false;
53
+ } catch (_unused) {
54
+ // Some embedded browsers throw instead of returning false.
55
+ return false;
56
+ }
57
+ }, [enabled, reducedMotion]);
58
+ return {
59
+ trigger: trigger,
60
+ supported: supported,
61
+ reducedMotion: reducedMotion
62
+ };
63
+ }
64
+
65
+ export { HAPTIC_PATTERNS, useHaptics };
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+ import { slicedToArray as _slicedToArray } from '../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { useState, useEffect } from 'react';
4
+ import { useReducedMotion } from './useReducedMotion.js';
5
+
6
+ // Internal (not exported). Keeps an overlay mounted through its exit
7
+ // transition so it can animate out before it unmounts.
8
+ /**
9
+ * @param {boolean} open - whether the overlay should be shown.
10
+ * @param {{ duration?: number }} [options] - transition length in ms (default
11
+ * 200). Forced to 0 when the user prefers reduced motion.
12
+ * @returns {{ mounted: boolean, state: 'entering'|'open'|'exiting'|'closed' }}
13
+ * `mounted` stays true through 'exiting' and turns false once `duration`
14
+ * has elapsed after `open` became false.
15
+ */
16
+ function usePresence(open) {
17
+ var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
18
+ _ref$duration = _ref.duration,
19
+ duration = _ref$duration === void 0 ? 200 : _ref$duration;
20
+ var reducedMotion = useReducedMotion();
21
+ var ms = reducedMotion ? 0 : Math.max(0, Number(duration) || 0);
22
+ var _useState = useState(function () {
23
+ if (!open) return "closed";
24
+ return ms > 0 ? "entering" : "open";
25
+ }),
26
+ _useState2 = _slicedToArray(_useState, 2),
27
+ phase = _useState2[0],
28
+ setPhase = _useState2[1];
29
+ // When `open` flips, start the transition during render (React's "adjust
30
+ // state on prop change" pattern), so the first render after the flip is
31
+ // already 'entering' / 'exiting' and no effect sets state synchronously.
32
+ var _useState3 = useState(open),
33
+ _useState4 = _slicedToArray(_useState3, 2),
34
+ prevOpen = _useState4[0],
35
+ setPrevOpen = _useState4[1];
36
+ if (open !== prevOpen) {
37
+ setPrevOpen(open);
38
+ if (open) setPhase(ms > 0 ? "entering" : "open");else setPhase(ms > 0 ? "exiting" : "closed");
39
+ }
40
+ // Settle on the resting phase once the transition has run.
41
+ useEffect(function () {
42
+ if (phase !== "entering" && phase !== "exiting") return undefined;
43
+ var next = phase === "entering" ? "open" : "closed";
44
+ var timer = setTimeout(function () {
45
+ return setPhase(next);
46
+ }, ms);
47
+ return function () {
48
+ return clearTimeout(timer);
49
+ };
50
+ }, [phase, ms]);
51
+ var state = phase;
52
+ return {
53
+ mounted: state !== "closed",
54
+ state: state
55
+ };
56
+ }
57
+
58
+ export { usePresence };
@@ -0,0 +1,21 @@
1
+ import { useMediaQuery } from './useMediaQuery.js';
2
+
3
+ // Internal (not exported). Picks how an overlay presents itself: as a bottom
4
+ // sheet on small touch screens, as an anchored popover everywhere else.
5
+ var SHEET_MEDIA_QUERY = "(pointer: coarse) and (max-width: 639px)";
6
+ /**
7
+ * @param {'auto'|'sheet'|'popover'} [prop='auto'] - an explicit 'sheet' or
8
+ * 'popover' passes through; 'auto' resolves from SHEET_MEDIA_QUERY.
9
+ * @returns {'sheet'|'popover'} - 'popover' on the server and while hydrating.
10
+ */
11
+ function usePresentation() {
12
+ var prop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "auto";
13
+ // Always subscribe (rules of hooks); the result is ignored when explicit.
14
+ var isSheet = useMediaQuery(SHEET_MEDIA_QUERY, {
15
+ defaultValue: false
16
+ });
17
+ if (prop === "sheet" || prop === "popover") return prop;
18
+ return isSheet ? "sheet" : "popover";
19
+ }
20
+
21
+ export { SHEET_MEDIA_QUERY, usePresentation };