@dreamtree-org/twreact-ui 1.3.11 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -6
- package/ai-skills/dreamtree-ui.md +47 -9
- package/codemods/v2.cjs +198 -0
- package/dist/ai-schema.json +609 -33
- package/dist/components/blocks/AppShell.d.ts.map +1 -1
- package/dist/components/blocks/BottomActionBar.d.ts +6 -0
- package/dist/components/blocks/BottomActionBar.d.ts.map +1 -0
- package/dist/components/blocks/ConfirmDialog.d.ts.map +1 -1
- package/dist/components/blocks/PageHeader.d.ts.map +1 -1
- package/dist/components/core/Checkbox.d.ts.map +1 -1
- package/dist/components/core/DatePicker.d.ts.map +1 -1
- package/dist/components/core/DateRangePicker.d.ts.map +1 -1
- package/dist/components/core/Form.d.ts.map +1 -1
- package/dist/components/core/Input.d.ts.map +1 -1
- package/dist/components/core/Popover.d.ts.map +1 -1
- package/dist/components/core/ProgressBar.d.ts.map +1 -1
- package/dist/components/core/Rate.d.ts.map +1 -1
- package/dist/components/core/SearchField.d.ts +9 -0
- package/dist/components/core/SearchField.d.ts.map +1 -0
- package/dist/components/core/SegmentedControl.d.ts +8 -0
- package/dist/components/core/SegmentedControl.d.ts.map +1 -0
- package/dist/components/core/Select.d.ts.map +1 -1
- package/dist/components/core/SwipeableListItem.d.ts +4 -0
- package/dist/components/core/SwipeableListItem.d.ts.map +1 -0
- package/dist/components/core/SwipeableListItemGesture.d.ts +58 -0
- package/dist/components/core/SwipeableListItemGesture.d.ts.map +1 -0
- package/dist/components/core/Switch.d.ts.map +1 -1
- package/dist/components/core/Table/ActionMenu.d.ts +10 -3
- package/dist/components/core/Table/ActionMenu.d.ts.map +1 -1
- package/dist/components/core/Table/MobileCard.d.ts +10 -1
- package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
- package/dist/components/core/Table/MobileFilters.d.ts +8 -2
- package/dist/components/core/Table/MobileFilters.d.ts.map +1 -1
- package/dist/components/core/Table/Table.d.ts.map +1 -1
- package/dist/components/core/Table/TableRow.d.ts +2 -1
- package/dist/components/core/Table/TableRow.d.ts.map +1 -1
- package/dist/components/core/Tabs.d.ts +3 -3
- package/dist/components/core/Tabs.d.ts.map +1 -1
- package/dist/components/core/ThreeDotPopover.d.ts +10 -22
- package/dist/components/core/ThreeDotPopover.d.ts.map +1 -1
- package/dist/components/core/Tooltip.d.ts +2 -1
- package/dist/components/core/Tooltip.d.ts.map +1 -1
- package/dist/components/core/internal/Calendar.d.ts +77 -0
- package/dist/components/core/internal/Calendar.d.ts.map +1 -0
- package/dist/components/core/menu/ActionMenuSurface.d.ts +42 -0
- package/dist/components/core/menu/ActionMenuSurface.d.ts.map +1 -0
- package/dist/components/core/menu/ActionSheetMenu.d.ts +17 -0
- package/dist/components/core/menu/ActionSheetMenu.d.ts.map +1 -0
- package/dist/components/core/menu/MenuPopover.d.ts +21 -0
- package/dist/components/core/menu/MenuPopover.d.ts.map +1 -0
- package/dist/components/core/menu/menuItems.d.ts +21 -0
- package/dist/components/core/menu/menuItems.d.ts.map +1 -0
- package/dist/components/core/menu/menuKeyboard.d.ts +16 -0
- package/dist/components/core/menu/menuKeyboard.d.ts.map +1 -0
- package/dist/components/feedback/ActionSheet.d.ts +4 -0
- package/dist/components/feedback/ActionSheet.d.ts.map +1 -0
- package/dist/components/feedback/Alert.d.ts.map +1 -1
- package/dist/components/feedback/Dialog.d.ts.map +1 -1
- package/dist/components/feedback/Drawer.d.ts.map +1 -1
- package/dist/components/feedback/Sheet.d.ts +4 -0
- package/dist/components/feedback/Sheet.d.ts.map +1 -0
- package/dist/components/feedback/Toast.d.ts +4 -3
- package/dist/components/feedback/Toast.d.ts.map +1 -1
- package/dist/components/feedback/ToastItem.d.ts +47 -0
- package/dist/components/feedback/ToastItem.d.ts.map +1 -0
- package/dist/components/feedback/ToastStack.d.ts +20 -0
- package/dist/components/feedback/ToastStack.d.ts.map +1 -0
- package/dist/components/feedback/ToastSwipe.d.ts +22 -0
- package/dist/components/feedback/ToastSwipe.d.ts.map +1 -0
- package/dist/components/feedback/Tour.d.ts.map +1 -1
- package/dist/components/feedback/sheetCore.d.ts +30 -0
- package/dist/components/feedback/sheetCore.d.ts.map +1 -0
- package/dist/components/navigation/Navbar.d.ts.map +1 -1
- package/dist/components/navigation/Sidebar.d.ts +6 -0
- package/dist/components/navigation/Sidebar.d.ts.map +1 -1
- package/dist/components/rich-text/RichTextEditor.d.ts.map +1 -1
- package/dist/components/rich-text/RichTextToolbar.d.ts +6 -2
- package/dist/components/rich-text/RichTextToolbar.d.ts.map +1 -1
- package/dist/components/utility/PullToRefresh.d.ts +5 -0
- package/dist/components/utility/PullToRefresh.d.ts.map +1 -0
- package/dist/esm/components/blocks/AppShell.js +97 -90
- package/dist/esm/components/blocks/BottomActionBar.js +132 -0
- package/dist/esm/components/blocks/ConfirmDialog.js +40 -4
- package/dist/esm/components/blocks/PageHeader.js +16 -3
- package/dist/esm/components/core/Checkbox.js +13 -24
- package/dist/esm/components/core/DatePicker.js +117 -304
- package/dist/esm/components/core/DateRangePicker.js +260 -308
- package/dist/esm/components/core/Form.js +11 -2
- package/dist/esm/components/core/Input.js +13 -18
- package/dist/esm/components/core/Popover.js +59 -48
- package/dist/esm/components/core/ProgressBar.js +36 -64
- package/dist/esm/components/core/Rate.js +5 -10
- package/dist/esm/components/core/SearchField.js +311 -0
- package/dist/esm/components/core/SegmentedControl.js +189 -0
- package/dist/esm/components/core/Select.js +185 -190
- package/dist/esm/components/core/SwipeableListItem.js +286 -0
- package/dist/esm/components/core/SwipeableListItemGesture.js +219 -0
- package/dist/esm/components/core/Switch.js +11 -13
- package/dist/esm/components/core/Table/ActionMenu.js +45 -231
- package/dist/esm/components/core/Table/ColumnMenu.js +1 -1
- package/dist/esm/components/core/Table/MobileCard.js +66 -16
- package/dist/esm/components/core/Table/MobileFilters.js +181 -10
- package/dist/esm/components/core/Table/Table.js +149 -102
- package/dist/esm/components/core/Table/TableRow.js +10 -2
- package/dist/esm/components/core/Tabs.js +7 -7
- package/dist/esm/components/core/ThreeDotPopover.js +93 -99
- package/dist/esm/components/core/Tooltip.js +138 -145
- package/dist/esm/components/core/internal/Calendar.js +381 -0
- package/dist/esm/components/core/menu/ActionMenuSurface.js +110 -0
- package/dist/esm/components/core/menu/ActionSheetMenu.js +140 -0
- package/dist/esm/components/core/menu/MenuPopover.js +177 -0
- package/dist/esm/components/core/menu/menuItems.js +61 -0
- package/dist/esm/components/core/menu/menuKeyboard.js +54 -0
- package/dist/esm/components/feedback/ActionSheet.js +55 -0
- package/dist/esm/components/feedback/Alert.js +63 -53
- package/dist/esm/components/feedback/Dialog.js +141 -53
- package/dist/esm/components/feedback/Drawer.js +130 -132
- package/dist/esm/components/feedback/Sheet.js +125 -0
- package/dist/esm/components/feedback/Toast.js +21 -291
- package/dist/esm/components/feedback/ToastItem.js +348 -0
- package/dist/esm/components/feedback/ToastStack.js +130 -0
- package/dist/esm/components/feedback/ToastSwipe.js +52 -0
- package/dist/esm/components/feedback/Tour.js +107 -30
- package/dist/esm/components/feedback/sheetCore.js +494 -0
- package/dist/esm/components/navigation/Dropdown.js +1 -1
- package/dist/esm/components/navigation/Navbar.js +172 -37
- package/dist/esm/components/navigation/Sidebar.js +263 -111
- package/dist/esm/components/rich-text/RichTextEditor.js +9 -3
- package/dist/esm/components/rich-text/RichTextToolbar.js +29 -9
- package/dist/esm/components/utility/Portal.js +31 -0
- package/dist/esm/components/utility/PullToRefresh.js +229 -0
- package/dist/esm/hooks/useFloatingPosition.js +82 -0
- package/dist/esm/hooks/useHaptics.js +65 -0
- package/dist/esm/hooks/usePresence.js +58 -0
- package/dist/esm/hooks/usePresentation.js +21 -0
- package/dist/esm/hooks/useSheetDrag.js +220 -0
- package/dist/esm/index.js +13 -2
- package/dist/esm/utils/position.js +10 -63
- package/dist/hooks/useFloatingPosition.d.ts +8 -4
- package/dist/hooks/useFloatingPosition.d.ts.map +1 -1
- package/dist/hooks/useHaptics.d.ts +19 -0
- package/dist/hooks/useHaptics.d.ts.map +1 -0
- package/dist/hooks/useSheetDrag.d.ts +31 -0
- package/dist/hooks/useSheetDrag.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +8851 -3198
- package/dist/index.js +8856 -3195
- package/dist/rich-text/index.js +41 -11
- package/dist/types/blocks.d.ts +32 -2
- package/dist/types/blocks.d.ts.map +1 -1
- package/dist/types/index.d.ts +353 -19
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/rich-text.d.ts +5 -0
- package/dist/types/rich-text.d.ts.map +1 -1
- package/dist/utils/position.d.ts +0 -15
- package/dist/utils/position.d.ts.map +1 -1
- package/doc/README.md +15 -0
- package/mcp/catalog.snapshot.json +1398 -464
- package/package.json +7 -2
- package/dist/esm/hooks/useSwipeToClose.js +0 -66
- package/dist/hooks/useSwipeToClose.d.ts +0 -24
- 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
|
-
|
|
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-
|
|
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
|
-
|
|
375
|
-
|
|
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-
|
|
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 };
|