@astryxdesign/core 0.4.4 → 0.4.5-canary.1dc3e5d

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.
Files changed (176) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts +11 -2
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +4 -0
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts +5 -2
  6. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.js +56 -13
  8. package/dist/BottomSheet/index.d.ts +1 -1
  9. package/dist/BottomSheet/index.d.ts.map +1 -1
  10. package/dist/BottomSheet/snapOffsets.d.ts +57 -20
  11. package/dist/BottomSheet/snapOffsets.d.ts.map +1 -1
  12. package/dist/BottomSheet/snapOffsets.js +106 -30
  13. package/dist/BottomSheet/useSheetGestures.d.ts +13 -8
  14. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  15. package/dist/BottomSheet/useSheetGestures.js +160 -64
  16. package/dist/Calendar/Calendar.d.ts +17 -0
  17. package/dist/Calendar/Calendar.d.ts.map +1 -1
  18. package/dist/Calendar/Calendar.js +34 -3
  19. package/dist/Calendar/getStandaloneShortWeekdayNames.d.ts +14 -0
  20. package/dist/Calendar/getStandaloneShortWeekdayNames.d.ts.map +1 -0
  21. package/dist/Calendar/getStandaloneShortWeekdayNames.js +26 -0
  22. package/dist/Calendar/hooks/useCalendarConstraints.d.ts +22 -1
  23. package/dist/Calendar/hooks/useCalendarConstraints.d.ts.map +1 -1
  24. package/dist/Calendar/hooks/useCalendarConstraints.js +26 -4
  25. package/dist/Calendar/hooks/useCalendarDays.d.ts.map +1 -1
  26. package/dist/Calendar/hooks/useCalendarDays.js +14 -10
  27. package/dist/Calendar/standaloneShortWeekdayNames.generated.d.ts +47 -0
  28. package/dist/Calendar/standaloneShortWeekdayNames.generated.d.ts.map +1 -0
  29. package/dist/Calendar/standaloneShortWeekdayNames.generated.js +40 -0
  30. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  31. package/dist/CheckboxInput/CheckboxInput.js +9 -0
  32. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  33. package/dist/ComplexSelector/ComplexSelector.js +6 -1
  34. package/dist/DateInput/DateInput.d.ts.map +1 -1
  35. package/dist/DateInput/DateInput.js +6 -1
  36. package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
  37. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  38. package/dist/DateRangeInput/DateRangeInput.js +38 -3
  39. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  40. package/dist/DateTimeInput/DateTimeInput.js +7 -2
  41. package/dist/Field/FieldLabel.d.ts +2 -1
  42. package/dist/Field/FieldLabel.d.ts.map +1 -1
  43. package/dist/Field/FieldLabel.js +21 -3
  44. package/dist/Field/InputClearButton.d.ts +2 -1
  45. package/dist/Field/InputClearButton.d.ts.map +1 -1
  46. package/dist/Field/InputClearButton.js +6 -1
  47. package/dist/FormLayout/FormLayout.d.ts +24 -2
  48. package/dist/FormLayout/FormLayout.d.ts.map +1 -1
  49. package/dist/FormLayout/FormLayout.js +7 -2
  50. package/dist/FormLayout/FormLayoutContext.d.ts +15 -2
  51. package/dist/FormLayout/FormLayoutContext.d.ts.map +1 -1
  52. package/dist/FormLayout/FormLayoutContext.js +19 -4
  53. package/dist/FormLayout/index.d.ts +1 -1
  54. package/dist/FormLayout/index.d.ts.map +1 -1
  55. package/dist/Markdown/Markdown.d.ts +8 -0
  56. package/dist/Markdown/Markdown.d.ts.map +1 -1
  57. package/dist/Markdown/Markdown.js +31 -3
  58. package/dist/Markdown/parser.d.ts +14 -2
  59. package/dist/Markdown/parser.d.ts.map +1 -1
  60. package/dist/Markdown/parser.js +50 -2
  61. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  62. package/dist/MultiSelector/MultiSelector.js +6 -1
  63. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  64. package/dist/NumberInput/NumberInput.js +6 -1
  65. package/dist/Outline/parseOutlineFromMarkdown.d.ts +2 -0
  66. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  67. package/dist/Outline/parseOutlineFromMarkdown.js +5 -31
  68. package/dist/RadioList/RadioList.d.ts.map +1 -1
  69. package/dist/RadioList/RadioList.js +12 -1
  70. package/dist/Selector/Selector.d.ts.map +1 -1
  71. package/dist/Selector/Selector.js +6 -1
  72. package/dist/StatusDot/StatusDot.d.ts +43 -3
  73. package/dist/StatusDot/StatusDot.d.ts.map +1 -1
  74. package/dist/StatusDot/StatusDot.js +42 -5
  75. package/dist/Switch/Switch.d.ts.map +1 -1
  76. package/dist/Switch/Switch.js +9 -0
  77. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  78. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
  79. package/dist/TextArea/TextArea.d.ts.map +1 -1
  80. package/dist/TextArea/TextArea.js +6 -1
  81. package/dist/TextInput/TextInput.d.ts.map +1 -1
  82. package/dist/TextInput/TextInput.js +6 -1
  83. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  84. package/dist/TimeInput/TimeInput.js +6 -1
  85. package/dist/hooks/useResolvedRequired.d.ts +19 -0
  86. package/dist/hooks/useResolvedRequired.d.ts.map +1 -0
  87. package/dist/hooks/useResolvedRequired.js +40 -0
  88. package/dist/utils/plainDate.d.ts +6 -0
  89. package/dist/utils/plainDate.d.ts.map +1 -1
  90. package/dist/utils/plainDate.js +12 -0
  91. package/locales/af-ZA.json +958 -0
  92. package/locales/ar-SA.json +998 -0
  93. package/locales/ca-ES.json +974 -0
  94. package/locales/cs-CZ.json +990 -0
  95. package/locales/da-DK.json +966 -0
  96. package/locales/de-DE.json +966 -0
  97. package/locales/el-GR.json +990 -0
  98. package/locales/en.json +4 -0
  99. package/locales/es-ES.json +978 -0
  100. package/locales/fi-FI.json +990 -0
  101. package/locales/fr-FR.json +928 -0
  102. package/locales/he-IL.json +990 -0
  103. package/locales/hu-HU.json +990 -0
  104. package/locales/it-IT.json +978 -0
  105. package/locales/ja-JP.json +998 -0
  106. package/locales/ko-KR.json +990 -0
  107. package/locales/nl-NL.json +954 -0
  108. package/locales/no-NO.json +978 -0
  109. package/locales/pl-PL.json +982 -0
  110. package/locales/pseudo.json +3 -0
  111. package/locales/pt-BR.json +982 -0
  112. package/locales/pt-PT.json +982 -0
  113. package/locales/ro-RO.json +982 -0
  114. package/locales/ru-RU.json +990 -0
  115. package/locales/sr-SP.json +990 -0
  116. package/locales/sv-SE.json +986 -0
  117. package/locales/tr-TR.json +990 -0
  118. package/locales/uk-UA.json +990 -0
  119. package/locales/vi-VN.json +986 -0
  120. package/locales/zh-CN.json +998 -0
  121. package/locales/zh-TW.json +998 -0
  122. package/package.json +3 -3
  123. package/src/BottomSheet/BottomSheet.doc.mjs +36 -3
  124. package/src/BottomSheet/BottomSheet.test.tsx +153 -2
  125. package/src/BottomSheet/BottomSheet.tsx +16 -2
  126. package/src/BottomSheet/BottomSheetPanel.tsx +67 -13
  127. package/src/BottomSheet/index.ts +5 -1
  128. package/src/BottomSheet/snapOffsets.test.ts +114 -47
  129. package/src/BottomSheet/snapOffsets.ts +119 -33
  130. package/src/BottomSheet/useSheetGestures.test.ts +113 -15
  131. package/src/BottomSheet/useSheetGestures.ts +196 -100
  132. package/src/Calendar/Calendar.doc.mjs +16 -0
  133. package/src/Calendar/Calendar.test.tsx +137 -0
  134. package/src/Calendar/Calendar.tsx +65 -2
  135. package/src/Calendar/getStandaloneShortWeekdayNames.test.ts +54 -0
  136. package/src/Calendar/getStandaloneShortWeekdayNames.ts +38 -0
  137. package/src/Calendar/hooks/useCalendarConstraints.ts +54 -3
  138. package/src/Calendar/hooks/useCalendarDays.ts +13 -10
  139. package/src/Calendar/standaloneShortWeekdayNames.generated.ts +50 -0
  140. package/src/CheckboxInput/CheckboxInput.tsx +6 -0
  141. package/src/ComplexSelector/ComplexSelector.tsx +3 -1
  142. package/src/DateInput/DateInput.tsx +3 -1
  143. package/src/DateRangeInput/DateRangeInput.doc.mjs +16 -0
  144. package/src/DateRangeInput/DateRangeInput.test.tsx +81 -1
  145. package/src/DateRangeInput/DateRangeInput.tsx +72 -1
  146. package/src/DateTimeInput/DateTimeInput.tsx +4 -2
  147. package/src/Field/Field.doc.mjs +1 -0
  148. package/src/Field/FieldLabel.tsx +20 -4
  149. package/src/Field/InputClearButton.test.tsx +25 -0
  150. package/src/Field/InputClearButton.tsx +4 -1
  151. package/src/FormLayout/FormLayout.doc.mjs +13 -0
  152. package/src/FormLayout/FormLayout.test.tsx +181 -4
  153. package/src/FormLayout/FormLayout.tsx +33 -2
  154. package/src/FormLayout/FormLayoutContext.ts +22 -7
  155. package/src/FormLayout/index.ts +1 -1
  156. package/src/Markdown/Markdown.doc.mjs +3 -0
  157. package/src/Markdown/Markdown.test.tsx +78 -0
  158. package/src/Markdown/Markdown.tsx +41 -1
  159. package/src/Markdown/parser.ts +60 -2
  160. package/src/MultiSelector/MultiSelector.tsx +3 -1
  161. package/src/NumberInput/NumberInput.tsx +3 -1
  162. package/src/Outline/Outline.doc.mjs +2 -0
  163. package/src/Outline/parseOutlineFromMarkdown.ts +10 -40
  164. package/src/RadioList/RadioList.tsx +9 -1
  165. package/src/Selector/Selector.tsx +3 -1
  166. package/src/StatusDot/StatusDot.doc.mjs +13 -0
  167. package/src/StatusDot/StatusDot.test.tsx +115 -8
  168. package/src/StatusDot/StatusDot.tsx +74 -9
  169. package/src/Switch/Switch.tsx +6 -0
  170. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +20 -2
  171. package/src/TextArea/TextArea.tsx +3 -1
  172. package/src/TextInput/TextInput.tsx +3 -1
  173. package/src/TimeInput/TimeInput.tsx +3 -1
  174. package/src/hooks/useResolvedRequired.ts +42 -0
  175. package/src/utils/plainDate.test.ts +50 -0
  176. package/src/utils/plainDate.ts +12 -0
