@jobber/components 8.23.0 → 8.23.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/dist/Autocomplete/index.cjs +1 -1
- package/dist/Autocomplete/index.mjs +1 -1
- package/dist/Card/index.cjs +4 -4
- package/dist/Card/index.mjs +4 -4
- package/dist/Chip/index.cjs +1 -1
- package/dist/Chip/index.mjs +1 -1
- package/dist/Chips/InternalChipDismissible/hooks/index.cjs +1 -1
- package/dist/Chips/InternalChipDismissible/hooks/index.mjs +1 -1
- package/dist/Chips/InternalChipDismissible/index.cjs +1 -1
- package/dist/Chips/InternalChipDismissible/index.mjs +1 -1
- package/dist/Chips/index.cjs +1 -1
- package/dist/Chips/index.mjs +1 -1
- package/dist/ComboboxChipRemove-cjs.js +668 -379
- package/dist/ComboboxChipRemove-es.js +508 -219
- package/dist/ConfirmationModal/index.cjs +1 -1
- package/dist/ConfirmationModal/index.mjs +1 -1
- package/dist/DataDump/index.cjs +4 -4
- package/dist/DataDump/index.mjs +4 -4
- package/dist/DataList/components/DataListActions/index.cjs +1 -1
- package/dist/DataList/components/DataListActions/index.mjs +1 -1
- package/dist/DataList/components/DataListBulkActions/index.cjs +1 -1
- package/dist/DataList/components/DataListBulkActions/index.mjs +1 -1
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +1 -1
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +1 -1
- package/dist/DataList/components/DataListFilters/index.cjs +1 -1
- package/dist/DataList/components/DataListFilters/index.mjs +1 -1
- package/dist/DataList/components/DataListHeader/index.cjs +1 -1
- package/dist/DataList/components/DataListHeader/index.mjs +1 -1
- package/dist/DataList/components/DataListItem/index.cjs +1 -1
- package/dist/DataList/components/DataListItem/index.mjs +1 -1
- package/dist/DataList/components/DataListItemActions/index.cjs +1 -1
- package/dist/DataList/components/DataListItemActions/index.mjs +1 -1
- package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +1 -1
- package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +1 -1
- package/dist/DataList/components/DataListItems/index.cjs +1 -1
- package/dist/DataList/components/DataListItems/index.mjs +1 -1
- package/dist/DataList/components/DataListLayout/index.cjs +1 -1
- package/dist/DataList/components/DataListLayout/index.mjs +1 -1
- package/dist/DataList/components/DataListLayoutActions/index.cjs +1 -1
- package/dist/DataList/components/DataListLayoutActions/index.mjs +1 -1
- package/dist/DataList/index.cjs +1 -1
- package/dist/DataList/index.mjs +1 -1
- package/dist/DatePicker/index.cjs +1 -1
- package/dist/DatePicker/index.mjs +1 -1
- package/dist/Dialog/index.cjs +3 -3
- package/dist/Dialog/index.mjs +3 -3
- package/dist/DrawerDescription-cjs.js +32 -33
- package/dist/DrawerDescription-es.js +19 -20
- package/dist/DrawerTitle-cjs.js +7 -5
- package/dist/DrawerTitle-es.js +7 -5
- package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +1 -1
- package/dist/FilterPicker/components/FilterPickerContent/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerContent/index.mjs +1 -1
- package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +1 -1
- package/dist/FilterPicker/index.cjs +1 -1
- package/dist/FilterPicker/index.mjs +1 -1
- package/dist/FormFieldWrapper-cjs.js +2 -2
- package/dist/FormFieldWrapper-es.js +2 -2
- package/dist/FormatFile/index.cjs +1 -1
- package/dist/FormatFile/index.mjs +1 -1
- package/dist/Gallery/index.cjs +1 -1
- package/dist/Gallery/index.mjs +1 -1
- package/dist/InputDate/index.cjs +1 -1
- package/dist/InputDate/index.mjs +1 -1
- package/dist/InputNumberExperimental-cjs.js +319 -102
- package/dist/InputNumberExperimental-es.js +321 -104
- package/dist/LightBox/index.cjs +1 -1
- package/dist/LightBox/index.mjs +1 -1
- package/dist/Menu/index.cjs +4 -4
- package/dist/Menu/index.mjs +4 -4
- package/dist/MenuSubmenuTrigger-cjs.js +545 -498
- package/dist/MenuSubmenuTrigger-es.js +490 -443
- package/dist/Modal/index.cjs +1 -1
- package/dist/Modal/index.mjs +1 -1
- package/dist/NumberFieldInput-cjs.js +609 -496
- package/dist/NumberFieldInput-es.js +612 -498
- package/dist/Page/index.cjs +4 -4
- package/dist/Page/index.mjs +4 -4
- package/dist/Popover/index.cjs +1 -1
- package/dist/Popover/index.mjs +1 -1
- package/dist/ScrollAreaViewport-cjs.js +326 -601
- package/dist/ScrollAreaViewport-es.js +278 -548
- package/dist/SelectGroupLabel-cjs.js +494 -528
- package/dist/SelectGroupLabel-es.js +411 -445
- package/dist/Separator-cjs.js +1 -0
- package/dist/Separator-es.js +1 -0
- package/dist/Tooltip/index.cjs +1 -1
- package/dist/Tooltip/index.mjs +1 -1
- package/dist/docs/InputTime/InputTime.md +8 -0
- package/dist/docs/SelectPrimitive/SelectPrimitive.md +38 -25
- package/dist/floating-ui.react-cjs.js +586 -10
- package/dist/floating-ui.react-dom-cjs.js +92 -92
- package/dist/floating-ui.react-dom-es.js +1 -1
- package/dist/floating-ui.react-es.js +580 -4
- package/dist/floating-ui.utils-cjs.js +159 -0
- package/dist/floating-ui.utils-es.js +136 -0
- package/dist/index.cjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/{ToolbarRootContext-cjs.js → isElementDisabled-cjs.js} +5 -0
- package/dist/{ToolbarRootContext-es.js → isElementDisabled-es.js} +5 -1
- package/dist/primitives/BottomSheet/index.cjs +3 -3
- package/dist/primitives/BottomSheet/index.mjs +3 -3
- package/dist/primitives/ComboboxPrimitive/index.cjs +2 -2
- package/dist/primitives/ComboboxPrimitive/index.mjs +2 -2
- package/dist/primitives/InputNumberExperimental/index.cjs +1 -1
- package/dist/primitives/InputNumberExperimental/index.mjs +1 -1
- package/dist/primitives/SelectPrimitive/index.cjs +5 -5
- package/dist/primitives/SelectPrimitive/index.mjs +5 -5
- package/dist/primitives/index.cjs +4 -4
- package/dist/primitives/index.mjs +4 -4
- package/dist/resolveAriaLabelledBy-cjs.js +5 -0
- package/dist/resolveAriaLabelledBy-es.js +5 -1
- package/dist/{CSPContext-cjs.js → scrollEdges-cjs.js} +30 -2
- package/dist/{CSPContext-es.js → scrollEdges-es.js} +28 -3
- package/dist/styles.css +9 -3
- package/dist/unstyledPrimitives/index.cjs +1217 -639
- package/dist/unstyledPrimitives/index.mjs +1163 -585
- package/dist/useButton-cjs.js +283 -317
- package/dist/useButton-es.js +277 -298
- package/dist/useCompositeListItem-cjs.js +328 -279
- package/dist/useCompositeListItem-es.js +293 -246
- package/dist/useLabel-cjs.js +11 -1
- package/dist/useLabel-es.js +11 -2
- package/dist/useLabelableId-cjs.js +37 -91
- package/dist/useLabelableId-es.js +37 -90
- package/dist/useRenderElement-cjs.js +81 -26
- package/dist/useRenderElement-es.js +81 -27
- package/dist/{InternalBackdrop-cjs.js → useScrollLock-cjs.js} +1286 -1220
- package/dist/{InternalBackdrop-es.js → useScrollLock-es.js} +1250 -1207
- package/package.json +3 -3
- package/dist/index.esm-cjs.js +0 -738
- package/dist/index.esm-es.js +0 -712
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var React = require('react');
|
|
4
|
-
var useButton = require('../useButton-cjs.js');
|
|
5
4
|
var ComboboxChipRemove = require('../ComboboxChipRemove-cjs.js');
|
|
6
5
|
var resolveAriaLabelledBy = require('../resolveAriaLabelledBy-cjs.js');
|
|
7
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
var useButton = require('../useButton-cjs.js');
|
|
8
8
|
var Separator = require('../Separator-cjs.js');
|
|
9
9
|
var DrawerDescription = require('../DrawerDescription-cjs.js');
|
|
10
10
|
var DrawerTitle = require('../DrawerTitle-cjs.js');
|
|
@@ -12,17 +12,17 @@ var ScrollAreaViewport = require('../ScrollAreaViewport-cjs.js');
|
|
|
12
12
|
var useRenderElement = require('../useRenderElement-cjs.js');
|
|
13
13
|
var floatingUi_utils_dom = require('../floating-ui.utils.dom-cjs.js');
|
|
14
14
|
var clamp = require('../clamp-cjs.js');
|
|
15
|
-
var
|
|
15
|
+
var useScrollLock = require('../useScrollLock-cjs.js');
|
|
16
16
|
var ReactDOM = require('react-dom');
|
|
17
17
|
var MenuSubmenuTrigger = require('../MenuSubmenuTrigger-cjs.js');
|
|
18
18
|
var useCompositeListItem = require('../useCompositeListItem-cjs.js');
|
|
19
|
-
var index_esm = require('../index.esm-cjs.js');
|
|
20
|
-
var NumberFieldInput = require('../NumberFieldInput-cjs.js');
|
|
21
19
|
var SelectGroupLabel = require('../SelectGroupLabel-cjs.js');
|
|
20
|
+
var floatingUi_utils = require('../floating-ui.utils-cjs.js');
|
|
21
|
+
var NumberFieldInput = require('../NumberFieldInput-cjs.js');
|
|
22
22
|
var useLabelableId = require('../useLabelableId-cjs.js');
|
|
23
23
|
var useLabel = require('../useLabel-cjs.js');
|
|
24
|
-
require('../
|
|
25
|
-
require('../
|
|
24
|
+
var scrollEdges = require('../scrollEdges-cjs.js');
|
|
25
|
+
require('../isElementDisabled-cjs.js');
|
|
26
26
|
require('../floating-ui.react-dom-cjs.js');
|
|
27
27
|
|
|
28
28
|
function _interopNamespaceDefault(e) {
|
|
@@ -78,13 +78,6 @@ function AutocompleteRoot(props) {
|
|
|
78
78
|
} else {
|
|
79
79
|
resolvedInputValue = internalValue;
|
|
80
80
|
}
|
|
81
|
-
const handleValueChange = useButton.useStableCallback((nextValue, eventDetails) => {
|
|
82
|
-
setInlineInputValue('');
|
|
83
|
-
if (!isControlled) {
|
|
84
|
-
setInternalValue(nextValue);
|
|
85
|
-
}
|
|
86
|
-
onValueChange?.(nextValue, eventDetails);
|
|
87
|
-
});
|
|
88
81
|
const collator = ComboboxChipRemove.useCoreFilter();
|
|
89
82
|
const baseFilter = React__namespace.useMemo(() => {
|
|
90
83
|
if (other.filter !== undefined) {
|
|
@@ -106,7 +99,14 @@ function AutocompleteRoot(props) {
|
|
|
106
99
|
return baseFilter(item, resolvedQuery, toString);
|
|
107
100
|
};
|
|
108
101
|
}, [baseFilter, mode, resolvedQuery, staticItems]);
|
|
109
|
-
|
|
102
|
+
function handleValueChange(nextValue, eventDetails) {
|
|
103
|
+
setInlineInputValue('');
|
|
104
|
+
if (!isControlled) {
|
|
105
|
+
setInternalValue(nextValue);
|
|
106
|
+
}
|
|
107
|
+
onValueChange?.(nextValue, eventDetails);
|
|
108
|
+
}
|
|
109
|
+
function handleItemHighlighted(highlightedValue, eventDetails) {
|
|
110
110
|
props.onItemHighlighted?.(highlightedValue, eventDetails);
|
|
111
111
|
if (eventDetails.reason === useButton.pointer) {
|
|
112
112
|
return;
|
|
@@ -120,7 +120,7 @@ function AutocompleteRoot(props) {
|
|
|
120
120
|
} else {
|
|
121
121
|
setInlineInputValue('');
|
|
122
122
|
}
|
|
123
|
-
}
|
|
123
|
+
}
|
|
124
124
|
return /*#__PURE__*/jsxRuntime.jsx(ComboboxChipRemove.AriaCombobox, {
|
|
125
125
|
...other,
|
|
126
126
|
itemToStringLabel: itemToStringValue,
|
|
@@ -154,6 +154,30 @@ function AutocompleteValue(props) {
|
|
|
154
154
|
});
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* A button that opens the popup.
|
|
159
|
+
* Renders a `<button>` element.
|
|
160
|
+
*
|
|
161
|
+
* Documentation: [Base UI Autocomplete](https://base-ui.com/react/components/autocomplete)
|
|
162
|
+
*/
|
|
163
|
+
const AutocompleteTrigger = ComboboxChipRemove.ComboboxTrigger;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* A wrapper for the input and its associated controls.
|
|
167
|
+
* Renders a `<div>` element.
|
|
168
|
+
*
|
|
169
|
+
* Documentation: [Base UI Autocomplete](https://base-ui.com/react/components/autocomplete)
|
|
170
|
+
*/
|
|
171
|
+
const AutocompleteInputGroup = ComboboxChipRemove.ComboboxInputGroup;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* An individual item in the list.
|
|
175
|
+
* Renders a `<div>` element.
|
|
176
|
+
*
|
|
177
|
+
* Documentation: [Base UI Autocomplete](https://base-ui.com/react/components/autocomplete)
|
|
178
|
+
*/
|
|
179
|
+
const AutocompleteItem = ComboboxChipRemove.ComboboxItem;
|
|
180
|
+
|
|
157
181
|
var index_parts$7 = /*#__PURE__*/Object.freeze({
|
|
158
182
|
__proto__: null,
|
|
159
183
|
Arrow: ComboboxChipRemove.ComboboxArrow,
|
|
@@ -165,8 +189,8 @@ var index_parts$7 = /*#__PURE__*/Object.freeze({
|
|
|
165
189
|
GroupLabel: ComboboxChipRemove.ComboboxGroupLabel,
|
|
166
190
|
Icon: ComboboxChipRemove.ComboboxIcon,
|
|
167
191
|
Input: ComboboxChipRemove.ComboboxInput,
|
|
168
|
-
InputGroup:
|
|
169
|
-
Item:
|
|
192
|
+
InputGroup: AutocompleteInputGroup,
|
|
193
|
+
Item: AutocompleteItem,
|
|
170
194
|
List: ComboboxChipRemove.ComboboxList,
|
|
171
195
|
Popup: ComboboxChipRemove.ComboboxPopup,
|
|
172
196
|
Portal: ComboboxChipRemove.ComboboxPortal,
|
|
@@ -175,7 +199,7 @@ var index_parts$7 = /*#__PURE__*/Object.freeze({
|
|
|
175
199
|
Row: ComboboxChipRemove.ComboboxRow,
|
|
176
200
|
Separator: Separator.Separator,
|
|
177
201
|
Status: ComboboxChipRemove.ComboboxStatus,
|
|
178
|
-
Trigger:
|
|
202
|
+
Trigger: AutocompleteTrigger,
|
|
179
203
|
Value: AutocompleteValue,
|
|
180
204
|
useFilter: ComboboxChipRemove.useCoreFilter,
|
|
181
205
|
useFilteredItems: ComboboxChipRemove.useFilteredItems
|
|
@@ -220,6 +244,7 @@ const DrawerIndent = /*#__PURE__*/React__namespace.forwardRef(function DrawerInd
|
|
|
220
244
|
const {
|
|
221
245
|
render,
|
|
222
246
|
className,
|
|
247
|
+
style,
|
|
223
248
|
...elementProps
|
|
224
249
|
} = componentProps;
|
|
225
250
|
const providerContext = ScrollAreaViewport.useDrawerProviderContext();
|
|
@@ -294,6 +319,7 @@ const DrawerIndentBackground = /*#__PURE__*/React__namespace.forwardRef(function
|
|
|
294
319
|
const {
|
|
295
320
|
render,
|
|
296
321
|
className,
|
|
322
|
+
style,
|
|
297
323
|
...elementProps
|
|
298
324
|
} = componentProps;
|
|
299
325
|
const providerContext = ScrollAreaViewport.useDrawerProviderContext();
|
|
@@ -393,36 +419,49 @@ function createVisualStateStore() {
|
|
|
393
419
|
};
|
|
394
420
|
}
|
|
395
421
|
|
|
396
|
-
function isScrollable(element, axis
|
|
422
|
+
function isScrollable(element, axis,
|
|
423
|
+
// When true, a container that overflows only once extra space is added (e.g. drawer
|
|
424
|
+
// keyboard scroll slack) still counts, as long as it has layout size on the axis.
|
|
425
|
+
allowOverflowIntent = false) {
|
|
397
426
|
const style = floatingUi_utils_dom.getComputedStyle(element);
|
|
398
427
|
if (axis === 'vertical') {
|
|
399
428
|
const overflowY = style.overflowY;
|
|
400
|
-
|
|
429
|
+
if (overflowY !== 'auto' && overflowY !== 'scroll') {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
return allowOverflowIntent ? element.clientHeight > 0 : element.scrollHeight > element.clientHeight;
|
|
401
433
|
}
|
|
402
434
|
const overflowX = style.overflowX;
|
|
403
|
-
|
|
435
|
+
if (overflowX !== 'auto' && overflowX !== 'scroll') {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
return allowOverflowIntent ? element.clientWidth > 0 : element.scrollWidth > element.clientWidth;
|
|
404
439
|
}
|
|
405
440
|
function hasScrollableAncestor(target, root, axes) {
|
|
441
|
+
// `getParentNode` crosses shadow boundaries (and slots), so a target inside a shadow root
|
|
442
|
+
// still walks up to scrollable ancestors in the light DOM.
|
|
406
443
|
let node = target;
|
|
407
|
-
while (node && node !== root) {
|
|
444
|
+
while (floatingUi_utils_dom.isHTMLElement(node) && node !== root && !floatingUi_utils_dom.isLastTraversableNode(node)) {
|
|
408
445
|
for (const axis of axes) {
|
|
409
446
|
if (isScrollable(node, axis)) {
|
|
410
447
|
return true;
|
|
411
448
|
}
|
|
412
449
|
}
|
|
413
|
-
node = node
|
|
450
|
+
node = floatingUi_utils_dom.getParentNode(node);
|
|
414
451
|
}
|
|
415
452
|
return false;
|
|
416
453
|
}
|
|
417
|
-
function findScrollableTouchTarget(target, root, axis = 'vertical') {
|
|
454
|
+
function findScrollableTouchTarget(target, root, axis = 'vertical', allowOverflowIntent = false) {
|
|
455
|
+
// `getParentNode` crosses shadow boundaries (and slots), so a target inside a shadow root
|
|
456
|
+
// still reaches a scrollable ancestor in the light DOM.
|
|
418
457
|
let node = floatingUi_utils_dom.isHTMLElement(target) ? target : null;
|
|
419
|
-
while (node && node !== root) {
|
|
420
|
-
if (isScrollable(node, axis)) {
|
|
458
|
+
while (floatingUi_utils_dom.isHTMLElement(node) && node !== root && !floatingUi_utils_dom.isLastTraversableNode(node)) {
|
|
459
|
+
if (isScrollable(node, axis, allowOverflowIntent)) {
|
|
421
460
|
return node;
|
|
422
461
|
}
|
|
423
|
-
node = node
|
|
462
|
+
node = floatingUi_utils_dom.getParentNode(node);
|
|
424
463
|
}
|
|
425
|
-
return isScrollable(root, axis) ? root : null;
|
|
464
|
+
return isScrollable(root, axis, allowOverflowIntent) ? root : null;
|
|
426
465
|
}
|
|
427
466
|
|
|
428
467
|
function getElementAtPoint(doc, x, y) {
|
|
@@ -451,7 +490,7 @@ function getDisplacement(direction, deltaX, deltaY) {
|
|
|
451
490
|
}
|
|
452
491
|
}
|
|
453
492
|
function getElementTransform(element) {
|
|
454
|
-
const computedStyle =
|
|
493
|
+
const computedStyle = floatingUi_utils_dom.getWindow(element).getComputedStyle(element);
|
|
455
494
|
const transform = computedStyle.transform;
|
|
456
495
|
let translateX = 0;
|
|
457
496
|
let translateY = 0;
|
|
@@ -480,6 +519,9 @@ function getElementTransform(element) {
|
|
|
480
519
|
function getValidTimeStamp(timeStamp) {
|
|
481
520
|
return Number.isFinite(timeStamp) && timeStamp > 0 ? timeStamp : null;
|
|
482
521
|
}
|
|
522
|
+
function getDragTransform(dragOffset, scale) {
|
|
523
|
+
return `translate3d(${dragOffset.x}px,${dragOffset.y}px,0) scale(${scale})`;
|
|
524
|
+
}
|
|
483
525
|
function hasPrimaryMouseButton(buttons) {
|
|
484
526
|
return buttons % 2 === 1;
|
|
485
527
|
}
|
|
@@ -509,6 +551,7 @@ function useSwipeDismiss(options) {
|
|
|
509
551
|
swipeThreshold: swipeThresholdProp,
|
|
510
552
|
onDismiss,
|
|
511
553
|
onProgress,
|
|
554
|
+
onCancel,
|
|
512
555
|
onSwipeStart,
|
|
513
556
|
onRelease,
|
|
514
557
|
onSwipingChange,
|
|
@@ -535,18 +578,7 @@ function useSwipeDismiss(options) {
|
|
|
535
578
|
}, [hasHorizontal, hasVertical]);
|
|
536
579
|
const [currentSwipeDirection, setCurrentSwipeDirection] = React__namespace.useState(undefined);
|
|
537
580
|
const [isSwiping, setIsSwiping] = React__namespace.useState(false);
|
|
538
|
-
const [isRealSwipe, setIsRealSwipe] = React__namespace.useState(false);
|
|
539
581
|
const [dragDismissed, setDragDismissed] = React__namespace.useState(false);
|
|
540
|
-
const [dragOffset, setDragOffset] = React__namespace.useState({
|
|
541
|
-
x: 0,
|
|
542
|
-
y: 0
|
|
543
|
-
});
|
|
544
|
-
const [initialTransform, setInitialTransform] = React__namespace.useState({
|
|
545
|
-
x: 0,
|
|
546
|
-
y: 0,
|
|
547
|
-
scale: 1
|
|
548
|
-
});
|
|
549
|
-
const [lockedDirection, setLockedDirection] = React__namespace.useState(null);
|
|
550
582
|
const dragStartPosRef = React__namespace.useRef({
|
|
551
583
|
x: 0,
|
|
552
584
|
y: 0
|
|
@@ -568,6 +600,7 @@ function useSwipeDismiss(options) {
|
|
|
568
600
|
x: 0,
|
|
569
601
|
y: 0
|
|
570
602
|
});
|
|
603
|
+
const lockedDirectionRef = React__namespace.useRef(null);
|
|
571
604
|
const isFirstPointerMoveRef = React__namespace.useRef(false);
|
|
572
605
|
const pendingSwipeRef = React__namespace.useRef(false);
|
|
573
606
|
const pendingSwipeStartPosRef = React__namespace.useRef(null);
|
|
@@ -587,6 +620,7 @@ function useSwipeDismiss(options) {
|
|
|
587
620
|
});
|
|
588
621
|
const lastProgressDetailsRef = React__namespace.useRef(null);
|
|
589
622
|
const isSwipingRef = React__namespace.useRef(false);
|
|
623
|
+
const dragStyleSnapshotRef = React__namespace.useRef(null);
|
|
590
624
|
const setSwiping = useButton.useStableCallback(nextSwiping => {
|
|
591
625
|
if (isSwipingRef.current === nextSwiping) {
|
|
592
626
|
return;
|
|
@@ -632,6 +666,35 @@ function useSwipeDismiss(options) {
|
|
|
632
666
|
}
|
|
633
667
|
onProgress?.(nextProgress, details);
|
|
634
668
|
});
|
|
669
|
+
const syncDragStyles = useButton.useStableCallback(swiping => {
|
|
670
|
+
const element = elementRef.current;
|
|
671
|
+
if (!trackDrag || !element) {
|
|
672
|
+
if (!swiping) {
|
|
673
|
+
dragStyleSnapshotRef.current = null;
|
|
674
|
+
}
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const style = element.style;
|
|
678
|
+
const dragStyleSnapshot = dragStyleSnapshotRef.current;
|
|
679
|
+
if (swiping) {
|
|
680
|
+
if (!dragStyleSnapshot) {
|
|
681
|
+
dragStyleSnapshotRef.current = [style.transition, style.transform];
|
|
682
|
+
}
|
|
683
|
+
style.transition = 'none';
|
|
684
|
+
} else if (dragStyleSnapshot) {
|
|
685
|
+
[style.transition, style.transform] = dragStyleSnapshot;
|
|
686
|
+
dragStyleSnapshotRef.current = null;
|
|
687
|
+
}
|
|
688
|
+
const dragOffset = dragOffsetRef.current;
|
|
689
|
+
const initialTransform = initialTransformRef.current;
|
|
690
|
+
const deltaX = dragOffset.x - initialTransform.x;
|
|
691
|
+
const deltaY = dragOffset.y - initialTransform.y;
|
|
692
|
+
if (swiping) {
|
|
693
|
+
style.transform = getDragTransform(dragOffset, initialTransform.scale);
|
|
694
|
+
}
|
|
695
|
+
style.setProperty(movementCssVars.x, `${deltaX}px`);
|
|
696
|
+
style.setProperty(movementCssVars.y, `${deltaY}px`);
|
|
697
|
+
});
|
|
635
698
|
function recordDragSample(offset, timeStamp) {
|
|
636
699
|
if (timeStamp === null) {
|
|
637
700
|
return;
|
|
@@ -653,18 +716,7 @@ function useSwipeDismiss(options) {
|
|
|
653
716
|
const reset = React__namespace.useCallback(() => {
|
|
654
717
|
setCurrentSwipeDirection(undefined);
|
|
655
718
|
setSwiping(false);
|
|
656
|
-
setIsRealSwipe(false);
|
|
657
719
|
setDragDismissed(false);
|
|
658
|
-
setDragOffset({
|
|
659
|
-
x: 0,
|
|
660
|
-
y: 0
|
|
661
|
-
});
|
|
662
|
-
setInitialTransform({
|
|
663
|
-
x: 0,
|
|
664
|
-
y: 0,
|
|
665
|
-
scale: 1
|
|
666
|
-
});
|
|
667
|
-
setLockedDirection(null);
|
|
668
720
|
updateSwipeProgress(0);
|
|
669
721
|
swipeThresholdRef.current = swipeThresholdDefault;
|
|
670
722
|
dragStartPosRef.current = {
|
|
@@ -687,6 +739,7 @@ function useSwipeDismiss(options) {
|
|
|
687
739
|
x: 0,
|
|
688
740
|
y: 0
|
|
689
741
|
};
|
|
742
|
+
lockedDirectionRef.current = null;
|
|
690
743
|
isFirstPointerMoveRef.current = false;
|
|
691
744
|
lastMovePosRef.current = null;
|
|
692
745
|
pendingSwipeRef.current = false;
|
|
@@ -704,7 +757,8 @@ function useSwipeDismiss(options) {
|
|
|
704
757
|
y: 0
|
|
705
758
|
};
|
|
706
759
|
lastProgressDetailsRef.current = null;
|
|
707
|
-
|
|
760
|
+
syncDragStyles(false);
|
|
761
|
+
}, [setSwiping, swipeThresholdDefault, syncDragStyles, updateSwipeProgress]);
|
|
708
762
|
React__namespace.useEffect(() => {
|
|
709
763
|
if (typeof swipeThresholdProp !== 'function') {
|
|
710
764
|
swipeThresholdRef.current = swipeThresholdDefault;
|
|
@@ -786,11 +840,6 @@ function useSwipeDismiss(options) {
|
|
|
786
840
|
x: transform.x,
|
|
787
841
|
y: transform.y
|
|
788
842
|
};
|
|
789
|
-
setInitialTransform(transform);
|
|
790
|
-
setDragOffset({
|
|
791
|
-
x: transform.x,
|
|
792
|
-
y: transform.y
|
|
793
|
-
});
|
|
794
843
|
recordDragSample({
|
|
795
844
|
x: transform.x,
|
|
796
845
|
y: transform.y
|
|
@@ -801,10 +850,10 @@ function useSwipeDismiss(options) {
|
|
|
801
850
|
}
|
|
802
851
|
onSwipeStart?.(event.nativeEvent);
|
|
803
852
|
setSwiping(true);
|
|
804
|
-
|
|
805
|
-
setLockedDirection(null);
|
|
853
|
+
lockedDirectionRef.current = null;
|
|
806
854
|
isFirstPointerMoveRef.current = true;
|
|
807
855
|
updateSwipeProgress(0);
|
|
856
|
+
syncDragStyles(true);
|
|
808
857
|
return true;
|
|
809
858
|
}
|
|
810
859
|
function resetPendingSwipeState() {
|
|
@@ -822,19 +871,15 @@ function useSwipeDismiss(options) {
|
|
|
822
871
|
return;
|
|
823
872
|
}
|
|
824
873
|
setSwiping(false);
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
const resolvedInitialTransform = trackDrag ? initialTransform : initialTransformRef.current;
|
|
874
|
+
lockedDirectionRef.current = null;
|
|
875
|
+
const resolvedInitialTransform = initialTransformRef.current;
|
|
828
876
|
dragOffsetRef.current = {
|
|
829
877
|
x: resolvedInitialTransform.x,
|
|
830
878
|
y: resolvedInitialTransform.y
|
|
831
879
|
};
|
|
832
|
-
setDragOffset({
|
|
833
|
-
x: resolvedInitialTransform.x,
|
|
834
|
-
y: resolvedInitialTransform.y
|
|
835
|
-
});
|
|
836
880
|
setCurrentSwipeDirection(undefined);
|
|
837
881
|
sawPrimaryButtonsOnMoveRef.current = false;
|
|
882
|
+
syncDragStyles(false);
|
|
838
883
|
const element = elementRef.current;
|
|
839
884
|
if (element) {
|
|
840
885
|
safelyChangePointerCapture(element, event.pointerId, 'releasePointerCapture');
|
|
@@ -844,6 +889,7 @@ function useSwipeDismiss(options) {
|
|
|
844
889
|
deltaY: 0,
|
|
845
890
|
direction: undefined
|
|
846
891
|
});
|
|
892
|
+
onCancel?.(event.nativeEvent);
|
|
847
893
|
}
|
|
848
894
|
function applyDirectionalDamping(deltaX, deltaY) {
|
|
849
895
|
const exponent = value => value >= 0 ? value ** 0.5 : -(Math.abs(value) ** 0.5);
|
|
@@ -964,17 +1010,12 @@ function useSwipeDismiss(options) {
|
|
|
964
1010
|
const deltaY = clientY - dragStartPosRef.current.y;
|
|
965
1011
|
const cancelDeltaY = clientY - swipeCancelBaselineRef.current.y;
|
|
966
1012
|
const cancelDeltaX = clientX - swipeCancelBaselineRef.current.x;
|
|
967
|
-
|
|
1013
|
+
let lockedDirection = lockedDirectionRef.current;
|
|
1014
|
+
if (lockedDirection === null && hasHorizontal && hasVertical) {
|
|
968
1015
|
const movementDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
969
1016
|
if (movementDistance >= MIN_DRAG_THRESHOLD) {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
if (hasHorizontal && hasVertical) {
|
|
973
|
-
const absX = Math.abs(deltaX);
|
|
974
|
-
const absY = Math.abs(deltaY);
|
|
975
|
-
setLockedDirection(absX > absY ? 'horizontal' : 'vertical');
|
|
976
|
-
}
|
|
977
|
-
}
|
|
1017
|
+
lockedDirection = Math.abs(deltaX) > Math.abs(deltaY) ? 'horizontal' : 'vertical';
|
|
1018
|
+
lockedDirectionRef.current = lockedDirection;
|
|
978
1019
|
}
|
|
979
1020
|
}
|
|
980
1021
|
let candidate;
|
|
@@ -1039,12 +1080,7 @@ function useSwipeDismiss(options) {
|
|
|
1039
1080
|
x: newOffsetX,
|
|
1040
1081
|
y: newOffsetY
|
|
1041
1082
|
};
|
|
1042
|
-
|
|
1043
|
-
setDragOffset({
|
|
1044
|
-
x: newOffsetX,
|
|
1045
|
-
y: newOffsetY
|
|
1046
|
-
});
|
|
1047
|
-
}
|
|
1083
|
+
syncDragStyles(true);
|
|
1048
1084
|
recordDragSample({
|
|
1049
1085
|
x: newOffsetX,
|
|
1050
1086
|
y: newOffsetY
|
|
@@ -1086,91 +1122,6 @@ function useSwipeDismiss(options) {
|
|
|
1086
1122
|
direction: swipeDirectionDetails
|
|
1087
1123
|
});
|
|
1088
1124
|
}
|
|
1089
|
-
const handleMove = useButton.useStableCallback(event => {
|
|
1090
|
-
const currentPos = getPrimaryPointerPosition(event);
|
|
1091
|
-
if (!currentPos) {
|
|
1092
|
-
return;
|
|
1093
|
-
}
|
|
1094
|
-
if (!('touches' in event)) {
|
|
1095
|
-
const hasPrimaryButton = hasPrimaryMouseButton(event.buttons);
|
|
1096
|
-
if (hasPrimaryButton) {
|
|
1097
|
-
sawPrimaryButtonsOnMoveRef.current = true;
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
// Cancel the swipe if a non-primary button takes over the interaction.
|
|
1101
|
-
// This handles cases where a right-click interrupts dragging.
|
|
1102
|
-
const lostPrimaryButtonDuringSwipe = event.buttons === 0 && sawPrimaryButtonsOnMoveRef.current;
|
|
1103
|
-
if (event.buttons !== 0 && !hasPrimaryButton || lostPrimaryButtonDuringSwipe) {
|
|
1104
|
-
cancelSwipeInteraction(event);
|
|
1105
|
-
return;
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
if (!isSwiping && pendingSwipeRef.current) {
|
|
1109
|
-
if (!isTouchLikeEvent(event) && (event.defaultPrevented || event.nativeEvent.defaultPrevented)) {
|
|
1110
|
-
resetPendingSwipeState();
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
const allowedToStart = canStart ? canStart(currentPos, {
|
|
1114
|
-
nativeEvent: event.nativeEvent,
|
|
1115
|
-
direction: primaryDirection
|
|
1116
|
-
}) : true;
|
|
1117
|
-
if (allowedToStart) {
|
|
1118
|
-
const pendingStartPos = pendingSwipeStartPosRef.current;
|
|
1119
|
-
let ignoreScrollableOnStart = false;
|
|
1120
|
-
if (isTouchLikeEvent(event)) {
|
|
1121
|
-
const element = elementRef.current;
|
|
1122
|
-
if (pendingStartPos && element) {
|
|
1123
|
-
const target = getTargetAtPoint(currentPos, event.nativeEvent);
|
|
1124
|
-
const doc = useButton.ownerDocument(element);
|
|
1125
|
-
const body = doc.body;
|
|
1126
|
-
const scrollTarget = body ? findGestureScrollableTouchTarget(target, body) : null;
|
|
1127
|
-
if (scrollTarget && (useButton.contains(element, scrollTarget) || useButton.contains(scrollTarget, element))) {
|
|
1128
|
-
const deltaX = currentPos.x - pendingStartPos.x;
|
|
1129
|
-
const deltaY = currentPos.y - pendingStartPos.y;
|
|
1130
|
-
const canSwipeFromEdge = canSwipeFromScrollEdgeOnPendingMove(scrollTarget, deltaX, deltaY);
|
|
1131
|
-
if (canSwipeFromEdge === false) {
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
|
-
if (canSwipeFromEdge === true) {
|
|
1135
|
-
ignoreScrollableOnStart = true;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
const started = startSwipeAtPosition(event, currentPos, {
|
|
1141
|
-
ignoreScrollableTarget: ignoreScrollableOnStart,
|
|
1142
|
-
ignoreScrollableAncestors: ignoreScrollableOnStart
|
|
1143
|
-
});
|
|
1144
|
-
if (started) {
|
|
1145
|
-
if (pendingStartPos && ignoreScrollableOnStart) {
|
|
1146
|
-
// Preserve displacement between touchstart and the move that activates swipe from
|
|
1147
|
-
// a scroll-edge so quick flicks can dismiss.
|
|
1148
|
-
clearPendingSwipeStartState();
|
|
1149
|
-
dragStartPosRef.current = pendingStartPos;
|
|
1150
|
-
swipeCancelBaselineRef.current = pendingStartPos;
|
|
1151
|
-
lastMovePosRef.current = pendingStartPos;
|
|
1152
|
-
isFirstPointerMoveRef.current = false;
|
|
1153
|
-
} else {
|
|
1154
|
-
// Start from the current in-bounds position without dropping follow-up move
|
|
1155
|
-
// displacement; this avoids jumps when entering from outside the element while
|
|
1156
|
-
// keeping swipe tracking responsive on the next move.
|
|
1157
|
-
clearPendingSwipeStartState();
|
|
1158
|
-
swipeFromScrollableRef.current = false;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
const previousPos = lastMovePosRef.current;
|
|
1164
|
-
const movement = previousPos === null ? {
|
|
1165
|
-
x: 0,
|
|
1166
|
-
y: 0
|
|
1167
|
-
} : {
|
|
1168
|
-
x: currentPos.x - previousPos.x,
|
|
1169
|
-
y: currentPos.y - previousPos.y
|
|
1170
|
-
};
|
|
1171
|
-
lastMovePosRef.current = currentPos;
|
|
1172
|
-
handleMoveCore(event, currentPos, movement);
|
|
1173
|
-
});
|
|
1174
1125
|
const handleEnd = useButton.useStableCallback(event => {
|
|
1175
1126
|
if (!enabled) {
|
|
1176
1127
|
return;
|
|
@@ -1182,7 +1133,7 @@ function useSwipeDismiss(options) {
|
|
|
1182
1133
|
const progressDetails = {
|
|
1183
1134
|
deltaX: releaseDeltaX,
|
|
1184
1135
|
deltaY: releaseDeltaY,
|
|
1185
|
-
direction:
|
|
1136
|
+
direction: intendedSwipeDirectionRef.current
|
|
1186
1137
|
};
|
|
1187
1138
|
if (!isSwipingRef.current) {
|
|
1188
1139
|
resetPendingSwipeState();
|
|
@@ -1190,8 +1141,7 @@ function useSwipeDismiss(options) {
|
|
|
1190
1141
|
return;
|
|
1191
1142
|
}
|
|
1192
1143
|
setSwiping(false);
|
|
1193
|
-
|
|
1194
|
-
setLockedDirection(null);
|
|
1144
|
+
lockedDirectionRef.current = null;
|
|
1195
1145
|
resetPendingSwipeState();
|
|
1196
1146
|
sawPrimaryButtonsOnMoveRef.current = false;
|
|
1197
1147
|
const element = elementRef.current;
|
|
@@ -1232,7 +1182,7 @@ function useSwipeDismiss(options) {
|
|
|
1232
1182
|
}
|
|
1233
1183
|
const releaseDecision = onRelease?.({
|
|
1234
1184
|
event: event.nativeEvent,
|
|
1235
|
-
direction:
|
|
1185
|
+
direction: intendedSwipeDirectionRef.current,
|
|
1236
1186
|
deltaX,
|
|
1237
1187
|
deltaY,
|
|
1238
1188
|
velocityX,
|
|
@@ -1246,11 +1196,8 @@ function useSwipeDismiss(options) {
|
|
|
1246
1196
|
x: resolvedInitialTransform.x,
|
|
1247
1197
|
y: resolvedInitialTransform.y
|
|
1248
1198
|
};
|
|
1249
|
-
setDragOffset({
|
|
1250
|
-
x: resolvedInitialTransform.x,
|
|
1251
|
-
y: resolvedInitialTransform.y
|
|
1252
|
-
});
|
|
1253
1199
|
setCurrentSwipeDirection(undefined);
|
|
1200
|
+
syncDragStyles(false);
|
|
1254
1201
|
updateSwipeProgress(0, progressDetails);
|
|
1255
1202
|
return;
|
|
1256
1203
|
}
|
|
@@ -1258,7 +1205,7 @@ function useSwipeDismiss(options) {
|
|
|
1258
1205
|
let dismissDirection;
|
|
1259
1206
|
if (hasReleaseDecision) {
|
|
1260
1207
|
shouldClose = releaseDecision;
|
|
1261
|
-
dismissDirection =
|
|
1208
|
+
dismissDirection = intendedSwipeDirectionRef.current ?? primaryDirection;
|
|
1262
1209
|
} else {
|
|
1263
1210
|
for (const direction of directions) {
|
|
1264
1211
|
switch (direction) {
|
|
@@ -1295,6 +1242,7 @@ function useSwipeDismiss(options) {
|
|
|
1295
1242
|
if (shouldClose && dismissDirection) {
|
|
1296
1243
|
setCurrentSwipeDirection(dismissDirection);
|
|
1297
1244
|
setDragDismissed(true);
|
|
1245
|
+
syncDragStyles(false);
|
|
1298
1246
|
onDismiss?.(event.nativeEvent, {
|
|
1299
1247
|
direction: dismissDirection
|
|
1300
1248
|
});
|
|
@@ -1303,34 +1251,150 @@ function useSwipeDismiss(options) {
|
|
|
1303
1251
|
x: resolvedInitialTransform.x,
|
|
1304
1252
|
y: resolvedInitialTransform.y
|
|
1305
1253
|
};
|
|
1306
|
-
setDragOffset({
|
|
1307
|
-
x: resolvedInitialTransform.x,
|
|
1308
|
-
y: resolvedInitialTransform.y
|
|
1309
|
-
});
|
|
1310
1254
|
setCurrentSwipeDirection(undefined);
|
|
1255
|
+
syncDragStyles(false);
|
|
1311
1256
|
updateSwipeProgress(0, progressDetails);
|
|
1312
1257
|
}
|
|
1313
1258
|
});
|
|
1259
|
+
const handleMove = useButton.useStableCallback(event => {
|
|
1260
|
+
const currentPos = getPrimaryPointerPosition(event);
|
|
1261
|
+
if (!currentPos) {
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
let endAfterMove = false;
|
|
1265
|
+
if (!('touches' in event)) {
|
|
1266
|
+
const hasPrimaryButton = hasPrimaryMouseButton(event.buttons);
|
|
1267
|
+
if (hasPrimaryButton) {
|
|
1268
|
+
sawPrimaryButtonsOnMoveRef.current = true;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// Cancel the swipe if a non-primary button takes over the interaction.
|
|
1272
|
+
// This handles cases where a right-click interrupts dragging.
|
|
1273
|
+
if (event.buttons !== 0 && !hasPrimaryButton) {
|
|
1274
|
+
cancelSwipeInteraction(event);
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// A `buttons: 0` pointermove means the primary button was already released, so the gesture is
|
|
1279
|
+
// over even if no pointerup reached us. On fast trackpad flicks this trailing move is
|
|
1280
|
+
// dispatched just before pointerup; treat it as the release (mirroring touchend) instead of
|
|
1281
|
+
// cancelling and snapping the element back.
|
|
1282
|
+
if (event.buttons === 0 && sawPrimaryButtonsOnMoveRef.current) {
|
|
1283
|
+
if (!isSwipingRef.current) {
|
|
1284
|
+
// The gesture never activated — discard it.
|
|
1285
|
+
handleEnd(event);
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
// This release move can itself carry the threshold-crossing displacement (and the peak
|
|
1289
|
+
// release velocity), so let it flow through `handleMoveCore` below to update the drag
|
|
1290
|
+
// offset / velocity sample, then commit the release afterwards.
|
|
1291
|
+
endAfterMove = true;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
if (!isSwiping && pendingSwipeRef.current) {
|
|
1295
|
+
if (!isTouchLikeEvent(event) && (event.defaultPrevented || event.nativeEvent.defaultPrevented)) {
|
|
1296
|
+
resetPendingSwipeState();
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
const allowedToStart = canStart ? canStart(currentPos, {
|
|
1300
|
+
nativeEvent: event.nativeEvent,
|
|
1301
|
+
direction: primaryDirection
|
|
1302
|
+
}) : true;
|
|
1303
|
+
if (allowedToStart) {
|
|
1304
|
+
const pendingStartPos = pendingSwipeStartPosRef.current;
|
|
1305
|
+
let ignoreScrollableOnStart = false;
|
|
1306
|
+
if (isTouchLikeEvent(event)) {
|
|
1307
|
+
const element = elementRef.current;
|
|
1308
|
+
if (pendingStartPos && element) {
|
|
1309
|
+
const target = getTargetAtPoint(currentPos, event.nativeEvent);
|
|
1310
|
+
const doc = useButton.ownerDocument(element);
|
|
1311
|
+
const body = doc.body;
|
|
1312
|
+
const scrollTarget = body ? findGestureScrollableTouchTarget(target, body) : null;
|
|
1313
|
+
if (scrollTarget && (useButton.contains(element, scrollTarget) || useButton.contains(scrollTarget, element))) {
|
|
1314
|
+
const deltaX = currentPos.x - pendingStartPos.x;
|
|
1315
|
+
const deltaY = currentPos.y - pendingStartPos.y;
|
|
1316
|
+
const canSwipeFromEdge = canSwipeFromScrollEdgeOnPendingMove(scrollTarget, deltaX, deltaY);
|
|
1317
|
+
if (canSwipeFromEdge === false) {
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
if (canSwipeFromEdge === true) {
|
|
1321
|
+
ignoreScrollableOnStart = true;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
const started = startSwipeAtPosition(event, currentPos, {
|
|
1327
|
+
ignoreScrollableTarget: ignoreScrollableOnStart,
|
|
1328
|
+
ignoreScrollableAncestors: ignoreScrollableOnStart
|
|
1329
|
+
});
|
|
1330
|
+
if (started) {
|
|
1331
|
+
if (pendingStartPos && ignoreScrollableOnStart) {
|
|
1332
|
+
// Preserve displacement between touchstart and the move that activates swipe from
|
|
1333
|
+
// a scroll-edge so quick flicks can dismiss.
|
|
1334
|
+
clearPendingSwipeStartState();
|
|
1335
|
+
dragStartPosRef.current = pendingStartPos;
|
|
1336
|
+
swipeCancelBaselineRef.current = pendingStartPos;
|
|
1337
|
+
lastMovePosRef.current = pendingStartPos;
|
|
1338
|
+
isFirstPointerMoveRef.current = false;
|
|
1339
|
+
} else {
|
|
1340
|
+
// Start from the current in-bounds position without dropping follow-up move
|
|
1341
|
+
// displacement; this avoids jumps when entering from outside the element while
|
|
1342
|
+
// keeping swipe tracking responsive on the next move.
|
|
1343
|
+
clearPendingSwipeStartState();
|
|
1344
|
+
swipeFromScrollableRef.current = false;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
const previousPos = lastMovePosRef.current;
|
|
1350
|
+
const movement = previousPos === null ? {
|
|
1351
|
+
x: 0,
|
|
1352
|
+
y: 0
|
|
1353
|
+
} : {
|
|
1354
|
+
x: currentPos.x - previousPos.x,
|
|
1355
|
+
y: currentPos.y - previousPos.y
|
|
1356
|
+
};
|
|
1357
|
+
lastMovePosRef.current = currentPos;
|
|
1358
|
+
handleMoveCore(event, currentPos, movement);
|
|
1359
|
+
|
|
1360
|
+
// `endAfterMove` is only set in the non-touch branch above; the `'touches'` guard re-narrows the
|
|
1361
|
+
// event type for `handleEnd` after the shared move handling has run.
|
|
1362
|
+
if (endAfterMove && !('touches' in event)) {
|
|
1363
|
+
handleEnd(event);
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
// Feeds a native touchmove into the swipe pipeline. Used by consumers that claim the gesture
|
|
1368
|
+
// in a capture-phase listener and stop it from reaching React's delegated touch handlers.
|
|
1369
|
+
const moveNative = useButton.useStableCallback((nativeEvent, currentTarget) => {
|
|
1370
|
+
handleMove({
|
|
1371
|
+
touches: nativeEvent.touches,
|
|
1372
|
+
currentTarget,
|
|
1373
|
+
nativeEvent,
|
|
1374
|
+
defaultPrevented: nativeEvent.defaultPrevented,
|
|
1375
|
+
timeStamp: nativeEvent.timeStamp
|
|
1376
|
+
});
|
|
1377
|
+
});
|
|
1314
1378
|
const getDragStyles = React__namespace.useCallback(() => {
|
|
1315
|
-
const
|
|
1316
|
-
const
|
|
1317
|
-
|
|
1379
|
+
const dragOffset = dragOffsetRef.current;
|
|
1380
|
+
const initialTransform = initialTransformRef.current;
|
|
1381
|
+
const deltaX = dragOffset.x - initialTransform.x;
|
|
1382
|
+
const deltaY = dragOffset.y - initialTransform.y;
|
|
1383
|
+
if (!isSwiping && deltaX === 0 && deltaY === 0 && !dragDismissed) {
|
|
1318
1384
|
return {
|
|
1319
1385
|
[movementCssVars.x]: '0px',
|
|
1320
1386
|
[movementCssVars.y]: '0px'
|
|
1321
1387
|
};
|
|
1322
1388
|
}
|
|
1323
|
-
const deltaX = resolvedDragOffset.x - resolvedInitialTransform.x;
|
|
1324
|
-
const deltaY = resolvedDragOffset.y - resolvedInitialTransform.y;
|
|
1325
1389
|
return {
|
|
1326
1390
|
transition: isSwiping ? 'none' : undefined,
|
|
1327
1391
|
// While swiping, freeze the element at its current visual transform so it doesn't snap to the
|
|
1328
1392
|
// end position.
|
|
1329
|
-
transform: isSwiping ?
|
|
1393
|
+
transform: isSwiping ? getDragTransform(dragOffset, initialTransform.scale) : undefined,
|
|
1330
1394
|
[movementCssVars.x]: `${deltaX}px`,
|
|
1331
1395
|
[movementCssVars.y]: `${deltaY}px`
|
|
1332
1396
|
};
|
|
1333
|
-
}, [dragDismissed,
|
|
1397
|
+
}, [dragDismissed, isSwiping, movementCssVars]);
|
|
1334
1398
|
const getPointerProps = React__namespace.useCallback(() => {
|
|
1335
1399
|
if (!enabled) {
|
|
1336
1400
|
return {};
|
|
@@ -1359,6 +1423,7 @@ function useSwipeDismiss(options) {
|
|
|
1359
1423
|
dragDismissed,
|
|
1360
1424
|
getPointerProps,
|
|
1361
1425
|
getTouchProps,
|
|
1426
|
+
moveNative,
|
|
1362
1427
|
getDragStyles,
|
|
1363
1428
|
reset
|
|
1364
1429
|
};
|
|
@@ -1368,11 +1433,11 @@ let DrawerSwipeAreaDataAttributes = function (DrawerSwipeAreaDataAttributes) {
|
|
|
1368
1433
|
/**
|
|
1369
1434
|
* Present when the drawer is open.
|
|
1370
1435
|
*/
|
|
1371
|
-
DrawerSwipeAreaDataAttributes[DrawerSwipeAreaDataAttributes["open"] =
|
|
1436
|
+
DrawerSwipeAreaDataAttributes[DrawerSwipeAreaDataAttributes["open"] = useScrollLock.CommonPopupDataAttributes.open] = "open";
|
|
1372
1437
|
/**
|
|
1373
1438
|
* Present when the drawer is closed.
|
|
1374
1439
|
*/
|
|
1375
|
-
DrawerSwipeAreaDataAttributes[DrawerSwipeAreaDataAttributes["closed"] =
|
|
1440
|
+
DrawerSwipeAreaDataAttributes[DrawerSwipeAreaDataAttributes["closed"] = useScrollLock.CommonPopupDataAttributes.closed] = "closed";
|
|
1376
1441
|
/**
|
|
1377
1442
|
* Present when the swipe area is disabled.
|
|
1378
1443
|
*/
|
|
@@ -1439,8 +1504,9 @@ function resolveTouchAction(direction) {
|
|
|
1439
1504
|
*/
|
|
1440
1505
|
const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function DrawerSwipeArea(componentProps, forwardedRef) {
|
|
1441
1506
|
const {
|
|
1442
|
-
className,
|
|
1443
1507
|
render,
|
|
1508
|
+
className,
|
|
1509
|
+
style,
|
|
1444
1510
|
disabled = false,
|
|
1445
1511
|
swipeDirection: swipeDirectionProp,
|
|
1446
1512
|
...elementProps
|
|
@@ -1466,15 +1532,15 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
|
|
|
1466
1532
|
const appliedSwipeStylesRef = React__namespace.useRef(false);
|
|
1467
1533
|
const popupTransitionRef = React__namespace.useRef(null);
|
|
1468
1534
|
const swipeAreaId = useButton.useBaseUiId(componentProps.id);
|
|
1469
|
-
const registerTrigger =
|
|
1535
|
+
const registerTrigger = useScrollLock.useTriggerRegistration(swipeAreaId, store);
|
|
1470
1536
|
const open = store.useState('open');
|
|
1471
|
-
const resolvedSwipeDirection = swipeDirectionProp ?? oppositeSwipeDirection[swipeDirection];
|
|
1472
|
-
const dismissDirection = oppositeSwipeDirection[resolvedSwipeDirection];
|
|
1473
|
-
const enabled = !disabled && (!open || swipeActive);
|
|
1474
1537
|
const resetDragDelta = useButton.useStableCallback(() => {
|
|
1475
1538
|
dragDeltaRef.current.x = 0;
|
|
1476
1539
|
dragDeltaRef.current.y = 0;
|
|
1477
1540
|
});
|
|
1541
|
+
const resolvedSwipeDirection = swipeDirectionProp ?? oppositeSwipeDirection[swipeDirection];
|
|
1542
|
+
const dismissDirection = oppositeSwipeDirection[resolvedSwipeDirection];
|
|
1543
|
+
const enabled = !disabled && (!open || swipeActive);
|
|
1478
1544
|
function disableDismissForSwipe() {
|
|
1479
1545
|
releaseDismissTimeout.clear();
|
|
1480
1546
|
store.context.outsidePressEnabledRef.current = false;
|
|
@@ -1614,6 +1680,18 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
|
|
|
1614
1680
|
}
|
|
1615
1681
|
store.setOpen(false, useButton.createChangeEventDetails(useButton.swipe, event, swipeAreaRef.current ?? undefined));
|
|
1616
1682
|
}
|
|
1683
|
+
function resetSwipeInteractionState() {
|
|
1684
|
+
swipeStartEventRef.current = null;
|
|
1685
|
+
openedBySwipeRef.current = false;
|
|
1686
|
+
closedOffsetRef.current = null;
|
|
1687
|
+
setSwipeActive(false);
|
|
1688
|
+
}
|
|
1689
|
+
function finishSwipeInteraction() {
|
|
1690
|
+
resetSwipeInteractionState();
|
|
1691
|
+
enableDismissAfterRelease();
|
|
1692
|
+
resetDragDelta();
|
|
1693
|
+
clearSwipeStyles();
|
|
1694
|
+
}
|
|
1617
1695
|
const swipe = useSwipeDismiss({
|
|
1618
1696
|
enabled,
|
|
1619
1697
|
directions: [resolvedSwipeDirection],
|
|
@@ -1672,15 +1750,10 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
|
|
|
1672
1750
|
} else if (openedBySwipeRef.current) {
|
|
1673
1751
|
closeDrawer(event);
|
|
1674
1752
|
}
|
|
1675
|
-
|
|
1676
|
-
openedBySwipeRef.current = false;
|
|
1677
|
-
setSwipeActive(false);
|
|
1678
|
-
closedOffsetRef.current = null;
|
|
1679
|
-
enableDismissAfterRelease();
|
|
1680
|
-
resetDragDelta();
|
|
1681
|
-
clearSwipeStyles();
|
|
1753
|
+
finishSwipeInteraction();
|
|
1682
1754
|
return false;
|
|
1683
|
-
}
|
|
1755
|
+
},
|
|
1756
|
+
onCancel: finishSwipeInteraction
|
|
1684
1757
|
});
|
|
1685
1758
|
const swipePointerProps = swipe.getPointerProps();
|
|
1686
1759
|
const swipeTouchProps = swipe.getTouchProps();
|
|
@@ -1690,10 +1763,7 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
|
|
|
1690
1763
|
resetSwipe();
|
|
1691
1764
|
resetDragDelta();
|
|
1692
1765
|
clearSwipeStyles();
|
|
1693
|
-
|
|
1694
|
-
openedBySwipeRef.current = false;
|
|
1695
|
-
swipeStartEventRef.current = null;
|
|
1696
|
-
closedOffsetRef.current = null;
|
|
1766
|
+
resetSwipeInteractionState();
|
|
1697
1767
|
}
|
|
1698
1768
|
}, [clearSwipeStyles, enabled, resetDragDelta, resetSwipe]);
|
|
1699
1769
|
React__namespace.useEffect(() => {
|
|
@@ -1723,6 +1793,7 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
|
|
|
1723
1793
|
return;
|
|
1724
1794
|
}
|
|
1725
1795
|
swipePointerProps.onPointerDown?.(event);
|
|
1796
|
+
|
|
1726
1797
|
// Prevent native text selection/drag gestures from competing with swipe-open dragging.
|
|
1727
1798
|
if (event.cancelable) {
|
|
1728
1799
|
event.preventDefault();
|
|
@@ -1753,6 +1824,12 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
|
|
|
1753
1824
|
});
|
|
1754
1825
|
if (process.env.NODE_ENV !== "production") DrawerSwipeArea.displayName = "DrawerSwipeArea";
|
|
1755
1826
|
|
|
1827
|
+
const DrawerVirtualKeyboardContext = /*#__PURE__*/React__namespace.createContext(undefined);
|
|
1828
|
+
if (process.env.NODE_ENV !== "production") DrawerVirtualKeyboardContext.displayName = "DrawerVirtualKeyboardContext";
|
|
1829
|
+
function useDrawerVirtualKeyboardContext() {
|
|
1830
|
+
return React__namespace.useContext(DrawerVirtualKeyboardContext);
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1756
1833
|
const MIN_SWIPE_THRESHOLD = 10;
|
|
1757
1834
|
const FAST_SWIPE_VELOCITY = 0.5;
|
|
1758
1835
|
const SNAP_VELOCITY_THRESHOLD = 0.5;
|
|
@@ -1773,14 +1850,17 @@ const DRAWER_CONTENT_SELECTOR = `[${ScrollAreaViewport.DRAWER_CONTENT_ATTRIBUTE}
|
|
|
1773
1850
|
*/
|
|
1774
1851
|
const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerViewport(props, forwardedRef) {
|
|
1775
1852
|
const {
|
|
1776
|
-
className,
|
|
1777
1853
|
render,
|
|
1854
|
+
className,
|
|
1855
|
+
style,
|
|
1778
1856
|
children,
|
|
1779
1857
|
...elementProps
|
|
1780
1858
|
} = props;
|
|
1781
1859
|
const {
|
|
1782
1860
|
store
|
|
1783
1861
|
} = ScrollAreaViewport.useDialogRootContext();
|
|
1862
|
+
const popupRef = store.context.popupRef;
|
|
1863
|
+
const backdropRef = store.context.backdropRef;
|
|
1784
1864
|
const {
|
|
1785
1865
|
swipeDirection,
|
|
1786
1866
|
notifyParentSwipingChange,
|
|
@@ -1789,17 +1869,6 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
1789
1869
|
snapToSequentialPoints
|
|
1790
1870
|
} = ScrollAreaViewport.useDrawerRootContext();
|
|
1791
1871
|
const providerContext = ScrollAreaViewport.useDrawerProviderContext();
|
|
1792
|
-
const visualStateStore = providerContext?.visualStateStore;
|
|
1793
|
-
const open = store.useState('open');
|
|
1794
|
-
const mounted = store.useState('mounted');
|
|
1795
|
-
const nested = store.useState('nested');
|
|
1796
|
-
const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
|
|
1797
|
-
const viewportElement = store.useState('viewportElement');
|
|
1798
|
-
const popupElementState = store.useState('popupElement');
|
|
1799
|
-
const nestedDrawerOpen = nestedOpenDialogCount > 0;
|
|
1800
|
-
const scrollAxis = swipeDirection === 'left' || swipeDirection === 'right' ? 'horizontal' : 'vertical';
|
|
1801
|
-
const isVerticalScrollAxis = scrollAxis === 'vertical';
|
|
1802
|
-
const crossScrollAxis = isVerticalScrollAxis ? 'horizontal' : 'vertical';
|
|
1803
1872
|
const {
|
|
1804
1873
|
snapPoints,
|
|
1805
1874
|
resolvedSnapPoints,
|
|
@@ -1808,15 +1877,28 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
1808
1877
|
setActiveSnapPoint,
|
|
1809
1878
|
popupHeight
|
|
1810
1879
|
} = ScrollAreaViewport.useDrawerSnapPoints();
|
|
1880
|
+
const open = store.useState('open');
|
|
1881
|
+
const mounted = store.useState('mounted');
|
|
1882
|
+
const nested = store.useState('nested');
|
|
1883
|
+
const nestedOpenDrawerCount = store.useState('nestedOpenDrawerCount');
|
|
1884
|
+
const viewportElement = store.useState('viewportElement');
|
|
1885
|
+
const popupElementState = store.useState('popupElement');
|
|
1886
|
+
const visualStateStore = providerContext?.visualStateStore;
|
|
1887
|
+
const nestedDrawerOpen = nestedOpenDrawerCount > 0;
|
|
1888
|
+
const scrollAxis = swipeDirection === 'left' || swipeDirection === 'right' ? 'horizontal' : 'vertical';
|
|
1889
|
+
const isVerticalScrollAxis = scrollAxis === 'vertical';
|
|
1890
|
+
const crossScrollAxis = isVerticalScrollAxis ? 'horizontal' : 'vertical';
|
|
1811
1891
|
const [swipeRelease, setSwipeRelease] = React__namespace.useState(null);
|
|
1812
1892
|
const pendingSwipeCloseSnapPointRef = React__namespace.useRef(undefined);
|
|
1813
1893
|
const resetSwipeRef = React__namespace.useRef(null);
|
|
1814
1894
|
const controlledDismissFrame = useButton.useAnimationFrame();
|
|
1895
|
+
const swipingRef = React__namespace.useRef(false);
|
|
1815
1896
|
const nestedSwipeActiveRef = React__namespace.useRef(false);
|
|
1816
1897
|
const lastPointerTypeRef = React__namespace.useRef('');
|
|
1817
1898
|
const ignoreNextTouchStartFromPenRef = React__namespace.useRef(false);
|
|
1818
1899
|
const ignoreTouchSwipeRef = React__namespace.useRef(false);
|
|
1819
1900
|
const touchScrollStateRef = React__namespace.useRef(null);
|
|
1901
|
+
const virtualKeyboard = useDrawerVirtualKeyboardContext();
|
|
1820
1902
|
const snapPointRange = React__namespace.useMemo(() => {
|
|
1821
1903
|
if (!snapPoints || snapPoints.length < 2) {
|
|
1822
1904
|
return null;
|
|
@@ -1860,13 +1942,20 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
1860
1942
|
return [swipeDirection];
|
|
1861
1943
|
}, [snapPoints, swipeDirection]);
|
|
1862
1944
|
const setSwipeDismissed = useButton.useStableCallback(dismissed => {
|
|
1863
|
-
setSwipeDismissedElements(
|
|
1945
|
+
setSwipeDismissedElements(popupRef.current, backdropRef.current, dismissed);
|
|
1864
1946
|
});
|
|
1865
1947
|
const clearSwipeRelease = useButton.useStableCallback(() => {
|
|
1866
1948
|
setSwipeDismissed(false);
|
|
1867
|
-
|
|
1949
|
+
popupRef.current?.removeAttribute(useButton.TransitionStatusDataAttributes.endingStyle);
|
|
1868
1950
|
setSwipeRelease(null);
|
|
1869
1951
|
});
|
|
1952
|
+
const finishNestedSwipe = useButton.useStableCallback(() => {
|
|
1953
|
+
if (!nestedSwipeActiveRef.current) {
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
nestedSwipeActiveRef.current = false;
|
|
1957
|
+
notifyParentSwipingChange?.(false);
|
|
1958
|
+
});
|
|
1870
1959
|
const applySwipeProgress = useButton.useStableCallback(({
|
|
1871
1960
|
resolvedProgress,
|
|
1872
1961
|
shouldTrackProgress,
|
|
@@ -1874,15 +1963,18 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
1874
1963
|
}) => {
|
|
1875
1964
|
const isActive = open && !nested && shouldTrackProgress;
|
|
1876
1965
|
const swipeProgress = isActive ? resolvedProgress : 0;
|
|
1966
|
+
const nestedSwipeProgress = open && shouldTrackProgress ? resolvedProgress : 0;
|
|
1877
1967
|
if (notifyParent && notifyParentSwipeProgressChange) {
|
|
1878
|
-
const nestedSwipeProgress = open && shouldTrackProgress ? resolvedProgress : 0;
|
|
1879
1968
|
notifyParentSwipeProgressChange(nestedSwipeProgress);
|
|
1969
|
+
if (nestedSwipeProgress <= 0) {
|
|
1970
|
+
finishNestedSwipe();
|
|
1971
|
+
}
|
|
1880
1972
|
}
|
|
1881
1973
|
visualStateStore?.set({
|
|
1882
1974
|
swipeProgress,
|
|
1883
1975
|
frontmostHeight: swipeProgress > 0 ? frontmostHeight : 0
|
|
1884
1976
|
});
|
|
1885
|
-
const backdropElement =
|
|
1977
|
+
const backdropElement = backdropRef.current;
|
|
1886
1978
|
if (!backdropElement) {
|
|
1887
1979
|
return;
|
|
1888
1980
|
}
|
|
@@ -1977,7 +2069,7 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
1977
2069
|
if ('touches' in event || 'pointerType' in event && event.pointerType === 'touch') {
|
|
1978
2070
|
return;
|
|
1979
2071
|
}
|
|
1980
|
-
const popupElement =
|
|
2072
|
+
const popupElement = popupRef.current;
|
|
1981
2073
|
if (!popupElement) {
|
|
1982
2074
|
return;
|
|
1983
2075
|
}
|
|
@@ -1994,10 +2086,10 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
1994
2086
|
selection.removeAllRanges();
|
|
1995
2087
|
},
|
|
1996
2088
|
onSwipingChange(swiping) {
|
|
2089
|
+
swipingRef.current = swiping;
|
|
1997
2090
|
setBackdropSwipingAttribute(store.context.backdropRef.current, swiping);
|
|
1998
|
-
if (!swiping) {
|
|
1999
|
-
|
|
2000
|
-
notifyParentSwipingChange?.(false);
|
|
2091
|
+
if (!swiping && !notifyParentSwipeProgressChange) {
|
|
2092
|
+
finishNestedSwipe();
|
|
2001
2093
|
}
|
|
2002
2094
|
},
|
|
2003
2095
|
swipeThreshold({
|
|
@@ -2028,9 +2120,16 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
2028
2120
|
},
|
|
2029
2121
|
onProgress(progress, details) {
|
|
2030
2122
|
updateNestedSwipeActive(details);
|
|
2123
|
+
const hasSnapPoints = Boolean(snapPoints && snapPoints.length > 0);
|
|
2124
|
+
if (swipingRef.current && swipeDirection === 'down' && hasSnapPoints && details && Number.isFinite(details.deltaY)) {
|
|
2125
|
+
const popupElement = store.context.popupRef.current;
|
|
2126
|
+
if (popupElement) {
|
|
2127
|
+
popupElement.style.removeProperty('transform');
|
|
2128
|
+
popupElement.style.setProperty(ScrollAreaViewport.DrawerPopupCssVars.swipeMovementY, `${ScrollAreaViewport.getSnapPointSwipeMovement(activeSnapPointOffset ?? 0, details.deltaY)}px`);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2031
2131
|
const currentDirection = details?.direction ?? swipe.swipeDirection;
|
|
2032
2132
|
const isDismissSwipe = currentDirection === undefined || currentDirection === swipeDirection;
|
|
2033
|
-
const hasSnapPoints = Boolean(snapPoints && snapPoints.length > 0);
|
|
2034
2133
|
const isVerticalSwipe = swipeDirection === 'down' || swipeDirection === 'up';
|
|
2035
2134
|
const shouldTrackProgress = hasSnapPoints && isVerticalSwipe || !hasSnapPoints || swipeDirection === 'left' || swipeDirection === 'right' || isDismissSwipe;
|
|
2036
2135
|
let resolvedProgress = progress;
|
|
@@ -2079,7 +2178,7 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
2079
2178
|
if (!popupElement) {
|
|
2080
2179
|
return;
|
|
2081
2180
|
}
|
|
2082
|
-
|
|
2181
|
+
finishNestedSwipe();
|
|
2083
2182
|
setSwipeDismissed(true);
|
|
2084
2183
|
popupElement.style.removeProperty('transition');
|
|
2085
2184
|
popupElement.setAttribute(useButton.TransitionStatusDataAttributes.endingStyle, '');
|
|
@@ -2286,7 +2385,10 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
2286
2385
|
});
|
|
2287
2386
|
const swipePointerProps = swipe.getPointerProps();
|
|
2288
2387
|
const swipeTouchProps = swipe.getTouchProps();
|
|
2289
|
-
const
|
|
2388
|
+
const {
|
|
2389
|
+
moveNative: moveSwipeNative,
|
|
2390
|
+
reset: resetSwipe
|
|
2391
|
+
} = swipe;
|
|
2290
2392
|
resetSwipeRef.current = resetSwipe;
|
|
2291
2393
|
React__namespace.useEffect(() => {
|
|
2292
2394
|
const rootElement = viewportElement ?? popupElementState;
|
|
@@ -2297,6 +2399,11 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
2297
2399
|
const doc = useButton.ownerDocument(resolvedRootElement);
|
|
2298
2400
|
const win = floatingUi_utils_dom.getWindow(doc);
|
|
2299
2401
|
function handleNativeTouchMove(event) {
|
|
2402
|
+
// The virtual keyboard provider observes the move to tell a tap apart from a drag.
|
|
2403
|
+
// It must run even when the swipe gesture below claims the event with
|
|
2404
|
+
// `stopPropagation()`, which would otherwise prevent React's delegated handlers
|
|
2405
|
+
// (and the provider) from ever seeing the move.
|
|
2406
|
+
virtualKeyboard?.onTouchMove(event);
|
|
2300
2407
|
if (ignoreTouchSwipeRef.current) {
|
|
2301
2408
|
return;
|
|
2302
2409
|
}
|
|
@@ -2333,6 +2440,10 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
2333
2440
|
if (event.cancelable) {
|
|
2334
2441
|
event.preventDefault();
|
|
2335
2442
|
}
|
|
2443
|
+
// Claim the gesture before React's delegated touch handlers see it; dispatching the
|
|
2444
|
+
// move through React re-rasterizes the popup content on every frame.
|
|
2445
|
+
event.stopPropagation();
|
|
2446
|
+
moveSwipeNative(event, resolvedRootElement);
|
|
2336
2447
|
updateTouchScrollPosition(touchState, touch);
|
|
2337
2448
|
return;
|
|
2338
2449
|
}
|
|
@@ -2343,6 +2454,7 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
2343
2454
|
if (event.cancelable) {
|
|
2344
2455
|
event.preventDefault();
|
|
2345
2456
|
}
|
|
2457
|
+
event.stopPropagation();
|
|
2346
2458
|
updateTouchScrollPosition(touchState, touch);
|
|
2347
2459
|
return;
|
|
2348
2460
|
}
|
|
@@ -2362,18 +2474,17 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
2362
2474
|
event.preventDefault();
|
|
2363
2475
|
}
|
|
2364
2476
|
}
|
|
2477
|
+
if (touchState.allowSwipe === true) {
|
|
2478
|
+
event.stopPropagation();
|
|
2479
|
+
moveSwipeNative(event, resolvedRootElement);
|
|
2480
|
+
}
|
|
2365
2481
|
updateTouchScrollPosition(touchState, touch);
|
|
2366
2482
|
}
|
|
2367
|
-
|
|
2483
|
+
return useButton.addEventListener(doc, 'touchmove', handleNativeTouchMove, {
|
|
2368
2484
|
passive: false,
|
|
2369
2485
|
capture: true
|
|
2370
2486
|
});
|
|
2371
|
-
|
|
2372
|
-
doc.removeEventListener('touchmove', handleNativeTouchMove, {
|
|
2373
|
-
capture: true
|
|
2374
|
-
});
|
|
2375
|
-
};
|
|
2376
|
-
}, [mounted, nestedDrawerOpen, open, popupElementState, isVerticalScrollAxis, scrollAxis, swipeDirection, viewportElement]);
|
|
2487
|
+
}, [mounted, nestedDrawerOpen, open, popupElementState, isVerticalScrollAxis, scrollAxis, swipeDirection, moveSwipeNative, viewportElement, virtualKeyboard]);
|
|
2377
2488
|
React__namespace.useEffect(() => {
|
|
2378
2489
|
if (!snapPointRange || swipe.swiping) {
|
|
2379
2490
|
return;
|
|
@@ -2403,23 +2514,30 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
2403
2514
|
}
|
|
2404
2515
|
}, [clearSwipeRelease, open, resetSwipe]);
|
|
2405
2516
|
React__namespace.useEffect(() => {
|
|
2517
|
+
const backdropElement = backdropRef.current;
|
|
2406
2518
|
return () => {
|
|
2407
2519
|
visualStateStore?.set({
|
|
2408
2520
|
swipeProgress: 0,
|
|
2409
2521
|
frontmostHeight: 0
|
|
2410
2522
|
});
|
|
2411
|
-
setBackdropSwipingAttribute(
|
|
2412
|
-
|
|
2523
|
+
setBackdropSwipingAttribute(backdropElement, false);
|
|
2524
|
+
// `data-swiping` is set on whichever backdrop is current when a swipe starts, which can
|
|
2525
|
+
// differ from the captured element if the backdrop mounted late or changed identity.
|
|
2526
|
+
// Reading the live ref here is intentional so the current backdrop is cleared too.
|
|
2527
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2528
|
+
const currentBackdrop = backdropRef.current;
|
|
2529
|
+
if (currentBackdrop !== backdropElement) {
|
|
2530
|
+
setBackdropSwipingAttribute(currentBackdrop, false);
|
|
2531
|
+
}
|
|
2532
|
+
finishNestedSwipe();
|
|
2413
2533
|
};
|
|
2414
|
-
}, [
|
|
2534
|
+
}, [backdropRef, finishNestedSwipe, visualStateStore]);
|
|
2415
2535
|
const swipeProviderValue = React__namespace.useMemo(() => ({
|
|
2416
2536
|
swiping: swipe.swiping,
|
|
2417
2537
|
getDragStyles: swipe.getDragStyles,
|
|
2418
2538
|
swipeStrength: swipeRelease ?? null,
|
|
2419
|
-
setSwipeDismissed
|
|
2420
|
-
|
|
2421
|
-
}
|
|
2422
|
-
}), [store, swipe.getDragStyles, swipe.swiping, swipeRelease]);
|
|
2539
|
+
setSwipeDismissed
|
|
2540
|
+
}), [setSwipeDismissed, swipe.getDragStyles, swipe.swiping, swipeRelease]);
|
|
2423
2541
|
function resetTouchTrackingState() {
|
|
2424
2542
|
ignoreTouchSwipeRef.current = false;
|
|
2425
2543
|
touchScrollStateRef.current = null;
|
|
@@ -2429,6 +2547,7 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
2429
2547
|
return /*#__PURE__*/jsxRuntime.jsx(DrawerDescription.DialogViewport, {
|
|
2430
2548
|
ref: forwardedRef,
|
|
2431
2549
|
className: className,
|
|
2550
|
+
style: style,
|
|
2432
2551
|
render: render,
|
|
2433
2552
|
...useRenderElement.mergeProps(elementProps, {
|
|
2434
2553
|
onPointerDown(event) {
|
|
@@ -2495,16 +2614,23 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
2495
2614
|
}
|
|
2496
2615
|
const doc = useButton.ownerDocument(event.currentTarget);
|
|
2497
2616
|
const elementAtPoint = getElementAtPoint(doc, touch.clientX, touch.clientY);
|
|
2617
|
+
const rootElement = viewportElement ?? popupElementState;
|
|
2618
|
+
const eventTarget = useButton.getTarget(event.nativeEvent);
|
|
2619
|
+
const target = floatingUi_utils_dom.isElement(eventTarget) ? eventTarget : null;
|
|
2620
|
+
if (rootElement && target && !useButton.contains(rootElement, target)) {
|
|
2621
|
+
ignoreTouchSwipeRef.current = true;
|
|
2622
|
+
touchScrollStateRef.current = null;
|
|
2623
|
+
return;
|
|
2624
|
+
}
|
|
2625
|
+
virtualKeyboard?.onTouchStart(event);
|
|
2498
2626
|
ignoreTouchSwipeRef.current = isSwipeIgnoredTarget(elementAtPoint);
|
|
2499
2627
|
if (ignoreTouchSwipeRef.current) {
|
|
2500
2628
|
touchScrollStateRef.current = null;
|
|
2501
2629
|
return;
|
|
2502
2630
|
}
|
|
2503
|
-
const rootElement = viewportElement ?? popupElementState;
|
|
2504
|
-
const target = floatingUi_utils_dom.isElement(event.target) ? event.target : null;
|
|
2505
2631
|
let scrollTarget = null;
|
|
2506
2632
|
let hasCrossAxisScrollableContent = false;
|
|
2507
|
-
if (rootElement && target
|
|
2633
|
+
if (rootElement && target) {
|
|
2508
2634
|
scrollTarget = findScrollableTouchTarget(target, rootElement, scrollAxis);
|
|
2509
2635
|
hasCrossAxisScrollableContent = findScrollableTouchTarget(target, rootElement, crossScrollAxis) != null;
|
|
2510
2636
|
}
|
|
@@ -2525,27 +2651,13 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
|
|
|
2525
2651
|
};
|
|
2526
2652
|
swipeTouchProps.onTouchStart?.(event);
|
|
2527
2653
|
},
|
|
2528
|
-
onTouchMove(event) {
|
|
2529
|
-
if (ignoreTouchSwipeRef.current) {
|
|
2530
|
-
return;
|
|
2531
|
-
}
|
|
2532
|
-
if (isReactTouchEventOnRangeInput(event)) {
|
|
2533
|
-
return;
|
|
2534
|
-
}
|
|
2535
|
-
const touchState = touchScrollStateRef.current;
|
|
2536
|
-
if (touchState?.preserveNativeCrossAxisScroll) {
|
|
2537
|
-
return;
|
|
2538
|
-
}
|
|
2539
|
-
if (touchState?.allowSwipe === false || touchState?.scrollTarget != null && !touchState.allowSwipe) {
|
|
2540
|
-
return;
|
|
2541
|
-
}
|
|
2542
|
-
swipeTouchProps.onTouchMove?.(event);
|
|
2543
|
-
},
|
|
2544
2654
|
onTouchEnd(event) {
|
|
2655
|
+
virtualKeyboard?.onTouchEnd(event);
|
|
2545
2656
|
resetTouchTrackingState();
|
|
2546
2657
|
swipeTouchProps.onTouchEnd?.(event);
|
|
2547
2658
|
},
|
|
2548
2659
|
onTouchCancel(event) {
|
|
2660
|
+
virtualKeyboard?.onTouchCancel();
|
|
2549
2661
|
resetTouchTrackingState();
|
|
2550
2662
|
swipeTouchProps.onTouchCancel?.(event);
|
|
2551
2663
|
},
|
|
@@ -2569,149 +2681,617 @@ function setSwipeDismissedElements(popupElement, backdropElement, dismissed) {
|
|
|
2569
2681
|
popupElement?.removeAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swipeDismiss);
|
|
2570
2682
|
backdropElement?.removeAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swipeDismiss);
|
|
2571
2683
|
}
|
|
2572
|
-
function setBackdropSwipingAttribute(backdropElement, swiping) {
|
|
2573
|
-
if (!backdropElement) {
|
|
2574
|
-
return;
|
|
2575
|
-
}
|
|
2576
|
-
if (swiping) {
|
|
2577
|
-
backdropElement.setAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swiping, '');
|
|
2578
|
-
return;
|
|
2684
|
+
function setBackdropSwipingAttribute(backdropElement, swiping) {
|
|
2685
|
+
if (!backdropElement) {
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
if (swiping) {
|
|
2689
|
+
backdropElement.setAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swiping, '');
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
backdropElement.removeAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swiping);
|
|
2693
|
+
}
|
|
2694
|
+
function isSwipeIgnoredTarget(target) {
|
|
2695
|
+
return Boolean(target?.closest(useScrollLock.BASE_UI_SWIPE_IGNORE_SELECTOR));
|
|
2696
|
+
}
|
|
2697
|
+
function isDrawerContentTarget(target) {
|
|
2698
|
+
return Boolean(target?.closest(DRAWER_CONTENT_SELECTOR));
|
|
2699
|
+
}
|
|
2700
|
+
function getBaseSwipeThreshold(element, direction) {
|
|
2701
|
+
const size = direction === 'left' || direction === 'right' ? element.offsetWidth : element.offsetHeight;
|
|
2702
|
+
return Math.max(size * 0.5, MIN_SWIPE_THRESHOLD);
|
|
2703
|
+
}
|
|
2704
|
+
function isRangeInput(target, win) {
|
|
2705
|
+
return target instanceof win.HTMLInputElement && target.type === 'range';
|
|
2706
|
+
}
|
|
2707
|
+
function isTextSelectionControl(target) {
|
|
2708
|
+
if (!floatingUi_utils_dom.isElement(target)) {
|
|
2709
|
+
return false;
|
|
2710
|
+
}
|
|
2711
|
+
return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA';
|
|
2712
|
+
}
|
|
2713
|
+
function hasExpandedSelectionWithinTarget(selection, target) {
|
|
2714
|
+
const anchorElement = floatingUi_utils_dom.isElement(selection.anchorNode) ? selection.anchorNode : selection.anchorNode?.parentElement;
|
|
2715
|
+
const focusElement = floatingUi_utils_dom.isElement(selection.focusNode) ? selection.focusNode : selection.focusNode?.parentElement;
|
|
2716
|
+
return selection.containsNode(target, true) || useButton.contains(target, anchorElement) || useButton.contains(target, focusElement);
|
|
2717
|
+
}
|
|
2718
|
+
function shouldIgnoreSwipeForTextSelection(doc, rootElement) {
|
|
2719
|
+
const activeEl = useButton.activeElement(doc);
|
|
2720
|
+
const activeElementWithinRoot = Boolean(activeEl && useButton.contains(rootElement, activeEl));
|
|
2721
|
+
if (activeElementWithinRoot && isTextSelectionControl(activeEl)) {
|
|
2722
|
+
const {
|
|
2723
|
+
selectionStart,
|
|
2724
|
+
selectionEnd
|
|
2725
|
+
} = activeEl;
|
|
2726
|
+
if (selectionStart != null && selectionEnd != null && selectionStart < selectionEnd) {
|
|
2727
|
+
return true;
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
const selection = doc.getSelection?.();
|
|
2731
|
+
if (!selection || selection.isCollapsed) {
|
|
2732
|
+
return false;
|
|
2733
|
+
}
|
|
2734
|
+
return hasExpandedSelectionWithinTarget(selection, rootElement);
|
|
2735
|
+
}
|
|
2736
|
+
function isEventOnRangeInput(event, win) {
|
|
2737
|
+
const composedPath = event.composedPath();
|
|
2738
|
+
if (composedPath) {
|
|
2739
|
+
return composedPath.some(pathTarget => isRangeInput(pathTarget, win));
|
|
2740
|
+
}
|
|
2741
|
+
return isRangeInput(useButton.getTarget(event), win);
|
|
2742
|
+
}
|
|
2743
|
+
function isReactTouchEventOnRangeInput(event) {
|
|
2744
|
+
return isEventOnRangeInput(event.nativeEvent, floatingUi_utils_dom.getWindow(event.currentTarget));
|
|
2745
|
+
}
|
|
2746
|
+
function updateTouchScrollPosition(touchState, touch) {
|
|
2747
|
+
touchState.lastX = touch.clientX;
|
|
2748
|
+
touchState.lastY = touch.clientY;
|
|
2749
|
+
}
|
|
2750
|
+
function preserveNativeCrossAxisScrollOnMove(touchState, touch, isVerticalScrollAxis) {
|
|
2751
|
+
if (touchState.preserveNativeCrossAxisScroll) {
|
|
2752
|
+
return true;
|
|
2753
|
+
}
|
|
2754
|
+
if (touchState.allowSwipe === true || !touchState.hasCrossAxisScrollableContent) {
|
|
2755
|
+
return false;
|
|
2756
|
+
}
|
|
2757
|
+
const drawerAxisGestureDelta = isVerticalScrollAxis ? touch.clientY - touchState.startY : touch.clientX - touchState.startX;
|
|
2758
|
+
const crossAxisGestureDelta = isVerticalScrollAxis ? touch.clientX - touchState.startX : touch.clientY - touchState.startY;
|
|
2759
|
+
const absDrawerAxisGestureDelta = Math.abs(drawerAxisGestureDelta);
|
|
2760
|
+
const absCrossAxisGestureDelta = Math.abs(crossAxisGestureDelta);
|
|
2761
|
+
if (absCrossAxisGestureDelta < 6 || absCrossAxisGestureDelta <= absDrawerAxisGestureDelta + 2) {
|
|
2762
|
+
return false;
|
|
2763
|
+
}
|
|
2764
|
+
touchState.preserveNativeCrossAxisScroll = true;
|
|
2765
|
+
return true;
|
|
2766
|
+
}
|
|
2767
|
+
function hasScrollableContentOnAxis(scrollTarget, axis) {
|
|
2768
|
+
return axis === 'vertical' ? scrollTarget.scrollHeight > scrollTarget.clientHeight : scrollTarget.scrollWidth > scrollTarget.clientWidth;
|
|
2769
|
+
}
|
|
2770
|
+
function getScrollMetrics(scrollTarget, axis) {
|
|
2771
|
+
if (axis === 'vertical') {
|
|
2772
|
+
const max = Math.max(0, scrollTarget.scrollHeight - scrollTarget.clientHeight);
|
|
2773
|
+
return {
|
|
2774
|
+
offset: scrollTarget.scrollTop,
|
|
2775
|
+
max
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2778
|
+
const max = Math.max(0, scrollTarget.scrollWidth - scrollTarget.clientWidth);
|
|
2779
|
+
return {
|
|
2780
|
+
offset: scrollTarget.scrollLeft,
|
|
2781
|
+
max
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
function isAtSwipeStartEdge(scrollTarget, axis, direction) {
|
|
2785
|
+
const {
|
|
2786
|
+
offset,
|
|
2787
|
+
max
|
|
2788
|
+
} = getScrollMetrics(scrollTarget, axis);
|
|
2789
|
+
const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
|
|
2790
|
+
if (dismissFromStartEdge === null) {
|
|
2791
|
+
return false;
|
|
2792
|
+
}
|
|
2793
|
+
return dismissFromStartEdge ? offset <= 0 : offset >= max;
|
|
2794
|
+
}
|
|
2795
|
+
function canSwipeFromScrollEdgeOnMove(scrollTarget, axis, direction, delta) {
|
|
2796
|
+
const {
|
|
2797
|
+
offset,
|
|
2798
|
+
max
|
|
2799
|
+
} = getScrollMetrics(scrollTarget, axis);
|
|
2800
|
+
const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
|
|
2801
|
+
if (dismissFromStartEdge === null) {
|
|
2802
|
+
return false;
|
|
2803
|
+
}
|
|
2804
|
+
const movingTowardDismiss = dismissFromStartEdge ? delta > 0 : delta < 0;
|
|
2805
|
+
if (!movingTowardDismiss) {
|
|
2806
|
+
return false;
|
|
2807
|
+
}
|
|
2808
|
+
return dismissFromStartEdge ? offset <= 0 : offset >= max;
|
|
2809
|
+
}
|
|
2810
|
+
function shouldDismissFromStartEdge(direction, axis) {
|
|
2811
|
+
if (axis === 'vertical') {
|
|
2812
|
+
if (direction === 'down') {
|
|
2813
|
+
return true;
|
|
2814
|
+
}
|
|
2815
|
+
if (direction === 'up') {
|
|
2816
|
+
return false;
|
|
2817
|
+
}
|
|
2818
|
+
return null;
|
|
2819
|
+
}
|
|
2820
|
+
if (direction === 'right') {
|
|
2821
|
+
return true;
|
|
2822
|
+
}
|
|
2823
|
+
if (direction === 'left') {
|
|
2824
|
+
return false;
|
|
2825
|
+
}
|
|
2826
|
+
return null;
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
let DrawerViewportCssVars = /*#__PURE__*/function (DrawerViewportCssVars) {
|
|
2830
|
+
/**
|
|
2831
|
+
* The software keyboard inset, measured from the bottom edge of the layout viewport.
|
|
2832
|
+
* Present only when the drawer is wrapped in `Drawer.VirtualKeyboardProvider`.
|
|
2833
|
+
* @type {CSS length}
|
|
2834
|
+
*/
|
|
2835
|
+
DrawerViewportCssVars["keyboardInset"] = "--drawer-keyboard-inset";
|
|
2836
|
+
return DrawerViewportCssVars;
|
|
2837
|
+
}({});
|
|
2838
|
+
|
|
2839
|
+
const KEYBOARD_RESIZE_THRESHOLD = 60;
|
|
2840
|
+
const KEYBOARD_VISIBILITY_MARGIN = 16;
|
|
2841
|
+
// Extra breathing room (px) added below the focused field, on top of its measured
|
|
2842
|
+
// keyboard overlap, so the field can be scrolled clear of the keyboard instead of
|
|
2843
|
+
// ending up flush against it. Only applied when there is actual overlap.
|
|
2844
|
+
const KEYBOARD_SCROLL_SLACK = 48;
|
|
2845
|
+
const INPUT_TAP_MOVE_THRESHOLD = 10;
|
|
2846
|
+
const INPUT_TAP_HIT_SLOP = 16;
|
|
2847
|
+
const KEYBOARD_INPUT_TYPES = new Set(['email', 'number', 'password', 'search', 'tel', 'text', 'url']);
|
|
2848
|
+
|
|
2849
|
+
// Snapshot of a scroll container's relevant styles taken before keyboard slack is
|
|
2850
|
+
// applied. The string fields are the exact inline values to restore on cleanup;
|
|
2851
|
+
// the parsed numbers are the computed baselines that slack is added on top of.
|
|
2852
|
+
|
|
2853
|
+
// Returned by the point-based resolver when the lift point lands on another
|
|
2854
|
+
// interactive/label element. It signals that the tap was intentionally rejected, so the
|
|
2855
|
+
// caller must NOT fall back to the touchstart target (`touchend.target` stays at the
|
|
2856
|
+
// touchstart node on mobile) — doing so would steal a tap meant for that element.
|
|
2857
|
+
const KEYBOARD_TAP_BLOCKED = Symbol('KeyboardTapBlocked');
|
|
2858
|
+
|
|
2859
|
+
/**
|
|
2860
|
+
* Provides keyboard-aware focus and scroll handling for bottom-sheet drawers with form fields.
|
|
2861
|
+
*
|
|
2862
|
+
* Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
|
|
2863
|
+
*/
|
|
2864
|
+
function DrawerVirtualKeyboardProvider(props) {
|
|
2865
|
+
const {
|
|
2866
|
+
children
|
|
2867
|
+
} = props;
|
|
2868
|
+
const {
|
|
2869
|
+
store
|
|
2870
|
+
} = ScrollAreaViewport.useDialogRootContext();
|
|
2871
|
+
const open = store.useState('open');
|
|
2872
|
+
const mounted = store.useState('mounted');
|
|
2873
|
+
const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
|
|
2874
|
+
const viewportElement = store.useState('viewportElement');
|
|
2875
|
+
|
|
2876
|
+
// The provider requires a `<Drawer.Viewport>` to act as the measurement and containment
|
|
2877
|
+
// root and to host the keyboard inset variable; `<Drawer.Popup>` already warns when the
|
|
2878
|
+
// viewport is missing, so there is no need to fall back to the popup element here.
|
|
2879
|
+
const rootElement = viewportElement;
|
|
2880
|
+
const nestedDrawerOpen = nestedOpenDialogCount > 0;
|
|
2881
|
+
const pendingKeyboardFocusMovedRef = React__namespace.useRef(false);
|
|
2882
|
+
const keyboardTouchStartRef = React__namespace.useRef(null);
|
|
2883
|
+
const focusedKeyboardTargetRef = React__namespace.useRef(null);
|
|
2884
|
+
const keyboardScrollAdjustmentRef = React__namespace.useRef(null);
|
|
2885
|
+
const keyboardFocusFrame = useButton.useAnimationFrame();
|
|
2886
|
+
const restoreKeyboardScrollAdjustment = useButton.useStableCallback(() => {
|
|
2887
|
+
const adjustment = keyboardScrollAdjustmentRef.current;
|
|
2888
|
+
if (!adjustment) {
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
adjustment.element.style.overflowAnchor = adjustment.overflowAnchor;
|
|
2892
|
+
adjustment.element.style.paddingBottom = adjustment.paddingBottom;
|
|
2893
|
+
adjustment.element.style.scrollPaddingBottom = adjustment.scrollPaddingBottom;
|
|
2894
|
+
keyboardScrollAdjustmentRef.current = null;
|
|
2895
|
+
});
|
|
2896
|
+
const setKeyboardScrollSlack = useButton.useStableCallback((element, slack) => {
|
|
2897
|
+
const roundedSlack = Math.max(0, Math.ceil(slack));
|
|
2898
|
+
let adjustment = keyboardScrollAdjustmentRef.current;
|
|
2899
|
+
if (adjustment && !adjustment.element.isConnected) {
|
|
2900
|
+
restoreKeyboardScrollAdjustment();
|
|
2901
|
+
adjustment = null;
|
|
2902
|
+
}
|
|
2903
|
+
if (roundedSlack === 0) {
|
|
2904
|
+
restoreKeyboardScrollAdjustment();
|
|
2905
|
+
return;
|
|
2906
|
+
}
|
|
2907
|
+
if (adjustment && adjustment.element !== element) {
|
|
2908
|
+
restoreKeyboardScrollAdjustment();
|
|
2909
|
+
adjustment = null;
|
|
2910
|
+
}
|
|
2911
|
+
if (!adjustment) {
|
|
2912
|
+
const styles = floatingUi_utils_dom.getComputedStyle(element);
|
|
2913
|
+
adjustment = {
|
|
2914
|
+
element,
|
|
2915
|
+
overflowAnchor: element.style.overflowAnchor,
|
|
2916
|
+
paddingBottom: element.style.paddingBottom,
|
|
2917
|
+
scrollPaddingBottom: element.style.scrollPaddingBottom,
|
|
2918
|
+
computedPaddingBottom: Number.parseFloat(styles.paddingBottom) || 0,
|
|
2919
|
+
computedScrollPaddingBottom: Number.parseFloat(styles.scrollPaddingBottom) || 0
|
|
2920
|
+
};
|
|
2921
|
+
keyboardScrollAdjustmentRef.current = adjustment;
|
|
2922
|
+
}
|
|
2923
|
+
element.style.overflowAnchor = 'none';
|
|
2924
|
+
element.style.paddingBottom = `${adjustment.computedPaddingBottom + roundedSlack}px`;
|
|
2925
|
+
element.style.scrollPaddingBottom = `${adjustment.computedScrollPaddingBottom + KEYBOARD_VISIBILITY_MARGIN}px`;
|
|
2926
|
+
});
|
|
2927
|
+
const animateKeyboardScroll = useButton.useStableCallback((element, scrollTop) => {
|
|
2928
|
+
const win = floatingUi_utils_dom.getWindow(element);
|
|
2929
|
+
const behavior = win.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches ? 'auto' : 'smooth';
|
|
2930
|
+
element.scrollTo({
|
|
2931
|
+
top: scrollTop,
|
|
2932
|
+
behavior
|
|
2933
|
+
});
|
|
2934
|
+
});
|
|
2935
|
+
const resetTouchTrackingState = useButton.useStableCallback(() => {
|
|
2936
|
+
pendingKeyboardFocusMovedRef.current = false;
|
|
2937
|
+
keyboardTouchStartRef.current = null;
|
|
2938
|
+
});
|
|
2939
|
+
React__namespace.useEffect(() => {
|
|
2940
|
+
if (!mounted || !open) {
|
|
2941
|
+
focusedKeyboardTargetRef.current = null;
|
|
2942
|
+
restoreKeyboardScrollAdjustment();
|
|
2943
|
+
keyboardFocusFrame.cancel();
|
|
2944
|
+
return undefined;
|
|
2945
|
+
}
|
|
2946
|
+
if (!rootElement) {
|
|
2947
|
+
restoreKeyboardScrollAdjustment();
|
|
2948
|
+
return undefined;
|
|
2949
|
+
}
|
|
2950
|
+
const doc = useButton.ownerDocument(rootElement);
|
|
2951
|
+
const win = floatingUi_utils_dom.getWindow(rootElement);
|
|
2952
|
+
const visualViewport = win.visualViewport;
|
|
2953
|
+
const setDrawerKeyboardInset = inset => {
|
|
2954
|
+
rootElement.style.setProperty(DrawerViewportCssVars.keyboardInset, `${Math.max(0, Math.ceil(inset))}px`);
|
|
2955
|
+
};
|
|
2956
|
+
const resetDrawerKeyboardInset = () => {
|
|
2957
|
+
setDrawerKeyboardInset(0);
|
|
2958
|
+
};
|
|
2959
|
+
const clearFocusedKeyboardTarget = () => {
|
|
2960
|
+
focusedKeyboardTargetRef.current = null;
|
|
2961
|
+
resetDrawerKeyboardInset();
|
|
2962
|
+
restoreKeyboardScrollAdjustment();
|
|
2963
|
+
keyboardFocusFrame.cancel();
|
|
2964
|
+
};
|
|
2965
|
+
const alignFocusedKeyboardTarget = () => {
|
|
2966
|
+
const target = focusedKeyboardTargetRef.current;
|
|
2967
|
+
// If the focused field is removed from the DOM without firing `focusout` (e.g. it is
|
|
2968
|
+
// conditionally rendered away), any applied scroll slack is restored here on the next
|
|
2969
|
+
// focus/viewport event or when the drawer closes. This self-corrects rather than
|
|
2970
|
+
// tracking each field's lifecycle.
|
|
2971
|
+
if (nestedDrawerOpen || !target || !useButton.contains(rootElement, target)) {
|
|
2972
|
+
resetDrawerKeyboardInset();
|
|
2973
|
+
restoreKeyboardScrollAdjustment();
|
|
2974
|
+
return;
|
|
2975
|
+
}
|
|
2976
|
+
const keyboardViewport = getKeyboardVisualViewport(win);
|
|
2977
|
+
if (!keyboardViewport) {
|
|
2978
|
+
resetDrawerKeyboardInset();
|
|
2979
|
+
restoreKeyboardScrollAdjustment();
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2982
|
+
setDrawerKeyboardInset(getDrawerKeyboardInset(win, keyboardViewport));
|
|
2983
|
+
const scrollTarget = findKeyboardScrollTarget(target, rootElement);
|
|
2984
|
+
if (!scrollTarget) {
|
|
2985
|
+
restoreKeyboardScrollAdjustment();
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
if (!scrollTarget.isConnected || !useButton.contains(rootElement, scrollTarget)) {
|
|
2989
|
+
resetDrawerKeyboardInset();
|
|
2990
|
+
restoreKeyboardScrollAdjustment();
|
|
2991
|
+
return;
|
|
2992
|
+
}
|
|
2993
|
+
const scrollTargetRect = scrollTarget.getBoundingClientRect();
|
|
2994
|
+
const clippedBottom = Math.min(scrollTargetRect.bottom, keyboardViewport.bottom);
|
|
2995
|
+
const overlap = Math.max(0, scrollTargetRect.bottom - keyboardViewport.bottom);
|
|
2996
|
+
setKeyboardScrollSlack(scrollTarget, overlap > 0 ? overlap + KEYBOARD_SCROLL_SLACK : 0);
|
|
2997
|
+
const maxScrollTop = Math.max(0, scrollTarget.scrollHeight - scrollTarget.clientHeight);
|
|
2998
|
+
if (maxScrollTop <= 0) {
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
const clippedTop = Math.max(scrollTargetRect.top, keyboardViewport.top);
|
|
3002
|
+
const visibleTop = clippedTop + KEYBOARD_VISIBILITY_MARGIN;
|
|
3003
|
+
const visibleBottom = clippedBottom - KEYBOARD_VISIBILITY_MARGIN;
|
|
3004
|
+
if (visibleBottom <= visibleTop) {
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
const targetRect = target.getBoundingClientRect();
|
|
3008
|
+
const targetCenter = (targetRect.top + targetRect.bottom) / 2;
|
|
3009
|
+
const visibleCenter = (visibleTop + visibleBottom) / 2;
|
|
3010
|
+
const nextScrollTop = scrollTarget.scrollTop + targetCenter - visibleCenter;
|
|
3011
|
+
animateKeyboardScroll(scrollTarget, clamp.clamp(nextScrollTop, 0, maxScrollTop));
|
|
3012
|
+
};
|
|
3013
|
+
const scheduleKeyboardFocusAlignment = () => {
|
|
3014
|
+
keyboardFocusFrame.request(alignFocusedKeyboardTarget);
|
|
3015
|
+
};
|
|
3016
|
+
const captureFocusedKeyboardTarget = eventTarget => {
|
|
3017
|
+
if (nestedDrawerOpen) {
|
|
3018
|
+
return false;
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
// Resolve through the same path as taps so contentEditable hosts (and labelled
|
|
3022
|
+
// controls) are normalized identically for the focus and touch paths.
|
|
3023
|
+
const target = resolveKeyboardInputTarget(eventTarget);
|
|
3024
|
+
if (!target || !useButton.contains(rootElement, target)) {
|
|
3025
|
+
return false;
|
|
3026
|
+
}
|
|
3027
|
+
focusedKeyboardTargetRef.current = target;
|
|
3028
|
+
return true;
|
|
3029
|
+
};
|
|
3030
|
+
const handleFocusIn = event => {
|
|
3031
|
+
if (captureFocusedKeyboardTarget(useButton.getTarget(event))) {
|
|
3032
|
+
scheduleKeyboardFocusAlignment();
|
|
3033
|
+
}
|
|
3034
|
+
};
|
|
3035
|
+
const handleFocusOut = event => {
|
|
3036
|
+
if (captureFocusedKeyboardTarget(event.relatedTarget)) {
|
|
3037
|
+
scheduleKeyboardFocusAlignment();
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
clearFocusedKeyboardTarget();
|
|
3041
|
+
};
|
|
3042
|
+
const handleViewportUpdate = () => {
|
|
3043
|
+
if (focusedKeyboardTargetRef.current || captureFocusedKeyboardTarget(useButton.activeElement(doc))) {
|
|
3044
|
+
scheduleKeyboardFocusAlignment();
|
|
3045
|
+
}
|
|
3046
|
+
};
|
|
3047
|
+
const cleanupListeners = [];
|
|
3048
|
+
if (visualViewport) {
|
|
3049
|
+
cleanupListeners.push(useButton.addEventListener(visualViewport, 'resize', handleViewportUpdate), useButton.addEventListener(visualViewport, 'scroll', handleViewportUpdate));
|
|
3050
|
+
}
|
|
3051
|
+
cleanupListeners.push(useButton.addEventListener(doc, 'focusin', handleFocusIn, true), useButton.addEventListener(doc, 'focusout', handleFocusOut, true));
|
|
3052
|
+
if (captureFocusedKeyboardTarget(useButton.activeElement(doc))) {
|
|
3053
|
+
scheduleKeyboardFocusAlignment();
|
|
3054
|
+
}
|
|
3055
|
+
return () => {
|
|
3056
|
+
cleanupListeners.forEach(cleanup => cleanup());
|
|
3057
|
+
clearFocusedKeyboardTarget();
|
|
3058
|
+
rootElement.style.removeProperty(DrawerViewportCssVars.keyboardInset);
|
|
3059
|
+
};
|
|
3060
|
+
}, [animateKeyboardScroll, keyboardFocusFrame, mounted, nestedDrawerOpen, open, restoreKeyboardScrollAdjustment, rootElement, setKeyboardScrollSlack]);
|
|
3061
|
+
const onTouchStart = useButton.useStableCallback(event => {
|
|
3062
|
+
if (!open || !mounted || nestedDrawerOpen) {
|
|
3063
|
+
resetTouchTrackingState();
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3066
|
+
const touch = event.touches[0];
|
|
3067
|
+
if (!touch) {
|
|
3068
|
+
return;
|
|
3069
|
+
}
|
|
3070
|
+
pendingKeyboardFocusMovedRef.current = false;
|
|
3071
|
+
keyboardTouchStartRef.current = {
|
|
3072
|
+
x: touch.clientX,
|
|
3073
|
+
y: touch.clientY
|
|
3074
|
+
};
|
|
3075
|
+
});
|
|
3076
|
+
const onTouchMove = useButton.useStableCallback(event => {
|
|
3077
|
+
const touch = event.touches[0];
|
|
3078
|
+
const touchStart = keyboardTouchStartRef.current;
|
|
3079
|
+
if (!touch || !touchStart || pendingKeyboardFocusMovedRef.current) {
|
|
3080
|
+
return;
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
// Treat the gesture as a scroll/swipe (not a tap-to-focus) once the finger
|
|
3084
|
+
// moves past the threshold, so we don't open the keyboard on a drag.
|
|
3085
|
+
if (Math.abs(touch.clientX - touchStart.x) > INPUT_TAP_MOVE_THRESHOLD || Math.abs(touch.clientY - touchStart.y) > INPUT_TAP_MOVE_THRESHOLD) {
|
|
3086
|
+
pendingKeyboardFocusMovedRef.current = true;
|
|
3087
|
+
}
|
|
3088
|
+
});
|
|
3089
|
+
const onTouchEnd = useButton.useStableCallback(event => {
|
|
3090
|
+
if (!open || !mounted || nestedDrawerOpen || !rootElement || !keyboardTouchStartRef.current || pendingKeyboardFocusMovedRef.current) {
|
|
3091
|
+
resetTouchTrackingState();
|
|
3092
|
+
return;
|
|
3093
|
+
}
|
|
3094
|
+
const touch = event.changedTouches[0] ?? event.touches[0];
|
|
3095
|
+
const doc = useButton.ownerDocument(event.currentTarget);
|
|
3096
|
+
const nativeEventTarget = useButton.getTarget(event.nativeEvent);
|
|
3097
|
+
const pointTarget = touch ? resolveKeyboardTouchTargetFromPoint(doc, touch.clientX, touch.clientY) : null;
|
|
3098
|
+
|
|
3099
|
+
// The lift point landed on another interactive/label element; let its native tap
|
|
3100
|
+
// through instead of stealing it for the touchstart input.
|
|
3101
|
+
if (pointTarget === KEYBOARD_TAP_BLOCKED) {
|
|
3102
|
+
resetTouchTrackingState();
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
3105
|
+
const keyboardTarget = touch && (pointTarget ?? resolveKeyboardTouchTarget(nativeEventTarget));
|
|
3106
|
+
if (keyboardTarget && (!useButton.contains(rootElement, keyboardTarget.focusTarget) || !useButton.contains(rootElement, keyboardTarget.clickTarget))) {
|
|
3107
|
+
resetTouchTrackingState();
|
|
3108
|
+
return;
|
|
3109
|
+
}
|
|
3110
|
+
if (keyboardTarget) {
|
|
3111
|
+
const {
|
|
3112
|
+
clickTarget: keyboardClickTarget,
|
|
3113
|
+
focusTarget: keyboardFocusTarget
|
|
3114
|
+
} = keyboardTarget;
|
|
3115
|
+
const win = floatingUi_utils_dom.getWindow(keyboardFocusTarget);
|
|
3116
|
+
|
|
3117
|
+
// While pinch-zoomed, keyboard alignment is suspended; let native behavior
|
|
3118
|
+
// handle focus and caret placement instead of blurring and re-focusing.
|
|
3119
|
+
if (win.visualViewport && win.visualViewport.scale !== 1) {
|
|
3120
|
+
resetTouchTrackingState();
|
|
3121
|
+
return;
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
// Already focused with the keyboard up: let the native tap through so it can
|
|
3125
|
+
// reposition the caret, rather than blurring and re-focusing the same input.
|
|
3126
|
+
if (useButton.activeElement(useButton.ownerDocument(keyboardFocusTarget)) === keyboardFocusTarget && isKeyboardVisualViewportOpen(win)) {
|
|
3127
|
+
resetTouchTrackingState();
|
|
3128
|
+
return;
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
// iOS only opens the software keyboard when focus happens synchronously
|
|
3132
|
+
// inside the touch gesture.
|
|
3133
|
+
event.preventDefault();
|
|
3134
|
+
focusKeyboardInputWithoutPageScroll(keyboardFocusTarget);
|
|
3135
|
+
// Preventing the touchend default also suppresses the compatibility mouse
|
|
3136
|
+
// events, including `click`; redispatch an untrusted replacement on the
|
|
3137
|
+
// original tap target so click handlers still run with the tap coordinates.
|
|
3138
|
+
dispatchKeyboardClick(keyboardClickTarget, touch);
|
|
3139
|
+
resetTouchTrackingState();
|
|
3140
|
+
return;
|
|
3141
|
+
}
|
|
3142
|
+
resetTouchTrackingState();
|
|
3143
|
+
});
|
|
3144
|
+
const contextValue = React__namespace.useMemo(() => ({
|
|
3145
|
+
onTouchStart,
|
|
3146
|
+
onTouchMove,
|
|
3147
|
+
onTouchEnd,
|
|
3148
|
+
onTouchCancel: resetTouchTrackingState
|
|
3149
|
+
}), [onTouchEnd, onTouchMove, onTouchStart, resetTouchTrackingState]);
|
|
3150
|
+
return /*#__PURE__*/jsxRuntime.jsx(DrawerVirtualKeyboardContext.Provider, {
|
|
3151
|
+
value: contextValue,
|
|
3152
|
+
children: children
|
|
3153
|
+
});
|
|
3154
|
+
}
|
|
3155
|
+
function isKeyboardInputElement(element) {
|
|
3156
|
+
if (element.isContentEditable) {
|
|
3157
|
+
return true;
|
|
2579
3158
|
}
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
function isDrawerContentTarget(target) {
|
|
2586
|
-
return Boolean(target?.closest(DRAWER_CONTENT_SELECTOR));
|
|
2587
|
-
}
|
|
2588
|
-
function getBaseSwipeThreshold(element, direction) {
|
|
2589
|
-
const size = direction === 'left' || direction === 'right' ? element.offsetWidth : element.offsetHeight;
|
|
2590
|
-
return Math.max(size * 0.5, MIN_SWIPE_THRESHOLD);
|
|
2591
|
-
}
|
|
2592
|
-
function isRangeInput(target, win) {
|
|
2593
|
-
return target instanceof win.HTMLInputElement && target.type === 'range';
|
|
2594
|
-
}
|
|
2595
|
-
function isTextSelectionControl(target) {
|
|
2596
|
-
if (!floatingUi_utils_dom.isElement(target)) {
|
|
2597
|
-
return false;
|
|
3159
|
+
const win = floatingUi_utils_dom.getWindow(element);
|
|
3160
|
+
if (element instanceof win.HTMLTextAreaElement || element instanceof win.HTMLInputElement && KEYBOARD_INPUT_TYPES.has(element.type)) {
|
|
3161
|
+
// Disabled controls can't focus or open the keyboard, so tap-to-focus must skip them —
|
|
3162
|
+
// otherwise the dispatched click fires handlers a native tap on a disabled control never would.
|
|
3163
|
+
return !element.matches(':disabled');
|
|
2598
3164
|
}
|
|
2599
|
-
return
|
|
2600
|
-
}
|
|
2601
|
-
function hasExpandedSelectionWithinTarget(selection, target) {
|
|
2602
|
-
const anchorElement = floatingUi_utils_dom.isElement(selection.anchorNode) ? selection.anchorNode : selection.anchorNode?.parentElement;
|
|
2603
|
-
const focusElement = floatingUi_utils_dom.isElement(selection.focusNode) ? selection.focusNode : selection.focusNode?.parentElement;
|
|
2604
|
-
return selection.containsNode(target, true) || useButton.contains(target, anchorElement) || useButton.contains(target, focusElement);
|
|
3165
|
+
return false;
|
|
2605
3166
|
}
|
|
2606
|
-
function
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
if (activeElementWithinRoot && isTextSelectionControl(activeElement)) {
|
|
2610
|
-
const {
|
|
2611
|
-
selectionStart,
|
|
2612
|
-
selectionEnd
|
|
2613
|
-
} = activeElement;
|
|
2614
|
-
if (selectionStart != null && selectionEnd != null && selectionStart < selectionEnd) {
|
|
2615
|
-
return true;
|
|
2616
|
-
}
|
|
3167
|
+
function resolveKeyboardInputTarget(target) {
|
|
3168
|
+
if (!floatingUi_utils_dom.isHTMLElement(target)) {
|
|
3169
|
+
return null;
|
|
2617
3170
|
}
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
return false;
|
|
3171
|
+
if (isKeyboardInputElement(target)) {
|
|
3172
|
+
return target.isContentEditable ? getContentEditableHost(target) : target;
|
|
2621
3173
|
}
|
|
2622
|
-
|
|
3174
|
+
const label = target.closest('label');
|
|
3175
|
+
const control = label?.control ?? null;
|
|
3176
|
+
return floatingUi_utils_dom.isHTMLElement(control) && isKeyboardInputElement(control) ? control : null;
|
|
2623
3177
|
}
|
|
2624
|
-
function
|
|
2625
|
-
const
|
|
2626
|
-
if (
|
|
2627
|
-
return
|
|
3178
|
+
function resolveKeyboardTouchTarget(target) {
|
|
3179
|
+
const focusTarget = resolveKeyboardInputTarget(target);
|
|
3180
|
+
if (!focusTarget) {
|
|
3181
|
+
return null;
|
|
2628
3182
|
}
|
|
2629
|
-
return
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
}
|
|
2634
|
-
function updateTouchScrollPosition(touchState, touch) {
|
|
2635
|
-
touchState.lastX = touch.clientX;
|
|
2636
|
-
touchState.lastY = touch.clientY;
|
|
3183
|
+
return {
|
|
3184
|
+
focusTarget,
|
|
3185
|
+
clickTarget: floatingUi_utils_dom.isHTMLElement(target) ? target : focusTarget
|
|
3186
|
+
};
|
|
2637
3187
|
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
const drawerAxisGestureDelta = isVerticalScrollAxis ? touch.clientY - touchState.startY : touch.clientX - touchState.startX;
|
|
2646
|
-
const crossAxisGestureDelta = isVerticalScrollAxis ? touch.clientX - touchState.startX : touch.clientY - touchState.startY;
|
|
2647
|
-
const absDrawerAxisGestureDelta = Math.abs(drawerAxisGestureDelta);
|
|
2648
|
-
const absCrossAxisGestureDelta = Math.abs(crossAxisGestureDelta);
|
|
2649
|
-
if (absCrossAxisGestureDelta < 6 || absCrossAxisGestureDelta <= absDrawerAxisGestureDelta + 2) {
|
|
2650
|
-
return false;
|
|
3188
|
+
|
|
3189
|
+
// Inherited-editable descendants (no `contenteditable` attribute of their own) are not
|
|
3190
|
+
// focusable, so focusing them is a no-op; resolve taps on them to the editing host.
|
|
3191
|
+
function getContentEditableHost(element) {
|
|
3192
|
+
let host = element;
|
|
3193
|
+
while (host.parentElement?.isContentEditable) {
|
|
3194
|
+
host = host.parentElement;
|
|
2651
3195
|
}
|
|
2652
|
-
|
|
2653
|
-
return true;
|
|
2654
|
-
}
|
|
2655
|
-
function hasScrollableContentOnAxis(scrollTarget, axis) {
|
|
2656
|
-
return axis === 'vertical' ? scrollTarget.scrollHeight > scrollTarget.clientHeight : scrollTarget.scrollWidth > scrollTarget.clientWidth;
|
|
3196
|
+
return host;
|
|
2657
3197
|
}
|
|
2658
|
-
function
|
|
2659
|
-
|
|
2660
|
-
|
|
3198
|
+
function resolveKeyboardTouchTargetFromPoint(doc, clientX, clientY) {
|
|
3199
|
+
const exactTarget = getElementAtPoint(doc, clientX, clientY);
|
|
3200
|
+
const exactKeyboardTarget = resolveKeyboardInputTarget(exactTarget);
|
|
3201
|
+
if (exactKeyboardTarget) {
|
|
2661
3202
|
return {
|
|
2662
|
-
|
|
2663
|
-
|
|
3203
|
+
focusTarget: exactKeyboardTarget,
|
|
3204
|
+
clickTarget: floatingUi_utils_dom.isHTMLElement(exactTarget) ? exactTarget : exactKeyboardTarget
|
|
2664
3205
|
};
|
|
2665
3206
|
}
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
max
|
|
2676
|
-
} = getScrollMetrics(scrollTarget, axis);
|
|
2677
|
-
const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
|
|
2678
|
-
if (dismissFromStartEdge === null) {
|
|
2679
|
-
return false;
|
|
2680
|
-
}
|
|
2681
|
-
return dismissFromStartEdge ? offset <= 0 : offset >= max;
|
|
2682
|
-
}
|
|
2683
|
-
function canSwipeFromScrollEdgeOnMove(scrollTarget, axis, direction, delta) {
|
|
2684
|
-
const {
|
|
2685
|
-
offset,
|
|
2686
|
-
max
|
|
2687
|
-
} = getScrollMetrics(scrollTarget, axis);
|
|
2688
|
-
const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
|
|
2689
|
-
if (dismissFromStartEdge === null) {
|
|
2690
|
-
return false;
|
|
3207
|
+
|
|
3208
|
+
// Probing nearby points compensates for iOS retargeting taps while the page reacts
|
|
3209
|
+
// to the keyboard, but it must not steal a tap that lands on another interactive
|
|
3210
|
+
// element — that would suppress its click and focus a neighboring field instead.
|
|
3211
|
+
// `closest('label')` covers labels of non-keyboard controls (e.g. checkboxes).
|
|
3212
|
+
// Returning the blocked sentinel (rather than `null`) stops the caller from falling
|
|
3213
|
+
// back to the touchstart target, which would re-steal the very tap rejected here.
|
|
3214
|
+
if (useScrollLock.isInteractiveElement(exactTarget) || exactTarget?.closest('label') != null) {
|
|
3215
|
+
return KEYBOARD_TAP_BLOCKED;
|
|
2691
3216
|
}
|
|
2692
|
-
const
|
|
2693
|
-
|
|
2694
|
-
|
|
3217
|
+
for (const [offsetX, offsetY] of [[0, INPUT_TAP_HIT_SLOP], [0, -INPUT_TAP_HIT_SLOP], [INPUT_TAP_HIT_SLOP, 0], [-INPUT_TAP_HIT_SLOP, 0]]) {
|
|
3218
|
+
const keyboardTarget = resolveKeyboardInputTarget(getElementAtPoint(doc, clientX + offsetX, clientY + offsetY));
|
|
3219
|
+
if (keyboardTarget) {
|
|
3220
|
+
return {
|
|
3221
|
+
focusTarget: keyboardTarget,
|
|
3222
|
+
clickTarget: keyboardTarget
|
|
3223
|
+
};
|
|
3224
|
+
}
|
|
2695
3225
|
}
|
|
2696
|
-
return
|
|
3226
|
+
return null;
|
|
2697
3227
|
}
|
|
2698
|
-
function
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
3228
|
+
function dispatchKeyboardClick(target, touch) {
|
|
3229
|
+
const win = floatingUi_utils_dom.getWindow(target);
|
|
3230
|
+
const ClickEvent = win.PointerEvent ?? win.MouseEvent;
|
|
3231
|
+
target.dispatchEvent(new ClickEvent('click', {
|
|
3232
|
+
bubbles: true,
|
|
3233
|
+
cancelable: true,
|
|
3234
|
+
clientX: touch.clientX,
|
|
3235
|
+
clientY: touch.clientY,
|
|
3236
|
+
detail: 1,
|
|
3237
|
+
view: win
|
|
3238
|
+
}));
|
|
3239
|
+
}
|
|
3240
|
+
function focusKeyboardInputWithoutPageScroll(target) {
|
|
3241
|
+
const wasFocused = useButton.activeElement(useButton.ownerDocument(target)) === target;
|
|
3242
|
+
const previousOpacity = target.style.opacity;
|
|
3243
|
+
const previousTransform = target.style.transform;
|
|
3244
|
+
const previousTransition = target.style.transition;
|
|
3245
|
+
|
|
3246
|
+
// iOS Safari can still scroll the page for transformed sheets even with preventScroll.
|
|
3247
|
+
// Move the input off-screen only for the synchronous focus call.
|
|
3248
|
+
target.style.transition = 'none';
|
|
3249
|
+
target.style.opacity = '0';
|
|
3250
|
+
target.style.transform = 'translateY(-2000px)';
|
|
3251
|
+
try {
|
|
3252
|
+
if (wasFocused) {
|
|
3253
|
+
target.blur();
|
|
2705
3254
|
}
|
|
2706
|
-
|
|
3255
|
+
target.focus({
|
|
3256
|
+
preventScroll: true
|
|
3257
|
+
});
|
|
3258
|
+
} finally {
|
|
3259
|
+
target.style.opacity = previousOpacity;
|
|
3260
|
+
target.style.transform = previousTransform;
|
|
3261
|
+
target.style.transition = previousTransition;
|
|
2707
3262
|
}
|
|
2708
|
-
|
|
2709
|
-
|
|
3263
|
+
}
|
|
3264
|
+
function findKeyboardScrollTarget(target, root) {
|
|
3265
|
+
// Start at the parent: scrolling the focused field's own content (an overflowing
|
|
3266
|
+
// textarea is scrollable itself) can never move its box out from under the keyboard.
|
|
3267
|
+
// `getParentNode` crosses shadow boundaries so an input inside a shadow root still reaches
|
|
3268
|
+
// the drawer body scroller. Prefer an already-scrollable ancestor, then fall back to one
|
|
3269
|
+
// that only becomes scrollable once keyboard slack is added (overflow intent without
|
|
3270
|
+
// current overflow).
|
|
3271
|
+
const scrollStart = floatingUi_utils_dom.getParentNode(target);
|
|
3272
|
+
return findScrollableTouchTarget(scrollStart, root, 'vertical') ?? findScrollableTouchTarget(scrollStart, root, 'vertical', true);
|
|
3273
|
+
}
|
|
3274
|
+
function getKeyboardVisualViewport(win) {
|
|
3275
|
+
const visualViewport = win.visualViewport;
|
|
3276
|
+
if (!visualViewport || visualViewport.scale !== 1) {
|
|
3277
|
+
return null;
|
|
2710
3278
|
}
|
|
2711
|
-
|
|
2712
|
-
|
|
3279
|
+
const reducedHeight = win.innerHeight - visualViewport.height;
|
|
3280
|
+
// Treat small viewport changes as browser chrome movement, not the software keyboard.
|
|
3281
|
+
if (reducedHeight <= KEYBOARD_RESIZE_THRESHOLD) {
|
|
3282
|
+
return null;
|
|
2713
3283
|
}
|
|
2714
|
-
|
|
3284
|
+
const top = Math.max(0, visualViewport.offsetTop);
|
|
3285
|
+
return {
|
|
3286
|
+
top,
|
|
3287
|
+
bottom: Math.min(win.innerHeight, top + visualViewport.height)
|
|
3288
|
+
};
|
|
3289
|
+
}
|
|
3290
|
+
function getDrawerKeyboardInset(win, keyboardViewport) {
|
|
3291
|
+
return Math.max(0, win.innerHeight - keyboardViewport.bottom);
|
|
3292
|
+
}
|
|
3293
|
+
function isKeyboardVisualViewportOpen(win) {
|
|
3294
|
+
return !win.visualViewport || getKeyboardVisualViewport(win) != null;
|
|
2715
3295
|
}
|
|
2716
3296
|
|
|
2717
3297
|
var index_parts$5 = /*#__PURE__*/Object.freeze({
|
|
@@ -2731,6 +3311,7 @@ var index_parts$5 = /*#__PURE__*/Object.freeze({
|
|
|
2731
3311
|
Title: DrawerTitle.DrawerTitle,
|
|
2732
3312
|
Trigger: ScrollAreaViewport.DrawerTrigger,
|
|
2733
3313
|
Viewport: DrawerViewport,
|
|
3314
|
+
VirtualKeyboardProvider: DrawerVirtualKeyboardProvider,
|
|
2734
3315
|
createHandle: DrawerDescription.createDialogHandle
|
|
2735
3316
|
});
|
|
2736
3317
|
|
|
@@ -2742,8 +3323,9 @@ var index_parts$5 = /*#__PURE__*/Object.freeze({
|
|
|
2742
3323
|
*/
|
|
2743
3324
|
const MenuArrow = /*#__PURE__*/React__namespace.forwardRef(function MenuArrow(componentProps, forwardedRef) {
|
|
2744
3325
|
const {
|
|
2745
|
-
className,
|
|
2746
3326
|
render,
|
|
3327
|
+
className,
|
|
3328
|
+
style,
|
|
2747
3329
|
...elementProps
|
|
2748
3330
|
} = componentProps;
|
|
2749
3331
|
const {
|
|
@@ -2765,7 +3347,7 @@ const MenuArrow = /*#__PURE__*/React__namespace.forwardRef(function MenuArrow(co
|
|
|
2765
3347
|
};
|
|
2766
3348
|
return useRenderElement.useRenderElement('div', componentProps, {
|
|
2767
3349
|
ref: [arrowRef, forwardedRef],
|
|
2768
|
-
stateAttributesMapping:
|
|
3350
|
+
stateAttributesMapping: useScrollLock.popupStateMapping,
|
|
2769
3351
|
state,
|
|
2770
3352
|
props: {
|
|
2771
3353
|
style: arrowStyles,
|
|
@@ -2804,6 +3386,7 @@ const MenuCheckboxItem = /*#__PURE__*/React__namespace.forwardRef(function MenuC
|
|
|
2804
3386
|
checked: checkedProp,
|
|
2805
3387
|
defaultChecked,
|
|
2806
3388
|
onCheckedChange,
|
|
3389
|
+
style,
|
|
2807
3390
|
...elementProps
|
|
2808
3391
|
} = componentProps;
|
|
2809
3392
|
const listItem = useCompositeListItem.useCompositeListItem({
|
|
@@ -2832,7 +3415,7 @@ const MenuCheckboxItem = /*#__PURE__*/React__namespace.forwardRef(function MenuC
|
|
|
2832
3415
|
id,
|
|
2833
3416
|
store,
|
|
2834
3417
|
nativeButton,
|
|
2835
|
-
nodeId: menuPositionerContext?.nodeId,
|
|
3418
|
+
nodeId: menuPositionerContext?.context.nodeId,
|
|
2836
3419
|
itemMetadata: MenuSubmenuTrigger.REGULAR_ITEM
|
|
2837
3420
|
});
|
|
2838
3421
|
const state = React__namespace.useMemo(() => ({
|
|
@@ -2840,17 +3423,16 @@ const MenuCheckboxItem = /*#__PURE__*/React__namespace.forwardRef(function MenuC
|
|
|
2840
3423
|
highlighted,
|
|
2841
3424
|
checked
|
|
2842
3425
|
}), [disabled, highlighted, checked]);
|
|
2843
|
-
|
|
2844
|
-
const details = {
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
};
|
|
3426
|
+
function handleClick(event) {
|
|
3427
|
+
const details = useButton.createChangeEventDetails(useButton.itemPress, event.nativeEvent, undefined, {
|
|
3428
|
+
preventUnmountOnClose() {}
|
|
3429
|
+
});
|
|
2848
3430
|
onCheckedChange?.(!checked, details);
|
|
2849
3431
|
if (details.isCanceled) {
|
|
2850
3432
|
return;
|
|
2851
3433
|
}
|
|
2852
3434
|
setChecked(currentlyChecked => !currentlyChecked);
|
|
2853
|
-
}
|
|
3435
|
+
}
|
|
2854
3436
|
const element = useRenderElement.useRenderElement('div', componentProps, {
|
|
2855
3437
|
state,
|
|
2856
3438
|
stateAttributesMapping: MenuSubmenuTrigger.itemMapping,
|
|
@@ -2878,6 +3460,7 @@ const MenuCheckboxItemIndicator = /*#__PURE__*/React__namespace.forwardRef(funct
|
|
|
2878
3460
|
const {
|
|
2879
3461
|
render,
|
|
2880
3462
|
className,
|
|
3463
|
+
style,
|
|
2881
3464
|
keepMounted = false,
|
|
2882
3465
|
...elementProps
|
|
2883
3466
|
} = componentProps;
|
|
@@ -2916,25 +3499,6 @@ const MenuCheckboxItemIndicator = /*#__PURE__*/React__namespace.forwardRef(funct
|
|
|
2916
3499
|
});
|
|
2917
3500
|
if (process.env.NODE_ENV !== "production") MenuCheckboxItemIndicator.displayName = "MenuCheckboxItemIndicator";
|
|
2918
3501
|
|
|
2919
|
-
/**
|
|
2920
|
-
* Returns a previous value of its argument.
|
|
2921
|
-
* @param value Current value.
|
|
2922
|
-
* @returns Previous value, or null if there is no previous value.
|
|
2923
|
-
*/
|
|
2924
|
-
function usePreviousValue(value) {
|
|
2925
|
-
const [state, setState] = React__namespace.useState({
|
|
2926
|
-
current: value,
|
|
2927
|
-
previous: null
|
|
2928
|
-
});
|
|
2929
|
-
if (value !== state.current) {
|
|
2930
|
-
setState({
|
|
2931
|
-
current: value,
|
|
2932
|
-
previous: state.current
|
|
2933
|
-
});
|
|
2934
|
-
}
|
|
2935
|
-
return state.previous;
|
|
2936
|
-
}
|
|
2937
|
-
|
|
2938
3502
|
function getCssDimensions(element) {
|
|
2939
3503
|
const css = floatingUi_utils_dom.getComputedStyle(element);
|
|
2940
3504
|
// In testing environments, the `width` and `height` properties are empty
|
|
@@ -2944,7 +3508,7 @@ function getCssDimensions(element) {
|
|
|
2944
3508
|
const hasOffset = floatingUi_utils_dom.isHTMLElement(element);
|
|
2945
3509
|
const offsetWidth = hasOffset ? element.offsetWidth : width;
|
|
2946
3510
|
const offsetHeight = hasOffset ? element.offsetHeight : height;
|
|
2947
|
-
const shouldFallback =
|
|
3511
|
+
const shouldFallback = floatingUi_utils.round(width) !== offsetWidth || floatingUi_utils.round(height) !== offsetHeight;
|
|
2948
3512
|
if (shouldFallback) {
|
|
2949
3513
|
width = offsetWidth;
|
|
2950
3514
|
height = offsetHeight;
|
|
@@ -2955,8 +3519,6 @@ function getCssDimensions(element) {
|
|
|
2955
3519
|
};
|
|
2956
3520
|
}
|
|
2957
3521
|
|
|
2958
|
-
const DEFAULT_ENABLED = () => true;
|
|
2959
|
-
|
|
2960
3522
|
/**
|
|
2961
3523
|
* Allows the element to automatically resize based on its content while supporting animations.
|
|
2962
3524
|
*/
|
|
@@ -2966,7 +3528,6 @@ function usePopupAutoResize(parameters) {
|
|
|
2966
3528
|
positionerElement,
|
|
2967
3529
|
content,
|
|
2968
3530
|
mounted,
|
|
2969
|
-
enabled = DEFAULT_ENABLED,
|
|
2970
3531
|
onMeasureLayout: onMeasureLayoutParam,
|
|
2971
3532
|
onMeasureLayoutComplete: onMeasureLayoutCompleteParam,
|
|
2972
3533
|
side,
|
|
@@ -2975,7 +3536,6 @@ function usePopupAutoResize(parameters) {
|
|
|
2975
3536
|
const runOnceAnimationsFinish = useButton.useAnimationsFinished(popupElement, true, false);
|
|
2976
3537
|
const animationFrame = useButton.useAnimationFrame();
|
|
2977
3538
|
const committedDimensionsRef = React__namespace.useRef(null);
|
|
2978
|
-
const liveDimensionsRef = React__namespace.useRef(null);
|
|
2979
3539
|
const isInitialRenderRef = React__namespace.useRef(true);
|
|
2980
3540
|
const restoreAnchoringStylesRef = React__namespace.useRef(useRenderElement.NOOP);
|
|
2981
3541
|
const onMeasureLayout = useButton.useStableCallback(onMeasureLayoutParam);
|
|
@@ -2999,27 +3559,16 @@ function usePopupAutoResize(parameters) {
|
|
|
2999
3559
|
}, [side, direction]);
|
|
3000
3560
|
useButton.useIsoLayoutEffect(() => {
|
|
3001
3561
|
// Reset the state when the popup is closed.
|
|
3002
|
-
if (!mounted
|
|
3562
|
+
if (!mounted) {
|
|
3003
3563
|
restoreAnchoringStylesRef.current = useRenderElement.NOOP;
|
|
3004
3564
|
isInitialRenderRef.current = true;
|
|
3005
3565
|
committedDimensionsRef.current = null;
|
|
3006
|
-
liveDimensionsRef.current = null;
|
|
3007
3566
|
return undefined;
|
|
3008
3567
|
}
|
|
3009
3568
|
if (!popupElement || !positionerElement) {
|
|
3010
3569
|
return undefined;
|
|
3011
3570
|
}
|
|
3012
3571
|
restoreAnchoringStylesRef.current = applyElementStyles(popupElement, anchoringStyles);
|
|
3013
|
-
const observer = new ResizeObserver(entries => {
|
|
3014
|
-
const entry = entries[0];
|
|
3015
|
-
if (entry) {
|
|
3016
|
-
liveDimensionsRef.current = {
|
|
3017
|
-
width: Math.ceil(entry.borderBoxSize[0].inlineSize),
|
|
3018
|
-
height: Math.ceil(entry.borderBoxSize[0].blockSize)
|
|
3019
|
-
};
|
|
3020
|
-
}
|
|
3021
|
-
});
|
|
3022
|
-
observer.observe(popupElement);
|
|
3023
3572
|
|
|
3024
3573
|
// Measure the rendered size to enable transitions:
|
|
3025
3574
|
setPopupCssSize(popupElement, 'auto');
|
|
@@ -3051,32 +3600,18 @@ function usePopupAutoResize(parameters) {
|
|
|
3051
3600
|
onMeasureLayoutComplete?.(null, dimensions);
|
|
3052
3601
|
isInitialRenderRef.current = false;
|
|
3053
3602
|
return () => {
|
|
3054
|
-
observer.disconnect();
|
|
3055
3603
|
restoreAnchoringStylesRef.current();
|
|
3056
3604
|
restoreAnchoringStylesRef.current = useRenderElement.NOOP;
|
|
3057
3605
|
};
|
|
3058
3606
|
}
|
|
3059
3607
|
|
|
3060
3608
|
// Subsequent renders while open (when `content` changes).
|
|
3061
|
-
setPopupCssSize(popupElement, 'auto');
|
|
3062
3609
|
setPositionerCssSize(positionerElement, 'max-content');
|
|
3063
|
-
const previousDimensions = committedDimensionsRef.current
|
|
3610
|
+
const previousDimensions = committedDimensionsRef.current;
|
|
3064
3611
|
const newDimensions = getCssDimensions(popupElement);
|
|
3065
3612
|
|
|
3066
|
-
// Commit immediately so future content changes have a stable previous size
|
|
3067
|
-
// ResizeObserver runs after this point.
|
|
3613
|
+
// Commit immediately so future content changes have a stable previous size.
|
|
3068
3614
|
committedDimensionsRef.current = newDimensions;
|
|
3069
|
-
if (!previousDimensions) {
|
|
3070
|
-
setPositionerCssSize(positionerElement, newDimensions);
|
|
3071
|
-
restoreMeasurementOverridesIncludingScale();
|
|
3072
|
-
onMeasureLayoutComplete?.(null, newDimensions);
|
|
3073
|
-
return () => {
|
|
3074
|
-
observer.disconnect();
|
|
3075
|
-
animationFrame.cancel();
|
|
3076
|
-
restoreAnchoringStylesRef.current();
|
|
3077
|
-
restoreAnchoringStylesRef.current = useRenderElement.NOOP;
|
|
3078
|
-
};
|
|
3079
|
-
}
|
|
3080
3615
|
setPopupCssSize(popupElement, previousDimensions);
|
|
3081
3616
|
restoreMeasurementOverridesIncludingScale();
|
|
3082
3617
|
onMeasureLayoutComplete?.(previousDimensions, newDimensions);
|
|
@@ -3090,13 +3625,12 @@ function usePopupAutoResize(parameters) {
|
|
|
3090
3625
|
}, abortController.signal);
|
|
3091
3626
|
});
|
|
3092
3627
|
return () => {
|
|
3093
|
-
observer.disconnect();
|
|
3094
3628
|
abortController.abort();
|
|
3095
3629
|
animationFrame.cancel();
|
|
3096
3630
|
restoreAnchoringStylesRef.current();
|
|
3097
3631
|
restoreAnchoringStylesRef.current = useRenderElement.NOOP;
|
|
3098
3632
|
};
|
|
3099
|
-
}, [content, popupElement, positionerElement, runOnceAnimationsFinish, animationFrame,
|
|
3633
|
+
}, [content, popupElement, positionerElement, runOnceAnimationsFinish, animationFrame, mounted, onMeasureLayout, onMeasureLayoutComplete, anchoringStyles]);
|
|
3100
3634
|
}
|
|
3101
3635
|
function overrideElementStyle(element, property, value) {
|
|
3102
3636
|
const originalValue = element.style.getPropertyValue(property);
|
|
@@ -3138,7 +3672,7 @@ function usePopupViewport(parameters) {
|
|
|
3138
3672
|
cssVars,
|
|
3139
3673
|
children
|
|
3140
3674
|
} = parameters;
|
|
3141
|
-
const direction =
|
|
3675
|
+
const direction = useScrollLock.useDirection();
|
|
3142
3676
|
const activeTrigger = store.useState('activeTriggerElement');
|
|
3143
3677
|
const activeTriggerId = store.useState('activeTriggerId');
|
|
3144
3678
|
const open = store.useState('open');
|
|
@@ -3146,7 +3680,7 @@ function usePopupViewport(parameters) {
|
|
|
3146
3680
|
const mounted = store.useState('mounted');
|
|
3147
3681
|
const popupElement = store.useState('popupElement');
|
|
3148
3682
|
const positionerElement = store.useState('positionerElement');
|
|
3149
|
-
const previousActiveTrigger = usePreviousValue(open ? activeTrigger : null);
|
|
3683
|
+
const previousActiveTrigger = SelectGroupLabel.usePreviousValue(open ? activeTrigger : null);
|
|
3150
3684
|
// Remount current content on trigger changes (and once more when payload lags) to avoid DOM reuse flashes.
|
|
3151
3685
|
// The key bumps immediately on trigger switches, then again if the payload arrives on a later render.
|
|
3152
3686
|
const currentContentKey = usePopupContentKey(activeTriggerId, payload);
|
|
@@ -3179,6 +3713,11 @@ function usePopupViewport(parameters) {
|
|
|
3179
3713
|
}
|
|
3180
3714
|
});
|
|
3181
3715
|
const lastHandledTriggerRef = React__namespace.useRef(null);
|
|
3716
|
+
useButton.useIsoLayoutEffect(() => {
|
|
3717
|
+
if (!open || !mounted) {
|
|
3718
|
+
lastHandledTriggerRef.current = null;
|
|
3719
|
+
}
|
|
3720
|
+
}, [open, mounted]);
|
|
3182
3721
|
useButton.useIsoLayoutEffect(() => {
|
|
3183
3722
|
// When a trigger changes, set the captured children HTML to state,
|
|
3184
3723
|
// so we can render both new and old content.
|
|
@@ -3217,7 +3756,7 @@ function usePopupViewport(parameters) {
|
|
|
3217
3756
|
if (!source) {
|
|
3218
3757
|
return;
|
|
3219
3758
|
}
|
|
3220
|
-
const wrapper =
|
|
3759
|
+
const wrapper = useButton.ownerDocument(source).createElement('div');
|
|
3221
3760
|
for (const child of Array.from(source.childNodes)) {
|
|
3222
3761
|
wrapper.appendChild(child.cloneNode(true));
|
|
3223
3762
|
}
|
|
@@ -3235,7 +3774,7 @@ function usePopupViewport(parameters) {
|
|
|
3235
3774
|
childrenToRender = /*#__PURE__*/jsxRuntime.jsxs(React__namespace.Fragment, {
|
|
3236
3775
|
children: [/*#__PURE__*/jsxRuntime.jsx("div", {
|
|
3237
3776
|
"data-previous": true,
|
|
3238
|
-
inert:
|
|
3777
|
+
inert: useScrollLock.inertValue(true),
|
|
3239
3778
|
ref: previousContainerRef,
|
|
3240
3779
|
style: {
|
|
3241
3780
|
...(previousContentDimensions ? {
|
|
@@ -3389,8 +3928,8 @@ const stateAttributesMapping$2 = {
|
|
|
3389
3928
|
|
|
3390
3929
|
/**
|
|
3391
3930
|
* A viewport for displaying content transitions.
|
|
3392
|
-
* This component is only required if one popup can be opened by multiple triggers, its content
|
|
3393
|
-
* and switching between them is animated.
|
|
3931
|
+
* This component is only required if one popup can be opened by multiple triggers, its content
|
|
3932
|
+
* changes based on the trigger, and switching between them is animated.
|
|
3394
3933
|
* Renders a `<div>` element.
|
|
3395
3934
|
*
|
|
3396
3935
|
* Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
|
|
@@ -3399,6 +3938,7 @@ const MenuViewport = /*#__PURE__*/React__namespace.forwardRef(function MenuViewp
|
|
|
3399
3938
|
const {
|
|
3400
3939
|
render,
|
|
3401
3940
|
className,
|
|
3941
|
+
style,
|
|
3402
3942
|
children,
|
|
3403
3943
|
...elementProps
|
|
3404
3944
|
} = componentProps;
|
|
@@ -3469,7 +4009,7 @@ class MenuHandle {
|
|
|
3469
4009
|
* Indicates whether the menu is currently open.
|
|
3470
4010
|
*/
|
|
3471
4011
|
get isOpen() {
|
|
3472
|
-
return this.store.
|
|
4012
|
+
return this.store.select('open');
|
|
3473
4013
|
}
|
|
3474
4014
|
}
|
|
3475
4015
|
|
|
@@ -3542,6 +4082,7 @@ const ScrollAreaScrollbar = /*#__PURE__*/React__namespace.forwardRef(function Sc
|
|
|
3542
4082
|
className,
|
|
3543
4083
|
orientation = 'vertical',
|
|
3544
4084
|
keepMounted = false,
|
|
4085
|
+
style,
|
|
3545
4086
|
...elementProps
|
|
3546
4087
|
} = componentProps;
|
|
3547
4088
|
const {
|
|
@@ -3557,6 +4098,7 @@ const ScrollAreaScrollbar = /*#__PURE__*/React__namespace.forwardRef(function Sc
|
|
|
3557
4098
|
thumbXRef,
|
|
3558
4099
|
handlePointerDown,
|
|
3559
4100
|
handlePointerUp,
|
|
4101
|
+
handleScroll,
|
|
3560
4102
|
rootId,
|
|
3561
4103
|
thumbSize,
|
|
3562
4104
|
hasMeasuredScrollbar
|
|
@@ -3576,9 +4118,14 @@ const ScrollAreaScrollbar = /*#__PURE__*/React__namespace.forwardRef(function Sc
|
|
|
3576
4118
|
overflowYEnd: overflowEdges.yEnd,
|
|
3577
4119
|
cornerHidden: hiddenState.corner
|
|
3578
4120
|
};
|
|
3579
|
-
const direction =
|
|
4121
|
+
const direction = useScrollLock.useDirection();
|
|
3580
4122
|
const hideTrackUntilMeasured = !hasMeasuredScrollbar && !keepMounted;
|
|
4123
|
+
const isHidden = orientation === 'vertical' ? hiddenState.y : hiddenState.x;
|
|
4124
|
+
const shouldRender = keepMounted || !isHidden;
|
|
3581
4125
|
React__namespace.useEffect(() => {
|
|
4126
|
+
if (!shouldRender) {
|
|
4127
|
+
return undefined;
|
|
4128
|
+
}
|
|
3582
4129
|
const viewportEl = viewportRef.current;
|
|
3583
4130
|
const scrollbarEl = orientation === 'vertical' ? scrollbarYRef.current : scrollbarXRef.current;
|
|
3584
4131
|
if (!scrollbarEl) {
|
|
@@ -3588,34 +4135,34 @@ const ScrollAreaScrollbar = /*#__PURE__*/React__namespace.forwardRef(function Sc
|
|
|
3588
4135
|
if (!viewportEl || !scrollbarEl || event.ctrlKey) {
|
|
3589
4136
|
return;
|
|
3590
4137
|
}
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
}
|
|
3596
|
-
} else if (viewportEl.scrollLeft === 0 && event.deltaX < 0) {
|
|
4138
|
+
const horizontal = orientation === 'horizontal';
|
|
4139
|
+
const scrollProperty = horizontal ? 'scrollLeft' : 'scrollTop';
|
|
4140
|
+
const delta = horizontal ? event.deltaX : event.deltaY;
|
|
4141
|
+
if (delta === 0) {
|
|
3597
4142
|
return;
|
|
3598
4143
|
}
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
4144
|
+
const maxScroll = horizontal ? viewportEl.scrollWidth - viewportEl.clientWidth : viewportEl.scrollHeight - viewportEl.clientHeight;
|
|
4145
|
+
// RTL horizontal scrolling uses a negative `scrollLeft` range, from 0 to `-maxScroll`.
|
|
4146
|
+
const minScroll = horizontal && direction === 'rtl' ? -maxScroll : 0;
|
|
4147
|
+
const maxScrollValue = horizontal && direction === 'rtl' ? 0 : maxScroll;
|
|
4148
|
+
const scrollValue = viewportEl[scrollProperty];
|
|
4149
|
+
|
|
4150
|
+
// At an edge (or with no overflow), let the wheel event chain to the
|
|
4151
|
+
// parent/page instead of swallowing it via `preventDefault`.
|
|
4152
|
+
if (scrollValue <= minScroll && delta < 0 || scrollValue >= maxScrollValue && delta > 0) {
|
|
3604
4153
|
return;
|
|
3605
4154
|
}
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
viewportEl.scrollLeft
|
|
3610
|
-
|
|
4155
|
+
event.preventDefault();
|
|
4156
|
+
viewportEl[scrollProperty] = Math.min(maxScrollValue, Math.max(minScroll, scrollValue + delta));
|
|
4157
|
+
handleScroll({
|
|
4158
|
+
x: viewportEl.scrollLeft,
|
|
4159
|
+
y: viewportEl.scrollTop
|
|
4160
|
+
});
|
|
3611
4161
|
}
|
|
3612
|
-
|
|
4162
|
+
return useButton.addEventListener(scrollbarEl, 'wheel', handleWheel, {
|
|
3613
4163
|
passive: false
|
|
3614
4164
|
});
|
|
3615
|
-
|
|
3616
|
-
scrollbarEl.removeEventListener('wheel', handleWheel);
|
|
3617
|
-
};
|
|
3618
|
-
}, [orientation, scrollbarXRef, scrollbarYRef, viewportRef]);
|
|
4165
|
+
}, [direction, handleScroll, orientation, scrollbarXRef, scrollbarYRef, shouldRender, viewportRef]);
|
|
3619
4166
|
const props = {
|
|
3620
4167
|
...(rootId && {
|
|
3621
4168
|
'data-id': `${rootId}-scrollbar`
|
|
@@ -3624,9 +4171,12 @@ const ScrollAreaScrollbar = /*#__PURE__*/React__namespace.forwardRef(function Sc
|
|
|
3624
4171
|
if (event.button !== 0) {
|
|
3625
4172
|
return;
|
|
3626
4173
|
}
|
|
4174
|
+
const target = useButton.getTarget(event.nativeEvent);
|
|
4175
|
+
const thumb = orientation === 'vertical' ? thumbYRef.current : thumbXRef.current;
|
|
3627
4176
|
|
|
3628
|
-
// Ignore clicks on thumb
|
|
3629
|
-
|
|
4177
|
+
// Ignore clicks on thumb, including cases where React retargets the
|
|
4178
|
+
// synthetic event to the track host across a shadow boundary.
|
|
4179
|
+
if (thumb && useButton.contains(thumb, target)) {
|
|
3630
4180
|
return;
|
|
3631
4181
|
}
|
|
3632
4182
|
if (!viewportRef.current) {
|
|
@@ -3671,9 +4221,16 @@ const ScrollAreaScrollbar = /*#__PURE__*/React__namespace.forwardRef(function Sc
|
|
|
3671
4221
|
}
|
|
3672
4222
|
viewportRef.current.scrollLeft = newScrollLeft;
|
|
3673
4223
|
}
|
|
4224
|
+
handleScroll({
|
|
4225
|
+
x: viewportRef.current.scrollLeft,
|
|
4226
|
+
y: viewportRef.current.scrollTop
|
|
4227
|
+
});
|
|
3674
4228
|
handlePointerDown(event);
|
|
3675
4229
|
},
|
|
3676
4230
|
onPointerUp: handlePointerUp,
|
|
4231
|
+
// Mirror `onPointerUp` so a browser-cancelled gesture on the track (no thumb
|
|
4232
|
+
// child captures the pointer) still clears the drag state.
|
|
4233
|
+
onPointerCancel: handlePointerUp,
|
|
3677
4234
|
style: {
|
|
3678
4235
|
position: 'absolute',
|
|
3679
4236
|
touchAction: 'none',
|
|
@@ -3703,8 +4260,6 @@ const ScrollAreaScrollbar = /*#__PURE__*/React__namespace.forwardRef(function Sc
|
|
|
3703
4260
|
const contextValue = React__namespace.useMemo(() => ({
|
|
3704
4261
|
orientation
|
|
3705
4262
|
}), [orientation]);
|
|
3706
|
-
const isHidden = orientation === 'vertical' ? hiddenState.y : hiddenState.x;
|
|
3707
|
-
const shouldRender = keepMounted || !isHidden;
|
|
3708
4263
|
if (!shouldRender) {
|
|
3709
4264
|
return null;
|
|
3710
4265
|
}
|
|
@@ -3725,34 +4280,42 @@ const ScrollAreaContent = /*#__PURE__*/React__namespace.forwardRef(function Scro
|
|
|
3725
4280
|
const {
|
|
3726
4281
|
render,
|
|
3727
4282
|
className,
|
|
4283
|
+
style,
|
|
3728
4284
|
...elementProps
|
|
3729
4285
|
} = componentProps;
|
|
3730
|
-
const contentWrapperRef = React__namespace.useRef(null);
|
|
3731
4286
|
const {
|
|
3732
4287
|
computeThumbPosition
|
|
3733
4288
|
} = ScrollAreaViewport.useScrollAreaViewportContext();
|
|
3734
4289
|
const {
|
|
4290
|
+
hasMeasuredScrollbar,
|
|
3735
4291
|
viewportState
|
|
3736
4292
|
} = ScrollAreaViewport.useScrollAreaRootContext();
|
|
4293
|
+
const contentWrapperRef = React__namespace.useRef(null);
|
|
4294
|
+
const computeOnInitialResizeRef = React__namespace.useRef(hasMeasuredScrollbar);
|
|
3737
4295
|
useButton.useIsoLayoutEffect(() => {
|
|
3738
4296
|
if (typeof ResizeObserver === 'undefined') {
|
|
3739
4297
|
return undefined;
|
|
3740
4298
|
}
|
|
3741
4299
|
let hasInitialized = false;
|
|
3742
|
-
const
|
|
3743
|
-
// ResizeObserver fires once upon observing, so we skip the initial call
|
|
3744
|
-
// to avoid double-calculating the thumb position on mount.
|
|
4300
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
3745
4301
|
if (!hasInitialized) {
|
|
3746
4302
|
hasInitialized = true;
|
|
3747
|
-
|
|
4303
|
+
|
|
4304
|
+
// ResizeObserver fires once upon observing. Skip that initial call to avoid
|
|
4305
|
+
// double-calculating the thumb position on mount, unless the content mounted
|
|
4306
|
+
// after the viewport's initial measurement (in which case this fire is what
|
|
4307
|
+
// brings the overflow state in sync).
|
|
4308
|
+
if (!computeOnInitialResizeRef.current) {
|
|
4309
|
+
return;
|
|
4310
|
+
}
|
|
3748
4311
|
}
|
|
3749
4312
|
computeThumbPosition();
|
|
3750
4313
|
});
|
|
3751
4314
|
if (contentWrapperRef.current) {
|
|
3752
|
-
|
|
4315
|
+
resizeObserver.observe(contentWrapperRef.current);
|
|
3753
4316
|
}
|
|
3754
4317
|
return () => {
|
|
3755
|
-
|
|
4318
|
+
resizeObserver.disconnect();
|
|
3756
4319
|
};
|
|
3757
4320
|
}, [computeThumbPosition]);
|
|
3758
4321
|
const element = useRenderElement.useRenderElement('div', componentProps, {
|
|
@@ -3780,6 +4343,7 @@ const ScrollAreaThumb = /*#__PURE__*/React__namespace.forwardRef(function Scroll
|
|
|
3780
4343
|
const {
|
|
3781
4344
|
render,
|
|
3782
4345
|
className,
|
|
4346
|
+
style,
|
|
3783
4347
|
...elementProps
|
|
3784
4348
|
} = componentProps;
|
|
3785
4349
|
const {
|
|
@@ -3790,29 +4354,34 @@ const ScrollAreaThumb = /*#__PURE__*/React__namespace.forwardRef(function Scroll
|
|
|
3790
4354
|
handlePointerUp,
|
|
3791
4355
|
setScrollingX,
|
|
3792
4356
|
setScrollingY,
|
|
4357
|
+
scrollingX,
|
|
4358
|
+
scrollingY,
|
|
3793
4359
|
hasMeasuredScrollbar
|
|
3794
4360
|
} = ScrollAreaViewport.useScrollAreaRootContext();
|
|
3795
4361
|
const {
|
|
3796
4362
|
orientation
|
|
3797
4363
|
} = useScrollAreaScrollbarContext();
|
|
3798
4364
|
const state = {
|
|
4365
|
+
scrolling: orientation === 'horizontal' ? scrollingX : scrollingY,
|
|
3799
4366
|
orientation
|
|
3800
4367
|
};
|
|
4368
|
+
function endDrag(event) {
|
|
4369
|
+
if (orientation === 'vertical') {
|
|
4370
|
+
setScrollingY(false);
|
|
4371
|
+
}
|
|
4372
|
+
if (orientation === 'horizontal') {
|
|
4373
|
+
setScrollingX(false);
|
|
4374
|
+
}
|
|
4375
|
+
handlePointerUp(event);
|
|
4376
|
+
}
|
|
3801
4377
|
const element = useRenderElement.useRenderElement('div', componentProps, {
|
|
3802
4378
|
ref: [forwardedRef, orientation === 'vertical' ? thumbYRef : thumbXRef],
|
|
3803
4379
|
state,
|
|
3804
4380
|
props: [{
|
|
3805
4381
|
onPointerDown: handlePointerDown,
|
|
3806
4382
|
onPointerMove: handlePointerMove,
|
|
3807
|
-
onPointerUp
|
|
3808
|
-
|
|
3809
|
-
setScrollingY(false);
|
|
3810
|
-
}
|
|
3811
|
-
if (orientation === 'horizontal') {
|
|
3812
|
-
setScrollingX(false);
|
|
3813
|
-
}
|
|
3814
|
-
handlePointerUp(event);
|
|
3815
|
-
},
|
|
4383
|
+
onPointerUp: endDrag,
|
|
4384
|
+
onPointerCancel: endDrag,
|
|
3816
4385
|
style: {
|
|
3817
4386
|
visibility: hasMeasuredScrollbar ? undefined : 'hidden',
|
|
3818
4387
|
...(orientation === 'vertical' && {
|
|
@@ -3838,6 +4407,7 @@ const ScrollAreaCorner = /*#__PURE__*/React__namespace.forwardRef(function Scrol
|
|
|
3838
4407
|
const {
|
|
3839
4408
|
render,
|
|
3840
4409
|
className,
|
|
4410
|
+
style,
|
|
3841
4411
|
...elementProps
|
|
3842
4412
|
} = componentProps;
|
|
3843
4413
|
const {
|
|
@@ -3916,32 +4486,32 @@ function useNumberFieldScrubAreaContext() {
|
|
|
3916
4486
|
return context;
|
|
3917
4487
|
}
|
|
3918
4488
|
|
|
3919
|
-
// Calculates the
|
|
4489
|
+
// Calculates the bounds the virtual cursor wraps within, as absolute edge coordinates.
|
|
3920
4490
|
function getViewportRect(teleportDistance, scrubAreaEl) {
|
|
3921
4491
|
const win = floatingUi_utils_dom.getWindow(scrubAreaEl);
|
|
3922
|
-
|
|
3923
|
-
|
|
4492
|
+
if (teleportDistance != null) {
|
|
4493
|
+
const rect = scrubAreaEl.getBoundingClientRect();
|
|
3924
4494
|
return {
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
4495
|
+
left: rect.left - teleportDistance / 2,
|
|
4496
|
+
top: rect.top - teleportDistance / 2,
|
|
4497
|
+
right: rect.right + teleportDistance / 2,
|
|
4498
|
+
bottom: rect.bottom + teleportDistance / 2
|
|
3929
4499
|
};
|
|
3930
4500
|
}
|
|
3931
4501
|
const vV = win.visualViewport;
|
|
3932
4502
|
if (vV) {
|
|
3933
4503
|
return {
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
4504
|
+
left: vV.offsetLeft,
|
|
4505
|
+
top: vV.offsetTop,
|
|
4506
|
+
right: vV.offsetLeft + vV.width,
|
|
4507
|
+
bottom: vV.offsetTop + vV.height
|
|
3938
4508
|
};
|
|
3939
4509
|
}
|
|
3940
4510
|
return {
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
4511
|
+
left: 0,
|
|
4512
|
+
top: 0,
|
|
4513
|
+
right: win.document.documentElement.clientWidth,
|
|
4514
|
+
bottom: win.document.documentElement.clientHeight
|
|
3945
4515
|
};
|
|
3946
4516
|
}
|
|
3947
4517
|
|
|
@@ -3958,12 +4528,21 @@ function subscribeToVisualViewportResize(element, visualScaleRef) {
|
|
|
3958
4528
|
}
|
|
3959
4529
|
}
|
|
3960
4530
|
handleVisualResize();
|
|
3961
|
-
|
|
3962
|
-
return () => {
|
|
3963
|
-
vV.removeEventListener('resize', handleVisualResize);
|
|
3964
|
-
};
|
|
4531
|
+
return useButton.addEventListener(vV, 'resize', handleVisualResize);
|
|
3965
4532
|
}
|
|
3966
4533
|
|
|
4534
|
+
const SCRUB_AREA_STYLE = {
|
|
4535
|
+
touchAction: 'none',
|
|
4536
|
+
WebkitUserSelect: 'none',
|
|
4537
|
+
userSelect: 'none'
|
|
4538
|
+
};
|
|
4539
|
+
|
|
4540
|
+
/**
|
|
4541
|
+
* An interactive area where the user can click and drag to change the field value.
|
|
4542
|
+
* Renders a `<span>` element.
|
|
4543
|
+
*
|
|
4544
|
+
* Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)
|
|
4545
|
+
*/
|
|
3967
4546
|
const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function NumberFieldScrubArea(componentProps, forwardedRef) {
|
|
3968
4547
|
const {
|
|
3969
4548
|
render,
|
|
@@ -3971,6 +4550,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
|
|
|
3971
4550
|
direction = 'horizontal',
|
|
3972
4551
|
pixelSensitivity = 2,
|
|
3973
4552
|
teleportDistance,
|
|
4553
|
+
style,
|
|
3974
4554
|
...elementProps
|
|
3975
4555
|
} = componentProps;
|
|
3976
4556
|
const {
|
|
@@ -3980,6 +4560,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
|
|
|
3980
4560
|
readOnly,
|
|
3981
4561
|
inputRef,
|
|
3982
4562
|
incrementValue,
|
|
4563
|
+
allowInputSyncRef,
|
|
3983
4564
|
getStepAmount,
|
|
3984
4565
|
onValueCommitted,
|
|
3985
4566
|
lastChangedValueRef,
|
|
@@ -4027,15 +4608,15 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
|
|
|
4027
4608
|
};
|
|
4028
4609
|
const cursorWidth = virtualCursor.offsetWidth;
|
|
4029
4610
|
const cursorHeight = virtualCursor.offsetHeight;
|
|
4030
|
-
if (newCoords.x + cursorWidth / 2 < rect.
|
|
4031
|
-
newCoords.x = rect.
|
|
4032
|
-
} else if (newCoords.x + cursorWidth / 2 > rect.
|
|
4033
|
-
newCoords.x = rect.
|
|
4611
|
+
if (newCoords.x + cursorWidth / 2 < rect.left) {
|
|
4612
|
+
newCoords.x = rect.right - cursorWidth / 2;
|
|
4613
|
+
} else if (newCoords.x + cursorWidth / 2 > rect.right) {
|
|
4614
|
+
newCoords.x = rect.left - cursorWidth / 2;
|
|
4034
4615
|
}
|
|
4035
|
-
if (newCoords.y + cursorHeight / 2 < rect.
|
|
4036
|
-
newCoords.y = rect.
|
|
4037
|
-
} else if (newCoords.y + cursorHeight / 2 > rect.
|
|
4038
|
-
newCoords.y = rect.
|
|
4616
|
+
if (newCoords.y + cursorHeight / 2 < rect.top) {
|
|
4617
|
+
newCoords.y = rect.bottom - cursorHeight / 2;
|
|
4618
|
+
} else if (newCoords.y + cursorHeight / 2 > rect.bottom) {
|
|
4619
|
+
newCoords.y = rect.top - cursorHeight / 2;
|
|
4039
4620
|
}
|
|
4040
4621
|
virtualCursorCoords.current = newCoords;
|
|
4041
4622
|
updateCursorTransform(newCoords.x, newCoords.y);
|
|
@@ -4078,8 +4659,10 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
|
|
|
4078
4659
|
|
|
4079
4660
|
// Manually dispatch a click event if no movement happened, since
|
|
4080
4661
|
// preventDefault on pointerdown prevents the browser click event.
|
|
4081
|
-
|
|
4082
|
-
|
|
4662
|
+
const pointerDownTarget = pointerDownTargetRef.current;
|
|
4663
|
+
const input = inputRef.current;
|
|
4664
|
+
if (!didMoveRef.current && pointerDownTarget != null && input) {
|
|
4665
|
+
pointerDownTarget.dispatchEvent(new (floatingUi_utils_dom.getWindow(input).MouseEvent)('click', {
|
|
4083
4666
|
bubbles: true,
|
|
4084
4667
|
cancelable: true
|
|
4085
4668
|
}));
|
|
@@ -4088,7 +4671,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
|
|
|
4088
4671
|
pointerDownTargetRef.current = null;
|
|
4089
4672
|
}
|
|
4090
4673
|
}
|
|
4091
|
-
if (
|
|
4674
|
+
if (useScrollLock.gecko) {
|
|
4092
4675
|
// Firefox needs a small delay here when soft-clicking as the pointer
|
|
4093
4676
|
// lock will not release otherwise.
|
|
4094
4677
|
exitPointerLockTimeout.start(20, handler);
|
|
@@ -4113,9 +4696,10 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
|
|
|
4113
4696
|
cumulativeDelta = 0;
|
|
4114
4697
|
didMoveRef.current = true;
|
|
4115
4698
|
const dValue = direction === 'vertical' ? -movementY : movementX;
|
|
4116
|
-
const stepAmount = getStepAmount(event)
|
|
4699
|
+
const stepAmount = getStepAmount(event);
|
|
4117
4700
|
const rawAmount = dValue * stepAmount;
|
|
4118
4701
|
if (rawAmount !== 0) {
|
|
4702
|
+
allowInputSyncRef.current = true;
|
|
4119
4703
|
incrementValue(Math.abs(rawAmount), {
|
|
4120
4704
|
direction: rawAmount >= 0 ? 1 : -1,
|
|
4121
4705
|
event,
|
|
@@ -4125,14 +4709,26 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
|
|
|
4125
4709
|
}
|
|
4126
4710
|
}
|
|
4127
4711
|
const win = floatingUi_utils_dom.getWindow(inputRef.current);
|
|
4128
|
-
|
|
4129
|
-
win.addEventListener('pointermove', handleScrubPointerMove, true);
|
|
4712
|
+
const unsubscribe = useScrollLock.mergeCleanups(useButton.addEventListener(win, 'pointerup', handleScrubPointerUp, true), useButton.addEventListener(win, 'pointermove', handleScrubPointerMove, true));
|
|
4130
4713
|
return () => {
|
|
4131
4714
|
exitPointerLockTimeout.clear();
|
|
4132
|
-
|
|
4133
|
-
win.removeEventListener('pointermove', handleScrubPointerMove, true);
|
|
4715
|
+
unsubscribe();
|
|
4134
4716
|
};
|
|
4135
|
-
}, [disabled, readOnly, incrementValue, isScrubbing, getStepAmount, inputRef, onScrubbingChange, onScrub, direction, pixelSensitivity, lastChangedValueRef, onValueCommitted, valueRef, exitPointerLockTimeout]);
|
|
4717
|
+
}, [disabled, readOnly, allowInputSyncRef, incrementValue, isScrubbing, getStepAmount, inputRef, onScrubbingChange, onScrub, direction, pixelSensitivity, lastChangedValueRef, onValueCommitted, valueRef, exitPointerLockTimeout]);
|
|
4718
|
+
|
|
4719
|
+
// If the scrub area unmounts mid-scrub, release pointer lock and clear the root's scrubbing
|
|
4720
|
+
// state so it doesn't stay locked or stuck. (No commit: there's no pointer release here.)
|
|
4721
|
+
React__namespace.useEffect(() => () => {
|
|
4722
|
+
if (isScrubbingRef.current) {
|
|
4723
|
+
isScrubbingRef.current = false;
|
|
4724
|
+
setRootScrubbing(false);
|
|
4725
|
+
try {
|
|
4726
|
+
useButton.ownerDocument(scrubAreaRef.current).exitPointerLock();
|
|
4727
|
+
} catch {
|
|
4728
|
+
// Ignore errors.
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4731
|
+
}, [setRootScrubbing]);
|
|
4136
4732
|
|
|
4137
4733
|
// Prevent scrolling using touch input when scrubbing.
|
|
4138
4734
|
React__namespace.useEffect(function registerScrubberTouchPreventListener() {
|
|
@@ -4145,18 +4741,11 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
|
|
|
4145
4741
|
event.preventDefault();
|
|
4146
4742
|
}
|
|
4147
4743
|
}
|
|
4148
|
-
|
|
4149
|
-
return () => {
|
|
4150
|
-
element.removeEventListener('touchstart', handleTouchStart);
|
|
4151
|
-
};
|
|
4744
|
+
return useButton.addEventListener(element, 'touchstart', handleTouchStart);
|
|
4152
4745
|
}, [disabled, readOnly]);
|
|
4153
4746
|
const defaultProps = {
|
|
4154
4747
|
role: 'presentation',
|
|
4155
|
-
style:
|
|
4156
|
-
touchAction: 'none',
|
|
4157
|
-
WebkitUserSelect: 'none',
|
|
4158
|
-
userSelect: 'none'
|
|
4159
|
-
},
|
|
4748
|
+
style: SCRUB_AREA_STYLE,
|
|
4160
4749
|
async onPointerDown(event) {
|
|
4161
4750
|
const isMainButton = !event.button || event.button === 0;
|
|
4162
4751
|
if (event.defaultPrevented || readOnly || !isMainButton || disabled) {
|
|
@@ -4170,11 +4759,11 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
|
|
|
4170
4759
|
}
|
|
4171
4760
|
isScrubbingRef.current = true;
|
|
4172
4761
|
didMoveRef.current = false;
|
|
4173
|
-
pointerDownTargetRef.current = event.
|
|
4762
|
+
pointerDownTargetRef.current = useButton.getTarget(event.nativeEvent);
|
|
4174
4763
|
onScrubbingChange(true, event.nativeEvent);
|
|
4175
4764
|
|
|
4176
4765
|
// WebKit causes significant layout shift with the native message, so we can't use it.
|
|
4177
|
-
if (!isTouch && !
|
|
4766
|
+
if (!isTouch && !useScrollLock.webkit) {
|
|
4178
4767
|
try {
|
|
4179
4768
|
// Avoid non-deterministic errors in testing environments. This error sometimes
|
|
4180
4769
|
// appears:
|
|
@@ -4184,10 +4773,11 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
|
|
|
4184
4773
|
} catch (error) {
|
|
4185
4774
|
setIsPointerLockDenied(true);
|
|
4186
4775
|
} finally {
|
|
4776
|
+
// `onScrubbingChange` already wraps its state updates in `flushSync`, so re-emit the
|
|
4777
|
+
// scrubbing state directly (no extra nested `flushSync`) to reflect the resolved
|
|
4778
|
+
// pointer-lock result on the cursor.
|
|
4187
4779
|
if (isScrubbingRef.current) {
|
|
4188
|
-
|
|
4189
|
-
onScrubbingChange(true, event.nativeEvent);
|
|
4190
|
-
});
|
|
4780
|
+
onScrubbingChange(true, event.nativeEvent);
|
|
4191
4781
|
}
|
|
4192
4782
|
}
|
|
4193
4783
|
}
|
|
@@ -4203,12 +4793,8 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
|
|
|
4203
4793
|
isScrubbing,
|
|
4204
4794
|
isTouchInput,
|
|
4205
4795
|
isPointerLockDenied,
|
|
4206
|
-
scrubAreaCursorRef
|
|
4207
|
-
|
|
4208
|
-
direction,
|
|
4209
|
-
pixelSensitivity,
|
|
4210
|
-
teleportDistance
|
|
4211
|
-
}), [isScrubbing, isTouchInput, isPointerLockDenied, direction, pixelSensitivity, teleportDistance]);
|
|
4796
|
+
scrubAreaCursorRef
|
|
4797
|
+
}), [isScrubbing, isTouchInput, isPointerLockDenied]);
|
|
4212
4798
|
return /*#__PURE__*/jsxRuntime.jsx(NumberFieldScrubAreaContext.Provider, {
|
|
4213
4799
|
value: contextValue,
|
|
4214
4800
|
children: element
|
|
@@ -4216,6 +4802,13 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
|
|
|
4216
4802
|
});
|
|
4217
4803
|
if (process.env.NODE_ENV !== "production") NumberFieldScrubArea.displayName = "NumberFieldScrubArea";
|
|
4218
4804
|
|
|
4805
|
+
const CURSOR_STYLE = {
|
|
4806
|
+
position: 'fixed',
|
|
4807
|
+
top: 0,
|
|
4808
|
+
left: 0,
|
|
4809
|
+
pointerEvents: 'none'
|
|
4810
|
+
};
|
|
4811
|
+
|
|
4219
4812
|
/**
|
|
4220
4813
|
* A custom element to display instead of the native cursor while using the scrub area.
|
|
4221
4814
|
* Renders a `<span>` element.
|
|
@@ -4229,6 +4822,7 @@ const NumberFieldScrubAreaCursor = /*#__PURE__*/React__namespace.forwardRef(func
|
|
|
4229
4822
|
const {
|
|
4230
4823
|
render,
|
|
4231
4824
|
className,
|
|
4825
|
+
style,
|
|
4232
4826
|
...elementProps
|
|
4233
4827
|
} = componentProps;
|
|
4234
4828
|
const {
|
|
@@ -4241,19 +4835,14 @@ const NumberFieldScrubAreaCursor = /*#__PURE__*/React__namespace.forwardRef(func
|
|
|
4241
4835
|
scrubAreaCursorRef
|
|
4242
4836
|
} = useNumberFieldScrubAreaContext();
|
|
4243
4837
|
const [domElement, setDomElement] = React__namespace.useState(null);
|
|
4244
|
-
const shouldRender = isScrubbing && !
|
|
4838
|
+
const shouldRender = isScrubbing && !useScrollLock.webkit && !isTouchInput && !isPointerLockDenied;
|
|
4245
4839
|
const element = useRenderElement.useRenderElement('span', componentProps, {
|
|
4246
4840
|
enabled: shouldRender,
|
|
4247
4841
|
ref: [forwardedRef, scrubAreaCursorRef, setDomElement],
|
|
4248
4842
|
state,
|
|
4249
4843
|
props: [{
|
|
4250
4844
|
role: 'presentation',
|
|
4251
|
-
style:
|
|
4252
|
-
position: 'fixed',
|
|
4253
|
-
top: 0,
|
|
4254
|
-
left: 0,
|
|
4255
|
-
pointerEvents: 'none'
|
|
4256
|
-
}
|
|
4845
|
+
style: CURSOR_STYLE
|
|
4257
4846
|
}, elementProps],
|
|
4258
4847
|
stateAttributesMapping: NumberFieldInput.stateAttributesMapping
|
|
4259
4848
|
});
|
|
@@ -4282,6 +4871,7 @@ const SelectLabel = /*#__PURE__*/React__namespace.forwardRef(function SelectLabe
|
|
|
4282
4871
|
const {
|
|
4283
4872
|
render,
|
|
4284
4873
|
className,
|
|
4874
|
+
style,
|
|
4285
4875
|
...elementProps
|
|
4286
4876
|
} = componentProps;
|
|
4287
4877
|
// Keep label id derived from the root and ignore runtime `id` overrides from untyped consumers.
|
|
@@ -4291,8 +4881,8 @@ const SelectLabel = /*#__PURE__*/React__namespace.forwardRef(function SelectLabe
|
|
|
4291
4881
|
const {
|
|
4292
4882
|
store
|
|
4293
4883
|
} = SelectGroupLabel.useSelectRootContext();
|
|
4294
|
-
const triggerElement =
|
|
4295
|
-
const rootId =
|
|
4884
|
+
const triggerElement = useScrollLock.useStore(store, SelectGroupLabel.selectors.triggerElement);
|
|
4885
|
+
const rootId = useScrollLock.useStore(store, SelectGroupLabel.selectors.id);
|
|
4296
4886
|
const defaultLabelId = resolveAriaLabelledBy.getDefaultLabelId(rootId);
|
|
4297
4887
|
const labelProps = useLabel.useLabel({
|
|
4298
4888
|
id: defaultLabelId,
|
|
@@ -4318,14 +4908,15 @@ if (process.env.NODE_ENV !== "production") SelectLabel.displayName = "SelectLabe
|
|
|
4318
4908
|
*/
|
|
4319
4909
|
const SelectIcon = /*#__PURE__*/React__namespace.forwardRef(function SelectIcon(componentProps, forwardedRef) {
|
|
4320
4910
|
const {
|
|
4321
|
-
className,
|
|
4322
4911
|
render,
|
|
4912
|
+
className,
|
|
4913
|
+
style,
|
|
4323
4914
|
...elementProps
|
|
4324
4915
|
} = componentProps;
|
|
4325
4916
|
const {
|
|
4326
4917
|
store
|
|
4327
4918
|
} = SelectGroupLabel.useSelectRootContext();
|
|
4328
|
-
const open =
|
|
4919
|
+
const open = useScrollLock.useStore(store, SelectGroupLabel.selectors.open);
|
|
4329
4920
|
const state = {
|
|
4330
4921
|
open
|
|
4331
4922
|
};
|
|
@@ -4336,35 +4927,36 @@ const SelectIcon = /*#__PURE__*/React__namespace.forwardRef(function SelectIcon(
|
|
|
4336
4927
|
'aria-hidden': true,
|
|
4337
4928
|
children: '▼'
|
|
4338
4929
|
}, elementProps],
|
|
4339
|
-
stateAttributesMapping:
|
|
4930
|
+
stateAttributesMapping: useScrollLock.triggerOpenStateMapping
|
|
4340
4931
|
});
|
|
4341
4932
|
return element;
|
|
4342
4933
|
});
|
|
4343
4934
|
if (process.env.NODE_ENV !== "production") SelectIcon.displayName = "SelectIcon";
|
|
4344
4935
|
|
|
4345
4936
|
const stateAttributesMapping$1 = {
|
|
4346
|
-
...
|
|
4937
|
+
...useScrollLock.popupStateMapping,
|
|
4347
4938
|
...useButton.transitionStatusMapping
|
|
4348
4939
|
};
|
|
4349
4940
|
|
|
4350
4941
|
/**
|
|
4351
|
-
* An overlay displayed beneath the
|
|
4942
|
+
* An overlay displayed beneath the select popup.
|
|
4352
4943
|
* Renders a `<div>` element.
|
|
4353
4944
|
*
|
|
4354
4945
|
* Documentation: [Base UI Select](https://base-ui.com/react/components/select)
|
|
4355
4946
|
*/
|
|
4356
4947
|
const SelectBackdrop = /*#__PURE__*/React__namespace.forwardRef(function SelectBackdrop(componentProps, forwardedRef) {
|
|
4357
4948
|
const {
|
|
4358
|
-
className,
|
|
4359
4949
|
render,
|
|
4950
|
+
className,
|
|
4951
|
+
style,
|
|
4360
4952
|
...elementProps
|
|
4361
4953
|
} = componentProps;
|
|
4362
4954
|
const {
|
|
4363
4955
|
store
|
|
4364
4956
|
} = SelectGroupLabel.useSelectRootContext();
|
|
4365
|
-
const open =
|
|
4366
|
-
const mounted =
|
|
4367
|
-
const transitionStatus =
|
|
4957
|
+
const open = useScrollLock.useStore(store, SelectGroupLabel.selectors.open);
|
|
4958
|
+
const mounted = useScrollLock.useStore(store, SelectGroupLabel.selectors.mounted);
|
|
4959
|
+
const transitionStatus = useScrollLock.useStore(store, SelectGroupLabel.selectors.transitionStatus);
|
|
4368
4960
|
const state = {
|
|
4369
4961
|
open,
|
|
4370
4962
|
transitionStatus
|
|
@@ -4387,7 +4979,7 @@ const SelectBackdrop = /*#__PURE__*/React__namespace.forwardRef(function SelectB
|
|
|
4387
4979
|
if (process.env.NODE_ENV !== "production") SelectBackdrop.displayName = "SelectBackdrop";
|
|
4388
4980
|
|
|
4389
4981
|
const stateAttributesMapping = {
|
|
4390
|
-
...
|
|
4982
|
+
...useScrollLock.popupStateMapping,
|
|
4391
4983
|
...useButton.transitionStatusMapping
|
|
4392
4984
|
};
|
|
4393
4985
|
|
|
@@ -4399,8 +4991,9 @@ const stateAttributesMapping = {
|
|
|
4399
4991
|
*/
|
|
4400
4992
|
const SelectArrow = /*#__PURE__*/React__namespace.forwardRef(function SelectArrow(componentProps, forwardedRef) {
|
|
4401
4993
|
const {
|
|
4402
|
-
className,
|
|
4403
4994
|
render,
|
|
4995
|
+
className,
|
|
4996
|
+
style,
|
|
4404
4997
|
...elementProps
|
|
4405
4998
|
} = componentProps;
|
|
4406
4999
|
const {
|
|
@@ -4414,7 +5007,7 @@ const SelectArrow = /*#__PURE__*/React__namespace.forwardRef(function SelectArro
|
|
|
4414
5007
|
arrowUncentered,
|
|
4415
5008
|
alignItemWithTriggerActive
|
|
4416
5009
|
} = SelectGroupLabel.useSelectPositionerContext();
|
|
4417
|
-
const open =
|
|
5010
|
+
const open = useScrollLock.useStore(store, SelectGroupLabel.selectors.open);
|
|
4418
5011
|
const state = {
|
|
4419
5012
|
open,
|
|
4420
5013
|
side,
|
|
@@ -4444,10 +5037,12 @@ const SelectScrollArrow = /*#__PURE__*/React__namespace.forwardRef(function Sele
|
|
|
4444
5037
|
const {
|
|
4445
5038
|
render,
|
|
4446
5039
|
className,
|
|
5040
|
+
style,
|
|
4447
5041
|
direction,
|
|
4448
5042
|
keepMounted = false,
|
|
4449
5043
|
...elementProps
|
|
4450
5044
|
} = componentProps;
|
|
5045
|
+
const isUp = direction === 'up';
|
|
4451
5046
|
const {
|
|
4452
5047
|
store,
|
|
4453
5048
|
popupRef,
|
|
@@ -4460,15 +5055,16 @@ const SelectScrollArrow = /*#__PURE__*/React__namespace.forwardRef(function Sele
|
|
|
4460
5055
|
scrollDownArrowRef,
|
|
4461
5056
|
scrollUpArrowRef
|
|
4462
5057
|
} = SelectGroupLabel.useSelectPositionerContext();
|
|
4463
|
-
const visibleSelector =
|
|
4464
|
-
const stateVisible =
|
|
4465
|
-
const openMethod =
|
|
5058
|
+
const visibleSelector = isUp ? SelectGroupLabel.selectors.scrollUpArrowVisible : SelectGroupLabel.selectors.scrollDownArrowVisible;
|
|
5059
|
+
const stateVisible = useScrollLock.useStore(store, visibleSelector);
|
|
5060
|
+
const openMethod = useScrollLock.useStore(store, SelectGroupLabel.selectors.openMethod);
|
|
4466
5061
|
|
|
4467
5062
|
// Scroll arrows are disabled for touch modality as they are a hover-only element.
|
|
4468
5063
|
const visible = stateVisible && openMethod !== 'touch';
|
|
4469
5064
|
const timeout = useButton.useTimeout();
|
|
4470
|
-
const scrollArrowRef =
|
|
5065
|
+
const scrollArrowRef = isUp ? scrollUpArrowRef : scrollDownArrowRef;
|
|
4471
5066
|
const {
|
|
5067
|
+
mounted,
|
|
4472
5068
|
transitionStatus,
|
|
4473
5069
|
setMounted
|
|
4474
5070
|
} = useButton.useTransitionStatus(visible);
|
|
@@ -4501,7 +5097,7 @@ const SelectScrollArrow = /*#__PURE__*/React__namespace.forwardRef(function Sele
|
|
|
4501
5097
|
};
|
|
4502
5098
|
const defaultProps = {
|
|
4503
5099
|
'aria-hidden': true,
|
|
4504
|
-
children:
|
|
5100
|
+
children: isUp ? '▲' : '▼',
|
|
4505
5101
|
style: {
|
|
4506
5102
|
position: 'absolute'
|
|
4507
5103
|
},
|
|
@@ -4517,72 +5113,25 @@ const SelectScrollArrow = /*#__PURE__*/React__namespace.forwardRef(function Sele
|
|
|
4517
5113
|
}
|
|
4518
5114
|
store.set('activeIndex', null);
|
|
4519
5115
|
handleScrollArrowVisibility();
|
|
4520
|
-
const
|
|
4521
|
-
const
|
|
4522
|
-
const
|
|
5116
|
+
const maxScrollTop = scrollEdges.getMaxScrollOffset(scroller.scrollHeight, scroller.clientHeight);
|
|
5117
|
+
const scrollTop = scrollEdges.normalizeScrollOffset(scroller.scrollTop, maxScrollTop);
|
|
5118
|
+
const isScrolledToEdge = scrollTop === (isUp ? 0 : maxScrollTop);
|
|
5119
|
+
const items = listRef.current;
|
|
5120
|
+
if (scrollTop !== scroller.scrollTop) {
|
|
5121
|
+
scroller.scrollTop = scrollTop;
|
|
5122
|
+
}
|
|
4523
5123
|
|
|
4524
5124
|
// Fallback when there are no items registered yet.
|
|
4525
|
-
if (
|
|
4526
|
-
|
|
4527
|
-
store.set('scrollUpArrowVisible', !isScrolledToTop);
|
|
4528
|
-
} else {
|
|
4529
|
-
store.set('scrollDownArrowVisible', !isScrolledToBottom);
|
|
4530
|
-
}
|
|
5125
|
+
if (items.length === 0) {
|
|
5126
|
+
store.set(isUp ? 'scrollUpArrowVisible' : 'scrollDownArrowVisible', !isScrolledToEdge);
|
|
4531
5127
|
}
|
|
4532
|
-
if (
|
|
5128
|
+
if (isScrolledToEdge) {
|
|
4533
5129
|
timeout.clear();
|
|
4534
5130
|
return;
|
|
4535
5131
|
}
|
|
4536
|
-
if (
|
|
4537
|
-
const items = listRef.current;
|
|
5132
|
+
if (items.length > 0) {
|
|
4538
5133
|
const scrollArrowHeight = scrollArrowRef.current?.offsetHeight || 0;
|
|
4539
|
-
|
|
4540
|
-
let firstVisibleIndex = 0;
|
|
4541
|
-
const scrollTop = scroller.scrollTop + scrollArrowHeight;
|
|
4542
|
-
for (let i = 0; i < items.length; i += 1) {
|
|
4543
|
-
const item = items[i];
|
|
4544
|
-
if (item) {
|
|
4545
|
-
const itemTop = item.offsetTop;
|
|
4546
|
-
if (itemTop >= scrollTop) {
|
|
4547
|
-
firstVisibleIndex = i;
|
|
4548
|
-
break;
|
|
4549
|
-
}
|
|
4550
|
-
}
|
|
4551
|
-
}
|
|
4552
|
-
const targetIndex = Math.max(0, firstVisibleIndex - 1);
|
|
4553
|
-
if (targetIndex < firstVisibleIndex) {
|
|
4554
|
-
const targetItem = items[targetIndex];
|
|
4555
|
-
if (targetItem) {
|
|
4556
|
-
scroller.scrollTop = Math.max(0, targetItem.offsetTop - scrollArrowHeight);
|
|
4557
|
-
}
|
|
4558
|
-
} else {
|
|
4559
|
-
// Already at the first item; ensure we reach the absolute top to account for group labels.
|
|
4560
|
-
scroller.scrollTop = 0;
|
|
4561
|
-
}
|
|
4562
|
-
} else {
|
|
4563
|
-
let lastVisibleIndex = items.length - 1;
|
|
4564
|
-
const scrollBottom = scroller.scrollTop + scroller.clientHeight - scrollArrowHeight;
|
|
4565
|
-
for (let i = 0; i < items.length; i += 1) {
|
|
4566
|
-
const item = items[i];
|
|
4567
|
-
if (item) {
|
|
4568
|
-
const itemBottom = item.offsetTop + item.offsetHeight;
|
|
4569
|
-
if (itemBottom > scrollBottom) {
|
|
4570
|
-
lastVisibleIndex = Math.max(0, i - 1);
|
|
4571
|
-
break;
|
|
4572
|
-
}
|
|
4573
|
-
}
|
|
4574
|
-
}
|
|
4575
|
-
const targetIndex = Math.min(items.length - 1, lastVisibleIndex + 1);
|
|
4576
|
-
if (targetIndex > lastVisibleIndex) {
|
|
4577
|
-
const targetItem = items[targetIndex];
|
|
4578
|
-
if (targetItem) {
|
|
4579
|
-
scroller.scrollTop = targetItem.offsetTop + targetItem.offsetHeight - scroller.clientHeight + scrollArrowHeight;
|
|
4580
|
-
}
|
|
4581
|
-
} else {
|
|
4582
|
-
// Already at the last item; ensure we reach the true bottom.
|
|
4583
|
-
scroller.scrollTop = scroller.scrollHeight - scroller.clientHeight;
|
|
4584
|
-
}
|
|
4585
|
-
}
|
|
5134
|
+
scroller.scrollTop = getTargetScrollTop(items, isUp, scrollTop, scroller.clientHeight, scrollArrowHeight, maxScrollTop);
|
|
4586
5135
|
}
|
|
4587
5136
|
timeout.start(40, scrollNextItem);
|
|
4588
5137
|
}
|
|
@@ -4595,15 +5144,44 @@ const SelectScrollArrow = /*#__PURE__*/React__namespace.forwardRef(function Sele
|
|
|
4595
5144
|
const element = useRenderElement.useRenderElement('div', componentProps, {
|
|
4596
5145
|
ref: [forwardedRef, scrollArrowRef],
|
|
4597
5146
|
state,
|
|
4598
|
-
props: [defaultProps, elementProps]
|
|
5147
|
+
props: [defaultProps, elementProps],
|
|
5148
|
+
stateAttributesMapping: useButton.transitionStatusMapping
|
|
4599
5149
|
});
|
|
4600
|
-
const shouldRender =
|
|
5150
|
+
const shouldRender = mounted || keepMounted;
|
|
4601
5151
|
if (!shouldRender) {
|
|
4602
5152
|
return null;
|
|
4603
5153
|
}
|
|
4604
5154
|
return element;
|
|
4605
5155
|
});
|
|
4606
5156
|
if (process.env.NODE_ENV !== "production") SelectScrollArrow.displayName = "SelectScrollArrow";
|
|
5157
|
+
function getTargetScrollTop(items, isUp, scrollTop, clientHeight, scrollArrowHeight, maxScrollTop) {
|
|
5158
|
+
if (isUp) {
|
|
5159
|
+
let firstVisibleIndex = 0;
|
|
5160
|
+
const visibleTop = scrollTop + scrollArrowHeight - scrollEdges.SCROLL_EDGE_TOLERANCE_PX;
|
|
5161
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
5162
|
+
const item = items[i];
|
|
5163
|
+
if (item && item.offsetTop >= visibleTop) {
|
|
5164
|
+
firstVisibleIndex = i;
|
|
5165
|
+
break;
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
const targetIndex = Math.max(0, firstVisibleIndex - 1);
|
|
5169
|
+
const targetItem = items[targetIndex];
|
|
5170
|
+
return targetIndex < firstVisibleIndex && targetItem ? scrollEdges.normalizeScrollOffset(targetItem.offsetTop - scrollArrowHeight, maxScrollTop) : 0;
|
|
5171
|
+
}
|
|
5172
|
+
let lastVisibleIndex = items.length - 1;
|
|
5173
|
+
const visibleBottom = scrollTop + clientHeight - scrollArrowHeight + scrollEdges.SCROLL_EDGE_TOLERANCE_PX;
|
|
5174
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
5175
|
+
const item = items[i];
|
|
5176
|
+
if (item && item.offsetTop + item.offsetHeight > visibleBottom) {
|
|
5177
|
+
lastVisibleIndex = Math.max(0, i - 1);
|
|
5178
|
+
break;
|
|
5179
|
+
}
|
|
5180
|
+
}
|
|
5181
|
+
const targetIndex = Math.min(items.length - 1, lastVisibleIndex + 1);
|
|
5182
|
+
const targetItem = items[targetIndex];
|
|
5183
|
+
return targetIndex > lastVisibleIndex && targetItem ? scrollEdges.normalizeScrollOffset(targetItem.offsetTop + targetItem.offsetHeight - clientHeight + scrollArrowHeight, maxScrollTop) : maxScrollTop;
|
|
5184
|
+
}
|
|
4607
5185
|
|
|
4608
5186
|
/**
|
|
4609
5187
|
* An element that scrolls the select popup down when hovered. Does not render when using touch input.
|