@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.
@@ -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 { getTarget, isHTMLElement } from '@mui/x-internals/domUtils';
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
- * Finds the closest ancestor element (or the element itself) that has the specified data attribute.
11
- * This is needed because drag/touch events can target child elements (e.g., text spans)
12
- * inside the button, which don't have the data attributes directly.
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
- * Resolves a button element from a given element.
48
- * Searches both upward (ancestors) and downward (children) since:
49
- * - Touch events may target child elements inside the button (e.g., TouchRipple)
50
- * - `elementFromPoint` may return wrapper divs containing the button
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 emptyDragImgRef = React.useRef(null);
118
- React.useEffect(() => {
119
- // Preload the image - required for Safari support: https://stackoverflow.com/a/40923520/3303436
120
- emptyDragImgRef.current = document.createElement('img');
121
- emptyDragImgRef.current.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
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
- const handleDragStart = useEventCallback(event => {
133
- const newDate = resolveDateFromTarget(getTarget(event.nativeEvent), adapter, timezone);
134
- if (!isElementDraggable(newDate)) {
135
- return;
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
- event.stopPropagation();
138
- if (emptyDragImgRef.current) {
139
- event.dataTransfer.setDragImage(emptyDragImgRef.current, 0, 0);
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
- setRangeDragDay(newDate);
142
- event.dataTransfer.effectAllowed = 'move';
143
- setIsDragging(true);
144
- // Use currentTarget (the element the handler is attached to) rather than target
145
- // because we need the button's dataset, not a potential child element's dataset.
146
- const element = getClosestElementWithDataAttribute(event.currentTarget, 'timestamp');
147
- const buttonDataset = element?.dataset;
148
- if (buttonDataset?.timestamp) {
149
- event.dataTransfer.setData('draggingDate', buttonDataset.timestamp);
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 (buttonDataset?.position) {
152
- onDatePositionChange(buttonDataset.position);
171
+ if (wasMoved && newDate && sourceDate && !isDropDisabled && !adapter.isEqual(newDate, sourceDate)) {
172
+ dropButton?.focus();
173
+ onDrop(newDate);
153
174
  }
154
- });
155
- const handleTouchStart = useEventCallback(event => {
156
- const target = resolveElementFromTouch(event);
157
- if (!target) {
158
- return;
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
- const newDate = resolveDateFromTarget(target, adapter, timezone);
161
- if (!isElementDraggable(newDate)) {
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
- setRangeDragDay(newDate);
165
- });
166
- const handleDragEnter = useEventCallback(event => {
167
- if (!isDragging) {
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.preventDefault();
171
- event.stopPropagation();
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
- const newDate = resolveDateFromTarget(target, adapter, timezone);
181
- if (newDate) {
182
- setRangeDragDay(newDate);
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
- // this prevents initiating drag when user starts touchmove outside and then moves over a draggable element
186
- const targetsAreIdentical = target === event.changedTouches[0].target;
187
- if (!targetsAreIdentical || !isElementDraggable(newDate)) {
188
- return;
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
- // on mobile we should only initialize dragging state after move is detected
192
- setIsDragging(true);
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
- // Use currentTarget (the element the handler is attached to) rather than target
195
- // because we need the button's dataset, not a potential child element's dataset.
196
- const element = getClosestElementWithDataAttribute(event.currentTarget, 'position');
197
- const buttonDataset = element?.dataset;
198
- if (buttonDataset?.position) {
199
- onDatePositionChange(buttonDataset.position);
200
- }
201
- });
202
- const handleDragLeave = useEventCallback(event => {
203
- if (!isDragging) {
204
- return;
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
- const handleDragOver = useEventCallback(event => {
210
- if (!isDragging) {
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.preventDefault();
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
- setRangeDragDay(null);
222
- setIsDragging(false);
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
- // make sure the focused element is the element where touch ended
229
- target.focus();
230
- const newDate = resolveDateFromTarget(target, adapter, timezone);
231
- if (newDate) {
232
- onDrop(newDate);
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
- event.preventDefault();
240
- event.stopPropagation();
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
- onDragStart: handleDragStart,
264
- onDragEnter: handleDragEnter,
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
- touchAction: 'none'
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: "MTc3ODYzMDQwMDAwMA==",
326
- version: "9.2.0",
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
- touchAction: 'none'
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: "MTc3ODYzMDQwMDAwMA==",
319
- version: "9.2.0",
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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-date-pickers-pro v9.2.0
2
+ * @mui/x-date-pickers-pro v9.3.0
3
3
  *
4
4
  * @license SEE LICENSE IN LICENSE
5
5
  * This source code is licensed under the SEE LICENSE IN LICENSE license found in the
package/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-date-pickers-pro v9.2.0
2
+ * @mui/x-date-pickers-pro v9.3.0
3
3
  *
4
4
  * @license SEE LICENSE IN LICENSE
5
5
  * This source code is licensed under the SEE LICENSE IN LICENSE license found in the
@@ -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: "MTc3ODYzMDQwMDAwMA==",
30
- version: "9.2.0",
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: "MTc3ODYzMDQwMDAwMA==",
23
- version: "9.2.0",
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: "MTc3ODYzMDQwMDAwMA==",
32
- version: "9.2.0",
31
+ releaseDate: "MTc3OTMyMTYwMDAwMA==",
32
+ version: "9.3.0",
33
33
  name: 'x-date-pickers-pro'
34
34
  });
35
35
  const {