@@ -15,14 +15,16 @@
15
15
  * the part the scrolling area gives up as layout height, and the remainder is
16
16
  * a transform. Gestures and snaps only ever move the transform, so they stay
17
17
  * on the compositor; layout height changes at rest, in one transition-free
18
- * render whose visible geometry is identical. The peek detent keeps the full
19
- * height and slides below the viewport instead of reflowing to a sliver.
18
+ * render whose visible geometry is identical. A peek detent a stop that is
19
+ * only a sliver of the sheet keeps the full height and slides below the
20
+ * viewport instead of reflowing to that sliver.
20
21
  *
21
- * Those are pixels, and the detents behind them are viewport fractions, so a
22
- * sheet at rest re-resolves its detent on `resize` / `orientationchange` and
23
- * re-anchors to the new geometry without animating. The detent it returns to
24
- * is tracked by index: the pixels stop meaning the same thing when the
25
- * viewport changes, the stop the user chose does not.
22
+ * Those are pixels, and the stops behind them are relative to the viewport, so
23
+ * a sheet at rest re-resolves its detent on `resize` / `orientationchange`,
24
+ * and whenever the host swaps the snap points, and re-anchors to the new
25
+ * geometry without animating. The detent it returns to is tracked by index:
26
+ * the pixels stop meaning the same thing when the viewport changes, the stop
27
+ * the user chose does not.
26
28
  *
