@moneyforward/mfui-components 3.19.0 → 3.20.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/dist/src/CheckboxCard/CheckboxCard.js +1 -7
- package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerProvider/DateRangePickerProvider.js +11 -12
- package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/hooks/useDateRangeTriggerValueController.js +4 -3
- package/dist/src/DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.d.ts +6 -0
- package/dist/src/DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.js +33 -0
- package/dist/src/DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.types.d.ts +17 -0
- package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.d.ts +14 -0
- package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.js +42 -0
- package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.types.d.ts +78 -0
- package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.types.js +1 -0
- package/dist/src/DateTimeSelection/FilterDateRangePicker/index.d.ts +2 -0
- package/dist/src/DateTimeSelection/FilterDateRangePicker/index.js +1 -0
- package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.d.ts +14 -0
- package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.js +75 -0
- package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.types.d.ts +83 -0
- package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.types.js +1 -0
- package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.d.ts +6 -0
- package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.js +45 -0
- package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.types.d.ts +24 -0
- package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.types.js +1 -0
- package/dist/src/DateTimeSelection/FilterMonthPicker/index.d.ts +2 -0
- package/dist/src/DateTimeSelection/FilterMonthPicker/index.js +1 -0
- package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.d.ts +15 -0
- package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.js +89 -0
- package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.types.d.ts +75 -0
- package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.types.js +1 -0
- package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.d.ts +5 -0
- package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.js +54 -0
- package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.types.d.ts +19 -0
- package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.types.js +1 -0
- package/dist/src/DateTimeSelection/FilterMonthRangePicker/index.d.ts +2 -0
- package/dist/src/DateTimeSelection/FilterMonthRangePicker/index.js +1 -0
- package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePicker.js +1 -1
- package/dist/src/DateTimeSelection/index.d.ts +3 -0
- package/dist/src/DateTimeSelection/index.js +3 -0
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.d.ts +1 -1
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.js +26 -10
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.types.d.ts +9 -0
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerProvider/BaseRangePickerProvider.js +32 -15
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerProvider/BaseRangePickerProvider.types.d.ts +10 -0
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/BaseRangePickerTrigger.js +4 -6
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/hooks/useDateRangeTriggerValueController.js +4 -3
- package/dist/src/DateTimeSelection/shared/MonthGrid/MonthGrid.js +3 -3
- package/dist/src/DateTimeSelection/shared/utilities/dateParsing.js +11 -0
- package/dist/src/MultilineTextBox/index.d.ts +10 -2
- package/dist/src/MultilineTextBox/index.js +9 -1
- package/dist/src/MultipleSelectBox/MultipleSelectBoxTrigger/MultipleSelectBoxTrigger.js +1 -3
- package/dist/src/RadioButtonCard/RadioButtonCard.js +1 -7
- package/dist/src/TextBox/TextBox.js +2 -6
- package/dist/src/{MultilineTextBox/MultilineTextBox.d.ts → Textarea/Textarea.d.ts} +5 -2
- package/dist/src/{MultilineTextBox/MultilineTextBox.js → Textarea/Textarea.js} +9 -5
- package/dist/src/{MultilineTextBox/MultilineTextBox.types.d.ts → Textarea/Textarea.types.d.ts} +2 -2
- package/dist/src/Textarea/Textarea.types.js +1 -0
- package/dist/src/Textarea/index.d.ts +2 -0
- package/dist/src/Textarea/index.js +1 -0
- package/dist/src/Tooltip/Tooltip.js +12 -3
- package/dist/src/Typography/Typography.js +1 -3
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/styled-system/recipes/filter-date-range-picker-slot-recipe.d.ts +33 -0
- package/dist/styled-system/recipes/filter-date-range-picker-slot-recipe.js +48 -0
- package/dist/styled-system/recipes/filter-month-picker-slot-recipe.d.ts +33 -0
- package/dist/styled-system/recipes/filter-month-picker-slot-recipe.js +44 -0
- package/dist/styled-system/recipes/filter-month-range-picker-slot-recipe.d.ts +33 -0
- package/dist/styled-system/recipes/filter-month-range-picker-slot-recipe.js +44 -0
- package/dist/styled-system/recipes/index.d.ts +4 -1
- package/dist/styled-system/recipes/index.js +4 -1
- package/dist/styled-system/recipes/textarea-slot-recipe.d.ts +52 -0
- package/dist/styled-system/recipes/textarea-slot-recipe.js +64 -0
- package/dist/styles.css +433 -24
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +10 -8
- package/dist/styled-system/recipes/multiline-text-box-slot-recipe.d.ts +0 -52
- package/dist/styled-system/recipes/multiline-text-box-slot-recipe.js +0 -64
- /package/dist/src/{MultilineTextBox/MultilineTextBox.types.js → DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.types.js} +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useId, useRef, useState } from 'react';
|
|
4
|
+
import { BaseRangePickerProvider } from '../shared/BaseRangePicker/BaseRangePickerProvider';
|
|
5
|
+
import { useDisclosure } from '../../utilities/state/useDisclosure';
|
|
6
|
+
import { dayjs } from '../../utilities/date/dayjs';
|
|
7
|
+
import { filterMonthRangePickerSlotRecipe } from '../../../styled-system/recipes';
|
|
8
|
+
import { SEPARATOR_CONTENT } from '../shared/BaseRangePicker/BaseRangePickerTrigger/constants';
|
|
9
|
+
import { InternalMonthRangePickerPopover } from './MonthRangePickerPopover/MonthRangePickerPopover';
|
|
10
|
+
const DEFAULT_FORMAT = 'YYYY/MM';
|
|
11
|
+
/**
|
|
12
|
+
* FilterMonthRangePicker component.
|
|
13
|
+
*
|
|
14
|
+
* A filter trigger component that allows selecting a month range via the same panel as
|
|
15
|
+
* {@link MonthRangePicker}. Displays the label alone when no range is selected, and
|
|
16
|
+
* `label:start〜end` after both bounds are selected. Conforms to the FilterSelectBox / FilterDateBox
|
|
17
|
+
* visual style.
|
|
18
|
+
*
|
|
19
|
+
* Note: The trigger markup intentionally does not reuse FilterTrigger because
|
|
20
|
+
* FilterTrigger hardcodes the DropdownIcon and adds a hidden `<input>` element,
|
|
21
|
+
* neither of which are appropriate here. FilterMonthRangePicker uses CalendarIcon and
|
|
22
|
+
* has no form-field semantics.
|
|
23
|
+
*/
|
|
24
|
+
export function FilterMonthRangePicker({ label, value, defaultValue, onChange, open, onOpenStateChanged, format = DEFAULT_FORMAT, initialDisplayedMonths = 'currentAndNext', clearButtonProps, disabled = false, className, targetDOMNode, minMonth, maxMonth, }) {
|
|
25
|
+
const classes = filterMonthRangePickerSlotRecipe();
|
|
26
|
+
const triggerRef = useRef(null);
|
|
27
|
+
const dialogId = useId();
|
|
28
|
+
const [internalValue, setInternalValue] = useState(() => defaultValue ?? [undefined, undefined]);
|
|
29
|
+
const currentValue = value !== undefined ? value : internalValue;
|
|
30
|
+
const previousValueRef = useRef(value);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const previousValue = previousValueRef.current;
|
|
33
|
+
previousValueRef.current = value;
|
|
34
|
+
if (previousValue !== undefined && value === undefined) {
|
|
35
|
+
setInternalValue(defaultValue ?? [undefined, undefined]);
|
|
36
|
+
}
|
|
37
|
+
}, [value, defaultValue]);
|
|
38
|
+
const { isOpen: isPickerOpen, toggle: togglePicker, close: closePicker, open: openPicker, } = useDisclosure({
|
|
39
|
+
value: disabled ? false : open,
|
|
40
|
+
onToggle: (nextOpen) => {
|
|
41
|
+
if (!nextOpen) {
|
|
42
|
+
triggerRef.current?.focus();
|
|
43
|
+
}
|
|
44
|
+
onOpenStateChanged?.(nextOpen);
|
|
45
|
+
},
|
|
46
|
+
onClose: () => {
|
|
47
|
+
triggerRef.current?.focus();
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const closePickerAndNotify = useCallback(() => {
|
|
51
|
+
closePicker();
|
|
52
|
+
onOpenStateChanged?.(false);
|
|
53
|
+
}, [closePicker, onOpenStateChanged]);
|
|
54
|
+
const handleRangeChange = useCallback((dates) => {
|
|
55
|
+
if (value === undefined) {
|
|
56
|
+
setInternalValue(dates);
|
|
57
|
+
}
|
|
58
|
+
onChange?.(dates);
|
|
59
|
+
}, [value, onChange]);
|
|
60
|
+
const handleClear = useCallback((event) => {
|
|
61
|
+
event.stopPropagation();
|
|
62
|
+
if (value === undefined) {
|
|
63
|
+
setInternalValue([undefined, undefined]);
|
|
64
|
+
}
|
|
65
|
+
onChange?.([undefined, undefined]);
|
|
66
|
+
triggerRef.current?.focus();
|
|
67
|
+
}, [value, onChange]);
|
|
68
|
+
const [startMonth, endMonth] = currentValue;
|
|
69
|
+
const formattedValue = startMonth && endMonth
|
|
70
|
+
? `${dayjs(startMonth).format(format)}${SEPARATOR_CONTENT}${dayjs(endMonth).format(format)}`
|
|
71
|
+
: undefined;
|
|
72
|
+
const hasValue = !!formattedValue;
|
|
73
|
+
const initialViewingDate = (() => {
|
|
74
|
+
if (initialDisplayedMonths !== 'previousAndCurrent') {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const startDate = value?.[0] ?? defaultValue?.[0];
|
|
78
|
+
if (!startDate) {
|
|
79
|
+
const today = new Date();
|
|
80
|
+
return new Date(today.getFullYear() - 1, 0, 1);
|
|
81
|
+
}
|
|
82
|
+
const endDate = value?.[1] ?? defaultValue?.[1];
|
|
83
|
+
if (endDate && startDate.getFullYear() !== endDate.getFullYear()) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
return new Date(startDate.getFullYear() - 1, 0, 1);
|
|
87
|
+
})();
|
|
88
|
+
return (_jsx(BaseRangePickerProvider, { value: currentValue, defaultValue: defaultValue, disabled: disabled, format: format, isOpen: isPickerOpen, open: openPicker, close: closePickerAndNotify, minDate: minMonth, maxDate: maxMonth, initialDisplayedMonths: initialDisplayedMonths, initialViewingDate: initialViewingDate, onChange: handleRangeChange, children: _jsx(InternalMonthRangePickerPopover, { isPickerOpen: isPickerOpen, togglePicker: togglePicker, handleClear: handleClear, disabled: disabled, className: className, classes: classes, formattedValue: formattedValue, hasValue: hasValue, label: label, clearButtonProps: clearButtonProps, minMonth: minMonth, maxMonth: maxMonth, triggerRef: triggerRef, targetDOMNode: targetDOMNode, dialogId: dialogId }) }));
|
|
89
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type PopoverProps } from '../../Popover';
|
|
2
|
+
import { type DateRangeInput } from '../shared/BaseRangePicker/BaseRangePicker.types';
|
|
3
|
+
export type FilterMonthRangePickerProps = {
|
|
4
|
+
/**
|
|
5
|
+
* The label to display for the filter.
|
|
6
|
+
*/
|
|
7
|
+
label: string;
|
|
8
|
+
/**
|
|
9
|
+
* The selected month range (controlled). Each entry is the first day of a month.
|
|
10
|
+
*/
|
|
11
|
+
value?: DateRangeInput;
|
|
12
|
+
/**
|
|
13
|
+
* The default month range for uncontrolled usage.
|
|
14
|
+
*/
|
|
15
|
+
defaultValue?: DateRangeInput;
|
|
16
|
+
/**
|
|
17
|
+
* Callback fired when the selected month range changes.
|
|
18
|
+
*
|
|
19
|
+
* @param value - The new range `[startMonth, endMonth]`, or partial/empty tuples while interacting.
|
|
20
|
+
*/
|
|
21
|
+
onChange?: (value: DateRangeInput) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the month range picker popover is open (controlled).
|
|
24
|
+
*/
|
|
25
|
+
open?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Callback fired when the open state of the picker changes.
|
|
28
|
+
*
|
|
29
|
+
* @param open - The new open state.
|
|
30
|
+
*/
|
|
31
|
+
onOpenStateChanged?: (open: boolean) => void;
|
|
32
|
+
/**
|
|
33
|
+
* The display format for each bound of the range.
|
|
34
|
+
* Follows dayjs format tokens.
|
|
35
|
+
*
|
|
36
|
+
* @default 'YYYY/MM'
|
|
37
|
+
*/
|
|
38
|
+
format?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Props for the clear button, shown automatically when a complete range is selected.
|
|
41
|
+
*
|
|
42
|
+
* @property aria-label - The alternative text for the clear button. Default is '値をクリアする'.
|
|
43
|
+
*/
|
|
44
|
+
clearButtonProps?: {
|
|
45
|
+
'aria-label'?: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Whether the component is disabled.
|
|
49
|
+
*
|
|
50
|
+
* @default false
|
|
51
|
+
*/
|
|
52
|
+
disabled?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Additional class name applied to the trigger button.
|
|
55
|
+
*/
|
|
56
|
+
className?: string;
|
|
57
|
+
/**
|
|
58
|
+
* The target DOM node to render the month range picker popover into.
|
|
59
|
+
*/
|
|
60
|
+
targetDOMNode?: PopoverProps['targetDOMNode'];
|
|
61
|
+
/**
|
|
62
|
+
* The minimum selectable month.
|
|
63
|
+
*/
|
|
64
|
+
minMonth?: Date;
|
|
65
|
+
/**
|
|
66
|
+
* The maximum selectable month.
|
|
67
|
+
*/
|
|
68
|
+
maxMonth?: Date;
|
|
69
|
+
/**
|
|
70
|
+
* Controls which years are initially displayed in the two-panel calendar when there is no value.
|
|
71
|
+
*
|
|
72
|
+
* @default 'currentAndNext'
|
|
73
|
+
*/
|
|
74
|
+
initialDisplayedMonths?: 'currentAndNext' | 'previousAndCurrent';
|
|
75
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type InternalMonthRangePickerPopoverProps } from './MonthRangePickerPopover.types';
|
|
2
|
+
/**
|
|
3
|
+
* Internal popover UI for FilterMonthRangePicker. Must render inside BaseRangePickerProvider.
|
|
4
|
+
*/
|
|
5
|
+
export declare function InternalMonthRangePickerPopover({ isPickerOpen, togglePicker, handleClear, disabled, className, classes, formattedValue, hasValue, label, clearButtonProps, minMonth, maxMonth, triggerRef, targetDOMNode, dialogId, }: InternalMonthRangePickerPopoverProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import { Calendar as CalendarIcon } from '@moneyforward/mfui-icons-react';
|
|
5
|
+
import { Popover } from '../../../Popover';
|
|
6
|
+
import { FocusIndicator } from '../../../FocusIndicator';
|
|
7
|
+
import { ClearButton } from '../../../shared';
|
|
8
|
+
import { Typography } from '../../../Typography';
|
|
9
|
+
import { cx } from '../../../../styled-system/css';
|
|
10
|
+
import { dayjs } from '../../../utilities/date/dayjs';
|
|
11
|
+
import { MonthRangePickerPanel } from '../../MonthRangePicker/MonthRangePickerPanel';
|
|
12
|
+
import { useBaseRangePickerContext } from '../../shared/BaseRangePicker/BaseRangePickerProvider';
|
|
13
|
+
import { useFocusTrap } from '../../../utilities/dom/useFocusTrap';
|
|
14
|
+
import { getFocusableNodes } from '../../../utilities/dom/getFocusableNodes';
|
|
15
|
+
/**
|
|
16
|
+
* Internal popover UI for FilterMonthRangePicker. Must render inside BaseRangePickerProvider.
|
|
17
|
+
*/
|
|
18
|
+
export function InternalMonthRangePickerPopover({ isPickerOpen, togglePicker, handleClear, disabled, className, classes, formattedValue, hasValue, label, clearButtonProps, minMonth, maxMonth, triggerRef, targetDOMNode, dialogId, }) {
|
|
19
|
+
const { viewingMonth, setViewingMonth, temporaryStart, temporaryEnd, startDate, endDate, setPendingFocusDate } = useBaseRangePickerContext();
|
|
20
|
+
const { handleOnKeyDown } = useFocusTrap({
|
|
21
|
+
onPressingTabOnNotFocusableElement: (event) => {
|
|
22
|
+
const nodes = getFocusableNodes(event.currentTarget);
|
|
23
|
+
const focusableNodeAfterFocusingNode = nodes.find((element) =>
|
|
24
|
+
// eslint-disable-next-line no-bitwise
|
|
25
|
+
element.compareDocumentPosition(event.target) & Node.DOCUMENT_POSITION_PRECEDING);
|
|
26
|
+
const firstFocusableNode = nodes.at(0);
|
|
27
|
+
const nextFocusableNode = focusableNodeAfterFocusingNode ?? firstFocusableNode;
|
|
28
|
+
nextFocusableNode?.focus();
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
const handlePopoverOpen = useCallback(() => {
|
|
33
|
+
let dateToFocus;
|
|
34
|
+
if (temporaryStart) {
|
|
35
|
+
dateToFocus = temporaryStart;
|
|
36
|
+
}
|
|
37
|
+
else if (temporaryEnd) {
|
|
38
|
+
dateToFocus = temporaryEnd;
|
|
39
|
+
}
|
|
40
|
+
else if (startDate) {
|
|
41
|
+
dateToFocus = startDate;
|
|
42
|
+
}
|
|
43
|
+
else if (endDate) {
|
|
44
|
+
dateToFocus = endDate;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
dateToFocus = new Date();
|
|
48
|
+
}
|
|
49
|
+
const monthToFocus = dayjs(dateToFocus).startOf('month').toDate();
|
|
50
|
+
setViewingMonth(monthToFocus);
|
|
51
|
+
setPendingFocusDate(dayjs(monthToFocus).format('YYYY-MM'));
|
|
52
|
+
}, [temporaryStart, temporaryEnd, startDate, endDate, setViewingMonth, setPendingFocusDate]);
|
|
53
|
+
return (_jsx(Popover, { open: isPickerOpen, targetDOMNode: targetDOMNode, renderTrigger: ({ setTriggerRef, togglePopover, handleTriggerKeyDown, handleTriggerBlur }) => (_jsxs("div", { ref: setTriggerRef, "data-selected": hasValue, className: cx(classes.wrapper, 'mfui-FilterMonthRangePicker__wrapper'), children: [_jsx(FocusIndicator, { children: _jsxs("button", { ref: triggerRef, type: "button", disabled: disabled, className: cx(classes.trigger, 'mfui-FilterMonthRangePicker__trigger', className), "data-selected": hasValue, "data-mfui-has-clear-button": hasValue, "aria-controls": dialogId, "aria-expanded": isPickerOpen, "aria-haspopup": "dialog", onClick: togglePopover, onKeyDown: handleTriggerKeyDown, onBlur: handleTriggerBlur, children: [_jsx("span", { className: cx(classes.content, 'mfui-FilterMonthRangePicker__content'), "data-mfui-content": "filter-month-range-picker-display-value", children: hasValue ? (_jsxs(Typography, { variant: "strongBody", children: [_jsx("span", { "data-mfui-content": "filter-month-range-picker-label", children: `${label}:` }), _jsx("span", { "data-mfui-content": "filter-month-range-picker-value", children: formattedValue })] })) : (_jsx(Typography, { variant: "controlLabel", children: _jsx("span", { "data-mfui-content": "filter-month-range-picker-label", children: label }) })) }), _jsx("span", { className: cx(classes.calendarIcon, 'mfui-FilterMonthRangePicker__calendarIcon'), children: _jsx(CalendarIcon, { "aria-hidden": true }) })] }) }), hasValue ? (_jsx("div", { className: cx(classes.clearButtonWrapper, 'mfui-FilterMonthRangePicker__clearButtonWrapper'), children: _jsx(ClearButton, { "aria-label": clearButtonProps?.['aria-label'] ?? '値をクリアする', "data-mfui-content": "filter-month-range-picker-clear-button", disabled: disabled, onClick: handleClear }) })) : null] })), renderContent: () => (_jsx("div", { id: dialogId, role: "dialog", "aria-label": label, className: "mfui-FilterMonthRangePicker__popoverWrapper", onKeyDown: handleOnKeyDown, children: _jsx(MonthRangePickerPanel, { viewingValue: viewingMonth, setViewingValue: setViewingMonth, value: [temporaryStart, temporaryEnd], minMonth: minMonth, maxMonth: maxMonth }) })), minWidth: "min-content", allowedPlacements: ['bottom-start', 'top-start'], onOpenStateChanged: togglePicker, onOpen: handlePopoverOpen }));
|
|
54
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type filterMonthRangePickerSlotRecipe } from '../../../../styled-system/recipes';
|
|
2
|
+
import { type FilterMonthRangePickerProps } from '../FilterMonthRangePicker.types';
|
|
3
|
+
export type InternalMonthRangePickerPopoverProps = {
|
|
4
|
+
isPickerOpen: boolean;
|
|
5
|
+
togglePicker: () => void;
|
|
6
|
+
handleClear: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
|
7
|
+
disabled: boolean;
|
|
8
|
+
className: string | undefined;
|
|
9
|
+
classes: ReturnType<typeof filterMonthRangePickerSlotRecipe>;
|
|
10
|
+
formattedValue: string | undefined;
|
|
11
|
+
hasValue: boolean;
|
|
12
|
+
label: string;
|
|
13
|
+
clearButtonProps: FilterMonthRangePickerProps['clearButtonProps'];
|
|
14
|
+
minMonth: FilterMonthRangePickerProps['minMonth'];
|
|
15
|
+
maxMonth: FilterMonthRangePickerProps['maxMonth'];
|
|
16
|
+
triggerRef: React.RefObject<HTMLButtonElement | null>;
|
|
17
|
+
targetDOMNode: FilterMonthRangePickerProps['targetDOMNode'];
|
|
18
|
+
dialogId: string;
|
|
19
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FilterMonthRangePicker } from './FilterMonthRangePicker';
|
|
@@ -51,7 +51,7 @@ export function MonthRangePicker({ format = 'YYYY/MM', minMonth, maxMonth, start
|
|
|
51
51
|
// Same-year value + previousAndCurrent: show previous year on the left
|
|
52
52
|
return new Date(startDate.getFullYear() - 1, 0, 1);
|
|
53
53
|
})();
|
|
54
|
-
return (_jsx(BaseRangePicker, { ...restProps, format: format, initialDisplayedMonths: initialDisplayedMonths, initialViewingDate: initialViewingDate, startInputProps: {
|
|
54
|
+
return (_jsx(BaseRangePicker, { ...restProps, format: format, pendingFocusDayjsFormat: "YYYY-MM", initialDisplayedMonths: initialDisplayedMonths, initialViewingDate: initialViewingDate, startInputProps: {
|
|
55
55
|
placeholder: '開始月',
|
|
56
56
|
...startInputProps,
|
|
57
57
|
}, endInputProps: {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export * from './DatePicker';
|
|
2
2
|
export * from './FilterDatePicker';
|
|
3
|
+
export * from './FilterDateRangePicker';
|
|
4
|
+
export * from './FilterMonthPicker';
|
|
5
|
+
export * from './FilterMonthRangePicker';
|
|
3
6
|
export * from './MonthPicker';
|
|
4
7
|
export * from './DateRangePicker';
|
|
5
8
|
export * from './MonthRangePicker';
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export * from './DatePicker';
|
|
2
2
|
export * from './FilterDatePicker';
|
|
3
|
+
export * from './FilterDateRangePicker';
|
|
4
|
+
export * from './FilterMonthPicker';
|
|
5
|
+
export * from './FilterMonthRangePicker';
|
|
3
6
|
export * from './MonthPicker';
|
|
4
7
|
export * from './DateRangePicker';
|
|
5
8
|
export * from './MonthRangePicker';
|
|
@@ -3,4 +3,4 @@ import { type BaseRangePickerProps } from './BaseRangePicker.types';
|
|
|
3
3
|
* BaseRangePicker component
|
|
4
4
|
* A generic component for selecting a range of dates with configurable format
|
|
5
5
|
*/
|
|
6
|
-
export declare function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format, onOpenStateChanged, disableAutoOpen, onBlur, allowedPlacements, enableViewportConstraint, enableClearButton, clearButtonProps, minDate, maxDate, enableAutoUnmount, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale, initialDisplayedMonths, initialViewingDate, }: BaseRangePickerProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format, onOpenStateChanged, disableAutoOpen, onBlur, allowedPlacements, enableViewportConstraint, enableClearButton, clearButtonProps, minDate, maxDate, enableAutoUnmount, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale, initialDisplayedMonths, initialViewingDate, pendingFocusDayjsFormat, }: BaseRangePickerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -9,8 +9,8 @@ import { dayjs } from '../../../utilities/date/dayjs';
|
|
|
9
9
|
/**
|
|
10
10
|
* Internal component that has access to BaseRangePickerContext
|
|
11
11
|
*/
|
|
12
|
-
function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid, format, enableClearButton, clearButtonProps, targetDOMNode, onBlur, isOpen, open, close, onOpenStateChanged, enableAutoUnmount, allowedPlacements, enableViewportConstraint, minWidth, renderPopoverContent, startInputProps = {}, endInputProps = {}, calendarLocale = 'ja', }) {
|
|
13
|
-
const { setPendingFocusDate, temporaryStart, temporaryEnd, viewingMonth, setViewingMonth } = useBaseRangePickerContext();
|
|
12
|
+
function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid, format, enableClearButton, clearButtonProps, targetDOMNode, onBlur, isOpen, open, close, onOpenStateChanged, enableAutoUnmount, allowedPlacements, enableViewportConstraint, minWidth, renderPopoverContent, startInputProps = {}, endInputProps = {}, calendarLocale = 'ja', pendingFocusDayjsFormat = 'YYYY-MM-DD', }) {
|
|
13
|
+
const { setPendingFocusDate, temporaryStart, temporaryEnd, startDate, endDate, viewingMonth, setViewingMonth } = useBaseRangePickerContext();
|
|
14
14
|
// Custom onOpen handler to prevent auto-focus when clicking on input elements
|
|
15
15
|
// and implement smart focus management for calendar dates
|
|
16
16
|
const handlePopoverOpen = useCallback((event) => {
|
|
@@ -31,7 +31,7 @@ function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid
|
|
|
31
31
|
// 3. Selected start date (if exists)
|
|
32
32
|
// 4. Selected end date (if exists and no start date)
|
|
33
33
|
// 5. Current date (today)
|
|
34
|
-
// 6. First non-disabled
|
|
34
|
+
// 6. First non-disabled cell (handled by CalendarGrid / MonthGrid automatically)
|
|
35
35
|
let dateToFocus;
|
|
36
36
|
if (temporaryStart) {
|
|
37
37
|
dateToFocus = temporaryStart;
|
|
@@ -39,16 +39,32 @@ function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid
|
|
|
39
39
|
else if (temporaryEnd) {
|
|
40
40
|
dateToFocus = temporaryEnd;
|
|
41
41
|
}
|
|
42
|
+
else if (startDate) {
|
|
43
|
+
dateToFocus = startDate;
|
|
44
|
+
}
|
|
45
|
+
else if (endDate) {
|
|
46
|
+
dateToFocus = endDate;
|
|
47
|
+
}
|
|
42
48
|
else {
|
|
43
|
-
// Default to today's date -
|
|
49
|
+
// Default to today's date - the grid will focus the first non-disabled cell if needed
|
|
44
50
|
dateToFocus = new Date();
|
|
45
51
|
}
|
|
46
|
-
// Set the pending focus
|
|
47
|
-
//
|
|
48
|
-
const formattedDate = dayjs(dateToFocus).format(
|
|
52
|
+
// Set the pending focus key so the popover grid can focus the matching cell when it renders.
|
|
53
|
+
// Must match DateCell `data-mfui-date` (YYYY-MM-DD) or MonthCell `data-mfui-month` (YYYY-MM).
|
|
54
|
+
const formattedDate = dayjs(dateToFocus).format(pendingFocusDayjsFormat);
|
|
49
55
|
setPendingFocusDate(formattedDate);
|
|
50
56
|
open();
|
|
51
|
-
}, [
|
|
57
|
+
}, [
|
|
58
|
+
startInputRef,
|
|
59
|
+
endInputRef,
|
|
60
|
+
temporaryStart,
|
|
61
|
+
temporaryEnd,
|
|
62
|
+
startDate,
|
|
63
|
+
endDate,
|
|
64
|
+
setPendingFocusDate,
|
|
65
|
+
open,
|
|
66
|
+
pendingFocusDayjsFormat,
|
|
67
|
+
]);
|
|
52
68
|
return (_jsx(Popover, { enableAutomaticPortalTargetResolution: true, open: isOpen, allowedPlacements: allowedPlacements, enableViewportConstraint: enableViewportConstraint, minWidth: minWidth, renderTrigger: ({ setTriggerRef, handleTriggerBlur, handleTriggerKeyDown, openPopover }) => (_jsx(BaseRangePickerTrigger, { ref: setTriggerRef, disabled: disabled, invalid: invalid, format: format, enableClearValue: enableClearButton, clearButtonProps: clearButtonProps, startInputRef: startInputRef, endInputRef: endInputRef, startInputProps: startInputProps, endInputProps: endInputProps, popoverOpenFunction: openPopover, calendarLocale: calendarLocale, onBlur: handleTriggerBlur, onKeyDown: handleTriggerKeyDown })), renderContent: () => renderPopoverContent({
|
|
53
69
|
viewingValue: viewingMonth,
|
|
54
70
|
setViewingValue: setViewingMonth,
|
|
@@ -61,12 +77,12 @@ function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid
|
|
|
61
77
|
* BaseRangePicker component
|
|
62
78
|
* A generic component for selecting a range of dates with configurable format
|
|
63
79
|
*/
|
|
64
|
-
export function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format = 'YYYY/MM/DD', onOpenStateChanged, disableAutoOpen = false, onBlur, allowedPlacements, enableViewportConstraint, enableClearButton = false, clearButtonProps, minDate, maxDate, enableAutoUnmount = true, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale = 'ja', initialDisplayedMonths, initialViewingDate, }) {
|
|
80
|
+
export function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format = 'YYYY/MM/DD', onOpenStateChanged, disableAutoOpen = false, onBlur, allowedPlacements, enableViewportConstraint, enableClearButton = false, clearButtonProps, minDate, maxDate, enableAutoUnmount = true, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale = 'ja', initialDisplayedMonths, initialViewingDate, pendingFocusDayjsFormat = 'YYYY-MM-DD', }) {
|
|
65
81
|
const { isOpen, open, close } = useDisclosure({ value: false });
|
|
66
82
|
const startInputRef = useRef(null);
|
|
67
83
|
const endInputRef = useRef(null);
|
|
68
84
|
// Default renderPopoverContent implementation for backward compatibility
|
|
69
85
|
const defaultRenderPopoverContent = useCallback(() => _jsx(BaseRangePickerPopover, { calendarLocale: calendarLocale }), [calendarLocale]);
|
|
70
86
|
const finalRenderPopoverContent = renderPopoverContent ?? defaultRenderPopoverContent;
|
|
71
|
-
return (_jsx(BaseRangePickerProvider, { value: value, defaultValue: defaultValue, disabled: disabled, format: format, isOpen: isOpen, open: open, close: close, disableAutoOpen: disableAutoOpen, minDate: minDate, maxDate: maxDate, initialDisplayedMonths: initialDisplayedMonths, initialViewingDate: initialViewingDate, onChange: onChange, children: _jsx(InternalBaseRangePicker, { startInputRef: startInputRef, endInputRef: endInputRef, disabled: disabled, invalid: invalid, format: format, enableClearButton: enableClearButton, clearButtonProps: clearButtonProps, targetDOMNode: targetDOMNode, allowedPlacements: allowedPlacements, enableViewportConstraint: enableViewportConstraint, enableAutoUnmount: enableAutoUnmount, minWidth: minWidth, startInputProps: startInputProps, endInputProps: endInputProps, isOpen: isOpen, open: open, close: close, renderPopoverContent: finalRenderPopoverContent, calendarLocale: calendarLocale, onBlur: onBlur, onOpenStateChanged: onOpenStateChanged }) }));
|
|
87
|
+
return (_jsx(BaseRangePickerProvider, { value: value, defaultValue: defaultValue, disabled: disabled, format: format, isOpen: isOpen, open: open, close: close, disableAutoOpen: disableAutoOpen, minDate: minDate, maxDate: maxDate, initialDisplayedMonths: initialDisplayedMonths, initialViewingDate: initialViewingDate, pendingFocusDayjsFormat: pendingFocusDayjsFormat, onChange: onChange, children: _jsx(InternalBaseRangePicker, { startInputRef: startInputRef, endInputRef: endInputRef, disabled: disabled, invalid: invalid, format: format, enableClearButton: enableClearButton, clearButtonProps: clearButtonProps, targetDOMNode: targetDOMNode, allowedPlacements: allowedPlacements, enableViewportConstraint: enableViewportConstraint, enableAutoUnmount: enableAutoUnmount, minWidth: minWidth, startInputProps: startInputProps, endInputProps: endInputProps, isOpen: isOpen, open: open, close: close, renderPopoverContent: finalRenderPopoverContent, calendarLocale: calendarLocale, pendingFocusDayjsFormat: pendingFocusDayjsFormat, onBlur: onBlur, onOpenStateChanged: onOpenStateChanged }) }));
|
|
72
88
|
}
|
|
@@ -307,4 +307,13 @@ export type BaseRangePickerProps = {
|
|
|
307
307
|
* @internal
|
|
308
308
|
*/
|
|
309
309
|
initialViewingDate?: Date;
|
|
310
|
+
/**
|
|
311
|
+
* dayjs format for pending grid focus strings (`pendingFocusDate` / `pendingFocusMonth`).
|
|
312
|
+
* Must match the grid: `YYYY-MM-DD` for day calendars, `YYYY-MM` for month grids.
|
|
313
|
+
*
|
|
314
|
+
* @internal
|
|
315
|
+
*
|
|
316
|
+
* @default 'YYYY-MM-DD'
|
|
317
|
+
*/
|
|
318
|
+
pendingFocusDayjsFormat?: 'YYYY-MM-DD' | 'YYYY-MM';
|
|
310
319
|
};
|
|
@@ -5,6 +5,7 @@ import { flushSync } from 'react-dom';
|
|
|
5
5
|
import { dayjs } from '../../../../utilities/date/dayjs';
|
|
6
6
|
import { useUpdateEffect } from '../../../../utilities/effect/useUpdateEffect';
|
|
7
7
|
import { useTransformedState } from '../../../../utilities/state/useTransformedState';
|
|
8
|
+
import { parseInputDateSimple } from '../../utilities/dateParsing';
|
|
8
9
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
9
10
|
const noop = () => { };
|
|
10
11
|
// Normalizes a date to the first of the month
|
|
@@ -97,7 +98,7 @@ function autoCompletePartialRange(dates) {
|
|
|
97
98
|
}
|
|
98
99
|
return dates;
|
|
99
100
|
}
|
|
100
|
-
const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false, onChange, format = 'YYYY/MM/DD', isOpen = false, open = noop, close = noop, disableAutoOpen, minDate, maxDate, initialDisplayedMonths = 'currentAndNext', initialViewingDate, }) => {
|
|
101
|
+
const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false, onChange, format = 'YYYY/MM/DD', isOpen = false, open = noop, close = noop, disableAutoOpen, minDate, maxDate, initialDisplayedMonths = 'currentAndNext', initialViewingDate, pendingFocusDayjsFormat = 'YYYY-MM-DD', }) => {
|
|
101
102
|
const [dates, setDates] = useTransformedState(defaultValue ?? [undefined, undefined], normalizeToStartOfDay);
|
|
102
103
|
const [dateStrings, setDateStrings] = useState(() => {
|
|
103
104
|
const [startDate, endDate] = value ?? defaultValue ?? [undefined, undefined];
|
|
@@ -176,39 +177,55 @@ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false,
|
|
|
176
177
|
const handleStartDateStringChange = useCallback((value) => {
|
|
177
178
|
setDateStrings([value, endDateString]);
|
|
178
179
|
// Parse and update both viewing month and temporary selection for immediate visual feedback
|
|
179
|
-
const parsedDate =
|
|
180
|
-
if (parsedDate
|
|
181
|
-
|
|
182
|
-
setViewingMonth(newDate);
|
|
180
|
+
const parsedDate = parseInputDateSimple(value, format);
|
|
181
|
+
if (parsedDate && !isDateOutsideConstraints(parsedDate, minDate, maxDate, format)) {
|
|
182
|
+
setViewingMonth(parsedDate);
|
|
183
183
|
// Update temporary selection for immediate calendar visual feedback
|
|
184
|
-
setTemporaryStart(
|
|
184
|
+
setTemporaryStart(parsedDate);
|
|
185
185
|
// Set pending focus date so when calendar opens, it focuses on this date
|
|
186
|
-
setPendingFocusDate(parsedDate.format(
|
|
186
|
+
setPendingFocusDate(dayjs(parsedDate).format(pendingFocusDayjsFormat));
|
|
187
187
|
}
|
|
188
188
|
else if (value === '') {
|
|
189
189
|
// Clear temporary selection if input is empty
|
|
190
190
|
setTemporaryStart(undefined);
|
|
191
191
|
setPendingFocusDate(null);
|
|
192
192
|
}
|
|
193
|
-
}, [
|
|
193
|
+
}, [
|
|
194
|
+
endDateString,
|
|
195
|
+
format,
|
|
196
|
+
minDate,
|
|
197
|
+
maxDate,
|
|
198
|
+
setViewingMonth,
|
|
199
|
+
setTemporaryStart,
|
|
200
|
+
setPendingFocusDate,
|
|
201
|
+
pendingFocusDayjsFormat,
|
|
202
|
+
]);
|
|
194
203
|
const handleEndDateStringChange = useCallback((value) => {
|
|
195
204
|
setDateStrings([startDateString, value]);
|
|
196
205
|
// Parse and update both viewing month and temporary selection for immediate visual feedback
|
|
197
|
-
const parsedDate =
|
|
198
|
-
if (parsedDate
|
|
199
|
-
|
|
200
|
-
setViewingMonth(newDate);
|
|
206
|
+
const parsedDate = parseInputDateSimple(value, format);
|
|
207
|
+
if (parsedDate && !isDateOutsideConstraints(parsedDate, minDate, maxDate, format)) {
|
|
208
|
+
setViewingMonth(parsedDate);
|
|
201
209
|
// Update temporary selection for immediate calendar visual feedback
|
|
202
|
-
setTemporaryEnd(
|
|
210
|
+
setTemporaryEnd(parsedDate);
|
|
203
211
|
// Set pending focus date so when calendar opens, it focuses on this date
|
|
204
|
-
setPendingFocusDate(parsedDate.format(
|
|
212
|
+
setPendingFocusDate(dayjs(parsedDate).format(pendingFocusDayjsFormat));
|
|
205
213
|
}
|
|
206
214
|
else if (value === '') {
|
|
207
215
|
// Clear temporary selection if input is empty
|
|
208
216
|
setTemporaryEnd(undefined);
|
|
209
217
|
setPendingFocusDate(null);
|
|
210
218
|
}
|
|
211
|
-
}, [
|
|
219
|
+
}, [
|
|
220
|
+
startDateString,
|
|
221
|
+
format,
|
|
222
|
+
minDate,
|
|
223
|
+
maxDate,
|
|
224
|
+
setViewingMonth,
|
|
225
|
+
setTemporaryEnd,
|
|
226
|
+
setPendingFocusDate,
|
|
227
|
+
pendingFocusDayjsFormat,
|
|
228
|
+
]);
|
|
212
229
|
const handleClear = useCallback(() => {
|
|
213
230
|
// handleChange already calls setDates and setDateStrings, so we don't need to call them again
|
|
214
231
|
handleChange([undefined, undefined]);
|
|
@@ -97,4 +97,14 @@ export type BaseRangePickerProviderProps = {
|
|
|
97
97
|
* @internal
|
|
98
98
|
*/
|
|
99
99
|
initialViewingDate?: Date;
|
|
100
|
+
/**
|
|
101
|
+
* dayjs format string for `pendingFocusDate` so it matches the focused grid cell's `data-mfui-*` attribute.
|
|
102
|
+
*
|
|
103
|
+
* @internal
|
|
104
|
+
*
|
|
105
|
+
* @default 'YYYY-MM-DD' — matches {@link DateCell} `data-mfui-date`
|
|
106
|
+
*
|
|
107
|
+
* @example 'YYYY-MM' — matches {@link MonthCell} `data-mfui-month` (MonthRangePicker)
|
|
108
|
+
*/
|
|
109
|
+
pendingFocusDayjsFormat?: 'YYYY-MM-DD' | 'YYYY-MM';
|
|
100
110
|
};
|
|
@@ -19,7 +19,7 @@ import { useDateRangeTriggerInputClick } from './hooks/useDateRangeTriggerInputC
|
|
|
19
19
|
*/
|
|
20
20
|
export const BaseRangePickerTrigger = forwardRef(({ enableClearValue = false, invalid = false, size = 'medium', disabled = false, wrapperProps = {}, clearButtonProps = {}, startInputProps = {}, endInputProps = {}, calendarButtonProps = {}, startInputRef, endInputRef, popoverOpenFunction, format = 'YYYY-MM-DD', calendarLocale = 'ja', ...props }, ref) => {
|
|
21
21
|
const isTouchDevice = useIsTouchDevice();
|
|
22
|
-
const { toggle, disableAutoOpen } = useBaseRangePickerContext();
|
|
22
|
+
const { toggle, disableAutoOpen, isOpen } = useBaseRangePickerContext();
|
|
23
23
|
const { handleInputClick } = useDateRangeTriggerInputClick(disableAutoOpen ?? false, popoverOpenFunction);
|
|
24
24
|
const { onClick: onClickClearButton, 'aria-label': clearAriaLabel } = clearButtonProps;
|
|
25
25
|
const { onClick: onCalendarClick, 'aria-label': calendarAriaLabel } = calendarButtonProps;
|
|
@@ -41,14 +41,12 @@ export const BaseRangePickerTrigger = forwardRef(({ enableClearValue = false, in
|
|
|
41
41
|
const finalEndPlaceholder = endInputPlaceholder ?? getBaseRangePickerTriggerLabel('END_DATE_PLACEHOLDER', calendarLocale);
|
|
42
42
|
const shouldShowClearButton = enableClearValue && (enhancedStartInputProps.value || enhancedEndInputProps.value);
|
|
43
43
|
const classes = baseRangePickerTriggerSlotRecipe({ invalid, disabled, size });
|
|
44
|
+
// Popover open: mark calendar icon active. Use `undefined` when closed — `data-active="false"` is still truthy in CSS.
|
|
45
|
+
const shouldCalendarIconActive = isOpen ? true : undefined;
|
|
44
46
|
const handleClickClearButton = useCallback((event) => {
|
|
45
47
|
handleClear();
|
|
46
48
|
onClickClearButton?.(event);
|
|
47
49
|
}, [handleClear, onClickClearButton]);
|
|
48
|
-
return (_jsxs("div", { ...wrapperProps, ...props, ref: ref, className: cx(classes.root, 'mfui-BaseRangePickerTrigger__root', wrapperProps.className), children: [_jsx("div", { className: cx(classes.inputGroup, 'mfui-BaseRangePickerTrigger__inputGroup'), children: isTouchDevice ? (
|
|
49
|
-
// Touch device: hidden inputs + display divs
|
|
50
|
-
_jsxs(_Fragment, { children: [_jsx("input", { type: "hidden", value: enhancedStartInputProps.value, name: startInputProps.name }), _jsx("input", { type: "hidden", value: enhancedEndInputProps.value, name: endInputProps.name }), _jsx("div", { className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), onClick: !disableAutoOpen && !disabled ? handleInputClick('start', startInputProps.onClick) : undefined, children: enhancedStartInputProps.value || finalStartPlaceholder }), _jsx(Typography, { variant: "controlLabel", className: cx(classes.separator, 'mfui-BaseRangePickerTrigger__separator'), children: SEPARATOR_CONTENT }), _jsx("div", { className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), onClick: !disableAutoOpen && !disabled ? handleInputClick('end', endInputProps.onClick) : undefined, children: enhancedEndInputProps.value || finalEndPlaceholder })] })) : (
|
|
51
|
-
// Desktop: Standard inputs with FocusIndicator
|
|
52
|
-
_jsxs(_Fragment, { children: [_jsx(FocusIndicator, { position: "inside", children: _jsx("input", { ref: startInputRef, disabled: disabled, className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), ...enhancedStartInputProps, placeholder: finalStartPlaceholder }) }), _jsx(Typography, { variant: "controlLabel", className: cx(classes.separator, 'mfui-BaseRangePickerTrigger__separator'), children: SEPARATOR_CONTENT }), _jsx(FocusIndicator, { position: "inside", children: _jsx("input", { ref: endInputRef, disabled: disabled, className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), ...enhancedEndInputProps, placeholder: finalEndPlaceholder }) })] })) }), shouldShowClearButton ? (_jsx(ClearButton, { size: size === 'small' ? 'small' : 'default', "aria-label": clearAriaLabel ?? getBaseRangePickerTriggerLabel('CLEAR_BUTTON_LABEL', calendarLocale), className: cx(classes.actionIcon, 'mfui-BaseRangePickerTrigger__actionIcon'), disabled: disabled, onClick: handleClickClearButton })) : null, _jsx(IconButton, { size: size === 'small' ? 'small' : 'default', "aria-label": calendarAriaLabel ?? getBaseRangePickerTriggerLabel('CALENDAR_BUTTON_LABEL', calendarLocale), className: cx(classes.actionIcon, 'mfui-BaseRangePickerTrigger__actionIcon'), disabled: disabled, onClick: onCalendarClick ?? toggle, children: _jsx(Calendar, {}) })] }));
|
|
50
|
+
return (_jsxs("div", { ...wrapperProps, ...props, ref: ref, className: cx(classes.root, 'mfui-BaseRangePickerTrigger__root', wrapperProps.className), children: [_jsx("div", { className: cx(classes.inputGroup, 'mfui-BaseRangePickerTrigger__inputGroup'), children: isTouchDevice ? (_jsxs(_Fragment, { children: [_jsx("input", { type: "hidden", value: enhancedStartInputProps.value, name: startInputProps.name }), _jsx("input", { type: "hidden", value: enhancedEndInputProps.value, name: endInputProps.name }), _jsx("div", { className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), onClick: !disableAutoOpen && !disabled ? handleInputClick('start', startInputProps.onClick) : undefined, children: enhancedStartInputProps.value || finalStartPlaceholder }), _jsx(Typography, { variant: "controlLabel", className: cx(classes.separator, 'mfui-BaseRangePickerTrigger__separator'), children: SEPARATOR_CONTENT }), _jsx("div", { className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), onClick: !disableAutoOpen && !disabled ? handleInputClick('end', endInputProps.onClick) : undefined, children: enhancedEndInputProps.value || finalEndPlaceholder })] })) : (_jsxs(_Fragment, { children: [_jsx(FocusIndicator, { position: "inside", children: _jsx("input", { ref: startInputRef, disabled: disabled, className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), ...enhancedStartInputProps, placeholder: finalStartPlaceholder }) }), _jsx(Typography, { variant: "controlLabel", className: cx(classes.separator, 'mfui-BaseRangePickerTrigger__separator'), children: SEPARATOR_CONTENT }), _jsx(FocusIndicator, { position: "inside", children: _jsx("input", { ref: endInputRef, disabled: disabled, className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), ...enhancedEndInputProps, placeholder: finalEndPlaceholder }) })] })) }), shouldShowClearButton ? (_jsx(ClearButton, { size: size === 'small' ? 'small' : 'default', "aria-label": clearAriaLabel ?? getBaseRangePickerTriggerLabel('CLEAR_BUTTON_LABEL', calendarLocale), className: cx(classes.actionIcon, 'mfui-BaseRangePickerTrigger__actionIcon'), disabled: disabled, onClick: handleClickClearButton })) : null, _jsx(IconButton, { size: size === 'small' ? 'small' : 'default', "aria-label": calendarAriaLabel ?? getBaseRangePickerTriggerLabel('CALENDAR_BUTTON_LABEL', calendarLocale), className: cx(classes.actionIcon, 'mfui-BaseRangePickerTrigger__actionIcon'), "data-active": shouldCalendarIconActive, disabled: disabled, onClick: onCalendarClick ?? toggle, children: _jsx(Calendar, {}) })] }));
|
|
53
51
|
});
|
|
54
52
|
BaseRangePickerTrigger.displayName = 'BaseRangePicker.Trigger';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import { dayjs } from '../../../../../utilities/date/dayjs';
|
|
3
3
|
import { useBaseRangePickerContext } from '../../BaseRangePickerProvider';
|
|
4
|
+
import { parseInputDateSimple } from '../../../utilities/dateParsing';
|
|
4
5
|
const createInputProps = (baseProps, value, format, onStringChange, onDateChange, otherDate, isDateDisabled) => ({
|
|
5
6
|
...baseProps,
|
|
6
7
|
value,
|
|
@@ -15,12 +16,12 @@ const createInputProps = (baseProps, value, format, onStringChange, onDateChange
|
|
|
15
16
|
if (event.defaultPrevented)
|
|
16
17
|
return;
|
|
17
18
|
baseProps.onBlur?.(event);
|
|
18
|
-
const
|
|
19
|
-
if (!
|
|
19
|
+
const parsedDate = parseInputDateSimple(value, format);
|
|
20
|
+
if (!parsedDate) {
|
|
20
21
|
onDateChange(undefined, otherDate);
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
|
-
const currentDate =
|
|
24
|
+
const currentDate = dayjs(parsedDate).startOf('day').toDate();
|
|
24
25
|
// Check if the date is disabled due to constraints
|
|
25
26
|
if (isDateDisabled?.(currentDate)) {
|
|
26
27
|
// Clear the input if the date violates constraints
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import React, {
|
|
2
|
+
import React, { useLayoutEffect } from 'react';
|
|
3
3
|
import { monthGridSlotRecipe } from '../../../../styled-system/recipes';
|
|
4
4
|
import { cx } from '../../../../styled-system/css';
|
|
5
5
|
/**
|
|
@@ -28,8 +28,8 @@ export function MonthGrid({ year, selectedMonth, minMonth, maxMonth, checkDisabl
|
|
|
28
28
|
const firstTabbableIndex = monthStates.find(({ disabled }) => !disabled)?.month ?? -1;
|
|
29
29
|
return { monthStates, firstTabbableIndex };
|
|
30
30
|
}, [months, year, minMonth, maxMonth, checkDisabledMonth]);
|
|
31
|
-
// Handle pending focus
|
|
32
|
-
|
|
31
|
+
// Handle pending focus before paint so the focused month cell matches keyboard / popover-open flows.
|
|
32
|
+
useLayoutEffect(() => {
|
|
33
33
|
if (!pendingFocusMonth)
|
|
34
34
|
return;
|
|
35
35
|
const targetCell = document.querySelector(`[data-mfui-month="${pendingFocusMonth}"]`);
|
|
@@ -37,6 +37,17 @@ export function parseInputDate(inputString, format) {
|
|
|
37
37
|
if (dayjsResult.isValid()) {
|
|
38
38
|
return { isValid: true, result: dayjsResult.toDate() };
|
|
39
39
|
}
|
|
40
|
+
// Fall back to single-digit month/day tokens when the format uses two-digit tokens (MM/DD).
|
|
41
|
+
// For example, "2026/6/6" fails with "YYYY/MM/DD" but succeeds with "YYYY/M/D".
|
|
42
|
+
// Strict mode is intentional here: it ensures YYYY matches exactly 4 digits, preventing
|
|
43
|
+
// format-mismatch inputs like "31/12/2023" from accidentally passing via native Date fallback.
|
|
44
|
+
const yyyymdFormat = format.replaceAll(/\bMM\b/g, 'M').replaceAll(/\bDD\b/g, 'D');
|
|
45
|
+
if (yyyymdFormat !== format) {
|
|
46
|
+
const yyyymdResult = dayjs(inputString, yyyymdFormat, true);
|
|
47
|
+
if (yyyymdResult.isValid()) {
|
|
48
|
+
return { isValid: true, result: yyyymdResult.toDate() };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
40
51
|
// Fall back to YYYYMMDD format (8-digit number without delimiters) if the specified format fails
|
|
41
52
|
// This allows inputs like "20251215" to be parsed even when format is "YYYY-MM-DD"
|
|
42
53
|
if (/^\d{8}$/.test(inputString)) {
|
|
@@ -1,2 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* MultilineTextBox backward compatibility re-exports.
|
|
3
|
+
*
|
|
4
|
+
* MultilineTextBox has been renamed to Textarea.
|
|
5
|
+
* Please migrate to Textarea. MultilineTextBox will be removed in a future major version (v5.0.0).
|
|
6
|
+
*
|
|
7
|
+
* @see Textarea
|
|
8
|
+
*/
|
|
9
|
+
export { Textarea as MultilineTextBox } from '../Textarea';
|
|
10
|
+
export type { TextareaProps as MultilineTextBoxProps } from '../Textarea';
|
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* MultilineTextBox backward compatibility re-exports.
|
|
3
|
+
*
|
|
4
|
+
* MultilineTextBox has been renamed to Textarea.
|
|
5
|
+
* Please migrate to Textarea. MultilineTextBox will be removed in a future major version (v5.0.0).
|
|
6
|
+
*
|
|
7
|
+
* @see Textarea
|
|
8
|
+
*/
|
|
9
|
+
export { Textarea as MultilineTextBox } from '../Textarea';
|