@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.
- package/CHANGELOG.md +39 -0
- package/dist/BottomSheet/BottomSheet.d.ts +11 -2
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +4 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts +5 -2
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +56 -13
- package/dist/BottomSheet/index.d.ts +1 -1
- package/dist/BottomSheet/index.d.ts.map +1 -1
- package/dist/BottomSheet/snapOffsets.d.ts +57 -20
- package/dist/BottomSheet/snapOffsets.d.ts.map +1 -1
- package/dist/BottomSheet/snapOffsets.js +106 -30
- package/dist/BottomSheet/useSheetGestures.d.ts +13 -8
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +160 -64
- package/dist/Calendar/Calendar.d.ts +17 -0
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +34 -3
- package/dist/Calendar/getStandaloneShortWeekdayNames.d.ts +14 -0
- package/dist/Calendar/getStandaloneShortWeekdayNames.d.ts.map +1 -0
- package/dist/Calendar/getStandaloneShortWeekdayNames.js +26 -0
- package/dist/Calendar/hooks/useCalendarConstraints.d.ts +22 -1
- package/dist/Calendar/hooks/useCalendarConstraints.d.ts.map +1 -1
- package/dist/Calendar/hooks/useCalendarConstraints.js +26 -4
- package/dist/Calendar/hooks/useCalendarDays.d.ts.map +1 -1
- package/dist/Calendar/hooks/useCalendarDays.js +14 -10
- package/dist/Calendar/standaloneShortWeekdayNames.generated.d.ts +47 -0
- package/dist/Calendar/standaloneShortWeekdayNames.generated.d.ts.map +1 -0
- package/dist/Calendar/standaloneShortWeekdayNames.generated.js +40 -0
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +9 -0
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +6 -1
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +6 -1
- package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +38 -3
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +7 -2
- package/dist/Field/FieldLabel.d.ts +2 -1
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +21 -3
- package/dist/Field/InputClearButton.d.ts +2 -1
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +6 -1
- package/dist/FormLayout/FormLayout.d.ts +24 -2
- package/dist/FormLayout/FormLayout.d.ts.map +1 -1
- package/dist/FormLayout/FormLayout.js +7 -2
- package/dist/FormLayout/FormLayoutContext.d.ts +15 -2
- package/dist/FormLayout/FormLayoutContext.d.ts.map +1 -1
- package/dist/FormLayout/FormLayoutContext.js +19 -4
- package/dist/FormLayout/index.d.ts +1 -1
- package/dist/FormLayout/index.d.ts.map +1 -1
- package/dist/Markdown/Markdown.d.ts +8 -0
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +31 -3
- package/dist/Markdown/parser.d.ts +14 -2
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +50 -2
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +6 -1
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +6 -1
- package/dist/Outline/parseOutlineFromMarkdown.d.ts +2 -0
- package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.js +5 -31
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +12 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +6 -1
- package/dist/StatusDot/StatusDot.d.ts +43 -3
- package/dist/StatusDot/StatusDot.d.ts.map +1 -1
- package/dist/StatusDot/StatusDot.js +42 -5
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +9 -0
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +6 -1
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +6 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +6 -1
- package/dist/hooks/useResolvedRequired.d.ts +19 -0
- package/dist/hooks/useResolvedRequired.d.ts.map +1 -0
- package/dist/hooks/useResolvedRequired.js +40 -0
- package/dist/utils/plainDate.d.ts +6 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +12 -0
- package/locales/af-ZA.json +958 -0
- package/locales/ar-SA.json +998 -0
- package/locales/ca-ES.json +974 -0
- package/locales/cs-CZ.json +990 -0
- package/locales/da-DK.json +966 -0
- package/locales/de-DE.json +966 -0
- package/locales/el-GR.json +990 -0
- package/locales/en.json +4 -0
- package/locales/es-ES.json +978 -0
- package/locales/fi-FI.json +990 -0
- package/locales/fr-FR.json +928 -0
- package/locales/he-IL.json +990 -0
- package/locales/hu-HU.json +990 -0
- package/locales/it-IT.json +978 -0
- package/locales/ja-JP.json +998 -0
- package/locales/ko-KR.json +990 -0
- package/locales/nl-NL.json +954 -0
- package/locales/no-NO.json +978 -0
- package/locales/pl-PL.json +982 -0
- package/locales/pseudo.json +3 -0
- package/locales/pt-BR.json +982 -0
- package/locales/pt-PT.json +982 -0
- package/locales/ro-RO.json +982 -0
- package/locales/ru-RU.json +990 -0
- package/locales/sr-SP.json +990 -0
- package/locales/sv-SE.json +986 -0
- package/locales/tr-TR.json +990 -0
- package/locales/uk-UA.json +990 -0
- package/locales/vi-VN.json +986 -0
- package/locales/zh-CN.json +998 -0
- package/locales/zh-TW.json +998 -0
- package/package.json +3 -3
- package/src/BottomSheet/BottomSheet.doc.mjs +36 -3
- package/src/BottomSheet/BottomSheet.test.tsx +153 -2
- package/src/BottomSheet/BottomSheet.tsx +16 -2
- package/src/BottomSheet/BottomSheetPanel.tsx +67 -13
- package/src/BottomSheet/index.ts +5 -1
- package/src/BottomSheet/snapOffsets.test.ts +114 -47
- package/src/BottomSheet/snapOffsets.ts +119 -33
- package/src/BottomSheet/useSheetGestures.test.ts +113 -15
- package/src/BottomSheet/useSheetGestures.ts +196 -100
- package/src/Calendar/Calendar.doc.mjs +16 -0
- package/src/Calendar/Calendar.test.tsx +137 -0
- package/src/Calendar/Calendar.tsx +65 -2
- package/src/Calendar/getStandaloneShortWeekdayNames.test.ts +54 -0
- package/src/Calendar/getStandaloneShortWeekdayNames.ts +38 -0
- package/src/Calendar/hooks/useCalendarConstraints.ts +54 -3
- package/src/Calendar/hooks/useCalendarDays.ts +13 -10
- package/src/Calendar/standaloneShortWeekdayNames.generated.ts +50 -0
- package/src/CheckboxInput/CheckboxInput.tsx +6 -0
- package/src/ComplexSelector/ComplexSelector.tsx +3 -1
- package/src/DateInput/DateInput.tsx +3 -1
- package/src/DateRangeInput/DateRangeInput.doc.mjs +16 -0
- package/src/DateRangeInput/DateRangeInput.test.tsx +81 -1
- package/src/DateRangeInput/DateRangeInput.tsx +72 -1
- package/src/DateTimeInput/DateTimeInput.tsx +4 -2
- package/src/Field/Field.doc.mjs +1 -0
- package/src/Field/FieldLabel.tsx +20 -4
- package/src/Field/InputClearButton.test.tsx +25 -0
- package/src/Field/InputClearButton.tsx +4 -1
- package/src/FormLayout/FormLayout.doc.mjs +13 -0
- package/src/FormLayout/FormLayout.test.tsx +181 -4
- package/src/FormLayout/FormLayout.tsx +33 -2
- package/src/FormLayout/FormLayoutContext.ts +22 -7
- package/src/FormLayout/index.ts +1 -1
- package/src/Markdown/Markdown.doc.mjs +3 -0
- package/src/Markdown/Markdown.test.tsx +78 -0
- package/src/Markdown/Markdown.tsx +41 -1
- package/src/Markdown/parser.ts +60 -2
- package/src/MultiSelector/MultiSelector.tsx +3 -1
- package/src/NumberInput/NumberInput.tsx +3 -1
- package/src/Outline/Outline.doc.mjs +2 -0
- package/src/Outline/parseOutlineFromMarkdown.ts +10 -40
- package/src/RadioList/RadioList.tsx +9 -1
- package/src/Selector/Selector.tsx +3 -1
- package/src/StatusDot/StatusDot.doc.mjs +13 -0
- package/src/StatusDot/StatusDot.test.tsx +115 -8
- package/src/StatusDot/StatusDot.tsx +74 -9
- package/src/Switch/Switch.tsx +6 -0
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +20 -2
- package/src/TextArea/TextArea.tsx +3 -1
- package/src/TextInput/TextInput.tsx +3 -1
- package/src/TimeInput/TimeInput.tsx +3 -1
- package/src/hooks/useResolvedRequired.ts +42 -0
- package/src/utils/plainDate.test.ts +50 -0
- 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.
|
|
19
|
-
*
|
|
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
|
|
22
|
-
* sheet at rest re-resolves its detent on `resize` / `orientationchange
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
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
|
|
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":"AAIA
|
|
1
|
+
{"version":3,"file":"useSheetGestures.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/useSheetGestures.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,UAAU,IAAI,eAAe,EAClC,KAAK,YAAY,IAAI,iBAAiB,EACtC,KAAK,SAAS,EACd,KAAK,OAAO,IAAI,YAAY,EAC7B,MAAM,OAAO,CAAC;AAgIf,MAAM,WAAW,uBAAuB;IACtC,0EAA0E;IAC1E,MAAM,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gFAAgF;IAChF,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,MAAM,MAAM,EAAE,CAAC;IAC7B,oEAAoE;IACpE,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,CAAC;IACrB,aAAa,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAChD,oBAAoB,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACzD,aAAa,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,aAAa,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,WAAW,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAChD,eAAe,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAChD,oBAAoB,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACzD,aAAa,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,aAAa,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,WAAW,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAChD,eAAe,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACpD,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;CACtD;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7C,0EAA0E;IAC1E,YAAY,EAAE,iBAAiB,CAAC;IAChC,gEAAgE;IAChE,WAAW,EAAE,gBAAgB,CAAC;IAC9B;;;;OAIG;IACH,SAAS,EAAE,cAAc,CAAC;IAC1B;;;;;OAKG;IACH,cAAc,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC9C,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,UAAU,EAAE,OAAO,CAAC;IACpB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,uBAAuB,EAAE,MAAM,CAAC;IAChC,qEAAqE;IACrE,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC;;;;;;;OAOG;IACH,wBAAwB,EAAE,MAAM,IAAI,CAAC;IACrC;;;;OAIG;IACH,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AASD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,UAAiB,EACjB,sBAA0B,EAC1B,SAAS,EACT,WAAW,EACX,MAAM,EACN,cAAc,GACf,EAAE,uBAAuB,GAAG,sBAAsB,CAmoClD"}
|
|
@@ -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.
|
|
23
|
-
*
|
|
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
|
|
26
|
-
* sheet at rest re-resolves its detent on `resize` / `orientationchange
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
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,
|
|
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
|
|
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
|
|
353
|
-
// the border-box portion reserved below the
|
|
354
|
-
// candidate visible heights; otherwise every
|
|
355
|
-
// too low. Snap heights are resolved lazily so
|
|
356
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
399
|
-
|
|
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',
|
|
431
|
-
window.removeEventListener('orientationchange',
|
|
469
|
+
window.removeEventListener('resize', reanchorToSettledDetent);
|
|
470
|
+
window.removeEventListener('orientationchange', reanchorToSettledDetent);
|
|
432
471
|
};
|
|
433
|
-
}, [
|
|
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
|
|
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
|
-
}, [
|
|
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
|
|
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 =
|
|
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
|
-
}, [
|
|
587
|
+
}, [prepareScrollAreaSettle, recordSettledLayoutOffset, resolveDetents]);
|
|
530
588
|
const beginDrag = useCallback((event, sheetHeight, startCoord) => {
|
|
531
589
|
const target = event.currentTarget;
|
|
532
|
-
|
|
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
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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,+
|
|
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
|
-
}),
|
|
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
|
-
},
|
|
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
|
* ],
|