@scalewing/react 1.39.0 → 1.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +102 -0
- package/dist/calendar-month.d.ts +6 -0
- package/dist/calendar-month.js +15 -0
- package/dist/components/DateRangeButton.d.ts +65 -0
- package/dist/components/DateRangeButton.js +54 -0
- package/dist/components/ListGroup.d.ts +33 -0
- package/dist/components/ListGroup.js +19 -0
- package/dist/components/ListRow.d.ts +51 -0
- package/dist/components/ListRow.js +59 -0
- package/dist/components/NavCount.d.ts +21 -0
- package/dist/components/NavCount.js +23 -0
- package/dist/components/SectionNav.js +1 -8
- package/dist/components/StatTile.d.ts +11 -4
- package/dist/components/StatTile.js +11 -4
- package/dist/components/{date-field → calendar}/CalendarGrid.d.ts +17 -2
- package/dist/components/{date-field → calendar}/CalendarGrid.js +11 -3
- package/dist/components/calendar/CalendarPopover.d.ts +25 -0
- package/dist/components/calendar/CalendarPopover.js +22 -0
- package/dist/components/calendar/MonthStep.d.ts +16 -0
- package/dist/components/calendar/MonthStep.js +17 -0
- package/dist/components/calendar/use-grid-focus.d.ts +18 -0
- package/dist/components/calendar/use-grid-focus.js +35 -0
- package/dist/components/calendar/wrap-tab.d.ts +7 -0
- package/dist/components/calendar/wrap-tab.js +29 -0
- package/dist/components/date-field/CalendarDialog.js +9 -67
- package/dist/components/date-field/CalendarHeader.js +2 -11
- package/dist/components/date-range/DateRangePopover.d.ts +7 -0
- package/dist/components/date-range/DateRangePopover.js +17 -0
- package/dist/components/date-range/DateRangeSheet.d.ts +7 -0
- package/dist/components/date-range/DateRangeSheet.js +15 -0
- package/dist/components/date-range/RangeMonths.d.ts +28 -0
- package/dist/components/date-range/RangeMonths.js +50 -0
- package/dist/components/date-range/RangePresets.d.ts +16 -0
- package/dist/components/date-range/RangePresets.js +10 -0
- package/dist/components/date-range/RangeSummary.d.ts +15 -0
- package/dist/components/date-range/RangeSummary.js +11 -0
- package/dist/components/date-range/range-panel-props.d.ts +31 -0
- package/dist/components/date-range/range-panel-props.js +1 -0
- package/dist/components/date-range/use-range-popup.d.ts +21 -0
- package/dist/components/date-range/use-range-popup.js +53 -0
- package/dist/components/link-press.d.ts +7 -0
- package/dist/components/link-press.js +13 -0
- package/dist/components/list/ListRowAccessoryMark.d.ts +5 -0
- package/dist/components/list/ListRowAccessoryMark.js +7 -0
- package/dist/components/list/ListRowContent.d.ts +20 -0
- package/dist/components/list/ListRowContent.js +10 -0
- package/dist/components/list/list-row-container.d.ts +5 -0
- package/dist/components/list/list-row-container.js +6 -0
- package/dist/components/use-media-query.d.ts +5 -0
- package/dist/components/use-media-query.js +18 -0
- package/dist/css/chevron.d.ts +6 -0
- package/dist/css/chevron.js +14 -0
- package/dist/css/css-button-group-attached.js +8 -4
- package/dist/css/css-button-group-track.js +1 -1
- package/dist/css/css-checkbox.js +2 -2
- package/dist/css/css-components.js +9 -0
- package/dist/css/css-count-pill.d.ts +2 -0
- package/dist/css/css-count-pill.js +54 -0
- package/dist/css/css-date-field.js +3 -2
- package/dist/css/css-date-range-presets.d.ts +2 -0
- package/dist/css/css-date-range-presets.js +109 -0
- package/dist/css/css-date-range.d.ts +2 -0
- package/dist/css/css-date-range.js +197 -0
- package/dist/css/css-list-row.d.ts +2 -0
- package/dist/css/css-list-row.js +193 -0
- package/dist/css/css-list.d.ts +3 -0
- package/dist/css/css-list.js +85 -0
- package/dist/css/css-scroll-region.d.ts +18 -0
- package/dist/css/css-scroll-region.js +41 -0
- package/dist/css/css-section-nav-filled.js +2 -25
- package/dist/css/css-stat-tile.d.ts +7 -0
- package/dist/css/css-stat-tile.js +16 -0
- package/dist/css/css-steps.js +2 -2
- package/dist/css/css-table-bounded-edges.js +4 -27
- package/dist/css/range-band.d.ts +2 -0
- package/dist/css/range-band.js +7 -0
- package/dist/css/stacking.js +2 -2
- package/dist/css/stylesheet.js +6 -0
- package/dist/date-range-draft.d.ts +26 -0
- package/dist/date-range-draft.js +36 -0
- package/dist/date-range-input.d.ts +26 -0
- package/dist/date-range-input.js +58 -0
- package/dist/date-range-labels.d.ts +22 -0
- package/dist/date-range-labels.js +26 -0
- package/dist/date-range-view.d.ts +12 -0
- package/dist/date-range-view.js +29 -0
- package/dist/date-range-words.d.ts +11 -0
- package/dist/date-range-words.js +40 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/list-options.d.ts +70 -0
- package/dist/list-options.js +77 -0
- package/dist/styles.css +507 -26
- package/package.json +1 -1
|
@@ -13,7 +13,22 @@ export type CalendarGridProps = {
|
|
|
13
13
|
onSelect: (value: string) => void;
|
|
14
14
|
/** Days to tint as a span, such as the period a page shows. */
|
|
15
15
|
range?: DateOnlyRange;
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* The selected day; `''` for none. A range instead selects every day in
|
|
18
|
+
* it (the grid is then multiselectable) and tints it as `range` does.
|
|
19
|
+
*/
|
|
20
|
+
selected: string | DateOnlyRange;
|
|
21
|
+
/**
|
|
22
|
+
* The last day a press may pick when it is earlier than `max`, such as a
|
|
23
|
+
* range's longest end. Later days are `aria-disabled`; keys still reach
|
|
24
|
+
* them up to `max`, for orientation.
|
|
25
|
+
*/
|
|
26
|
+
latest?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Leave the neighbouring months' days blank, so months shown side by
|
|
29
|
+
* side hold each day once. By default they are shown, muted.
|
|
30
|
+
*/
|
|
31
|
+
blankOutside?: boolean;
|
|
17
32
|
titleId: string;
|
|
18
33
|
today: string;
|
|
19
34
|
weekStartsOn: WeekStart;
|
|
@@ -23,4 +38,4 @@ export type CalendarGridProps = {
|
|
|
23
38
|
* `[min, max]` stay focusable for orientation but are `aria-disabled`;
|
|
24
39
|
* padding past the four-digit years is blank and inert.
|
|
25
40
|
*/
|
|
26
|
-
export declare function CalendarGrid({ focusDate, locale, max, min, month, onFocusDate, onMoveFocus, onSelect, range, selected, titleId, today, weekStartsOn, }: CalendarGridProps): import("react").JSX.Element;
|
|
41
|
+
export declare function CalendarGrid({ focusDate, locale, max, min, month, onFocusDate, onMoveFocus, onSelect, range, selected, latest, blankOutside, titleId, today, weekStartsOn, }: CalendarGridProps): import("react").JSX.Element;
|
|
@@ -12,14 +12,22 @@ function rangeClassNames(place) {
|
|
|
12
12
|
(place === 'end' || place === 'only') && 'sw-date-field-day-range-end',
|
|
13
13
|
];
|
|
14
14
|
}
|
|
15
|
+
function isSelectedDay(value, selected) {
|
|
16
|
+
return typeof selected === 'string'
|
|
17
|
+
? value === selected
|
|
18
|
+
: dayInRange(value, selected) !== null;
|
|
19
|
+
}
|
|
15
20
|
/**
|
|
16
21
|
* The month as a WAI-ARIA grid with one roving tab stop. Days outside
|
|
17
22
|
* `[min, max]` stay focusable for orientation but are `aria-disabled`;
|
|
18
23
|
* padding past the four-digit years is blank and inert.
|
|
19
24
|
*/
|
|
20
|
-
export function CalendarGrid({ focusDate, locale, max, min, month, onFocusDate, onMoveFocus, onSelect, range, selected, titleId, today, weekStartsOn, }) {
|
|
25
|
+
export function CalendarGrid({ focusDate, locale, max, min, month, onFocusDate, onMoveFocus, onSelect, range, selected, latest, blankOutside = false, titleId, today, weekStartsOn, }) {
|
|
26
|
+
const pickMax = latest !== undefined && (max === undefined || latest < max) ? latest : max;
|
|
27
|
+
const tint = typeof selected === 'string' ? range : selected;
|
|
28
|
+
const unavailable = (value) => isOutsideDateRange(value, min, pickMax);
|
|
21
29
|
function choose(value) {
|
|
22
|
-
if (!
|
|
30
|
+
if (!unavailable(value))
|
|
23
31
|
onSelect(value);
|
|
24
32
|
}
|
|
25
33
|
function onKeyDown(event) {
|
|
@@ -35,7 +43,7 @@ export function CalendarGrid({ focusDate, locale, max, min, month, onFocusDate,
|
|
|
35
43
|
event.preventDefault();
|
|
36
44
|
onMoveFocus(target);
|
|
37
45
|
}
|
|
38
|
-
return (_jsxs("table", { "aria-labelledby": titleId, className: "sw-date-field-grid", onKeyDown: onKeyDown, role: "grid", children: [_jsx("thead", { children: _jsx("tr", { children: weekdayLabels(locale, weekStartsOn).map((weekday) => (_jsx("th", { abbr: weekday.long, scope: "col", children: weekday.short }, weekday.long))) }) }), _jsx("tbody", { children: monthGrid(month, weekStartsOn).map((row, rowIndex) => (_jsx("tr", { children: row.map((cell, column) => cell === null ? (_jsx("td", { "aria-disabled": "true", className: "sw-date-field-day sw-date-field-day-outside", role: "gridcell" }, `blank-${column}`)) : (_jsx("td", { "aria-current": cell.value === today ? 'date' : undefined, "aria-disabled":
|
|
46
|
+
return (_jsxs("table", { "aria-labelledby": titleId, "aria-multiselectable": typeof selected === 'string' ? undefined : true, className: "sw-date-field-grid", onKeyDown: onKeyDown, role: "grid", children: [_jsx("thead", { children: _jsx("tr", { children: weekdayLabels(locale, weekStartsOn).map((weekday) => (_jsx("th", { abbr: weekday.long, scope: "col", children: weekday.short }, weekday.long))) }) }), _jsx("tbody", { children: monthGrid(month, weekStartsOn).map((row, rowIndex) => (_jsx("tr", { children: row.map((cell, column) => cell === null || (blankOutside && !cell.inMonth) ? (_jsx("td", { "aria-disabled": "true", className: "sw-date-field-day sw-date-field-day-outside", role: "gridcell" }, `blank-${column}`)) : (_jsx("td", { "aria-current": cell.value === today ? 'date' : undefined, "aria-disabled": unavailable(cell.value) || undefined, "aria-label": formatDateLabel(cell.value, locale), "aria-selected": isSelectedDay(cell.value, selected), className: cx('sw-date-field-day', !cell.inMonth && 'sw-date-field-day-outside', ...rangeClassNames(dayInRange(cell.value, tint))), "data-date": cell.value, onClick: () => choose(cell.value), onFocus: () => {
|
|
39
47
|
// A press on a neighboring month's day selects it; moving
|
|
40
48
|
// the grid first would pull the day out from under it.
|
|
41
49
|
if (cell.inMonth && cell.value !== focusDate)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type ReactNode, type RefObject } from 'react';
|
|
2
|
+
import { type GridFocus } from './use-grid-focus.js';
|
|
3
|
+
export type CalendarPopoverProps = {
|
|
4
|
+
/** The element the popover sits beside, such as the field or button. */
|
|
5
|
+
anchorRef: RefObject<HTMLElement | null>;
|
|
6
|
+
/** The button that toggles it; a press there is not a press outside. */
|
|
7
|
+
buttonRef: RefObject<HTMLElement | null>;
|
|
8
|
+
popoverRef: RefObject<HTMLDivElement | null>;
|
|
9
|
+
id: string;
|
|
10
|
+
/** Names the dialog by an element, or by `aria-label`. */
|
|
11
|
+
'aria-labelledby'?: string;
|
|
12
|
+
'aria-label'?: string;
|
|
13
|
+
/** `true` returns focus to the button: Escape does, a press outside does not. */
|
|
14
|
+
onClose: (restoreFocus: boolean) => void;
|
|
15
|
+
/** Follows focus in the grid when the popover itself holds it. */
|
|
16
|
+
focusHandlers?: GridFocus['focusHandlers'];
|
|
17
|
+
className?: string;
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A calendar's popover: a modal dialog on the top layer beside its anchor,
|
|
22
|
+
* as the WAI-ARIA date picker dialog pattern asks. Escape closes it and
|
|
23
|
+
* returns focus; Tab stays inside; a press outside closes it.
|
|
24
|
+
*/
|
|
25
|
+
export declare function CalendarPopover({ anchorRef, buttonRef, popoverRef, id, 'aria-labelledby': labelledBy, 'aria-label': label, onClose, focusHandlers, className, children, }: CalendarPopoverProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../../class-names.js';
|
|
3
|
+
import { useAnchoredPopover } from '../use-anchored-popover.js';
|
|
4
|
+
import { wrapTab } from './wrap-tab.js';
|
|
5
|
+
/**
|
|
6
|
+
* A calendar's popover: a modal dialog on the top layer beside its anchor,
|
|
7
|
+
* as the WAI-ARIA date picker dialog pattern asks. Escape closes it and
|
|
8
|
+
* returns focus; Tab stays inside; a press outside closes it.
|
|
9
|
+
*/
|
|
10
|
+
export function CalendarPopover({ anchorRef, buttonRef, popoverRef, id, 'aria-labelledby': labelledBy, 'aria-label': label, onClose, focusHandlers, className, children, }) {
|
|
11
|
+
useAnchoredPopover(popoverRef, anchorRef, buttonRef, () => onClose(false));
|
|
12
|
+
function onKeyDown(event) {
|
|
13
|
+
if (event.key === 'Escape') {
|
|
14
|
+
event.preventDefault();
|
|
15
|
+
event.stopPropagation();
|
|
16
|
+
onClose(true);
|
|
17
|
+
}
|
|
18
|
+
else if (event.key === 'Tab')
|
|
19
|
+
wrapTab(event);
|
|
20
|
+
}
|
|
21
|
+
return (_jsx("div", { ...focusHandlers, "aria-label": label, "aria-labelledby": labelledBy, "aria-modal": "true", className: cx('sw-date-field-calendar', className), id: id, onKeyDown: onKeyDown, ref: popoverRef, role: "dialog", children: children }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type CalendarMonth } from '../../calendar-month.js';
|
|
2
|
+
export type MonthStepProps = {
|
|
3
|
+
/** The month the step leaves from: the first or the last month shown. */
|
|
4
|
+
month: CalendarMonth;
|
|
5
|
+
delta: 1 | -1;
|
|
6
|
+
label: string;
|
|
7
|
+
min?: string;
|
|
8
|
+
max?: string;
|
|
9
|
+
onMonthChange: (month: CalendarMonth) => void;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* A calendar's previous or next month step. Past the four-digit years or
|
|
13
|
+
* outside `[min, max]` it is `aria-disabled`, so it keeps focus and its
|
|
14
|
+
* press does nothing.
|
|
15
|
+
*/
|
|
16
|
+
export declare function MonthStep({ delta, label, max, min, month, onMonthChange, }: MonthStepProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { monthInRange, shiftMonth, } from '../../calendar-month.js';
|
|
3
|
+
import { tabStop } from '../../tab-stop.js';
|
|
4
|
+
import { DateFieldGlyph } from '../date-field/DateFieldGlyph.js';
|
|
5
|
+
/**
|
|
6
|
+
* A calendar's previous or next month step. Past the four-digit years or
|
|
7
|
+
* outside `[min, max]` it is `aria-disabled`, so it keeps focus and its
|
|
8
|
+
* press does nothing.
|
|
9
|
+
*/
|
|
10
|
+
export function MonthStep({ delta, label, max, min, month, onMonthChange, }) {
|
|
11
|
+
const target = shiftMonth(month, delta);
|
|
12
|
+
const unavailable = target.year < 1 || target.year > 9999 || !monthInRange(target, min, max);
|
|
13
|
+
return (_jsx("button", { "aria-disabled": unavailable || undefined, "aria-label": label, className: "sw-date-field-step", onClick: () => {
|
|
14
|
+
if (!unavailable)
|
|
15
|
+
onMonthChange(target);
|
|
16
|
+
}, tabIndex: tabStop, type: "button", children: _jsx(DateFieldGlyph, { name: delta < 0 ? 'previous' : 'next' }) }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type FocusEvent, type RefObject } from 'react';
|
|
2
|
+
export type GridFocus = {
|
|
3
|
+
/** The next focus date takes DOM focus, as after a grid key. */
|
|
4
|
+
requestFocus: () => void;
|
|
5
|
+
/** Spread on the element holding the grids, to follow where focus is. */
|
|
6
|
+
focusHandlers: {
|
|
7
|
+
onBlur: () => void;
|
|
8
|
+
onFocus: (event: FocusEvent<HTMLElement>) => void;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Moves DOM focus to a calendar's focus date when asked (on mount when
|
|
13
|
+
* `focusOnMount`, and after a grid key) and while a day already holds
|
|
14
|
+
* focus, so a new value or new bounds never strand focus on a removed day.
|
|
15
|
+
* Header controls and presses change the date without taking focus from
|
|
16
|
+
* where it is. Shared by DateField's calendar and DateRangeButton.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useGridFocus(containerRef: RefObject<HTMLElement | null>, focusDate: string, focusOnMount: boolean): GridFocus;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef, } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Moves DOM focus to a calendar's focus date when asked (on mount when
|
|
4
|
+
* `focusOnMount`, and after a grid key) and while a day already holds
|
|
5
|
+
* focus, so a new value or new bounds never strand focus on a removed day.
|
|
6
|
+
* Header controls and presses change the date without taking focus from
|
|
7
|
+
* where it is. Shared by DateField's calendar and DateRangeButton.
|
|
8
|
+
*/
|
|
9
|
+
export function useGridFocus(containerRef, focusDate, focusOnMount) {
|
|
10
|
+
const focusGrid = useRef(focusOnMount);
|
|
11
|
+
// Whether a day holds DOM focus. Removing that day fires no blur, so this
|
|
12
|
+
// stays true when a new value or bounds move the grid out from under it.
|
|
13
|
+
const gridFocused = useRef(false);
|
|
14
|
+
useLayoutEffect(() => {
|
|
15
|
+
if (!focusGrid.current && !gridFocused.current)
|
|
16
|
+
return;
|
|
17
|
+
focusGrid.current = false;
|
|
18
|
+
containerRef.current
|
|
19
|
+
?.querySelector(`[role="grid"] [data-date="${focusDate}"]`)
|
|
20
|
+
?.focus({ preventScroll: true });
|
|
21
|
+
}, [containerRef, focusDate]);
|
|
22
|
+
return {
|
|
23
|
+
requestFocus: () => {
|
|
24
|
+
focusGrid.current = true;
|
|
25
|
+
},
|
|
26
|
+
focusHandlers: {
|
|
27
|
+
onBlur: () => {
|
|
28
|
+
gridFocused.current = false;
|
|
29
|
+
},
|
|
30
|
+
onFocus: (event) => {
|
|
31
|
+
gridFocused.current = event.target.closest('[role="grid"]') !== null;
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type KeyboardEvent } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Keeps Tab and Shift+Tab inside a calendar popover, as a modal dialog
|
|
4
|
+
* does: from the last stop to the first, and back. Shared by DateField's
|
|
5
|
+
* calendar and DateRangeButton's popover.
|
|
6
|
+
*/
|
|
7
|
+
export declare function wrapTab(event: KeyboardEvent<HTMLElement>): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** A calendar popover's tab stops: enabled buttons and a grid's roving day. */
|
|
2
|
+
function isTabStop(node) {
|
|
3
|
+
if (node instanceof HTMLButtonElement)
|
|
4
|
+
return !node.disabled;
|
|
5
|
+
return node.getAttribute('tabindex') === '0';
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Keeps Tab and Shift+Tab inside a calendar popover, as a modal dialog
|
|
9
|
+
* does: from the last stop to the first, and back. Shared by DateField's
|
|
10
|
+
* calendar and DateRangeButton's popover.
|
|
11
|
+
*/
|
|
12
|
+
export function wrapTab(event) {
|
|
13
|
+
// One universal selector keeps document order in every DOM implementation.
|
|
14
|
+
const items = [
|
|
15
|
+
...event.currentTarget.querySelectorAll('*'),
|
|
16
|
+
].filter(isTabStop);
|
|
17
|
+
const first = items[0];
|
|
18
|
+
const last = items[items.length - 1];
|
|
19
|
+
if (!first || !last)
|
|
20
|
+
return;
|
|
21
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
last.focus();
|
|
24
|
+
}
|
|
25
|
+
else if (!event.shiftKey && document.activeElement === last) {
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
first.focus();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,43 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useId,
|
|
3
|
-
import { monthOf, } from '../../calendar-month.js';
|
|
4
|
-
import { clampDateOnly,
|
|
2
|
+
import { useId, useRef, useState } from 'react';
|
|
3
|
+
import { monthOf, sameDayIn } from '../../calendar-month.js';
|
|
4
|
+
import { clampDateOnly, isOutsideDateRange, } from '../../date-only.js';
|
|
5
5
|
import { Button } from '../Button.js';
|
|
6
|
-
import { CalendarGrid } from '
|
|
6
|
+
import { CalendarGrid } from '../calendar/CalendarGrid.js';
|
|
7
7
|
import { CalendarHeader } from './CalendarHeader.js';
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
function isTabStop(node) {
|
|
11
|
-
if (node instanceof HTMLButtonElement)
|
|
12
|
-
return !node.disabled;
|
|
13
|
-
return node.getAttribute('tabindex') === '0';
|
|
14
|
-
}
|
|
15
|
-
/** Keeps Tab and Shift+Tab inside the dialog, as a modal dialog does. */
|
|
16
|
-
function wrapTab(event) {
|
|
17
|
-
// One universal selector keeps document order in every DOM implementation.
|
|
18
|
-
const items = [
|
|
19
|
-
...event.currentTarget.querySelectorAll('*'),
|
|
20
|
-
].filter(isTabStop);
|
|
21
|
-
const first = items[0];
|
|
22
|
-
const last = items[items.length - 1];
|
|
23
|
-
if (!first || !last)
|
|
24
|
-
return;
|
|
25
|
-
if (event.shiftKey && document.activeElement === first) {
|
|
26
|
-
event.preventDefault();
|
|
27
|
-
last.focus();
|
|
28
|
-
}
|
|
29
|
-
else if (!event.shiftKey && document.activeElement === last) {
|
|
30
|
-
event.preventDefault();
|
|
31
|
-
first.focus();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
function sameDayIn(month, focused) {
|
|
35
|
-
const day = parseDateOnly(focused)?.day ?? 1;
|
|
36
|
-
return formatDateOnly({
|
|
37
|
-
...month,
|
|
38
|
-
day: Math.min(day, daysInMonth(month.year, month.month)),
|
|
39
|
-
});
|
|
40
|
-
}
|
|
8
|
+
import { CalendarPopover } from '../calendar/CalendarPopover.js';
|
|
9
|
+
import { useGridFocus } from '../calendar/use-grid-focus.js';
|
|
41
10
|
/**
|
|
42
11
|
* The calendar popover: a labelled modal dialog anchored under the field,
|
|
43
12
|
* following the WAI-ARIA date picker dialog pattern. It opens on the
|
|
@@ -57,37 +26,10 @@ export function CalendarDialog({ anchorRef, buttonRef, id, labelId, labels, loca
|
|
|
57
26
|
// Clamped on every render, so new bounds while open move it inside.
|
|
58
27
|
const focusDate = clampDateOnly(rawFocusDate, min, max);
|
|
59
28
|
const month = monthOf(focusDate);
|
|
60
|
-
|
|
61
|
-
// and presses change the date without taking focus from where it is.
|
|
62
|
-
const focusGrid = useRef(true);
|
|
63
|
-
useAnchoredPopover(dialogRef, anchorRef, buttonRef, () => onClose(false));
|
|
64
|
-
// Whether a day holds DOM focus. Removing that day fires no blur, so this
|
|
65
|
-
// stays true when a new value or bounds move the grid out from under it.
|
|
66
|
-
const gridFocused = useRef(false);
|
|
67
|
-
useLayoutEffect(() => {
|
|
68
|
-
if (!focusGrid.current && !gridFocused.current)
|
|
69
|
-
return;
|
|
70
|
-
focusGrid.current = false;
|
|
71
|
-
dialogRef.current
|
|
72
|
-
?.querySelector(`[role="grid"] [data-date="${focusDate}"]`)
|
|
73
|
-
?.focus({ preventScroll: true });
|
|
74
|
-
}, [focusDate]);
|
|
75
|
-
function onKeyDown(event) {
|
|
76
|
-
if (event.key === 'Escape') {
|
|
77
|
-
event.preventDefault();
|
|
78
|
-
event.stopPropagation();
|
|
79
|
-
onClose(true);
|
|
80
|
-
}
|
|
81
|
-
else if (event.key === 'Tab')
|
|
82
|
-
wrapTab(event);
|
|
83
|
-
}
|
|
29
|
+
const { requestFocus, focusHandlers } = useGridFocus(dialogRef, focusDate, true);
|
|
84
30
|
const todayUnavailable = isOutsideDateRange(today, min, max);
|
|
85
|
-
return (_jsxs(
|
|
86
|
-
|
|
87
|
-
}, onFocus: (event) => {
|
|
88
|
-
gridFocused.current = event.target.closest('[role="grid"]') !== null;
|
|
89
|
-
}, onKeyDown: onKeyDown, ref: dialogRef, role: "dialog", children: [_jsx(CalendarHeader, { labels: labels, locale: locale, max: max, min: min, month: month, onMonthChange: (next) => setFocusDate(sameDayIn(next, focusDate)), titleId: titleId, todayYear: monthOf(today).year }), _jsx(CalendarGrid, { focusDate: focusDate, locale: locale, max: max, min: min, month: month, onFocusDate: setFocusDate, onMoveFocus: (next) => {
|
|
90
|
-
focusGrid.current = true;
|
|
31
|
+
return (_jsxs(CalendarPopover, { anchorRef: anchorRef, "aria-labelledby": labelId, buttonRef: buttonRef, focusHandlers: focusHandlers, id: id, onClose: onClose, popoverRef: dialogRef, children: [_jsx(CalendarHeader, { labels: labels, locale: locale, max: max, min: min, month: month, onMonthChange: (next) => setFocusDate(sameDayIn(next, focusDate)), titleId: titleId, todayYear: monthOf(today).year }), _jsx(CalendarGrid, { focusDate: focusDate, locale: locale, max: max, min: min, month: month, onFocusDate: setFocusDate, onMoveFocus: (next) => {
|
|
32
|
+
requestFocus();
|
|
91
33
|
setFocusDate(next);
|
|
92
34
|
}, onSelect: onSelect, range: range, selected: value, titleId: titleId, today: today, weekStartsOn: weekStartsOn }), _jsxs("div", { className: "sw-date-field-footer", children: [required ? null : (_jsx(Button, { disabled: value === '', onPress: () => onSelect(''), size: "sm", variant: "ghost", children: labels.clear })), _jsx(Button, { disabled: todayUnavailable, onPress: () => onSelect(today), size: "sm", variant: "ghost", children: labels.today })] })] }));
|
|
93
35
|
}
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { monthNames, monthTitle } from '../../calendar-labels.js';
|
|
3
|
-
import {
|
|
3
|
+
import { monthsInRange } from '../../calendar-month.js';
|
|
4
4
|
import { calendarYearRange, yearsIn } from '../../calendar-years.js';
|
|
5
5
|
import { Select } from '../Select.js';
|
|
6
|
-
import {
|
|
7
|
-
import { tabStop } from '../../tab-stop.js';
|
|
8
|
-
function MonthStep({ delta, label, max, min, month, onMonthChange, }) {
|
|
9
|
-
const target = shiftMonth(month, delta);
|
|
10
|
-
const unavailable = target.year < 1 || target.year > 9999 || !monthInRange(target, min, max);
|
|
11
|
-
return (_jsx("button", { "aria-disabled": unavailable || undefined, "aria-label": label, className: "sw-date-field-step", onClick: () => {
|
|
12
|
-
if (!unavailable)
|
|
13
|
-
onMonthChange(target);
|
|
14
|
-
}, tabIndex: tabStop, type: "button", children: _jsx(DateFieldGlyph, { name: delta < 0 ? 'previous' : 'next' }) }));
|
|
15
|
-
}
|
|
6
|
+
import { MonthStep } from '../calendar/MonthStep.js';
|
|
16
7
|
/**
|
|
17
8
|
* Month stepping plus month and year selectors for jumping far, both cut to
|
|
18
9
|
* `min` and `max`, over a visually hidden title that announces the shown
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type RangePanelProps } from './range-panel-props.js';
|
|
2
|
+
/**
|
|
3
|
+
* DateRangeButton from `md` up: a popover beside the button with the
|
|
4
|
+
* presets, two months, the range being picked, Cancel and Apply. Escape
|
|
5
|
+
* and a press outside cancel.
|
|
6
|
+
*/
|
|
7
|
+
export declare function DateRangePopover(props: RangePanelProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
import { Button } from '../Button.js';
|
|
4
|
+
import { CalendarPopover } from '../calendar/CalendarPopover.js';
|
|
5
|
+
import { RangeMonths } from './RangeMonths.js';
|
|
6
|
+
import { RangePresets } from './RangePresets.js';
|
|
7
|
+
import { RangeSummary } from './RangeSummary.js';
|
|
8
|
+
/**
|
|
9
|
+
* DateRangeButton from `md` up: a popover beside the button with the
|
|
10
|
+
* presets, two months, the range being picked, Cancel and Apply. Escape
|
|
11
|
+
* and a press outside cancel.
|
|
12
|
+
*/
|
|
13
|
+
export function DateRangePopover(props) {
|
|
14
|
+
const popoverRef = useRef(null);
|
|
15
|
+
const { labels, presets } = props;
|
|
16
|
+
return (_jsxs(CalendarPopover, { anchorRef: props.buttonRef, "aria-label": props.label, buttonRef: props.buttonRef, className: "sw-date-range-popover", id: props.id, onClose: props.onCancel, popoverRef: popoverRef, children: [_jsxs("div", { className: "sw-date-range-body", children: [presets.length > 0 ? (_jsx(RangePresets, { label: labels.presets, layout: "list", onChoose: props.onChoosePreset, presets: presets, pressedId: props.pressedPresetId })) : null, _jsx(RangeMonths, { count: 2, draft: props.draft, focusOnMount: true, labels: labels, latest: props.latest, locale: props.locale, max: props.max, min: props.min, onPress: props.onPress, today: props.today, weekStartsOn: props.weekStartsOn })] }), _jsxs("div", { className: "sw-date-range-footer", children: [_jsx(RangeSummary, { count: props.count, hint: labels.hint, line: props.line }), _jsxs("div", { className: "sw-date-range-actions", children: [_jsx(Button, { onPress: () => props.onCancel(true), size: "sm", variant: "neutral", children: labels.cancel }), _jsx(Button, { onPress: props.onApply, size: "sm", children: labels.apply })] })] })] }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type RangePanelProps } from './range-panel-props.js';
|
|
2
|
+
/**
|
|
3
|
+
* DateRangeButton below `md`: a bottom sheet titled by its label, the
|
|
4
|
+
* range being picked under the title, the presets in a row, one month,
|
|
5
|
+
* and the hint over Cancel and Apply. Closing it any other way cancels.
|
|
6
|
+
*/
|
|
7
|
+
export declare function DateRangeSheet(props: RangePanelProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from '../Button.js';
|
|
3
|
+
import { Dialog } from '../Dialog.js';
|
|
4
|
+
import { RangeMonths } from './RangeMonths.js';
|
|
5
|
+
import { RangePresets } from './RangePresets.js';
|
|
6
|
+
import { RangeSummary } from './RangeSummary.js';
|
|
7
|
+
/**
|
|
8
|
+
* DateRangeButton below `md`: a bottom sheet titled by its label, the
|
|
9
|
+
* range being picked under the title, the presets in a row, one month,
|
|
10
|
+
* and the hint over Cancel and Apply. Closing it any other way cancels.
|
|
11
|
+
*/
|
|
12
|
+
export function DateRangeSheet(props) {
|
|
13
|
+
const { labels, presets } = props;
|
|
14
|
+
return (_jsxs(Dialog, { className: "sw-date-range-sheet", closeLabel: labels.close, description: `${props.line} · ${props.count}`, footer: _jsxs("div", { className: "sw-date-range-sheet-footer", children: [_jsx("span", { className: "sw-date-range-hint", children: labels.hint }), _jsxs("div", { className: "sw-date-range-actions", children: [_jsx(Button, { onPress: () => props.onCancel(true), variant: "neutral", children: labels.cancel }), _jsx(Button, { onPress: props.onApply, children: labels.apply })] })] }), id: props.id, onClose: () => props.onCancel(true), open: true, sheetBelow: "md", title: props.label, children: [_jsx(RangeSummary, { count: props.count, line: props.line, visuallyHidden: true }), presets.length > 0 ? (_jsx(RangePresets, { label: labels.presets, layout: "row", onChoose: props.onChoosePreset, presets: presets, pressedId: props.pressedPresetId })) : null, _jsx(RangeMonths, { count: 1, draft: props.draft, focusOnMount: false, labels: labels, latest: props.latest, locale: props.locale, max: props.max, min: props.min, onPress: props.onPress, today: props.today, weekStartsOn: props.weekStartsOn })] }));
|
|
15
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type WeekStart } from '../../calendar-month.js';
|
|
2
|
+
import { type RangeDraft } from '../../date-range-draft.js';
|
|
3
|
+
import { type RangeMonthCount } from '../../date-range-view.js';
|
|
4
|
+
export type RangeMonthsProps = {
|
|
5
|
+
count: RangeMonthCount;
|
|
6
|
+
draft: RangeDraft;
|
|
7
|
+
onPress: (day: string) => void;
|
|
8
|
+
/** The last day a press may pick (`max`, or `maxDays` from the start). */
|
|
9
|
+
latest: string | undefined;
|
|
10
|
+
min?: string;
|
|
11
|
+
max?: string;
|
|
12
|
+
locale: string;
|
|
13
|
+
today: string;
|
|
14
|
+
weekStartsOn: WeekStart;
|
|
15
|
+
labels: {
|
|
16
|
+
previousMonth: string;
|
|
17
|
+
nextMonth: string;
|
|
18
|
+
};
|
|
19
|
+
/** Take DOM focus on the focus day when shown, as a popover does. */
|
|
20
|
+
focusOnMount: boolean;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* One month, or two side by side, of the range being picked: each a
|
|
24
|
+
* labelled grid with one roving day across them, the previous step before
|
|
25
|
+
* the first month and the next step after the last. It opens on the
|
|
26
|
+
* range's end, and the months follow the focus day.
|
|
27
|
+
*/
|
|
28
|
+
export declare function RangeMonths({ count, draft, onPress, latest, min, max, locale, today, weekStartsOn, labels, focusOnMount, }: RangeMonthsProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId, useRef, useState } from 'react';
|
|
3
|
+
import { monthTitle } from '../../calendar-labels.js';
|
|
4
|
+
import { firstOfMonth, lastOfMonth, monthsBetween, sameDayIn, shiftMonth, } from '../../calendar-month.js';
|
|
5
|
+
import { firstShownMonth, shownMonths, } from '../../date-range-view.js';
|
|
6
|
+
import { clampDateOnly } from '../../date-only.js';
|
|
7
|
+
import { CalendarGrid } from '../calendar/CalendarGrid.js';
|
|
8
|
+
import { MonthStep } from '../calendar/MonthStep.js';
|
|
9
|
+
import { useGridFocus } from '../calendar/use-grid-focus.js';
|
|
10
|
+
/** `focusDate` moved into the shown months, after a month step. */
|
|
11
|
+
function keepInView(focusDate, first, count) {
|
|
12
|
+
const months = shownMonths(first, count);
|
|
13
|
+
const last = months[months.length - 1] ?? first;
|
|
14
|
+
if (focusDate < firstOfMonth(first))
|
|
15
|
+
return sameDayIn(first, focusDate);
|
|
16
|
+
if (focusDate > lastOfMonth(last))
|
|
17
|
+
return sameDayIn(last, focusDate);
|
|
18
|
+
return focusDate;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* One month, or two side by side, of the range being picked: each a
|
|
22
|
+
* labelled grid with one roving day across them, the previous step before
|
|
23
|
+
* the first month and the next step after the last. It opens on the
|
|
24
|
+
* range's end, and the months follow the focus day.
|
|
25
|
+
*/
|
|
26
|
+
export function RangeMonths({ count, draft, onPress, latest, min, max, locale, today, weekStartsOn, labels, focusOnMount, }) {
|
|
27
|
+
const id = useId();
|
|
28
|
+
const containerRef = useRef(null);
|
|
29
|
+
const [rawFocusDate, setFocusDate] = useState(draft.range.end);
|
|
30
|
+
// Clamped on every render, so new bounds while open move it inside.
|
|
31
|
+
const focusDate = clampDateOnly(rawFocusDate, min, max);
|
|
32
|
+
const [first, setFirst] = useState(() => firstShownMonth(focusDate, count, undefined, min));
|
|
33
|
+
const shown = firstShownMonth(focusDate, count, first, min);
|
|
34
|
+
if (monthsBetween(first, shown) !== 0)
|
|
35
|
+
setFirst(shown);
|
|
36
|
+
const { requestFocus, focusHandlers } = useGridFocus(containerRef, focusDate, focusOnMount);
|
|
37
|
+
function stepBy(delta) {
|
|
38
|
+
const nextFirst = shiftMonth(shown, delta);
|
|
39
|
+
setFirst(nextFirst);
|
|
40
|
+
setFocusDate(clampDateOnly(keepInView(focusDate, nextFirst, count), min, max));
|
|
41
|
+
}
|
|
42
|
+
const months = shownMonths(shown, count);
|
|
43
|
+
return (_jsxs("div", { className: "sw-date-range-months", ref: containerRef, ...focusHandlers, children: [_jsx("div", { "aria-live": "polite", className: "sw-sr-only", children: months.map((month) => (_jsx("div", { children: monthTitle(month, locale) }, `${month.year}-${month.month}`))) }), months.map((month, index) => {
|
|
44
|
+
const titleId = `${id}-${index}`;
|
|
45
|
+
return (_jsxs("div", { className: "sw-date-range-month", children: [_jsxs("div", { className: "sw-date-range-month-header", children: [index === 0 ? (_jsx(MonthStep, { delta: -1, label: labels.previousMonth, max: max, min: min, month: month, onMonthChange: () => stepBy(-1) })) : null, _jsx("div", { className: "sw-date-range-month-title", id: titleId, children: monthTitle(month, locale) }), index === months.length - 1 ? (_jsx(MonthStep, { delta: 1, label: labels.nextMonth, max: max, min: min, month: month, onMonthChange: () => stepBy(1) })) : null] }), _jsx(CalendarGrid, { blankOutside: true, focusDate: focusDate, latest: latest, locale: locale, max: max, min: min, month: month, onFocusDate: setFocusDate, onMoveFocus: (next) => {
|
|
46
|
+
requestFocus();
|
|
47
|
+
setFocusDate(next);
|
|
48
|
+
}, onSelect: onPress, selected: draft.range, titleId: titleId, today: today, weekStartsOn: weekStartsOn })] }, titleId));
|
|
49
|
+
})] }));
|
|
50
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type DateRangePreset } from '../../date-range-input.js';
|
|
2
|
+
export type RangePresetsProps = {
|
|
3
|
+
presets: readonly DateRangePreset[];
|
|
4
|
+
/** Names the group, such as "Quick ranges". */
|
|
5
|
+
label: string;
|
|
6
|
+
/** The preset whose range is the one shown, if any. */
|
|
7
|
+
pressedId: string | undefined;
|
|
8
|
+
onChoose: (preset: DateRangePreset) => void;
|
|
9
|
+
/** `list` beside the months; `row` above one month, scrolling sideways. */
|
|
10
|
+
layout: 'list' | 'row';
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* DateRangeButton's presets: a named group of buttons, the one matching
|
|
14
|
+
* the range shown `aria-pressed`. A press applies its range at once.
|
|
15
|
+
*/
|
|
16
|
+
export declare function RangePresets({ presets, label, pressedId, onChoose, layout, }: RangePresetsProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../../class-names.js';
|
|
3
|
+
import { tabStop } from '../../tab-stop.js';
|
|
4
|
+
/**
|
|
5
|
+
* DateRangeButton's presets: a named group of buttons, the one matching
|
|
6
|
+
* the range shown `aria-pressed`. A press applies its range at once.
|
|
7
|
+
*/
|
|
8
|
+
export function RangePresets({ presets, label, pressedId, onChoose, layout, }) {
|
|
9
|
+
return (_jsx("div", { "aria-label": label, className: cx('sw-date-range-presets', layout === 'row' && 'sw-date-range-presets-row'), role: "group", children: presets.map((preset) => (_jsx("button", { "aria-pressed": preset.id === pressedId, className: "sw-date-range-preset", onClick: () => onChoose(preset), tabIndex: tabStop, type: "button", children: preset.label }, preset.id))) }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type RangeSummaryProps = {
|
|
2
|
+
/** The range being picked, with its weekdays. */
|
|
3
|
+
line: string;
|
|
4
|
+
/** Its length, such as "7 days". */
|
|
5
|
+
count: string;
|
|
6
|
+
/** How to pick, after the length; left out where the page shows it elsewhere. */
|
|
7
|
+
hint?: string;
|
|
8
|
+
/** Keep the words for assistive technology only, where the page shows them elsewhere. */
|
|
9
|
+
visuallyHidden?: boolean;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The range being picked, in a polite live region so each press is heard:
|
|
13
|
+
* the range on its own line, then its length and the hint.
|
|
14
|
+
*/
|
|
15
|
+
export declare function RangeSummary({ line, count, hint, visuallyHidden, }: RangeSummaryProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The range being picked, in a polite live region so each press is heard:
|
|
4
|
+
* the range on its own line, then its length and the hint.
|
|
5
|
+
*/
|
|
6
|
+
export function RangeSummary({ line, count, hint, visuallyHidden = false, }) {
|
|
7
|
+
const status = (_jsxs("div", { "aria-atomic": "true", className: "sw-date-range-status", role: "status", children: [_jsx("span", { className: "sw-date-range-line", children: line }), ' ', _jsx("span", { className: "sw-date-range-count", children: count })] }));
|
|
8
|
+
if (visuallyHidden)
|
|
9
|
+
return _jsx("div", { className: "sw-sr-only", children: status });
|
|
10
|
+
return (_jsxs("div", { className: "sw-date-range-summary", children: [status, hint === undefined ? null : (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", className: "sw-date-range-separator", children: ' · ' }), _jsx("span", { className: "sw-date-range-hint", children: hint })] }))] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import { type WeekStart } from '../../calendar-month.js';
|
|
3
|
+
import { type RangeDraft } from '../../date-range-draft.js';
|
|
4
|
+
import { type DateRangePreset } from '../../date-range-input.js';
|
|
5
|
+
import { type DateRangeButtonLabels } from '../../date-range-labels.js';
|
|
6
|
+
/** What both of DateRangeButton's presentations, popover and sheet, draw. */
|
|
7
|
+
export type RangePanelProps = {
|
|
8
|
+
id: string;
|
|
9
|
+
/** Names the popover or titles the sheet. */
|
|
10
|
+
label: string;
|
|
11
|
+
labels: DateRangeButtonLabels;
|
|
12
|
+
draft: RangeDraft;
|
|
13
|
+
onPress: (day: string) => void;
|
|
14
|
+
presets: readonly DateRangePreset[];
|
|
15
|
+
pressedPresetId: string | undefined;
|
|
16
|
+
onChoosePreset: (preset: DateRangePreset) => void;
|
|
17
|
+
/** Applies the draft. */
|
|
18
|
+
onApply: () => void;
|
|
19
|
+
/** Keeps the value; `true` returns focus to the button. */
|
|
20
|
+
onCancel: (restoreFocus: boolean) => void;
|
|
21
|
+
latest: string | undefined;
|
|
22
|
+
min?: string;
|
|
23
|
+
max?: string;
|
|
24
|
+
locale: string;
|
|
25
|
+
today: string;
|
|
26
|
+
weekStartsOn: WeekStart;
|
|
27
|
+
buttonRef: RefObject<HTMLButtonElement | null>;
|
|
28
|
+
/** The draft's words: the range with weekdays, and its length. */
|
|
29
|
+
line: string;
|
|
30
|
+
count: string;
|
|
31
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import { type RangeDraft } from '../../date-range-draft.js';
|
|
3
|
+
import { type DateOnlyRange } from '../../date-only.js';
|
|
4
|
+
export type RangePopup = {
|
|
5
|
+
buttonRef: RefObject<HTMLButtonElement | null>;
|
|
6
|
+
id: string;
|
|
7
|
+
shown: boolean;
|
|
8
|
+
toggle: () => void;
|
|
9
|
+
draft: RangeDraft;
|
|
10
|
+
press: (day: string) => void;
|
|
11
|
+
/** Closes and gives `range` to `onChange` when it is a new range. */
|
|
12
|
+
apply: (range: DateOnlyRange) => void;
|
|
13
|
+
/** Closes and keeps the value; `true` returns focus to the button. */
|
|
14
|
+
cancel: (restoreFocus: boolean) => void;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* DateRangeButton's open state and the range being picked. Opening starts
|
|
18
|
+
* from the value; a new value while open starts over from it. Disabling
|
|
19
|
+
* closes it for good.
|
|
20
|
+
*/
|
|
21
|
+
export declare function useRangePopup(value: DateOnlyRange, disabled: boolean, onChange: (value: DateOnlyRange) => void): RangePopup;
|