@mui/x-date-pickers-pro 9.2.0 → 9.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +118 -0
- package/DateRangeCalendar/DateRangeCalendar.js +2 -2
- package/DateRangeCalendar/DateRangeCalendar.mjs +2 -2
- package/DateRangeCalendar/useDragRange.d.mts +3 -11
- package/DateRangeCalendar/useDragRange.d.ts +3 -11
- package/DateRangeCalendar/useDragRange.js +264 -196
- package/DateRangeCalendar/useDragRange.mjs +265 -197
- package/DateRangePickerDay/DateRangePickerDay.js +10 -5
- package/DateRangePickerDay/DateRangePickerDay.mjs +10 -5
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.js +2 -2
- package/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.mjs +2 -2
- package/internals/hooks/useMobileRangePicker/useMobileRangePicker.js +2 -2
- package/internals/hooks/useMobileRangePicker/useMobileRangePicker.mjs +2 -2
- package/package.json +2 -2
|
@@ -3,18 +3,12 @@
|
|
|
3
3
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import useEventCallback from '@mui/utils/useEventCallback';
|
|
6
|
-
import {
|
|
6
|
+
import { isHTMLElement } from '@mui/x-internals/domUtils';
|
|
7
7
|
import { isEndOfRange, isStartOfRange } from "../internals/utils/date-utils.mjs";
|
|
8
|
-
const isEnabledButtonElement = element => isHTMLElement(element) && element.tagName === 'BUTTON' && !element.disabled;
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* @param element The element to start searching from.
|
|
15
|
-
* @param dataAttribute The data attribute name — must be a single lowercase word
|
|
16
|
-
* (e.g., 'timestamp', 'position') because `dataset[attr]` uses camelCase
|
|
17
|
-
* while `.closest()` uses kebab-case, and these only align for single-word names.
|
|
9
|
+
* Returns the element (or its closest ancestor) carrying `data-{attr}`.
|
|
10
|
+
* Single-word `attr` only — `dataset[attr]` (camelCase) and `.closest()`
|
|
11
|
+
* (kebab-case) only agree for single-word names.
|
|
18
12
|
*/
|
|
19
13
|
const getClosestElementWithDataAttribute = (element, dataAttribute) => {
|
|
20
14
|
if (!element) {
|
|
@@ -31,95 +25,40 @@ const resolveDateFromTarget = (target, adapter, timezone) => {
|
|
|
31
25
|
if (!timestampString) {
|
|
32
26
|
return null;
|
|
33
27
|
}
|
|
34
|
-
const timestamp = Number(timestampString);
|
|
35
|
-
return adapter.date(new Date(timestamp).toISOString(), timezone);
|
|
36
|
-
};
|
|
37
|
-
const isSameAsDraggingDate = event => {
|
|
38
|
-
const target = getTarget(event.nativeEvent);
|
|
39
|
-
if (!isHTMLElement(target)) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
const element = getClosestElementWithDataAttribute(target, 'timestamp');
|
|
43
|
-
return element?.dataset.timestamp === event.dataTransfer.getData('draggingDate');
|
|
44
|
-
};
|
|
45
28
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*/
|
|
52
|
-
const resolveButtonElement = element => {
|
|
53
|
-
if (!element) {
|
|
29
|
+
// Guard against malformed `data-timestamp` — `Number('abc')` is `NaN` and
|
|
30
|
+
// `new Date(NaN).toISOString()` throws, which would otherwise wedge the
|
|
31
|
+
// gesture mid-`pointerover`.
|
|
32
|
+
const timestamp = Number(timestampString);
|
|
33
|
+
if (!Number.isFinite(timestamp)) {
|
|
54
34
|
return null;
|
|
55
35
|
}
|
|
56
|
-
|
|
57
|
-
// Check if element itself is a valid button
|
|
58
|
-
if (isEnabledButtonElement(element)) {
|
|
59
|
-
return element;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Search upward - element could be a child of the button (e.g., text span, TouchRipple)
|
|
63
|
-
const closestButton = element.closest('button');
|
|
64
|
-
if (isEnabledButtonElement(closestButton)) {
|
|
65
|
-
return closestButton;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Search downward (breadth-first, max 3 levels) - element could be a wrapper containing the button.
|
|
69
|
-
// Day cells have shallow DOM, so a small depth limit keeps this efficient.
|
|
70
|
-
const queue = Array.from(element.children).map(el => ({
|
|
71
|
-
el,
|
|
72
|
-
depth: 1
|
|
73
|
-
}));
|
|
74
|
-
const maxDepth = 3;
|
|
75
|
-
while (queue.length > 0) {
|
|
76
|
-
const {
|
|
77
|
-
el: current,
|
|
78
|
-
depth
|
|
79
|
-
} = queue.shift();
|
|
80
|
-
if (isEnabledButtonElement(current)) {
|
|
81
|
-
return current;
|
|
82
|
-
}
|
|
83
|
-
if (depth < maxDepth) {
|
|
84
|
-
queue.push(...Array.from(current.children).map(el => ({
|
|
85
|
-
el,
|
|
86
|
-
depth: depth + 1
|
|
87
|
-
})));
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return null;
|
|
91
|
-
};
|
|
92
|
-
const resolveElementFromTouch = (event, ignoreTouchTarget) => {
|
|
93
|
-
// don't parse multi-touch result
|
|
94
|
-
if (event.changedTouches?.length === 1 && event.touches.length <= 1) {
|
|
95
|
-
const element = document.elementFromPoint(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
|
|
96
|
-
// `elementFromPoint` could have resolved preview div or wrapping div
|
|
97
|
-
// might need to recursively find the nested button
|
|
98
|
-
const buttonElement = resolveButtonElement(element);
|
|
99
|
-
if (ignoreTouchTarget && buttonElement === event.changedTouches[0].target) {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
return buttonElement;
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
36
|
+
return adapter.date(new Date(timestamp).toISOString(), timezone);
|
|
105
37
|
};
|
|
106
38
|
const useDragRangeEvents = ({
|
|
107
39
|
adapter,
|
|
108
40
|
setRangeDragDay,
|
|
109
41
|
setIsDragging,
|
|
110
|
-
isDragging,
|
|
111
42
|
onDatePositionChange,
|
|
112
43
|
onDrop,
|
|
113
44
|
disableDragEditing,
|
|
114
45
|
dateRange,
|
|
115
46
|
timezone
|
|
116
47
|
}) => {
|
|
117
|
-
const
|
|
118
|
-
React.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
48
|
+
const isDraggingRef = React.useRef(false);
|
|
49
|
+
const pointerIdRef = React.useRef(null);
|
|
50
|
+
const sourceDateRef = React.useRef(null);
|
|
51
|
+
const sourcePositionRef = React.useRef(null);
|
|
52
|
+
const didMoveRef = React.useRef(false);
|
|
53
|
+
// Last cell the pointer hovered. Used to dedupe `pointerover` (which fires
|
|
54
|
+
// repeatedly within the same cell), and as the drop fallback for
|
|
55
|
+
// `pointercancel` (whose `event.target` is unreliable across browsers).
|
|
56
|
+
const lastHoveredCellRef = React.useRef(null);
|
|
57
|
+
// Each entry removes one document listener the gesture installed.
|
|
58
|
+
const listenerCleanupsRef = React.useRef([]);
|
|
59
|
+
// Outstanding capture-phase click suppressor, if any. Tracked so back-to-back
|
|
60
|
+
// drags can tear the prior one down before installing a new one.
|
|
61
|
+
const clickSuppressorRef = React.useRef(null);
|
|
123
62
|
const isElementDraggable = day => {
|
|
124
63
|
if (day == null) {
|
|
125
64
|
return false;
|
|
@@ -129,146 +68,276 @@ const useDragRangeEvents = ({
|
|
|
129
68
|
const isSelectedEndDate = isEndOfRange(adapter, day, dateRange);
|
|
130
69
|
return shouldInitDragging && (isSelectedStartDate || isSelectedEndDate);
|
|
131
70
|
};
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
71
|
+
|
|
72
|
+
// Resets every ref the gesture mutated and removes any listeners installed
|
|
73
|
+
// during the gesture. Safe to call from event handlers and from unmount.
|
|
74
|
+
// Only reads refs, so its closure never changes — memoized for stable
|
|
75
|
+
// identity (referenced by `cleanup` and the unmount effect).
|
|
76
|
+
const clearGestureState = useEventCallback(() => {
|
|
77
|
+
isDraggingRef.current = false;
|
|
78
|
+
pointerIdRef.current = null;
|
|
79
|
+
sourceDateRef.current = null;
|
|
80
|
+
sourcePositionRef.current = null;
|
|
81
|
+
didMoveRef.current = false;
|
|
82
|
+
lastHoveredCellRef.current = null;
|
|
83
|
+
listenerCleanupsRef.current.forEach(teardown => teardown());
|
|
84
|
+
listenerCleanupsRef.current = [];
|
|
85
|
+
// Also tear down any in-flight click suppressor — without this, an
|
|
86
|
+
// unmount in the brief window between `pointerup` and the
|
|
87
|
+
// `setTimeout(0)` teardown leaves a capture-phase listener attached
|
|
88
|
+
// to `document` that swallows the next click on unrelated UI.
|
|
89
|
+
clickSuppressorRef.current?.();
|
|
90
|
+
});
|
|
91
|
+
const cleanup = useEventCallback(() => {
|
|
92
|
+
const wasActive = didMoveRef.current;
|
|
93
|
+
clearGestureState();
|
|
94
|
+
// A press without movement never activated drag UI, so skip the re-render.
|
|
95
|
+
if (wasActive) {
|
|
96
|
+
setIsDragging(false);
|
|
97
|
+
setRangeDragDay(null);
|
|
136
98
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
99
|
+
});
|
|
100
|
+
const installClickSuppressor = doc => {
|
|
101
|
+
// Tear down a prior outstanding suppressor first; back-to-back drags
|
|
102
|
+
// would otherwise race two listeners on the document.
|
|
103
|
+
clickSuppressorRef.current?.();
|
|
104
|
+
|
|
105
|
+
// suppress and teardown reference each other, so forward-declare suppress.
|
|
106
|
+
let suppress;
|
|
107
|
+
const teardown = () => {
|
|
108
|
+
doc.removeEventListener('click', suppress, {
|
|
109
|
+
capture: true
|
|
110
|
+
});
|
|
111
|
+
if (clickSuppressorRef.current === teardown) {
|
|
112
|
+
clickSuppressorRef.current = null;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
suppress = clickEvent => {
|
|
116
|
+
clickEvent.preventDefault();
|
|
117
|
+
// `stopImmediatePropagation` (rather than just `stopPropagation`) so
|
|
118
|
+
// other capture-phase click listeners on `document` — analytics, focus
|
|
119
|
+
// traps, third-party overlays — don't observe the synthesized
|
|
120
|
+
// post-drag click as if the user intentionally clicked the cell.
|
|
121
|
+
clickEvent.stopImmediatePropagation();
|
|
122
|
+
teardown();
|
|
123
|
+
};
|
|
124
|
+
doc.addEventListener('click', suppress, {
|
|
125
|
+
capture: true
|
|
126
|
+
});
|
|
127
|
+
clickSuppressorRef.current = teardown;
|
|
128
|
+
// If no click ever fires (drop on a different cell, browser doesn't
|
|
129
|
+
// synthesize), tear the listener down so it doesn't leak.
|
|
130
|
+
setTimeout(teardown, 0);
|
|
131
|
+
};
|
|
132
|
+
const finalizeGesture = (event, ownerDoc, eventType) => {
|
|
133
|
+
const wasMoved = didMoveRef.current;
|
|
134
|
+
const sourceDate = sourceDateRef.current;
|
|
135
|
+
|
|
136
|
+
// For `pointerup`, the drop target is whatever element the pointer was
|
|
137
|
+
// actually over at release time — releasing into a gap or off the calendar
|
|
138
|
+
// resolves to `null` and cancels, matching native HTML5 drag.
|
|
139
|
+
// For `pointercancel`, `event.target` can be unreliable (browsers vary on
|
|
140
|
+
// whether it's the current under-pointer element or the gesture's start
|
|
141
|
+
// element). Fall back to the last cell the user hovered, which is the
|
|
142
|
+
// closest expression of their intent.
|
|
143
|
+
let dropOrigin;
|
|
144
|
+
if (eventType === 'pointercancel') {
|
|
145
|
+
dropOrigin = lastHoveredCellRef.current;
|
|
146
|
+
} else {
|
|
147
|
+
dropOrigin = event.target instanceof HTMLElement ? event.target : null;
|
|
140
148
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
149
|
+
const dropCell = getClosestElementWithDataAttribute(dropOrigin, 'timestamp');
|
|
150
|
+
const newDate = dropCell ? resolveDateFromTarget(dropCell, adapter, timezone) : null;
|
|
151
|
+
// Resolve the focusable `<button>` separately from `dropCell`. Today
|
|
152
|
+
// `data-timestamp` lives on the button itself, but a future custom slot
|
|
153
|
+
// could put it on a wrapper; in that case the cell isn't focusable and
|
|
154
|
+
// the disabled state lives on the inner button.
|
|
155
|
+
const dropButton = dropOrigin?.closest('button') ?? dropCell?.querySelector('button') ?? null;
|
|
156
|
+
|
|
157
|
+
// `shouldDisableDate` / min-max / readOnly mark the day's button as
|
|
158
|
+
// `disabled`. `pointerup` still lands on a disabled `<button>` in
|
|
159
|
+
// Chromium/WebKit, so guard explicitly — `DateRangeCalendar.handleDrop`
|
|
160
|
+
// doesn't re-validate the date.
|
|
161
|
+
const isDropDisabled = dropButton?.disabled === true;
|
|
162
|
+
cleanup();
|
|
163
|
+
if (eventType === 'pointerup' && wasMoved && dropCell) {
|
|
164
|
+
// The click that follows pointerup on a day cell would re-enter the
|
|
165
|
+
// day's selection logic and undo the drop (or, when the drag returned
|
|
166
|
+
// to the source, replace the range with a single-day selection).
|
|
167
|
+
// Swallow it. Gated on `dropCell` so a release outside the calendar
|
|
168
|
+
// doesn't swallow an unrelated click on the host UI.
|
|
169
|
+
installClickSuppressor(ownerDoc);
|
|
150
170
|
}
|
|
151
|
-
if (
|
|
152
|
-
|
|
171
|
+
if (wasMoved && newDate && sourceDate && !isDropDisabled && !adapter.isEqual(newDate, sourceDate)) {
|
|
172
|
+
dropButton?.focus();
|
|
173
|
+
onDrop(newDate);
|
|
153
174
|
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// `touchmove`-blocks-scroll listener. Attached eagerly in
|
|
178
|
+
// `handlePointerDown` for touch pointers only. Mouse/pen don't fire touch
|
|
179
|
+
// events. Stable at hook level so the listener identity is consistent
|
|
180
|
+
// across renders.
|
|
181
|
+
const onTouchMove = useEventCallback(touchEvent => {
|
|
182
|
+
if (isDraggingRef.current) {
|
|
183
|
+
// `touch-action: none` on the source cell isn't enough once the finger
|
|
184
|
+
// crosses cell boundaries.
|
|
185
|
+
touchEvent.preventDefault();
|
|
159
186
|
}
|
|
160
|
-
|
|
161
|
-
|
|
187
|
+
});
|
|
188
|
+
const handlePointerDown = useEventCallback(event => {
|
|
189
|
+
// Ignore secondary mouse buttons (middle = 1, right = 2). `> 0` rather
|
|
190
|
+
// than `!== 0` keeps the gesture permissive when `event.button` is left
|
|
191
|
+
// unset by a synthetic event (some test environments).
|
|
192
|
+
if (event.button > 0) {
|
|
162
193
|
return;
|
|
163
194
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (
|
|
195
|
+
|
|
196
|
+
// Secondary multi-touch pointers (second finger, etc.) are explicitly
|
|
197
|
+
// not-primary; let them pass through without disturbing the active gesture.
|
|
198
|
+
if (event.isPrimary === false) {
|
|
168
199
|
return;
|
|
169
200
|
}
|
|
170
|
-
event.
|
|
171
|
-
|
|
172
|
-
event.dataTransfer.dropEffect = 'move';
|
|
173
|
-
setRangeDragDay(resolveDateFromTarget(getTarget(event.nativeEvent), adapter, timezone));
|
|
174
|
-
});
|
|
175
|
-
const handleTouchMove = useEventCallback(event => {
|
|
176
|
-
const target = resolveElementFromTouch(event);
|
|
177
|
-
if (!target) {
|
|
201
|
+
const newDate = resolveDateFromTarget(event.currentTarget, adapter, timezone);
|
|
202
|
+
if (!isElementDraggable(newDate)) {
|
|
178
203
|
return;
|
|
179
204
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
205
|
+
|
|
206
|
+
// A fresh primary pointerdown definitionally ends any previous gesture
|
|
207
|
+
// (covers pen+touch, where each pointer type has its own primary, and
|
|
208
|
+
// the recovery case where the original gesture's `pointerup` was lost).
|
|
209
|
+
if (pointerIdRef.current != null) {
|
|
210
|
+
cleanup();
|
|
183
211
|
}
|
|
184
212
|
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
213
|
+
// Touch implicitly captures the pointer on `pointerdown`, pinning all
|
|
214
|
+
// subsequent events to the source. Release so sibling cells receive their
|
|
215
|
+
// own `pointerover`. jsdom lacks the API; Safari 15 / some Android WebViews
|
|
216
|
+
// race between the `hasPointerCapture` check and the release call and throw
|
|
217
|
+
// `InvalidPointerId` — benign, swallow it.
|
|
218
|
+
try {
|
|
219
|
+
if (typeof event.currentTarget.hasPointerCapture === 'function' && event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
220
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
221
|
+
}
|
|
222
|
+
} catch {
|
|
223
|
+
// already released, nothing to do
|
|
189
224
|
}
|
|
190
225
|
|
|
191
|
-
//
|
|
192
|
-
|
|
226
|
+
// Note: deliberately not calling `event.preventDefault()` here. Doing so
|
|
227
|
+
// would suppress the synthesized click that follows pointerup, which is
|
|
228
|
+
// load-bearing for tap-to-advance on an endpoint cell. The iOS magnifier
|
|
229
|
+
// is held off by `touch-action: none` + `user-select: none` on the cell.
|
|
193
230
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
231
|
+
pointerIdRef.current = event.pointerId;
|
|
232
|
+
isDraggingRef.current = true;
|
|
233
|
+
sourceDateRef.current = newDate;
|
|
234
|
+
didMoveRef.current = false;
|
|
235
|
+
lastHoveredCellRef.current = event.currentTarget;
|
|
236
|
+
|
|
237
|
+
// Walk up rather than reading `currentTarget.dataset` directly so the
|
|
238
|
+
// hook keeps working if a future slot puts `data-position` on a wrapper
|
|
239
|
+
// around the cell (mirrors how we resolve `data-timestamp`).
|
|
240
|
+
const positionHost = getClosestElementWithDataAttribute(event.currentTarget, 'position');
|
|
241
|
+
sourcePositionRef.current = positionHost?.dataset.position ?? null;
|
|
242
|
+
|
|
243
|
+
// Use the owner document (matters for iframe-hosted pickers) for all
|
|
244
|
+
// document-level listeners.
|
|
245
|
+
const ownerDoc = event.currentTarget.ownerDocument ?? document;
|
|
246
|
+
|
|
247
|
+
// Drag UI activation is deferred until the first real move — a pure
|
|
248
|
+
// press on an endpoint must leave selection state alone so the click
|
|
249
|
+
// handler can advance it normally.
|
|
250
|
+
|
|
251
|
+
const onPointerUp = pointerEvent => {
|
|
252
|
+
if (pointerEvent.pointerId !== pointerIdRef.current) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
finalizeGesture(pointerEvent, ownerDoc, 'pointerup');
|
|
256
|
+
};
|
|
257
|
+
const onPointerCancel = pointerEvent => {
|
|
258
|
+
if (pointerEvent.pointerId !== pointerIdRef.current) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
// Spec intent of `pointercancel` is "UA interrupted, not the user".
|
|
262
|
+
// After real movement, commit the drop the user worked for; the snap-back
|
|
263
|
+
// would otherwise be silent and inexplicable.
|
|
264
|
+
finalizeGesture(pointerEvent, ownerDoc, 'pointercancel');
|
|
265
|
+
};
|
|
266
|
+
const onKeyDown = keyEvent => {
|
|
267
|
+
if (keyEvent.key !== 'Escape' || !didMoveRef.current) {
|
|
268
|
+
// No visible drag to cancel. Leave the gesture intact and let
|
|
269
|
+
// Escape propagate (host modal/popover can still close on it).
|
|
270
|
+
// A press without movement behaves identically to a tap on
|
|
271
|
+
// release — letting cleanup run here would only half-collapse
|
|
272
|
+
// the gesture without suppressing the eventual tap-to-advance.
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
keyEvent.preventDefault();
|
|
276
|
+
cleanup();
|
|
277
|
+
};
|
|
278
|
+
ownerDoc.addEventListener('pointerup', onPointerUp);
|
|
279
|
+
ownerDoc.addEventListener('pointercancel', onPointerCancel);
|
|
280
|
+
ownerDoc.addEventListener('keydown', onKeyDown);
|
|
281
|
+
listenerCleanupsRef.current.push(() => ownerDoc.removeEventListener('pointerup', onPointerUp), () => ownerDoc.removeEventListener('pointercancel', onPointerCancel), () => ownerDoc.removeEventListener('keydown', onKeyDown));
|
|
282
|
+
|
|
283
|
+
// For touch input, attach the scroll-suppression listener up front rather
|
|
284
|
+
// than lazily on first movement. The Pointer Events spec latches
|
|
285
|
+
// `touch-action: none` from the source cell over the rest of the gesture,
|
|
286
|
+
// but real-world WebKit/Chromium versions don't always honor that —
|
|
287
|
+
// attaching eagerly closes that window. Mouse and pen don't fire touch
|
|
288
|
+
// events so they don't need it.
|
|
289
|
+
if (event.pointerType === 'touch') {
|
|
290
|
+
ownerDoc.addEventListener('touchmove', onTouchMove, {
|
|
291
|
+
passive: false
|
|
292
|
+
});
|
|
293
|
+
listenerCleanupsRef.current.push(() => ownerDoc.removeEventListener('touchmove', onTouchMove));
|
|
205
294
|
}
|
|
206
|
-
event.preventDefault();
|
|
207
|
-
event.stopPropagation();
|
|
208
295
|
});
|
|
209
|
-
|
|
210
|
-
|
|
296
|
+
|
|
297
|
+
// Use `pointerover` (bubbles) rather than `pointerenter`: React's
|
|
298
|
+
// `onPointerEnter` is implemented on top of over/out.
|
|
299
|
+
const handlePointerOver = useEventCallback(event => {
|
|
300
|
+
if (!isDraggingRef.current || event.pointerId !== pointerIdRef.current) {
|
|
211
301
|
return;
|
|
212
302
|
}
|
|
213
|
-
event.
|
|
214
|
-
event.stopPropagation();
|
|
215
|
-
event.dataTransfer.dropEffect = 'move';
|
|
216
|
-
});
|
|
217
|
-
const handleTouchEnd = useEventCallback(event => {
|
|
218
|
-
if (!isDragging) {
|
|
303
|
+
if (lastHoveredCellRef.current === event.currentTarget) {
|
|
219
304
|
return;
|
|
220
305
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const target = resolveElementFromTouch(event, true);
|
|
224
|
-
if (!target) {
|
|
306
|
+
const newDate = resolveDateFromTarget(event.currentTarget, adapter, timezone);
|
|
307
|
+
if (!newDate) {
|
|
225
308
|
return;
|
|
226
309
|
}
|
|
310
|
+
lastHoveredCellRef.current = event.currentTarget;
|
|
311
|
+
const isDifferentFromSource = sourceDateRef.current && !adapter.isEqual(newDate, sourceDateRef.current);
|
|
312
|
+
if (!didMoveRef.current && isDifferentFromSource) {
|
|
313
|
+
// A custom day slot could strip `data-position`; without it the preview
|
|
314
|
+
// would compute against the wrong endpoint, so abort the drag rather
|
|
315
|
+
// than rendering something misleading.
|
|
316
|
+
if (!sourcePositionRef.current) {
|
|
317
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
318
|
+
console.warn('MUI X: A drag was initiated on a day cell missing `data-position`. ' + 'Drag editing requires the cell to advertise which range endpoint it represents.');
|
|
319
|
+
}
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
227
322
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
const handleDragEnd = useEventCallback(event => {
|
|
236
|
-
if (!isDragging) {
|
|
237
|
-
return;
|
|
323
|
+
// First real move: activate drag UI and tell the parent which endpoint
|
|
324
|
+
// is being dragged so the preview computes against the correct side.
|
|
325
|
+
didMoveRef.current = true;
|
|
326
|
+
onDatePositionChange(sourcePositionRef.current);
|
|
327
|
+
setIsDragging(true);
|
|
238
328
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
setIsDragging(false);
|
|
242
|
-
setRangeDragDay(null);
|
|
243
|
-
});
|
|
244
|
-
const handleDrop = useEventCallback(event => {
|
|
245
|
-
if (!isDragging) {
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
event.preventDefault();
|
|
249
|
-
event.stopPropagation();
|
|
250
|
-
setIsDragging(false);
|
|
251
|
-
setRangeDragDay(null);
|
|
252
|
-
// make sure the focused element is the element where drop ended
|
|
253
|
-
event.currentTarget.focus();
|
|
254
|
-
if (isSameAsDraggingDate(event)) {
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
const newDate = resolveDateFromTarget(getTarget(event.nativeEvent), adapter, timezone);
|
|
258
|
-
if (newDate) {
|
|
259
|
-
onDrop(newDate);
|
|
329
|
+
if (didMoveRef.current) {
|
|
330
|
+
setRangeDragDay(newDate);
|
|
260
331
|
}
|
|
261
332
|
});
|
|
333
|
+
|
|
334
|
+
// On unmount, clear gesture state so a remount can start fresh and any
|
|
335
|
+
// detached DOM nodes still referenced by gesture refs can be GC'd.
|
|
336
|
+
// `clearGestureState` is `useEventCallback`-stable, so the effect runs once.
|
|
337
|
+
React.useEffect(() => () => clearGestureState(), [clearGestureState]);
|
|
262
338
|
return {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
onDragLeave: handleDragLeave,
|
|
266
|
-
onDragOver: handleDragOver,
|
|
267
|
-
onDragEnd: handleDragEnd,
|
|
268
|
-
onDrop: handleDrop,
|
|
269
|
-
onTouchStart: handleTouchStart,
|
|
270
|
-
onTouchMove: handleTouchMove,
|
|
271
|
-
onTouchEnd: handleTouchEnd
|
|
339
|
+
onPointerDown: handlePointerDown,
|
|
340
|
+
onPointerOver: handlePointerOver
|
|
272
341
|
};
|
|
273
342
|
};
|
|
274
343
|
export const useDragRange = ({
|
|
@@ -303,7 +372,6 @@ export const useDragRange = ({
|
|
|
303
372
|
onDatePositionChange,
|
|
304
373
|
onDrop,
|
|
305
374
|
setIsDragging,
|
|
306
|
-
isDragging,
|
|
307
375
|
setRangeDragDay: handleRangeDragDayChange,
|
|
308
376
|
disableDragEditing,
|
|
309
377
|
dateRange,
|
|
@@ -171,7 +171,13 @@ const DateRangePickerDayRoot = (0, _styles.styled)(_ButtonBase.default, {
|
|
|
171
171
|
},
|
|
172
172
|
style: {
|
|
173
173
|
cursor: 'grab',
|
|
174
|
-
|
|
174
|
+
// Stop the browser from scrolling the page when the user drags a finger
|
|
175
|
+
// across the cell — the drag is driven by our own Pointer Events handler.
|
|
176
|
+
touchAction: 'none',
|
|
177
|
+
// Prevent the iOS text-selection callout from racing the drag gesture.
|
|
178
|
+
WebkitTouchCallout: 'none',
|
|
179
|
+
WebkitUserSelect: 'none',
|
|
180
|
+
userSelect: 'none'
|
|
175
181
|
}
|
|
176
182
|
}, {
|
|
177
183
|
props: {
|
|
@@ -322,8 +328,8 @@ const DateRangePickerDayRaw = /*#__PURE__*/React.forwardRef(function DateRangePi
|
|
|
322
328
|
name: 'MuiDateRangePickerDay'
|
|
323
329
|
});
|
|
324
330
|
(0, _internals.useLicenseVerifier)({
|
|
325
|
-
releaseDate: "
|
|
326
|
-
version: "9.
|
|
331
|
+
releaseDate: "MTc3OTMyMTYwMDAwMA==",
|
|
332
|
+
version: "9.3.0",
|
|
327
333
|
name: 'x-date-pickers-pro'
|
|
328
334
|
});
|
|
329
335
|
const adapter = (0, _hooks.usePickerAdapter)();
|
|
@@ -441,8 +447,7 @@ const DateRangePickerDayRaw = /*#__PURE__*/React.forwardRef(function DateRangePi
|
|
|
441
447
|
onBlur: event => onBlur(event, day),
|
|
442
448
|
onMouseEnter: event => onMouseEnter(event, day),
|
|
443
449
|
onClick: handleClick,
|
|
444
|
-
onMouseDown: handleMouseDown
|
|
445
|
-
draggable: draggable
|
|
450
|
+
onMouseDown: handleMouseDown
|
|
446
451
|
}, other, {
|
|
447
452
|
ownerState: ownerState,
|
|
448
453
|
className: (0, _clsx.default)(classes.root, className),
|
|
@@ -164,7 +164,13 @@ const DateRangePickerDayRoot = styled(ButtonBase, {
|
|
|
164
164
|
},
|
|
165
165
|
style: {
|
|
166
166
|
cursor: 'grab',
|
|
167
|
-
|
|
167
|
+
// Stop the browser from scrolling the page when the user drags a finger
|
|
168
|
+
// across the cell — the drag is driven by our own Pointer Events handler.
|
|
169
|
+
touchAction: 'none',
|
|
170
|
+
// Prevent the iOS text-selection callout from racing the drag gesture.
|
|
171
|
+
WebkitTouchCallout: 'none',
|
|
172
|
+
WebkitUserSelect: 'none',
|
|
173
|
+
userSelect: 'none'
|
|
168
174
|
}
|
|
169
175
|
}, {
|
|
170
176
|
props: {
|
|
@@ -315,8 +321,8 @@ const DateRangePickerDayRaw = /*#__PURE__*/React.forwardRef(function DateRangePi
|
|
|
315
321
|
name: 'MuiDateRangePickerDay'
|
|
316
322
|
});
|
|
317
323
|
useLicenseVerifier({
|
|
318
|
-
releaseDate: "
|
|
319
|
-
version: "9.
|
|
324
|
+
releaseDate: "MTc3OTMyMTYwMDAwMA==",
|
|
325
|
+
version: "9.3.0",
|
|
320
326
|
name: 'x-date-pickers-pro'
|
|
321
327
|
});
|
|
322
328
|
const adapter = usePickerAdapter();
|
|
@@ -434,8 +440,7 @@ const DateRangePickerDayRaw = /*#__PURE__*/React.forwardRef(function DateRangePi
|
|
|
434
440
|
onBlur: event => onBlur(event, day),
|
|
435
441
|
onMouseEnter: event => onMouseEnter(event, day),
|
|
436
442
|
onClick: handleClick,
|
|
437
|
-
onMouseDown: handleMouseDown
|
|
438
|
-
draggable: draggable
|
|
443
|
+
onMouseDown: handleMouseDown
|
|
439
444
|
}, other, {
|
|
440
445
|
ownerState: ownerState,
|
|
441
446
|
className: clsx(classes.root, className),
|
package/index.js
CHANGED
package/index.mjs
CHANGED
|
@@ -26,8 +26,8 @@ const useDesktopRangePicker = _ref => {
|
|
|
26
26
|
} = _ref,
|
|
27
27
|
pickerParams = (0, _objectWithoutPropertiesLoose2.default)(_ref, _excluded);
|
|
28
28
|
(0, _internals.useLicenseVerifier)({
|
|
29
|
-
releaseDate: "
|
|
30
|
-
version: "9.
|
|
29
|
+
releaseDate: "MTc3OTMyMTYwMDAwMA==",
|
|
30
|
+
version: "9.3.0",
|
|
31
31
|
name: 'x-date-pickers-pro'
|
|
32
32
|
});
|
|
33
33
|
const {
|
|
@@ -19,8 +19,8 @@ export const useDesktopRangePicker = _ref => {
|
|
|
19
19
|
} = _ref,
|
|
20
20
|
pickerParams = _objectWithoutPropertiesLoose(_ref, _excluded);
|
|
21
21
|
useLicenseVerifier({
|
|
22
|
-
releaseDate: "
|
|
23
|
-
version: "9.
|
|
22
|
+
releaseDate: "MTc3OTMyMTYwMDAwMA==",
|
|
23
|
+
version: "9.3.0",
|
|
24
24
|
name: 'x-date-pickers-pro'
|
|
25
25
|
});
|
|
26
26
|
const {
|
|
@@ -28,8 +28,8 @@ const useMobileRangePicker = _ref => {
|
|
|
28
28
|
} = _ref,
|
|
29
29
|
pickerParams = (0, _objectWithoutPropertiesLoose2.default)(_ref, _excluded);
|
|
30
30
|
(0, _internals.useLicenseVerifier)({
|
|
31
|
-
releaseDate: "
|
|
32
|
-
version: "9.
|
|
31
|
+
releaseDate: "MTc3OTMyMTYwMDAwMA==",
|
|
32
|
+
version: "9.3.0",
|
|
33
33
|
name: 'x-date-pickers-pro'
|
|
34
34
|
});
|
|
35
35
|
const {
|