@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
|
@@ -1,37 +1,28 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
|
|
3
3
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
-
import React, { forwardRef, useRef,
|
|
4
|
+
import React, { forwardRef, useRef, useState, useCallback, useId } from 'react';
|
|
5
5
|
import { MoreVertical } from 'lucide-react';
|
|
6
6
|
import { cn } from '../../utils/cn.js';
|
|
7
|
+
import { TOUCH_TARGET } from '../../utils/touchTarget.js';
|
|
8
|
+
import ActionMenuSurface from './menu/ActionMenuSurface.js';
|
|
9
|
+
import { renderIcon } from './menu/menuItems.js';
|
|
7
10
|
|
|
8
|
-
var _excluded = ["items", "trigger", "className", "menuClass", "menuItemClass", "closeOnSelect", "ariaLabel", "wrapperClassName"];
|
|
11
|
+
var _excluded = ["items", "trigger", "className", "menuClass", "menuItemClass", "closeOnSelect", "ariaLabel", "wrapperClassName", "presentation", "cancelLabel"];
|
|
9
12
|
/**
|
|
10
|
-
* ThreeDotPopover
|
|
13
|
+
* ThreeDotPopover
|
|
11
14
|
*
|
|
12
|
-
* A
|
|
15
|
+
* A kebab (three-dot) button that opens an action menu.
|
|
13
16
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* -
|
|
18
|
-
*
|
|
17
|
+
* - Desktop (`presentation="popover"`): a portaled floating `role="menu"`
|
|
18
|
+
* that flips/shifts to stay on screen, with roving focus (ArrowUp/Down,
|
|
19
|
+
* Home/End, typeahead). Escape closes and returns focus to the trigger.
|
|
20
|
+
* - Phones (`presentation="sheet"`): an iOS-style action sheet with a
|
|
21
|
+
* separate Cancel button.
|
|
22
|
+
* - `presentation="auto"` (default) picks the sheet on small touch screens.
|
|
19
23
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* - trigger: optional React node to use as trigger (falls back to three-dot button)
|
|
23
|
-
* - className: additional class for trigger wrapper
|
|
24
|
-
* - menuClass: additional class for menu container
|
|
25
|
-
* - menuItemClass: additional class for every menu item
|
|
26
|
-
* - closeOnSelect: boolean (default true)
|
|
27
|
-
*
|
|
28
|
-
* Example:
|
|
29
|
-
* const items = [
|
|
30
|
-
* { key: 'edit', label: 'Edit', icon: Edit2, onClick: () => {} },
|
|
31
|
-
* { key: 'view', label: 'View', icon: Eye, onClick: () => {} },
|
|
32
|
-
* { key: 'delete', label: 'Delete', destructive: true, onClick: () => {} },
|
|
33
|
-
* ];
|
|
34
|
-
* <ThreeDotPopoverSimple items={items} />
|
|
24
|
+
* Items: { key, label, icon?, onClick?, disabled?, destructive?, tone? }.
|
|
25
|
+
* `tone: 'destructive'` and `destructive: true` are equivalent.
|
|
35
26
|
*/
|
|
36
27
|
var ThreeDotPopoverSimple = /*#__PURE__*/forwardRef(function ThreeDotPopoverSimple(_ref, ref) {
|
|
37
28
|
var _ref$items = _ref.items,
|
|
@@ -50,108 +41,111 @@ var ThreeDotPopoverSimple = /*#__PURE__*/forwardRef(function ThreeDotPopoverSimp
|
|
|
50
41
|
ariaLabel = _ref$ariaLabel === void 0 ? "More options" : _ref$ariaLabel,
|
|
51
42
|
_ref$wrapperClassName = _ref.wrapperClassName,
|
|
52
43
|
wrapperClassName = _ref$wrapperClassName === void 0 ? "" : _ref$wrapperClassName,
|
|
44
|
+
_ref$presentation = _ref.presentation,
|
|
45
|
+
presentation = _ref$presentation === void 0 ? "auto" : _ref$presentation,
|
|
46
|
+
_ref$cancelLabel = _ref.cancelLabel,
|
|
47
|
+
cancelLabel = _ref$cancelLabel === void 0 ? "Cancel" : _ref$cancelLabel,
|
|
53
48
|
rest = _objectWithoutProperties(_ref, _excluded);
|
|
54
49
|
var triggerRef = useRef(null);
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
var _useState = useState(null),
|
|
51
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
52
|
+
anchorEl = _useState2[0],
|
|
53
|
+
setAnchorEl = _useState2[1];
|
|
54
|
+
// Callback ref for the trigger (default or consumer-supplied): handing a
|
|
55
|
+
// function (not the ref object) to cloneElement keeps ref access out of render.
|
|
57
56
|
var setTriggerNode = useCallback(function (node) {
|
|
58
57
|
triggerRef.current = node;
|
|
58
|
+
setAnchorEl(node);
|
|
59
59
|
}, []);
|
|
60
|
-
var
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (menuRef.current && menuRef.current.contains(e.target)) return;
|
|
70
|
-
setOpen(false);
|
|
71
|
-
}
|
|
72
|
-
function onKey(e) {
|
|
73
|
-
if (!open) return;
|
|
74
|
-
if (e.key === "Escape") setOpen(false);
|
|
75
|
-
}
|
|
76
|
-
document.addEventListener("mousedown", onDocClick);
|
|
77
|
-
document.addEventListener("keydown", onKey);
|
|
78
|
-
return function () {
|
|
79
|
-
document.removeEventListener("mousedown", onDocClick);
|
|
80
|
-
document.removeEventListener("keydown", onKey);
|
|
81
|
-
};
|
|
82
|
-
}, [open]);
|
|
60
|
+
var _useState3 = useState(false),
|
|
61
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
|
62
|
+
open = _useState4[0],
|
|
63
|
+
setOpen = _useState4[1];
|
|
64
|
+
var _useState5 = useState("first"),
|
|
65
|
+
_useState6 = _slicedToArray(_useState5, 2),
|
|
66
|
+
initialFocus = _useState6[0],
|
|
67
|
+
setInitialFocus = _useState6[1];
|
|
68
|
+
var menuId = "three-dot-menu-".concat(useId());
|
|
83
69
|
function toggleOpen(e) {
|
|
84
70
|
e && e.stopPropagation();
|
|
71
|
+
setInitialFocus("first");
|
|
85
72
|
setOpen(function (v) {
|
|
86
73
|
return !v;
|
|
87
74
|
});
|
|
88
75
|
}
|
|
89
|
-
function
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
triggerRef.current && triggerRef.current.focus();
|
|
76
|
+
function onTriggerKeyDown(e) {
|
|
77
|
+
if (open || e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
setInitialFocus(e.key === "ArrowUp" ? "last" : "first");
|
|
80
|
+
setOpen(true);
|
|
95
81
|
}
|
|
96
|
-
var
|
|
97
|
-
|
|
98
|
-
|
|
82
|
+
var menuItems = (Array.isArray(items) ? items : []).map(function (it, i) {
|
|
83
|
+
var _it$key, _renderIcon;
|
|
84
|
+
return {
|
|
85
|
+
key: (_it$key = it.key) !== null && _it$key !== void 0 ? _it$key : i,
|
|
86
|
+
label: it.label,
|
|
87
|
+
icon: (_renderIcon = renderIcon(it.icon, "w-5 h-5")) !== null && _renderIcon !== void 0 ? _renderIcon : jsx("span", {
|
|
88
|
+
className: "w-5 h-5"
|
|
89
|
+
}),
|
|
90
|
+
disabled: Boolean(it.disabled),
|
|
91
|
+
tone: it.tone,
|
|
92
|
+
destructive: it.destructive,
|
|
93
|
+
onSelect: function onSelect() {
|
|
94
|
+
return it.onClick && it.onClick(it);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
var triggerA11y = {
|
|
99
|
+
"aria-haspopup": "menu",
|
|
99
100
|
"aria-expanded": open,
|
|
100
|
-
|
|
101
|
+
"aria-controls": open ? menuId : undefined
|
|
102
|
+
};
|
|
103
|
+
var DefaultTrigger = jsx("button", _objectSpread2(_objectSpread2({
|
|
104
|
+
type: "button",
|
|
105
|
+
"aria-label": ariaLabel
|
|
106
|
+
}, triggerA11y), {}, {
|
|
107
|
+
ref: setTriggerNode,
|
|
101
108
|
onClick: toggleOpen,
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
109
|
+
onKeyDown: onTriggerKeyDown,
|
|
110
|
+
className: cn("inline-flex items-center justify-center rounded-md text-muted hover:text-foreground", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2", TOUCH_TARGET, className),
|
|
111
|
+
children: jsx(MoreVertical, {
|
|
112
|
+
"aria-hidden": "true"
|
|
113
|
+
})
|
|
114
|
+
}));
|
|
105
115
|
var triggerNode = trigger ?
|
|
106
116
|
/*#__PURE__*/
|
|
107
117
|
// eslint-disable-next-line react-hooks/refs -- setTriggerNode is a callback ref, run by React at commit
|
|
108
|
-
React.cloneElement(trigger, {
|
|
118
|
+
React.cloneElement(trigger, _objectSpread2({
|
|
109
119
|
ref: setTriggerNode,
|
|
110
120
|
onClick: function onClick(e) {
|
|
111
121
|
trigger.props && trigger.props.onClick && trigger.props.onClick(e);
|
|
112
122
|
toggleOpen(e);
|
|
113
123
|
},
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
124
|
+
onKeyDown: function onKeyDown(e) {
|
|
125
|
+
trigger.props && trigger.props.onKeyDown && trigger.props.onKeyDown(e);
|
|
126
|
+
if (!e.defaultPrevented) onTriggerKeyDown(e);
|
|
127
|
+
}
|
|
128
|
+
}, triggerA11y)) : DefaultTrigger;
|
|
117
129
|
return jsxs("div", _objectSpread2(_objectSpread2({
|
|
118
130
|
ref: ref,
|
|
119
131
|
className: cn("inline-block relative", wrapperClassName)
|
|
120
132
|
}, rest), {}, {
|
|
121
|
-
children: [triggerNode, jsx(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
display: open ? "block" : "none"
|
|
133
|
+
children: [triggerNode, jsx(ActionMenuSurface, {
|
|
134
|
+
open: open,
|
|
135
|
+
onClose: function onClose() {
|
|
136
|
+
return setOpen(false);
|
|
126
137
|
},
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
onClick: function onClick() {
|
|
139
|
-
return handleItemClick(it);
|
|
140
|
-
},
|
|
141
|
-
disabled: it.disabled,
|
|
142
|
-
className: "w-full text-left px-4 py-3 flex items-center gap-3 text-sm leading-5 ".concat(it.destructive ? "text-error-600" : "text-primary-900", " ").concat(it.disabled ? "opacity-50 cursor-not-allowed" : "hover:bg-gray-50", " ").concat(menuItemClass),
|
|
143
|
-
children: [Icon ? jsx(Icon, {
|
|
144
|
-
className: "w-5 h-5"
|
|
145
|
-
}) : jsx("span", {
|
|
146
|
-
className: "w-5 h-5"
|
|
147
|
-
}), jsx("span", {
|
|
148
|
-
className: "flex-1",
|
|
149
|
-
children: it.label
|
|
150
|
-
})]
|
|
151
|
-
})
|
|
152
|
-
}, (_it$key = it.key) !== null && _it$key !== void 0 ? _it$key : i);
|
|
153
|
-
})
|
|
154
|
-
})
|
|
138
|
+
anchorEl: anchorEl,
|
|
139
|
+
items: menuItems,
|
|
140
|
+
presentation: presentation,
|
|
141
|
+
placement: "bottom-start",
|
|
142
|
+
initialFocus: initialFocus,
|
|
143
|
+
closeOnSelect: closeOnSelect,
|
|
144
|
+
id: menuId,
|
|
145
|
+
label: typeof ariaLabel === "string" ? ariaLabel : undefined,
|
|
146
|
+
cancelLabel: cancelLabel,
|
|
147
|
+
menuClassName: menuClass,
|
|
148
|
+
itemClassName: menuItemClass
|
|
155
149
|
})]
|
|
156
150
|
}));
|
|
157
151
|
});
|
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray,
|
|
2
|
+
import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import React, { forwardRef, useRef, useState, useId, useCallback, useEffect, useMemo } from 'react';
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
6
|
import { cn } from '../../utils/cn.js';
|
|
7
|
+
import { setRefs } from '../../utils/position.js';
|
|
8
|
+
import { useFloatingPosition } from '../../hooks/useFloatingPosition.js';
|
|
9
|
+
import { useLatestRef } from '../../hooks/useLatestRef.js';
|
|
7
10
|
|
|
8
|
-
var _excluded = ["position", "trigger", "content", "customTrigger", "children", "open", "defaultOpen", "onChange", "delay", "offset", "textClass", "bgClass", "className", "portal", "id"];
|
|
11
|
+
var _excluded = ["position", "trigger", "content", "customTrigger", "children", "open", "defaultOpen", "onChange", "delay", "offset", "textClass", "bgClass", "className", "portal", "id", "style"];
|
|
9
12
|
// Grace period (ms) before a hover tooltip closes after the pointer leaves the
|
|
10
13
|
// trigger, so the pointer can cross the `offset` gap onto the tooltip itself
|
|
11
14
|
// (WCAG 1.4.13 "hoverable").
|
|
12
15
|
var HOVER_CLOSE_GRACE = 100;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return null;
|
|
16
|
+
// Touch (pointerType "touch"): a hover/focus tooltip opens on a long-press
|
|
17
|
+
// instead of on the emulated mouseenter/focus that follows every tap, and
|
|
18
|
+
// hides on its own shortly after the finger lifts so it never sticks open.
|
|
19
|
+
var LONG_PRESS_MS = 500;
|
|
20
|
+
var TOUCH_HIDE_MS = 1500;
|
|
21
|
+
// Emulated mouse/focus events fire right after a tap; ignore them this long.
|
|
22
|
+
var TOUCH_COMPAT_MS = 800;
|
|
23
|
+
// A finger that moves further than this (px) is scrolling, not pressing.
|
|
24
|
+
var LONG_PRESS_SLOP = 10;
|
|
25
|
+
// True right after a touch: the mouseenter/focus that follows is emulated.
|
|
26
|
+
var isTouchEcho = function isTouchEcho(touch) {
|
|
27
|
+
return Date.now() - touch.lastAt < TOUCH_COMPAT_MS;
|
|
26
28
|
};
|
|
27
29
|
var NATIVE_FOCUSABLE_TAGS = new Set(["button", "input", "select", "textarea"]);
|
|
28
30
|
/**
|
|
@@ -47,7 +49,8 @@ var isNativelyFocusable = function isNativelyFocusable(el) {
|
|
|
47
49
|
};
|
|
48
50
|
/**
|
|
49
51
|
* Tooltip props
|
|
50
|
-
* - position: 'auto' | 'top' | 'bottom' | 'left' | 'right' (default 'auto'
|
|
52
|
+
* - position: 'auto' | 'top' | 'bottom' | 'left' | 'right' (default 'auto';
|
|
53
|
+
* 'auto' prefers top and flips/shifts to stay in view)
|
|
51
54
|
* - trigger: 'hover' | 'click' | 'focus' | Array of those (default 'hover')
|
|
52
55
|
* - content: ReactNode | ( { close } ) => ReactNode
|
|
53
56
|
* - customTrigger: ReactElement (rendered in place of children as trigger)
|
|
@@ -88,40 +91,54 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
88
91
|
_ref$portal = _ref.portal,
|
|
89
92
|
portal = _ref$portal === void 0 ? true : _ref$portal,
|
|
90
93
|
propsId = _ref.id,
|
|
94
|
+
style = _ref.style,
|
|
91
95
|
rest = _objectWithoutProperties(_ref, _excluded);
|
|
92
96
|
var triggerRef = useRef(null);
|
|
93
97
|
var tooltipRef = useRef(null);
|
|
94
98
|
var delayRef = useRef(null);
|
|
95
99
|
var closeTimerRef = useRef(null);
|
|
100
|
+
// Touch long-press bookkeeping (timers, start point, timestamps).
|
|
101
|
+
var touchRef = useRef({
|
|
102
|
+
press: null,
|
|
103
|
+
hide: null,
|
|
104
|
+
pressing: false,
|
|
105
|
+
fired: false,
|
|
106
|
+
x: 0,
|
|
107
|
+
y: 0,
|
|
108
|
+
lastAt: -Infinity,
|
|
109
|
+
suppressClickUntil: 0
|
|
110
|
+
});
|
|
96
111
|
var _useState = useState(Boolean(controlledOpen !== null && controlledOpen !== void 0 ? controlledOpen : defaultOpen)),
|
|
97
112
|
_useState2 = _slicedToArray(_useState, 2),
|
|
98
113
|
open = _useState2[0],
|
|
99
114
|
setOpen = _useState2[1];
|
|
100
|
-
var _useState3 = useState(
|
|
115
|
+
var _useState3 = useState(false),
|
|
101
116
|
_useState4 = _slicedToArray(_useState3, 2),
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
left: 0,
|
|
105
|
-
top: 0
|
|
106
|
-
}),
|
|
107
|
-
_useState6 = _slicedToArray(_useState5, 2),
|
|
108
|
-
coords = _useState6[0],
|
|
109
|
-
setCoords = _useState6[1];
|
|
110
|
-
var _useState7 = useState(false),
|
|
111
|
-
_useState8 = _slicedToArray(_useState7, 2),
|
|
112
|
-
mounted = _useState8[0],
|
|
113
|
-
setMounted = _useState8[1];
|
|
117
|
+
mounted = _useState4[0],
|
|
118
|
+
setMounted = _useState4[1];
|
|
114
119
|
var reactId = useId();
|
|
115
120
|
var id = propsId !== null && propsId !== void 0 ? propsId : "tooltip-".concat(reactId);
|
|
121
|
+
var openRef = useLatestRef(open);
|
|
122
|
+
var _useFloatingPosition = useFloatingPosition({
|
|
123
|
+
placement: position === "auto" ? "top" : position,
|
|
124
|
+
offset: offset,
|
|
125
|
+
open: open
|
|
126
|
+
}),
|
|
127
|
+
refs = _useFloatingPosition.refs,
|
|
128
|
+
floatingStyles = _useFloatingPosition.floatingStyles,
|
|
129
|
+
placement = _useFloatingPosition.placement;
|
|
130
|
+
var setReference = refs.setReference,
|
|
131
|
+
setFloating = refs.setFloating;
|
|
116
132
|
var setTriggerRef = useCallback(function (node) {
|
|
117
133
|
triggerRef.current = node;
|
|
134
|
+
setReference(node);
|
|
118
135
|
if (typeof forwardedRef === "function") forwardedRef(node);else if (forwardedRef) forwardedRef.current = node;
|
|
119
|
-
}, [forwardedRef]);
|
|
136
|
+
}, [forwardedRef, setReference]);
|
|
120
137
|
// sync controlled (adjust-during-render pattern)
|
|
121
|
-
var
|
|
122
|
-
|
|
123
|
-
prevControlledOpen =
|
|
124
|
-
setPrevControlledOpen =
|
|
138
|
+
var _useState5 = useState(controlledOpen),
|
|
139
|
+
_useState6 = _slicedToArray(_useState5, 2),
|
|
140
|
+
prevControlledOpen = _useState6[0],
|
|
141
|
+
setPrevControlledOpen = _useState6[1];
|
|
125
142
|
if (controlledOpen !== prevControlledOpen) {
|
|
126
143
|
setPrevControlledOpen(controlledOpen);
|
|
127
144
|
if (typeof controlledOpen === "boolean") setOpen(controlledOpen);
|
|
@@ -132,106 +149,6 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
132
149
|
var notify = useCallback(function (val) {
|
|
133
150
|
if (typeof onChange === "function") onChange(val);
|
|
134
151
|
}, [onChange]);
|
|
135
|
-
var computePosition = useCallback(function () {
|
|
136
|
-
var t = triggerRef.current;
|
|
137
|
-
var el = tooltipRef.current;
|
|
138
|
-
if (!t || !el) return;
|
|
139
|
-
var tr = t.getBoundingClientRect();
|
|
140
|
-
var ttW = el.offsetWidth;
|
|
141
|
-
var ttH = el.offsetHeight;
|
|
142
|
-
var winW = window.innerWidth;
|
|
143
|
-
var winH = window.innerHeight;
|
|
144
|
-
var candidates = ["top", "bottom", "left", "right"];
|
|
145
|
-
var chosen = position === "auto" ? "top" : position;
|
|
146
|
-
if (position === "auto") {
|
|
147
|
-
var _iterator = _createForOfIteratorHelper(candidates),
|
|
148
|
-
_step;
|
|
149
|
-
try {
|
|
150
|
-
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
151
|
-
var p = _step.value;
|
|
152
|
-
if (p === "top" && tr.top >= ttH + offset) {
|
|
153
|
-
chosen = "top";
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
if (p === "bottom" && winH - tr.bottom >= ttH + offset) {
|
|
157
|
-
chosen = "bottom";
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
if (p === "right" && winW - tr.right >= ttW + offset) {
|
|
161
|
-
chosen = "right";
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
if (p === "left" && tr.left >= ttW + offset) {
|
|
165
|
-
chosen = "left";
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
} catch (err) {
|
|
170
|
-
_iterator.e(err);
|
|
171
|
-
} finally {
|
|
172
|
-
_iterator.f();
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
var left = 0;
|
|
176
|
-
var top = 0;
|
|
177
|
-
switch (chosen) {
|
|
178
|
-
case "top":
|
|
179
|
-
left = tr.left + tr.width / 2 - ttW / 2;
|
|
180
|
-
top = tr.top - ttH - offset;
|
|
181
|
-
break;
|
|
182
|
-
case "bottom":
|
|
183
|
-
left = tr.left + tr.width / 2 - ttW / 2;
|
|
184
|
-
top = tr.bottom + offset;
|
|
185
|
-
break;
|
|
186
|
-
case "left":
|
|
187
|
-
left = tr.left - ttW - offset;
|
|
188
|
-
top = tr.top + tr.height / 2 - ttH / 2;
|
|
189
|
-
break;
|
|
190
|
-
case "right":
|
|
191
|
-
left = tr.right + offset;
|
|
192
|
-
top = tr.top + tr.height / 2 - ttH / 2;
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
195
|
-
var pad = 8;
|
|
196
|
-
left = Math.max(pad, Math.min(left, winW - ttW - pad));
|
|
197
|
-
top = Math.max(pad, Math.min(top, winH - ttH - pad));
|
|
198
|
-
// `left`/`top` are viewport coords; convert to the containing block of the
|
|
199
|
-
// absolutely positioned tooltip. Portaled into <body> (or inline with no
|
|
200
|
-
// positioned ancestor) that is the document, so add page scroll; inline
|
|
201
|
-
// inside a positioned ancestor, offset by that ancestor instead.
|
|
202
|
-
var cb = getContainingBlock(el);
|
|
203
|
-
var originX = -window.scrollX;
|
|
204
|
-
var originY = -window.scrollY;
|
|
205
|
-
if (cb && cb !== document.body && cb !== document.documentElement) {
|
|
206
|
-
var cr = cb.getBoundingClientRect();
|
|
207
|
-
originX = cr.left + (cb.clientLeft || 0) - cb.scrollLeft;
|
|
208
|
-
originY = cr.top + (cb.clientTop || 0) - cb.scrollTop;
|
|
209
|
-
}
|
|
210
|
-
setPlacement(chosen);
|
|
211
|
-
setCoords({
|
|
212
|
-
left: Math.round(left - originX),
|
|
213
|
-
top: Math.round(top - originY)
|
|
214
|
-
});
|
|
215
|
-
}, [offset, position]);
|
|
216
|
-
useEffect(function () {
|
|
217
|
-
if (!open) return;
|
|
218
|
-
computePosition();
|
|
219
|
-
var ro = new ResizeObserver(function () {
|
|
220
|
-
return computePosition();
|
|
221
|
-
});
|
|
222
|
-
if (triggerRef.current) ro.observe(triggerRef.current);
|
|
223
|
-
if (tooltipRef.current) ro.observe(tooltipRef.current);
|
|
224
|
-
var onScroll = function onScroll() {
|
|
225
|
-
return computePosition();
|
|
226
|
-
};
|
|
227
|
-
window.addEventListener("scroll", onScroll, true);
|
|
228
|
-
window.addEventListener("resize", computePosition);
|
|
229
|
-
return function () {
|
|
230
|
-
ro.disconnect();
|
|
231
|
-
window.removeEventListener("scroll", onScroll, true);
|
|
232
|
-
window.removeEventListener("resize", computePosition);
|
|
233
|
-
};
|
|
234
|
-
}, [open, computePosition]);
|
|
235
152
|
// open/close helpers
|
|
236
153
|
var cancelScheduledClose = useCallback(function () {
|
|
237
154
|
clearTimeout(closeTimerRef.current);
|
|
@@ -253,6 +170,7 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
253
170
|
var closeTooltip = useCallback(function () {
|
|
254
171
|
clearTimeout(delayRef.current);
|
|
255
172
|
clearTimeout(closeTimerRef.current);
|
|
173
|
+
clearTimeout(touchRef.current.hide);
|
|
256
174
|
setOpen(false);
|
|
257
175
|
notify(false);
|
|
258
176
|
}, [notify]);
|
|
@@ -263,9 +181,12 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
263
181
|
closeTimerRef.current = setTimeout(closeTooltip, HOVER_CLOSE_GRACE);
|
|
264
182
|
}, [closeTooltip]);
|
|
265
183
|
useEffect(function () {
|
|
184
|
+
var touch = touchRef.current;
|
|
266
185
|
return function () {
|
|
267
186
|
clearTimeout(delayRef.current);
|
|
268
187
|
clearTimeout(closeTimerRef.current);
|
|
188
|
+
clearTimeout(touch.press);
|
|
189
|
+
clearTimeout(touch.hide);
|
|
269
190
|
};
|
|
270
191
|
}, []);
|
|
271
192
|
// triggers normalization
|
|
@@ -280,7 +201,8 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
280
201
|
// onMouseLeave props (see tooltipNode) — it doesn't exist yet here.
|
|
281
202
|
if (triggers.includes("hover")) {
|
|
282
203
|
var onMouseEnter = function onMouseEnter() {
|
|
283
|
-
|
|
204
|
+
if (isTouchEcho(touchRef.current)) return;
|
|
205
|
+
openTooltip();
|
|
284
206
|
};
|
|
285
207
|
var onMouseLeave = function onMouseLeave() {
|
|
286
208
|
return scheduleClose();
|
|
@@ -329,11 +251,13 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
329
251
|
if (!t) return;
|
|
330
252
|
if (opensOnFocus) {
|
|
331
253
|
var onFocusIn = function onFocusIn() {
|
|
332
|
-
|
|
254
|
+
if (isTouchEcho(touchRef.current)) return;
|
|
255
|
+
openTooltip(true);
|
|
333
256
|
};
|
|
334
257
|
var onFocusOut = function onFocusOut(e) {
|
|
335
258
|
var tip = tooltipRef.current;
|
|
336
259
|
if (e.relatedTarget && tip && tip.contains(e.relatedTarget)) return;
|
|
260
|
+
if (touchRef.current.fired) return; // a long-press tip hides on its own timer
|
|
337
261
|
closeTooltip();
|
|
338
262
|
};
|
|
339
263
|
t.addEventListener("focusin", onFocusIn);
|
|
@@ -344,14 +268,84 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
344
268
|
};
|
|
345
269
|
}
|
|
346
270
|
}, [opensOnFocus, openTooltip, closeTooltip]);
|
|
271
|
+
// Touch: long-press opens a hover/focus tooltip; a plain tap is left alone
|
|
272
|
+
// so the trigger's own onClick still runs. Click tooltips toggle on tap.
|
|
273
|
+
var longPress = opensOnFocus || triggers.includes("hover");
|
|
274
|
+
useEffect(function () {
|
|
275
|
+
var t = triggerRef.current;
|
|
276
|
+
if (!t || !longPress) return;
|
|
277
|
+
var touch = touchRef.current;
|
|
278
|
+
var onPointerDown = function onPointerDown(e) {
|
|
279
|
+
if (e.pointerType !== "touch") return;
|
|
280
|
+
clearTimeout(touch.press);
|
|
281
|
+
clearTimeout(touch.hide);
|
|
282
|
+
touch.lastAt = Date.now();
|
|
283
|
+
touch.pressing = true;
|
|
284
|
+
touch.fired = false;
|
|
285
|
+
touch.x = e.clientX;
|
|
286
|
+
touch.y = e.clientY;
|
|
287
|
+
touch.press = setTimeout(function () {
|
|
288
|
+
touch.fired = true;
|
|
289
|
+
openTooltip(true);
|
|
290
|
+
}, LONG_PRESS_MS);
|
|
291
|
+
};
|
|
292
|
+
var onPointerMove = function onPointerMove(e) {
|
|
293
|
+
if (!touch.pressing || e.pointerType !== "touch") return;
|
|
294
|
+
if (Math.hypot(e.clientX - touch.x, e.clientY - touch.y) > LONG_PRESS_SLOP) {
|
|
295
|
+
clearTimeout(touch.press);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
var onPointerEnd = function onPointerEnd(e) {
|
|
299
|
+
if (!touch.pressing || e.pointerType !== "touch") return;
|
|
300
|
+
touch.pressing = false;
|
|
301
|
+
touch.lastAt = Date.now();
|
|
302
|
+
clearTimeout(touch.press);
|
|
303
|
+
if (touch.fired) {
|
|
304
|
+
// The press already did its job: swallow the click it may produce.
|
|
305
|
+
touch.suppressClickUntil = Date.now() + TOUCH_COMPAT_MS;
|
|
306
|
+
touch.hide = setTimeout(function () {
|
|
307
|
+
touch.fired = false;
|
|
308
|
+
closeTooltip();
|
|
309
|
+
}, TOUCH_HIDE_MS);
|
|
310
|
+
} else if (openRef.current) {
|
|
311
|
+
closeTooltip(); // a plain tap dismisses an open tip (and still clicks)
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
var onClickCapture = function onClickCapture(e) {
|
|
315
|
+
if (Date.now() < touch.suppressClickUntil) {
|
|
316
|
+
touch.suppressClickUntil = 0;
|
|
317
|
+
e.preventDefault();
|
|
318
|
+
e.stopPropagation();
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
// Stop the OS long-press menu / callout from covering the tooltip.
|
|
322
|
+
var onContextMenu = function onContextMenu(e) {
|
|
323
|
+
if (touch.pressing || touch.fired) e.preventDefault();
|
|
324
|
+
};
|
|
325
|
+
t.addEventListener("pointerdown", onPointerDown);
|
|
326
|
+
t.addEventListener("pointermove", onPointerMove);
|
|
327
|
+
t.addEventListener("pointerup", onPointerEnd);
|
|
328
|
+
t.addEventListener("pointercancel", onPointerEnd);
|
|
329
|
+
t.addEventListener("click", onClickCapture, true);
|
|
330
|
+
t.addEventListener("contextmenu", onContextMenu);
|
|
331
|
+
return function () {
|
|
332
|
+
t.removeEventListener("pointerdown", onPointerDown);
|
|
333
|
+
t.removeEventListener("pointermove", onPointerMove);
|
|
334
|
+
t.removeEventListener("pointerup", onPointerEnd);
|
|
335
|
+
t.removeEventListener("pointercancel", onPointerEnd);
|
|
336
|
+
t.removeEventListener("click", onClickCapture, true);
|
|
337
|
+
t.removeEventListener("contextmenu", onContextMenu);
|
|
338
|
+
};
|
|
339
|
+
}, [longPress, openTooltip, closeTooltip, openRef]);
|
|
347
340
|
// Dismiss on a pointer/tap outside. Touch has no mouseleave, so without this
|
|
348
|
-
// a tapped-open
|
|
341
|
+
// a tapped-open tooltip would never close.
|
|
349
342
|
useEffect(function () {
|
|
350
343
|
if (!open) return;
|
|
351
344
|
var onPointerDown = function onPointerDown(e) {
|
|
352
345
|
var t = triggerRef.current;
|
|
353
346
|
var tip = tooltipRef.current;
|
|
354
347
|
if (t && t.contains(e.target) || tip && tip.contains(e.target)) return;
|
|
348
|
+
touchRef.current.fired = false;
|
|
355
349
|
closeTooltip();
|
|
356
350
|
};
|
|
357
351
|
document.addEventListener("pointerdown", onPointerDown);
|
|
@@ -419,16 +413,15 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
419
413
|
} : {};
|
|
420
414
|
// Tooltip box element
|
|
421
415
|
var tooltipNode = open ? jsx("div", _objectSpread2(_objectSpread2(_objectSpread2({
|
|
422
|
-
ref:
|
|
416
|
+
ref: function ref(node) {
|
|
417
|
+
return setRefs(node, tooltipRef, setFloating);
|
|
418
|
+
},
|
|
423
419
|
id: id,
|
|
424
|
-
role: "tooltip"
|
|
420
|
+
role: "tooltip",
|
|
421
|
+
"data-placement": placement
|
|
425
422
|
}, rest), tipHoverProps), {}, {
|
|
426
|
-
className: cn("
|
|
427
|
-
style: {
|
|
428
|
-
left: coords.left,
|
|
429
|
-
top: coords.top,
|
|
430
|
-
position: "absolute"
|
|
431
|
-
},
|
|
423
|
+
className: cn("z-tooltip transform-gpu transition-opacity duration-150 motion-reduce:transition-none", className),
|
|
424
|
+
style: _objectSpread2(_objectSpread2({}, floatingStyles), style),
|
|
432
425
|
children: jsx("div", {
|
|
433
426
|
className: cn("max-w-xs rounded-md border border-line px-3 py-2 text-sm shadow-lg shadow-black/5", bgClass, textClass),
|
|
434
427
|
children: renderContent()
|