27
29
  * On touch, the scrolling body hands the gesture to the sheet at a scroll
28
30
  * edge. Two shapes, because the browser only offers one of them a choice: a
@@ -70,6 +72,9 @@ export interface UseSheetGesturesOptions {
70
72
  * (rotation, a resized desktop window, collapsing browser chrome). The fully
71
73
  * open height is always the tallest detent. Omit for a single-height sheet
72
74
  * (a drag then only dismisses or springs back).
75
+ *
76
+ * Keep the identity stable: a new function is read as new stops, and
77
+ * re-anchors a resting sheet to them.
73
78
  */
74
79
  snapHeights?: () => number[];
75
80
  /** Notified when the settled visible detent height (px) changes. */
@@ -153,7 +158,7 @@ export interface UseSheetGesturesResult {
153
158
  /**
154
159
  * How much of `settledOffset` is expressed as layout height rather than as a
155
160
  * transform. Equals `settledOffset` at the resizing detents; 0 at fully open
156
- * and at the peek, which keep the sheet's full height (see isPeekOffset).
161
+ * and at the peek, which keep the sheet's full height (see peekOffsetFor).
157
162
  */
158
163
  settledLayoutOffset: number;
159
164
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useSheetGestures.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/useSheetGestures.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useSheetGestures.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/useSheetGestures.ts"],"names":[],"mappings":"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"}
@@ -19,14 +19,16 @@
19
19
  * the part the scrolling area gives up as layout height, and the remainder is
20
20
  * a transform. Gestures and snaps only ever move the transform, so they stay
21
21
  * on the compositor; layout height changes at rest, in one transition-free
22
- * render whose visible geometry is identical. The peek detent keeps the full
23
- * height and slides below the viewport instead of reflowing to a sliver.
22
+ * render whose visible geometry is identical. A peek detent a stop that is
23
+ * only a sliver of the sheet keeps the full height and slides below the
24
+ * viewport instead of reflowing to that sliver.
24
25
  *
25
- * Those are pixels, and the detents behind them are viewport fractions, so a
26
- * sheet at rest re-resolves its detent on `resize` / `orientationchange` and
27
- * re-anchors to the new geometry without animating. The detent it returns to
28
- * is tracked by index: the pixels stop meaning the same thing when the
29
- * viewport changes, the stop the user chose does not.
26
+ * Those are pixels, and the stops behind them are relative to the viewport, so
27
+ * a sheet at rest re-resolves its detent on `resize` / `orientationchange`,
28
+ * and whenever the host swaps the snap points, and re-anchors to the new
29
+ * geometry without animating. The detent it returns to is tracked by index:
30
+ * the pixels stop meaning the same thing when the viewport changes, the stop
31
+ * the user chose does not.
30
32
  *
31
33
  * On touch, the scrolling body hands the gesture to the sheet at a scroll
32
34
  * edge. Two shapes, because the browser only offers one of them a choice: a
@@ -51,7 +53,12 @@
51
53
  */
52
54
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
53
55
  import { useMediaQuery } from "../hooks/index.js";
54
- import { computeDetentOffsets, isPeekOffset, resolveSettleOffset, scrimOpacityForOffset } from "./snapOffsets.js";
56
+ import { computeDetentOffsets, peekOffsetFor, resolveSettleOffset, scrimOpacityForOffset } from "./snapOffsets.js";
57
+
58
+ /**
59
+ * The stops a sheet of a given height rests at: offsets from fully-open in px,
60
+ * ascending, plus which of them (if any) is the peek.
61
+ */
55
62
 
56
63
  // A flick (fast throw) dismisses (down) or expands (up) regardless of where
57
64
  // it ends. Requires both a speed and a distance floor so a small nudge
@@ -64,6 +71,18 @@ const DISMISS_OVERSHOOT_RATIO = 0.4;
64
71
  // Within this many px of a detent, the live drag is magnetically eased toward
65
72
  // it so it "clicks" into place instead of hovering just off the mark.
66
73
  const MAGNET_RANGE = 40;
74
+ // The touch path drives the sheet through the same machinery the pointer path
75
+ // uses, by handing it a pointer-shaped object built from a `Touch`. On iOS
76
+ // Safari that object is indistinguishable from the real thing: WebKit raises
77
+ // PointerEvents for a finger under the SAME numeric id it puts in
78
+ // `Touch.identifier`, so such a drag is keyed to a live pointer and the
79
+ // handlers that guard a mouse drag fire against it. Mark the synthetic object
80
+ // so they can tell the two apart.
81
+
82
+ function isSyntheticTouch(event) {
83
+ return event.syntheticTouch === true;
84
+ }
85
+
67
86
  // Rubber-band factor for dragging up past fully-open, capped at OVERSCROLL_MAX
68
87
  // (the sheet reserves that much bottom padding for the lift to reveal).
69
88
  const OVERSCROLL_RESISTANCE = 0.35;
@@ -174,7 +193,7 @@ export function useSheetGestures({
174
193
  const [scrollPreservationInset, setScrollPreservationInset] = useState(0);
175
194
  // How much of the settled travel is expressed as layout height. Equal to
176
195
  // settledOffset for the resizing detents, and 0 at fully open and at the
177
- // peek (see isPeekOffset), which keep the full height and use a transform.
196
+ // peek (see peekOffsetFor), which keep the full height and use a transform.
178
197
  const [settledLayoutOffset, setSettledLayoutOffset] = useState(0);
179
198
  const settledLayoutOffsetRef = useRef(0);
180
199
  // WHICH detent the sheet rests on, as an index into the resolved list. The
@@ -242,17 +261,22 @@ export function useSheetGestures({
242
261
  activeOffsetRef.current = isDragging ? dragOffset : settledOffset;
243
262
  const isOpenRef = useRef(isOpen);
244
263
  isOpenRef.current = isOpen;
264
+
265
+ // The resolver is refreshed during render, not with the callbacks below: the
266
+ // layout effect that re-anchors on a change to it runs BEFORE passive
267
+ // effects, so a resolver parked in one would still be the previous set of
268
+ // stops by the time the re-anchor read it.
269
+ const snapHeightsRef = useRef(snapHeights);
270
+ snapHeightsRef.current = snapHeights;
245
271
  const onDismissRef = useRef(onDismiss);
246
272
  const canDismissRef = useRef(canDismiss);
247
273
  const onSnapRef = useRef(onSnap);
248
274
  const onScrimOpacityRef = useRef(onScrimOpacity);
249
- const snapHeightsRef = useRef(snapHeights);
250
275
  useEffect(() => {
251
276
  onDismissRef.current = onDismiss;
252
277
  canDismissRef.current = canDismiss;
253
278
  onSnapRef.current = onSnap;
254
279
  onScrimOpacityRef.current = onScrimOpacity;
255
- snapHeightsRef.current = snapHeights;
256
280
  });
257
281
 
258
282
  // Live drag bookkeeping (refs so pointermove doesn't churn renders).
@@ -349,13 +373,18 @@ export function useSheetGestures({
349
373
  return sheetElRef.current?.getBoundingClientRect().height ?? 0;
350
374
  }, []);
351
375
 
352
- // Detent translate offsets (px) from the tallest detent, ascending. Exclude
353
- // the border-box portion reserved below the viewport before comparing the
354
- // candidate visible heights; otherwise every snap point lands that many px
355
- // too low. Snap heights are resolved lazily so they track the viewport.
356
- const detentOffsets = useCallback(height => {
376
+ // Detent translate offsets (px) from the tallest detent, ascending, plus the
377
+ // peek among them. Exclude the border-box portion reserved below the
378
+ // viewport before comparing the candidate visible heights; otherwise every
379
+ // snap point lands that many px too low. Snap heights are resolved lazily so
380
+ // they track the viewport.
381
+ const resolveDetents = useCallback(height => {
357
382
  const visibleSheetHeight = visibleHeightForOffset(height, 0, offscreenBlockEndInsetRef.current);
358
- return computeDetentOffsets(visibleSheetHeight, snapHeightsRef.current?.() ?? []);
383
+ const offsets = computeDetentOffsets(visibleSheetHeight, snapHeightsRef.current?.() ?? []);
384
+ return {
385
+ offsets,
386
+ peekOffset: peekOffsetFor(offsets, visibleSheetHeight)
387
+ };
359
388
  }, []);
360
389
 
361
390
  // The height the sheet WOULD have fully open, right now. While it rests at a
@@ -389,48 +418,71 @@ export function useSheetGestures({
389
418
  //
390
419
  // Re-resolve the SAME detent — by index, the one thing that survives the
391
420
  // units changing — against the new geometry, and re-anchor without
392
- // animating: the viewport moved, not the user's finger, so there is no
421
+ // animating: the geometry moved, not the user's finger, so there is no
393
422
  // gesture to continue and nothing to ease.
423
+ const reanchorToSettledDetent = useCallback(() => {
424
+ // A closed sheet re-anchors on its way back open; a live drag re-measures
425
+ // on its own, and a settle in flight owns the layout until it lands.
426
+ if (!isOpenRef.current || dragStateRef.current != null || settlingLayoutOffsetRef.current != null) {
427
+ return;
428
+ }
429
+ const height = measureFullyOpenHeight();
430
+ if (height <= 0) {
431
+ return;
432
+ }
433
+ const {
434
+ offsets,
435
+ peekOffset
436
+ } = resolveDetents(height);
437
+ const index = Math.min(settledDetentIndexRef.current, offsets.length - 1);
438
+ const target = offsets[index];
439
+ const targetLayoutOffset = target === peekOffset ? 0 : target;
440
+ const baseLayoutOffset = settledLayoutOffsetRef.current;
441
+ if (height === sheetHeightRef.current && Math.abs(target - activeOffsetRef.current) <= 0.5 && Math.abs(targetLayoutOffset - baseLayoutOffset) <= 0.5) {
442
+ return;
443
+ }
444
+ sheetHeightRef.current = height;
445
+ // Re-anchoring is a measurement: the geometry it reads is only knowable
446
+ // after layout, so the state it corrects can only be set from an effect
447
+ // (or, on the resize path, from the listener). Both writes below are
448
+ // guarded by the equality check above, so a re-anchor that finds nothing
449
+ // to change sets nothing.
450
+ // eslint-disable-next-line @eslint-react/set-state-in-effect -- corrects settled geometry from a measurement
451
+ setSheetHeight(height);
452
+ settledDetentIndexRef.current = index;
453
+ prepareScrollAreaSettle(baseLayoutOffset, target, targetLayoutOffset, target, baseLayoutOffset, naturalEndGapFor(bodyNodeRef.current), false);
454
+ recordSettledLayoutOffset(targetLayoutOffset);
455
+ // eslint-disable-next-line @eslint-react/set-state-in-effect -- corrects settled geometry from a measurement
456
+ setSettledOffset(target);
457
+ onSnapRef.current?.(visibleHeightForOffset(height, target, offscreenBlockEndInsetRef.current));
458
+ const maxOffset = offsets[offsets.length - 1];
459
+ const shortestDetentHeight = visibleHeightForOffset(height, maxOffset, offscreenBlockEndInsetRef.current);
460
+ onScrimOpacityRef.current?.(scrimOpacityForOffset(target, offsets, maxOffset + shortestDetentHeight * DISMISS_OVERSHOOT_RATIO, peekOffset));
461
+ }, [measureFullyOpenHeight, prepareScrollAreaSettle, recordSettledLayoutOffset, resolveDetents]);
394
462
  useEffect(() => {
395
463
  if (!isOpen || typeof window === 'undefined') {
396
464
  return;
397
465
  }
398
- const handleViewportChange = () => {
399
- // A live drag re-measures on its own, and a settle in flight owns the
400
- // layout until it lands.
401
- if (dragStateRef.current != null || settlingLayoutOffsetRef.current != null) {
402
- return;
403
- }
404
- const height = measureFullyOpenHeight();
405
- if (height <= 0) {
406
- return;
407
- }
408
- const offsets = detentOffsets(height);
409
- const index = Math.min(settledDetentIndexRef.current, offsets.length - 1);
410
- const target = offsets[index];
411
- const targetLayoutOffset = isPeekOffset(target, offsets) ? 0 : target;
412
- const baseLayoutOffset = settledLayoutOffsetRef.current;
413
- if (height === sheetHeightRef.current && Math.abs(target - activeOffsetRef.current) <= 0.5 && Math.abs(targetLayoutOffset - baseLayoutOffset) <= 0.5) {
414
- return;
415
- }
416
- sheetHeightRef.current = height;
417
- setSheetHeight(height);
418
- settledDetentIndexRef.current = index;
419
- prepareScrollAreaSettle(baseLayoutOffset, target, targetLayoutOffset, target, baseLayoutOffset, naturalEndGapFor(bodyNodeRef.current), false);
420
- recordSettledLayoutOffset(targetLayoutOffset);
421
- setSettledOffset(target);
422
- onSnapRef.current?.(visibleHeightForOffset(height, target, offscreenBlockEndInsetRef.current));
423
- const maxOffset = offsets[offsets.length - 1];
424
- const shortestDetentHeight = visibleHeightForOffset(height, maxOffset, offscreenBlockEndInsetRef.current);
425
- onScrimOpacityRef.current?.(scrimOpacityForOffset(target, offsets, maxOffset + shortestDetentHeight * DISMISS_OVERSHOOT_RATIO));
426
- };
427
- window.addEventListener('resize', handleViewportChange);
428
- window.addEventListener('orientationchange', handleViewportChange);
466
+ window.addEventListener('resize', reanchorToSettledDetent);
467
+ window.addEventListener('orientationchange', reanchorToSettledDetent);
429
468
  return () => {
430
- window.removeEventListener('resize', handleViewportChange);
431
- window.removeEventListener('orientationchange', handleViewportChange);
469
+ window.removeEventListener('resize', reanchorToSettledDetent);
470
+ window.removeEventListener('orientationchange', reanchorToSettledDetent);
432
471
  };
433
- }, [detentOffsets, isOpen, measureFullyOpenHeight, prepareScrollAreaSettle, recordSettledLayoutOffset]);
472
+ }, [isOpen, reanchorToSettledDetent]);
473
+
474
+ // The other input to the same geometry: the snap points themselves. A host
475
+ // that swaps them while the sheet rests has moved the stops out from under
476
+ // it, exactly as a rotation does, so re-anchor the same way. Skipped on the
477
+ // first run — the sheet is already anchored to the detents it opened with.
478
+ const hasAnchoredSnapHeightsRef = useRef(false);
479
+ useLayoutEffect(() => {
480
+ if (!hasAnchoredSnapHeightsRef.current) {
481
+ hasAnchoredSnapHeightsRef.current = true;
482
+ return;
483
+ }
484
+ reanchorToSettledDetent();
485
+ }, [reanchorToSettledDetent, snapHeights]);
434
486
  const cancelDrag = useCallback(target => {
435
487
  const state = dragStateRef.current;
436
488
  if (state == null) {
@@ -450,12 +502,15 @@ export function useSheetGestures({
450
502
  // An interrupted drag returns to its previous resting detent. Restore
451
503
  // the matching scrim opacity as well so the modal shell cannot remain
452
504
  // dimmed with its sheet translated out of view.
453
- const offsets = detentOffsets(state.height);
505
+ const {
506
+ offsets,
507
+ peekOffset
508
+ } = resolveDetents(state.height);
454
509
  const maxOffset = offsets[offsets.length - 1];
455
510
  const shortestDetentHeight = visibleHeightForOffset(state.height, maxOffset, offscreenBlockEndInsetRef.current);
456
511
  const dismissOffset = maxOffset + shortestDetentHeight * DISMISS_OVERSHOOT_RATIO;
457
- onScrimOpacityRef.current?.(scrimOpacityForOffset(state.baseOffset, offsets, dismissOffset));
458
- }, [detentOffsets, prepareScrollAreaSettle]);
512
+ onScrimOpacityRef.current?.(scrimOpacityForOffset(state.baseOffset, offsets, dismissOffset, peekOffset));
513
+ }, [prepareScrollAreaSettle, resolveDetents]);
459
514
  useEffect(() => {
460
515
  const handleWindowBlur = () => cancelDrag();
461
516
  const handleVisibilityChange = () => {
@@ -471,7 +526,10 @@ export function useSheetGestures({
471
526
  };
472
527
  }, [cancelDrag]);
473
528
  const settleFromDrag = useCallback((offset, velocity, height, dir, travel, baseOffset, baseLayoutOffset, renderedOffset, layoutOffset, naturalEndGap) => {
474
- const offsets = detentOffsets(height);
529
+ const {
530
+ offsets,
531
+ peekOffset
532
+ } = resolveDetents(height);
475
533
  const maxOffset = offsets[offsets.length - 1];
476
534
  const shortestDetentHeight = visibleHeightForOffset(height, maxOffset, offscreenBlockEndInsetRef.current);
477
535
  const speed = Math.abs(velocity);
@@ -479,14 +537,14 @@ export function useSheetGestures({
479
537
  const settleAt = target => {
480
538
  // A peek keeps the full layout height and slides below the viewport;
481
539
  // every taller detent resizes the scrolling area to what it shows.
482
- const targetLayoutOffset = isPeekOffset(target, offsets) ? 0 : target;
540
+ const targetLayoutOffset = target === peekOffset ? 0 : target;
483
541
  prepareScrollAreaSettle(baseLayoutOffset, target, targetLayoutOffset, renderedOffset, layoutOffset, naturalEndGap, true);
484
542
  recordSettledLayoutOffset(targetLayoutOffset);
485
543
  settledDetentIndexRef.current = Math.max(0, offsets.indexOf(target));
486
544
  setSettledOffset(target);
487
545
  onSnapRef.current?.(visibleHeightForOffset(height, target, offscreenBlockEndInsetRef.current));
488
546
  const dismissOffset = maxOffset + shortestDetentHeight * DISMISS_OVERSHOOT_RATIO;
489
- onScrimOpacityRef.current?.(scrimOpacityForOffset(target, offsets, dismissOffset));
547
+ onScrimOpacityRef.current?.(scrimOpacityForOffset(target, offsets, dismissOffset, peekOffset));
490
548
  if (target !== baseOffset) {
491
549
  hapticTick();
492
550
  }
@@ -526,10 +584,19 @@ export function useSheetGestures({
526
584
  // the starting detent), de-duped and direction-clamped by the util.
527
585
  const target = resolveSettleOffset(offset, offsets, dir, baseOffset);
528
586
  settleAt(target);
529
- }, [detentOffsets, prepareScrollAreaSettle, recordSettledLayoutOffset]);
587
+ }, [prepareScrollAreaSettle, recordSettledLayoutOffset, resolveDetents]);
530
588
  const beginDrag = useCallback((event, sheetHeight, startCoord) => {
531
589
  const target = event.currentTarget;
532
- target.setPointerCapture?.(event.pointerId);
590
+ const syntheticTouch = isSyntheticTouch(event);
591
+ // Never capture for a touch drag. The id came from `Touch.identifier`,
592
+ // which on iOS names a live pointer: the capture succeeds, WebKit takes
593
+ // it straight back for its own gesture handling, and the
594
+ // `lostpointercapture` a millisecond later cancels the drag that just
595
+ // started. Touch drags need no capture — the listener is on the
596
+ // scroller itself.
597
+ if (!syntheticTouch) {
598
+ target.setPointerCapture?.(event.pointerId);
599
+ }
533
600
  // `startCoord` lets a body-overscroll drag anchor at the original
534
601
  // pointer-down position (not the promotion point), so the first frame's
535
602
  // delta reflects the full pull distance.
@@ -537,6 +604,7 @@ export function useSheetGestures({
537
604
  const naturalEndGap = naturalEndGapFor(bodyNodeRef.current);
538
605
  const baseLayoutOffset = settledLayoutOffsetRef.current;
539
606
  dragStateRef.current = {
607
+ syntheticTouch,
540
608
  pointerId: event.pointerId,
541
609
  startCoord: start,
542
610
  lastCoord: event.clientY,
@@ -573,6 +641,12 @@ export function useSheetGestures({
573
641
  cancelDrag(event.currentTarget);
574
642
  }, [cancelDrag]);
575
643
  const handleLostPointerCapture = useCallback(event => {
644
+ // A touch drag never took capture, so this is WebKit reclaiming its own
645
+ // pointer for the finger doing the dragging — not the drag losing its
646
+ // grip.
647
+ if (dragStateRef.current?.syntheticTouch) {
648
+ return;
649
+ }
576
650
  if (dragStateRef.current?.pointerId === event.pointerId) {
577
651
  cancelDrag();
578
652
  }
@@ -582,6 +656,11 @@ export function useSheetGestures({
582
656
  if (!state || state.pointerId !== event.pointerId) {
583
657
  return;
584
658
  }
659
+ // Same finger, real pointer event: the touch path already drove this
660
+ // move. Let it own the drag rather than driving it twice.
661
+ if (state.syntheticTouch && !isSyntheticTouch(event)) {
662
+ return;
663
+ }
585
664
  const delta = event.clientY - state.startCoord;
586
665
  const dt = event.timeStamp - state.lastTime;
587
666
  if (dt > 0) {
@@ -589,7 +668,10 @@ export function useSheetGestures({
589
668
  state.lastCoord = event.clientY;
590
669
  state.lastTime = event.timeStamp;
591
670
  }
592
- const offsets = detentOffsets(state.height);
671
+ const {
672
+ offsets,
673
+ peekOffset
674
+ } = resolveDetents(state.height);
593
675
  const raw = state.baseOffset + delta;
594
676
  const maxDetentOffset = offsets[offsets.length - 1];
595
677
  let next;
@@ -618,19 +700,27 @@ export function useSheetGestures({
618
700
  const floorOffset = offsets[offsets.length - 1];
619
701
  const shortestDetentHeight = visibleHeightForOffset(state.height, floorOffset, offscreenBlockEndInsetRef.current);
620
702
  const dismissOffset = floorOffset + shortestDetentHeight * DISMISS_OVERSHOOT_RATIO;
621
- onScrimOpacityRef.current?.(scrimOpacityForOffset(next, offsets, dismissOffset));
622
- }, [detentOffsets, updateScrollPreservationInset]);
703
+ onScrimOpacityRef.current?.(scrimOpacityForOffset(next, offsets, dismissOffset, peekOffset));
704
+ }, [resolveDetents, updateScrollPreservationInset]);
623
705
  const endDrag = useCallback(event => {
624
706
  const state = dragStateRef.current;
625
707
  if (!state || state.pointerId !== event.pointerId) {
626
708
  return;
627
709
  }
710
+ // `pointercancel` fires for the finger the moment WebKit claims the
711
+ // gesture; ending a touch drag on it settles the sheet mid-pull. The
712
+ // touchend handler is what finishes a touch drag.
713
+ if (state.syntheticTouch && !isSyntheticTouch(event)) {
714
+ return;
715
+ }
628
716
  const target = event.currentTarget;
629
717
  const delta = event.clientY - state.startCoord;
630
718
  const offset = Math.max(0, state.baseOffset + delta);
631
719
  const dir = delta === 0 ? 0 : delta > 0 ? 1 : -1;
632
720
  dragStateRef.current = null;
633
- target.releasePointerCapture?.(event.pointerId);
721
+ if (!state.syntheticTouch) {
722
+ target.releasePointerCapture?.(event.pointerId);
723
+ }
634
724
  setIsDragging(false);
635
725
  settleFromDrag(offset, state.velocity, state.height || 1, dir, Math.abs(delta), state.baseOffset, state.baseLayoutOffset, state.renderedOffset, state.layoutOffset, state.naturalEndGap);
636
726
  }, [settleFromDrag]);
@@ -677,6 +767,11 @@ export function useSheetGestures({
677
767
  }, [beginDrag, handlePointerMove, measureHeight]);
678
768
  const handleBodyEnd = useCallback(event => {
679
769
  armedBodyRef.current = null;
770
+ // The pointerup/pointercancel for a finger already driving a touch drag
771
+ // arrives here carrying that drag's own id; touchend ends those.
772
+ if (dragStateRef.current?.syntheticTouch) {
773
+ return;
774
+ }
680
775
  if (dragStateRef.current) {
681
776
  endDrag(event);
682
777
  }
@@ -705,6 +800,7 @@ export function useSheetGestures({
705
800
  });
706
801
  const bodyRef = useCallback(node => {
707
802
  const asPointer = (touch, target) => ({
803
+ syntheticTouch: true,
708
804
  pointerId: touch.identifier,
709
805
  clientY: touch.clientY,
710
806
  timeStamp: Date.now(),
@@ -22,6 +22,23 @@ interface CalendarBaseProps extends Omit<BaseProps<HTMLDivElement>, 'onChange' |
22
22
  * Use for complex rules like "weekdays only" or "no holidays".
23
23
  */
24
24
  dateConstraints?: ReadonlyArray<(date: Date) => boolean>;
25
+ /**
26
+ * Range mode only. Maximum number of days a selected range may span,
27
+ * counting both endpoints — `maxRangeSpan={7}` allows a 7-day window
28
+ * (start + 6 days). Once a start date is picked, days beyond this distance
29
+ * from it are disabled in either direction; before a start is picked every
30
+ * otherwise-valid day stays selectable. Use for rolling windows like "at
31
+ * most a week from the chosen day". For fixed calendar bounds use min/max.
32
+ */
33
+ maxRangeSpan?: number;
34
+ /**
35
+ * Range mode only. Minimum number of days a selected range must span,
36
+ * counting both endpoints — `minRangeSpan={2}` forbids a single-day range.
37
+ * Once a start date is picked, days closer than this to it are disabled —
38
+ * except the start itself, which stays selectable as the active anchor.
39
+ * Defaults to 1 (a same-day start and end is allowed).
40
+ */
41
+ minRangeSpan?: number;
25
42
  /**
26
43
  * Controlled focus date (which month is visible).
27
44
  * If not provided, defaults to selected date or today.
@@ -1 +1 @@
1
- {"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../src/Calendar/Calendar.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA+C5C,YAAY,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,SAAS,GACV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,SAAS,EACV,MAAM,oBAAoB,CAAC;AAK5B,+CAA+C;AAE/C,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,UAAU,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CAC3C;AAID,UAAU,iBAAkB,SAAQ,IAAI,CACtC,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,GAAG,cAAc,CAC5B;IACC,kDAAkD;IAClD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,qDAAqD;IACrD,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACtC,+CAA+C;IAC/C,cAAc,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAEvB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB;;;OAGG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC;IAEzD;;;OAGG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAE1B,yDAAyD;IACzD,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,CAAC;IAEvD;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;CAC1C;AAID,UAAU,mBAAoB,SAAQ,iBAAiB;IACrD,qBAAqB;IACrB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAEhB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,aAAa,CAAC;IAEtB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,aAAa,CAAC;IAE7B,qCAAqC;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,IAAI,KAAK,IAAI,CAAC;CAC9D;AAED,UAAU,kBAAmB,SAAQ,iBAAiB;IACpD,qBAAqB;IACrB,IAAI,EAAE,OAAO,CAAC;IAEd,0BAA0B;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAC;IAElB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,SAAS,CAAC;IAEzB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AAMrE;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EAAC,GAAG,EAAE,GAAG,KAAK,EAAC,EAAE,aAAa,+BA+VtD;yBA/Ve,QAAQ"}
1
+ {"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../src/Calendar/Calendar.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA+C5C,YAAY,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,SAAS,GACV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,SAAS,EACV,MAAM,oBAAoB,CAAC;AAK5B,+CAA+C;AAE/C,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,UAAU,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CAC3C;AAID,UAAU,iBAAkB,SAAQ,IAAI,CACtC,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,GAAG,cAAc,CAC5B;IACC,kDAAkD;IAClD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,qDAAqD;IACrD,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACtC,+CAA+C;IAC/C,cAAc,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAEvB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB;;;OAGG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC;IAEzD;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAE1B,yDAAyD;IACzD,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,CAAC;IAEvD;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;CAC1C;AAID,UAAU,mBAAoB,SAAQ,iBAAiB;IACrD,qBAAqB;IACrB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAEhB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,aAAa,CAAC;IAEtB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,aAAa,CAAC;IAE7B,qCAAqC;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,IAAI,KAAK,IAAI,CAAC;CAC9D;AAED,UAAU,kBAAmB,SAAQ,iBAAiB;IACpD,qBAAqB;IACrB,IAAI,EAAE,OAAO,CAAC;IAEd,0BAA0B;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAC;IAElB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,SAAS,CAAC;IAEzB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AAMrE;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EAAC,GAAG,EAAE,GAAG,KAAK,EAAC,EAAE,aAAa,+BAoXtD;yBApXe,QAAQ"}
@@ -69,6 +69,8 @@ export function Calendar({
69
69
  min,
70
70
  max,
71
71
  dateConstraints,
72
+ maxRangeSpan,
73
+ minRangeSpan,
72
74
  focusDate: focusDateProp,
73
75
  onFocusDateChange,
74
76
  hasOutsideDays = true,
@@ -247,6 +249,20 @@ export function Calendar({
247
249
  } else {
248
250
  // Second click - complete the range
249
251
  const startPd = plainDateFromISO(rangeSelectionStart);
252
+
253
+ // Clicking the anchor again clears the in-progress start rather than
254
+ // committing a zero-length range. This is also the escape hatch when
255
+ // `minRangeSpan` disables the days around the anchor: without it the
256
+ // anchor would be the only clickable day left and the start could
257
+ // never be moved. `minRangeSpan` leaves the anchor itself enabled
258
+ // precisely so this toggle stays reachable.
259
+ if (plainDateIsEqual(date, startPd)) {
260
+ setRangeSelectionStart(null);
261
+ announce(t('@astryx.calendar.rangeClearedAnnounce', {
262
+ date: plainDateFormat(date, DATE_FORMAT_WITH_WEEKDAY)
263
+ }));
264
+ return;
265
+ }
250
266
  let start;
251
267
  let end;
252
268
 
@@ -339,6 +355,8 @@ export function Calendar({
339
355
  min: min,
340
356
  max: max,
341
357
  dateConstraints: dateConstraints,
358
+ maxRangeSpan: maxRangeSpan,
359
+ minRangeSpan: minRangeSpan,
342
360
  hasOutsideDays: hasOutsideDays,
343
361
  hasWeekNumbers: hasWeekNumbers,
344
362
  hasVariableRowCount: hasVariableRowCount,
@@ -369,6 +387,8 @@ function MonthGrid({
369
387
  min,
370
388
  max,
371
389
  dateConstraints,
390
+ maxRangeSpan,
391
+ minRangeSpan,
372
392
  hasOutsideDays,
373
393
  hasWeekNumbers,
374
394
  hasVariableRowCount,
@@ -394,12 +414,20 @@ function MonthGrid({
394
414
  weekStartsOn,
395
415
  hasVariableRowCount
396
416
  });
417
+ const dayNameHeaders = useMemo(() => dayNames.map((name, offset) => ({
418
+ dayOfWeek: (weekStartsOn + offset) % 7,
419
+ name
420
+ })), [dayNames, weekStartsOn]);
421
+ const rangeAnchor = useMemo(() => mode === 'range' && rangeSelectionStart ? plainDateFromISO(rangeSelectionStart) : null, [mode, rangeSelectionStart]);
397
422
  const {
398
423
  isDateDisabled
399
424
  } = useCalendarConstraints({
400
425
  min,
401
426
  max,
402
- dateConstraints
427
+ dateConstraints,
428
+ maxRangeSpan,
429
+ minRangeSpan,
430
+ rangeAnchor
403
431
  });
404
432
 
405
433
  // Parse selected date for roving tabindex priority
@@ -585,11 +613,14 @@ function MonthGrid({
585
613
  ...stylex.props(monthGridStyles.weekRow),
586
614
  children: [hasWeekNumbers && /*#__PURE__*/_jsx("div", {
587
615
  ...stylex.props(monthGridStyles.dayName, monthGridStyles.weekNumberHeader)
588
- }), dayNames.map(name => /*#__PURE__*/_jsx("div", {
616
+ }), dayNameHeaders.map(({
617
+ dayOfWeek,
618
+ name
619
+ }) => /*#__PURE__*/_jsx("div", {
589
620
  role: "columnheader",
590
621
  ...stylex.props(monthGridStyles.dayName),
591
622
  children: name
592
- }, name))]
623
+ }, dayOfWeek))]
593
624
  }), weeks.map(week => {
594
625
  const weekDate = week.find(d => !d.isOutside)?.date || week[0].date;
595
626
  const weekNum = plainDateGetWeekNumber(weekDate);
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @file getStandaloneShortWeekdayNames.ts
3
+ * @input Generated CLDR stand-alone-short weekday table
4
+ * @output Private locale resolver for Calendar weekday headers
5
+ * @position Calendar-internal pure utility; not exported from the package
6
+ */
7
+ import { type StandaloneShortWeekdayNames } from './standaloneShortWeekdayNames.generated';
8
+ /**
9
+ * Resolve generated weekday reference data as exact locale -> language -> English.
10
+ * Unicode extensions are ignored through Intl.Locale.baseName. Invalid tags and
11
+ * valid but unsupported languages safely use English.
12
+ */
13
+ export declare function getStandaloneShortWeekdayNames(locale: string): StandaloneShortWeekdayNames;
14
+ //# sourceMappingURL=getStandaloneShortWeekdayNames.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getStandaloneShortWeekdayNames.d.ts","sourceRoot":"","sources":["../../src/Calendar/getStandaloneShortWeekdayNames.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,yCAAyC,CAAC;AAOjD;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,MAAM,GACb,2BAA2B,CAW7B"}
@@ -0,0 +1,26 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file getStandaloneShortWeekdayNames.ts
5
+ * @input Generated CLDR stand-alone-short weekday table
6
+ * @output Private locale resolver for Calendar weekday headers
7
+ * @position Calendar-internal pure utility; not exported from the package
8
+ */
9
+
10
+ import { standaloneShortWeekdayNamesByLocale } from "./standaloneShortWeekdayNames.generated.js";
11
+ const weekdayNamesByLocale = standaloneShortWeekdayNamesByLocale;
12
+ const englishWeekdayNames = weekdayNamesByLocale.en;
13
+
14
+ /**
15
+ * Resolve generated weekday reference data as exact locale -> language -> English.
16
+ * Unicode extensions are ignored through Intl.Locale.baseName. Invalid tags and
17
+ * valid but unsupported languages safely use English.
18
+ */
19
+ export function getStandaloneShortWeekdayNames(locale) {
20
+ try {
21
+ const parsed = new Intl.Locale(locale);
22
+ return weekdayNamesByLocale[parsed.baseName] ?? weekdayNamesByLocale[parsed.language] ?? englishWeekdayNames;
23
+ } catch {
24
+ return englishWeekdayNames;
25
+ }
26
+ }
@@ -13,6 +13,25 @@ export interface UseCalendarConstraintsOptions {
13
13
  * Date is disabled if ANY function returns false.
14
14
  */
15
15
  dateConstraints?: ReadonlyArray<(date: Date) => boolean>;
16
+ /**
17
+ * Maximum number of days a range may span, counting both endpoints
18
+ * (a value of 7 allows a 7-day window). In range mode, once a start is
19
+ * picked, dates further than this from it are disabled. No effect until a
20
+ * range anchor exists.
21
+ */
22
+ maxRangeSpan?: number;
23
+ /**
24
+ * Minimum number of days a range must span, counting both endpoints
25
+ * (a value of 2 forbids a single-day range). In range mode, once a start
26
+ * is picked, dates closer than this to it are disabled.
27
+ */
28
+ minRangeSpan?: number;
29
+ /**
30
+ * The in-progress range start (first click, awaiting the second). Span
31
+ * constraints are measured from this date. Null when no selection is
32
+ * underway.
33
+ */
34
+ rangeAnchor?: PlainDate | null;
16
35
  }
17
36
  /**
18
37
  * Return type for useCalendarConstraints hook
@@ -29,13 +48,15 @@ export interface UseCalendarConstraintsReturn {
29
48
  * Hook for managing calendar date validation constraints.
30
49
  *
31
50
  * Provides a function to check if a date is disabled based on
32
- * min/max bounds and custom constraint functions.
51
+ * min/max bounds, range-span bounds, and custom constraint functions.
33
52
  *
34
53
  * @example
35
54
  * ```
36
55
  * const {isDateDisabled} = useCalendarConstraints({
37
56
  * min: '2026-01-01',
38
57
  * max: '2026-12-31',
58
+ * maxRangeSpan: 7, // once a start is picked, cap the window at 7 days
59
+ * rangeAnchor, // the in-progress start (null before the first click)
39
60
  * dateConstraints: [
40
61
  * (date) => date.getDay() !== 0, // No Sundays (receives native Date)
41
62
  * ],