@allxsmith/bestax-bulma 5.26.4 → 5.26.6
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/bestax.css +1 -1
- package/dist/bestax.css.map +1 -1
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +445 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +445 -55
- package/dist/index.esm.js.map +1 -1
- package/dist/types/form/Autocomplete.d.ts +6 -1
- package/dist/types/form/DateInput.d.ts +8 -4
- package/dist/types/form/DateTimeInput.d.ts +8 -4
- package/dist/types/form/FormContext.d.ts +47 -0
- package/dist/types/form/Input.d.ts +4 -3
- package/dist/types/form/Numberinput.d.ts +11 -1
- package/dist/types/form/Select.d.ts +4 -3
- package/dist/types/form/TextArea.d.ts +4 -3
- package/dist/types/form/TimeInput.d.ts +8 -4
- package/dist/types/form/_pickerInternals/Calendar.d.ts +45 -4
- package/dist/types/form/_pickerInternals/dateUtils.d.ts +17 -0
- package/dist/types/form/_pickerInternals/pickerTypes.d.ts +14 -0
- package/dist/types/helpers/statusRegion.d.ts +1 -0
- package/dist/types-cjs/form/Autocomplete.d.ts +6 -1
- package/dist/types-cjs/form/DateInput.d.ts +8 -4
- package/dist/types-cjs/form/DateTimeInput.d.ts +8 -4
- package/dist/types-cjs/form/FormContext.d.ts +47 -0
- package/dist/types-cjs/form/Input.d.ts +4 -3
- package/dist/types-cjs/form/Numberinput.d.ts +11 -1
- package/dist/types-cjs/form/Select.d.ts +4 -3
- package/dist/types-cjs/form/TextArea.d.ts +4 -3
- package/dist/types-cjs/form/TimeInput.d.ts +8 -4
- package/dist/types-cjs/form/_pickerInternals/Calendar.d.ts +45 -4
- package/dist/types-cjs/form/_pickerInternals/dateUtils.d.ts +17 -0
- package/dist/types-cjs/form/_pickerInternals/pickerTypes.d.ts +14 -0
- package/dist/types-cjs/helpers/statusRegion.d.ts +1 -0
- package/dist/versions/bestax-no-dark-mode.css +1 -1
- package/dist/versions/bestax-no-dark-mode.css.map +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
- package/dist/versions/bestax-no-helpers.css +1 -1
- package/dist/versions/bestax-no-helpers.css.map +1 -1
- package/dist/versions/bestax-prefixed.css +1 -1
- package/dist/versions/bestax-prefixed.css.map +1 -1
- package/package.json +1 -1
- package/src/scss/form/_dateinput.scss +8 -1
- package/src/scss/form/_taginput.scss +8 -0
package/dist/index.esm.js
CHANGED
|
@@ -7689,7 +7689,8 @@ const noAnnouncementsYet = {
|
|
|
7689
7689
|
// the accessibility tree. The declarations match the `extras-sr-only` mixin,
|
|
7690
7690
|
// plus `clip-path`, which replaces the deprecated `clip`. The negative margin
|
|
7691
7691
|
// pulls the 1px box back inside the page, so it doesn't add scrollable
|
|
7692
|
-
// overflow.
|
|
7692
|
+
// overflow. Exported for other live regions that must work without a
|
|
7693
|
+
// stylesheet, such as a range calendar's.
|
|
7693
7694
|
const visuallyHidden = {
|
|
7694
7695
|
position: 'absolute',
|
|
7695
7696
|
width: '1px',
|
|
@@ -8819,6 +8820,58 @@ const rendersOwnField = (component, options) => {
|
|
|
8819
8820
|
(moved.length > 0 ? `, and set ${listOf(moved)} on that <Field>.` : '.'));
|
|
8820
8821
|
return true;
|
|
8821
8822
|
};
|
|
8823
|
+
// Each Control-level prop, with what takes its place on the `Control` the
|
|
8824
|
+
// caller wrapped around the wrapper. `bare-control.test.tsx` holds this to
|
|
8825
|
+
// what each wrapper hands its own `Control`, so a new Control-level prop
|
|
8826
|
+
// fails there until it is added here and passed to `rendersOwnControl`.
|
|
8827
|
+
const ON_CONTROL = {
|
|
8828
|
+
isLoading: 'isLoading',
|
|
8829
|
+
iconLeft: 'iconLeft',
|
|
8830
|
+
iconLeftName: 'iconLeftName',
|
|
8831
|
+
iconLeftSize: 'iconLeftSize',
|
|
8832
|
+
iconRight: 'iconRight',
|
|
8833
|
+
iconRightName: 'iconRightName',
|
|
8834
|
+
iconRightSize: 'iconRightSize',
|
|
8835
|
+
hasIconsLeft: 'hasIconsLeft',
|
|
8836
|
+
hasIconsRight: 'hasIconsRight',
|
|
8837
|
+
isExpanded: 'isExpanded',
|
|
8838
|
+
controlSize: 'size (for controlSize)',
|
|
8839
|
+
controlClassName: 'className (for controlClassName)',
|
|
8840
|
+
};
|
|
8841
|
+
/**
|
|
8842
|
+
* Whether a convenience form wrapper renders a `Control` of its own (#921):
|
|
8843
|
+
* only outside one. Inside a `Control` it renders none, so the props it
|
|
8844
|
+
* would have handed its own do nothing there, and it warns in development,
|
|
8845
|
+
* naming them and saying to set them on that `Control`. An `inline` picker
|
|
8846
|
+
* renders none in either place, so it drops them inside a `Control` or not,
|
|
8847
|
+
* and its warning says so without pointing at a `Control`. A wrapper passes
|
|
8848
|
+
* only the props it hands its own `Control`, as the caller gave them, so a
|
|
8849
|
+
* default the wrapper fills in itself never warns. A prop counts when it is
|
|
8850
|
+
* truthy, since a falsy one would change nothing on a `Control` either.
|
|
8851
|
+
* Internal; not part of the public API.
|
|
8852
|
+
*/
|
|
8853
|
+
const rendersOwnControl = (component, options) => {
|
|
8854
|
+
if (!options.inline && !options.insideControl)
|
|
8855
|
+
return true;
|
|
8856
|
+
const held = Object.keys(ON_CONTROL).filter(prop => options[prop]);
|
|
8857
|
+
if (held.length === 0)
|
|
8858
|
+
return false;
|
|
8859
|
+
const names = held.join(' ');
|
|
8860
|
+
const doNothing = held.length > 1 ? 'those props do' : 'that prop does';
|
|
8861
|
+
if (options.inline) {
|
|
8862
|
+
warnOnce(`${component}:Control-props-inline:${held.join('+')}`, `[bestax-bulma] <${component} inline ${names}> renders no <Control> ` +
|
|
8863
|
+
`in inline mode, inside a <Control> or not, so ${doNothing} ` +
|
|
8864
|
+
`nothing. Leave ${held.length > 1 ? 'them' : 'it'} out of an ` +
|
|
8865
|
+
`inline picker.`);
|
|
8866
|
+
}
|
|
8867
|
+
else {
|
|
8868
|
+
warnOnce(`${component}:Control-props-in-Control:${held.join('+')}`, `[bestax-bulma] <${component} ${names}> inside a <Control> renders ` +
|
|
8869
|
+
`no <Control> of its own, so ${doNothing} nothing there. Set ` +
|
|
8870
|
+
`${listOf(held.map(prop => ON_CONTROL[prop]))} on that <Control> ` +
|
|
8871
|
+
`instead.`);
|
|
8872
|
+
}
|
|
8873
|
+
return false;
|
|
8874
|
+
};
|
|
8822
8875
|
const FieldLabelIdContext = createContext(undefined);
|
|
8823
8876
|
/**
|
|
8824
8877
|
* The id a labeled Field wants its single composed control to adopt (#495).
|
|
@@ -8953,6 +9006,7 @@ Checkbox.displayName = 'Checkbox';
|
|
|
8953
9006
|
// picker bases read it so their right-side launcher gives way to that
|
|
8954
9007
|
// spinner, which sits at the same edge, whether the `Control` is the one a
|
|
8955
9008
|
// convenience input renders or one the caller wrapped around the input.
|
|
9009
|
+
// Autocomplete reads it for its clear button, which sits there too.
|
|
8956
9010
|
// Kept apart from `FormContext`, whose contexts only report whether a `Field`
|
|
8957
9011
|
// or `Control` is present. Internal; not part of the public API.
|
|
8958
9012
|
const ControlLoadingContext = createContext(false);
|
|
@@ -9282,7 +9336,7 @@ const CheckboxesComponent = ({ label, labelSize, labelProps, horizontal, message
|
|
|
9282
9336
|
}), [name, groupActive, currentValue, handleChange]);
|
|
9283
9337
|
const checkboxesElement = (jsx("div", { className: wrapperClass, role: "group", "aria-labelledby": ariaLabelledBy, ...rest, children: jsx(CheckboxesProvider, { value: ctx, children: children }) }));
|
|
9284
9338
|
let content = checkboxesElement;
|
|
9285
|
-
if (
|
|
9339
|
+
if (rendersOwnControl('Checkboxes', { insideControl })) {
|
|
9286
9340
|
content = jsx(Control, { children: content });
|
|
9287
9341
|
}
|
|
9288
9342
|
if (ownField) {
|
|
@@ -9522,7 +9576,7 @@ const RadiosComponent = ({ label, labelSize, labelProps, horizontal, message, me
|
|
|
9522
9576
|
}), [name, groupActive, currentValue, handleChange]);
|
|
9523
9577
|
const radiosElement = (jsx("div", { className: wrapperClass, role: "radiogroup", "aria-labelledby": ariaLabelledBy, ...rest, children: jsx(RadiosProvider, { value: ctx, children: children }) }));
|
|
9524
9578
|
let content = radiosElement;
|
|
9525
|
-
if (
|
|
9579
|
+
if (rendersOwnControl('Radios', { insideControl })) {
|
|
9526
9580
|
content = jsx(Control, { children: content });
|
|
9527
9581
|
}
|
|
9528
9582
|
if (ownField) {
|
|
@@ -10056,7 +10110,7 @@ const Slider = forwardRef((props, ref) => {
|
|
|
10056
10110
|
'--slider-output-arrow-offset': `calc(50% - ${nudgeXHigh}px)`,
|
|
10057
10111
|
}, children: formatValue(currentRange[1]) })), renderTicks()] }));
|
|
10058
10112
|
let content = sliderElement;
|
|
10059
|
-
if (
|
|
10113
|
+
if (rendersOwnControl('Slider', { insideControl })) {
|
|
10060
10114
|
content = jsx(Control, { children: content });
|
|
10061
10115
|
}
|
|
10062
10116
|
if (ownField) {
|
|
@@ -10087,7 +10141,11 @@ const Numberinput = forwardRef(({
|
|
|
10087
10141
|
// Field props
|
|
10088
10142
|
label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, value: controlledValue, defaultValue, min, max, step = 1, size, color, inputColor, controlsPosition = 'both', controlsRounded = false, compact = false, bare, variant = 'plusminus', disabled = false, editable = true, isLoading = false, exponential = false, onChange, className, ...props }, ref) => {
|
|
10089
10143
|
const insideField = useInsideField();
|
|
10090
|
-
|
|
10144
|
+
// Bare controls join their parent's row, which a `.control` does not lay
|
|
10145
|
+
// out. `useInsideControl()` is true anywhere below a `Control`, even past
|
|
10146
|
+
// a `Field` nested in it, so there they keep their own row as well.
|
|
10147
|
+
const insideControl = useInsideControl();
|
|
10148
|
+
const effectiveBare = bare ?? (insideField && !insideControl);
|
|
10091
10149
|
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
10092
10150
|
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
10093
10151
|
const inputRef = useRef(null);
|
|
@@ -10500,7 +10558,7 @@ const Rate = forwardRef(({ label, labelSize, labelProps, horizontal, message, me
|
|
|
10500
10558
|
const rateCustomTextClass = usePrefixedClassNames('rate-custom-text');
|
|
10501
10559
|
const rateElement = (jsxs("div", { ref: ref, className: combinedClasses, role: "radiogroup", "aria-label": ariaLabelledBy ? undefined : 'Rating', "aria-labelledby": ariaLabelledBy, "aria-valuenow": currentValue, "aria-valuemin": 0, "aria-valuemax": max, "aria-valuetext": ariaValueText, tabIndex: disabled ? -1 : 0, onKeyDown: handleKeyDown, ...rest, children: [jsx("div", { className: rateItemsClass, children: renderIcons() }), showScore && (jsx("span", { className: rateScoreClass, children: getScoreDisplay() })), text && jsx("span", { className: rateTextClass, children: text }), customText && (jsx("span", { className: rateCustomTextClass, children: customText })), name && (jsx("input", { type: "hidden", name: name, value: currentValue, form: form }))] }));
|
|
10502
10560
|
let content = rateElement;
|
|
10503
|
-
if (
|
|
10561
|
+
if (rendersOwnControl('Rate', { insideControl })) {
|
|
10504
10562
|
content = jsx(Control, { children: content });
|
|
10505
10563
|
}
|
|
10506
10564
|
if (ownField) {
|
|
@@ -10739,7 +10797,11 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
10739
10797
|
[`is-${messageColor}`]: !!messageColor,
|
|
10740
10798
|
});
|
|
10741
10799
|
const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;
|
|
10742
|
-
|
|
10800
|
+
// The clear button gives way to a spinner at its spot: the `loading` one,
|
|
10801
|
+
// or that of a loading Control this sits in.
|
|
10802
|
+
const controlLoading = useControlLoading();
|
|
10803
|
+
const showClear = clearable && !!inputValue && !disabled && !loading && !controlLoading;
|
|
10804
|
+
const autocompleteElement = (jsxs("div", { ref: containerRef, className: combinedClasses, ...rest, children: [jsxs("div", { className: controlClasses, children: [jsx("input", { ref: combinedRef, type: "text", className: inputClasses, id: controlId, value: inputValue, placeholder: placeholder, disabled: disabled, name: name, form: form, required: required, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, role: "combobox", "aria-expanded": isActive, "aria-haspopup": "listbox", "aria-autocomplete": "list", autoComplete: "off" }), showClear && (jsx("button", { type: "button", className: iconRightClickableClass, onClick: handleClear, "aria-label": "Clear", children: jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", width: "16", height: "16", "aria-hidden": "true", children: [jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })), loading && (jsx("span", { className: iconRightClass, children: jsx("span", { className: loaderClass }) }))] }), isActive && (filteredData.length > 0 || empty) && (jsx("div", { className: dropdownMenuClasses, children: jsxs("div", { ref: dropdownRef, className: dropdownContentClass, style: { maxHeight: `${maxHeight}px`, overflowY: 'auto' }, role: "listbox", onScroll: handleDropdownScroll, children: [header && jsx("div", { className: dropdownHeaderClass, children: header }), filteredData.length > 0 ? (filteredData.map((item, index) => {
|
|
10743
10805
|
const isDisabled = typeof item !== 'string' && item.disabled;
|
|
10744
10806
|
const isHighlighted = index === highlightedIndex;
|
|
10745
10807
|
const itemClasses = prefixedClassNames(classPrefix, 'dropdown-item', {
|
|
@@ -11247,6 +11309,21 @@ fieldClassName, controlClassName,
|
|
|
11247
11309
|
horizontal,
|
|
11248
11310
|
fieldClassName,
|
|
11249
11311
|
});
|
|
11312
|
+
const ownControl = rendersOwnControl('Input', {
|
|
11313
|
+
insideControl,
|
|
11314
|
+
isLoading,
|
|
11315
|
+
iconLeft,
|
|
11316
|
+
iconLeftName,
|
|
11317
|
+
iconLeftSize,
|
|
11318
|
+
iconRight,
|
|
11319
|
+
iconRightName,
|
|
11320
|
+
iconRightSize,
|
|
11321
|
+
hasIconsLeft,
|
|
11322
|
+
hasIconsRight,
|
|
11323
|
+
isExpanded,
|
|
11324
|
+
controlSize,
|
|
11325
|
+
controlClassName,
|
|
11326
|
+
});
|
|
11250
11327
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
11251
11328
|
label,
|
|
11252
11329
|
id: inputProps.id,
|
|
@@ -11257,7 +11334,7 @@ fieldClassName, controlClassName,
|
|
|
11257
11334
|
[`is-${messageColor}`]: !!messageColor,
|
|
11258
11335
|
});
|
|
11259
11336
|
let content = jsx(InputBase, { ref: ref, id: controlId, ...inputProps });
|
|
11260
|
-
if (
|
|
11337
|
+
if (ownControl) {
|
|
11261
11338
|
content = (jsx(Control, { iconLeft: iconLeft, iconRight: iconRight, iconLeftName: iconLeftName, iconRightName: iconRightName, iconLeftSize: iconLeftSize, iconRightSize: iconRightSize, hasIconsLeft: hasIconsLeft, hasIconsRight: hasIconsRight, isLoading: isLoading, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
|
|
11262
11339
|
}
|
|
11263
11340
|
const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;
|
|
@@ -11305,6 +11382,17 @@ fieldClassName, controlClassName,
|
|
|
11305
11382
|
horizontal,
|
|
11306
11383
|
fieldClassName,
|
|
11307
11384
|
});
|
|
11385
|
+
// Not isLoading, which the select draws itself and so keeps in a Control.
|
|
11386
|
+
const ownControl = rendersOwnControl('Select', {
|
|
11387
|
+
insideControl,
|
|
11388
|
+
iconLeft,
|
|
11389
|
+
iconLeftName,
|
|
11390
|
+
iconLeftSize,
|
|
11391
|
+
hasIconsLeft,
|
|
11392
|
+
isExpanded,
|
|
11393
|
+
controlSize,
|
|
11394
|
+
controlClassName,
|
|
11395
|
+
});
|
|
11308
11396
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
11309
11397
|
label,
|
|
11310
11398
|
id: selectProps.id,
|
|
@@ -11315,7 +11403,7 @@ fieldClassName, controlClassName,
|
|
|
11315
11403
|
[`is-${messageColor}`]: !!messageColor,
|
|
11316
11404
|
});
|
|
11317
11405
|
let content = jsx(SelectBase, { ref: ref, id: controlId, ...selectProps });
|
|
11318
|
-
if (
|
|
11406
|
+
if (ownControl) {
|
|
11319
11407
|
content = (jsx(Control, { iconLeft: iconLeft, iconLeftName: iconLeftName, iconLeftSize: iconLeftSize, hasIconsLeft: hasIconsLeft, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
|
|
11320
11408
|
}
|
|
11321
11409
|
const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;
|
|
@@ -11364,6 +11452,12 @@ fieldClassName, controlClassName,
|
|
|
11364
11452
|
horizontal,
|
|
11365
11453
|
fieldClassName,
|
|
11366
11454
|
});
|
|
11455
|
+
const ownControl = rendersOwnControl('TextArea', {
|
|
11456
|
+
insideControl,
|
|
11457
|
+
isLoading: controlIsLoading,
|
|
11458
|
+
controlSize,
|
|
11459
|
+
controlClassName,
|
|
11460
|
+
});
|
|
11367
11461
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
11368
11462
|
label,
|
|
11369
11463
|
id: textAreaProps.id,
|
|
@@ -11374,7 +11468,7 @@ fieldClassName, controlClassName,
|
|
|
11374
11468
|
[`is-${messageColor}`]: !!messageColor,
|
|
11375
11469
|
});
|
|
11376
11470
|
let content = jsx(TextAreaBase, { ref: ref, id: controlId, ...textAreaProps });
|
|
11377
|
-
if (
|
|
11471
|
+
if (ownControl) {
|
|
11378
11472
|
content = (jsx(Control, { isLoading: controlIsLoading, size: controlSize, className: controlClassName, children: content }));
|
|
11379
11473
|
}
|
|
11380
11474
|
const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;
|
|
@@ -11393,6 +11487,9 @@ const DEFAULT_PICKER_LABELS = {
|
|
|
11393
11487
|
chooseDate: 'Choose date',
|
|
11394
11488
|
chooseMonth: 'Choose month',
|
|
11395
11489
|
chooseYear: 'Choose year',
|
|
11490
|
+
rangeStart: 'Start date',
|
|
11491
|
+
rangeEnd: 'End date',
|
|
11492
|
+
rangePreviewEnd: 'Choose as end date',
|
|
11396
11493
|
hours: 'hours',
|
|
11397
11494
|
minutes: 'minutes',
|
|
11398
11495
|
seconds: 'seconds',
|
|
@@ -11545,6 +11642,43 @@ function isPeriodUnselectable(d, granularity, c) {
|
|
|
11545
11642
|
}
|
|
11546
11643
|
return true;
|
|
11547
11644
|
}
|
|
11645
|
+
/**
|
|
11646
|
+
* The most days {@link canCloseRange} walks between a range's ends, about
|
|
11647
|
+
* eleven years. A span it would have to walk further is refused, so the check
|
|
11648
|
+
* stays cheap enough to run on every pointer move over the calendar.
|
|
11649
|
+
*/
|
|
11650
|
+
const RANGE_WALK_LIMIT = 4000;
|
|
11651
|
+
/**
|
|
11652
|
+
* Whether `end` can close a range that opens on `start`: it falls on `start`'s
|
|
11653
|
+
* day or later, and, unless `allowDisabled`, no day between the two is one
|
|
11654
|
+
* that `shouldDisableDate` or `unselectableDates` disables. Checking that
|
|
11655
|
+
* walks the days between, so with either rule set and `allowDisabled` off,
|
|
11656
|
+
* an `end` more than {@link RANGE_WALK_LIMIT} days after `start` is refused.
|
|
11657
|
+
* The ends' own constraints, `min` and `max` among them, are the caller's to
|
|
11658
|
+
* check. Bounds can't fall between two days inside them, so they are not
|
|
11659
|
+
* walked.
|
|
11660
|
+
*/
|
|
11661
|
+
function canCloseRange(start, end, c, allowDisabled = false) {
|
|
11662
|
+
const first = startOfDay(start);
|
|
11663
|
+
const last = startOfDay(end).getTime();
|
|
11664
|
+
if (last < first.getTime())
|
|
11665
|
+
return false;
|
|
11666
|
+
if (allowDisabled)
|
|
11667
|
+
return true;
|
|
11668
|
+
const blocks = {
|
|
11669
|
+
shouldDisableDate: c.shouldDisableDate,
|
|
11670
|
+
unselectableDates: c.unselectableDates,
|
|
11671
|
+
};
|
|
11672
|
+
if (!blocks.shouldDisableDate && !blocks.unselectableDates?.length) {
|
|
11673
|
+
return true;
|
|
11674
|
+
}
|
|
11675
|
+
for (let d = addDays(first, 1), walked = 1; d.getTime() < last; d = addDays(d, 1), walked++) {
|
|
11676
|
+
if (walked >= RANGE_WALK_LIMIT || isDayUnselectable(d, blocks)) {
|
|
11677
|
+
return false;
|
|
11678
|
+
}
|
|
11679
|
+
}
|
|
11680
|
+
return true;
|
|
11681
|
+
}
|
|
11548
11682
|
function addDays(d, n) {
|
|
11549
11683
|
const r = new Date(d);
|
|
11550
11684
|
r.setDate(r.getDate() + n);
|
|
@@ -12005,11 +12139,34 @@ function nearestEnabled(i, disabled) {
|
|
|
12005
12139
|
}
|
|
12006
12140
|
return i;
|
|
12007
12141
|
}
|
|
12142
|
+
/** The long, spoken form of a date in a range's announcement. */
|
|
12143
|
+
const LONG_DATE = {
|
|
12144
|
+
year: 'numeric',
|
|
12145
|
+
month: 'long',
|
|
12146
|
+
day: 'numeric',
|
|
12147
|
+
};
|
|
12148
|
+
/** A range's pending start: its start, when it has no end. */
|
|
12149
|
+
const pendingStartOf = (range) => range?.[0] && !range[1] ? startOfDay(range[0]) : null;
|
|
12008
12150
|
/**
|
|
12009
|
-
* The
|
|
12010
|
-
*
|
|
12011
|
-
*
|
|
12012
|
-
|
|
12151
|
+
* The day grid's cells in rows of a week. A grid owns rows and rows own
|
|
12152
|
+
* cells, so assistive technology can walk the grid, and the selection it
|
|
12153
|
+
* reports, row by row.
|
|
12154
|
+
*/
|
|
12155
|
+
const inWeeks = (days, className) => {
|
|
12156
|
+
const rows = [];
|
|
12157
|
+
for (let i = 0; i < days.length; i += 7) {
|
|
12158
|
+
rows.push(jsx("div", { role: "row", className: className, children: days.slice(i, i + 7) }, i));
|
|
12159
|
+
}
|
|
12160
|
+
return rows;
|
|
12161
|
+
};
|
|
12162
|
+
/** A date's time, or `null` for none, for comparing ranges by value. */
|
|
12163
|
+
const timeOf = (d) => d ? d.getTime() : null;
|
|
12164
|
+
/**
|
|
12165
|
+
* The year list's keyboard, as the selection surface and as navigation:
|
|
12166
|
+
* arrows move a year or a row, Home/End go to the list's ends. Returns the
|
|
12167
|
+
* year to start from and the direction to search past disabled years, or
|
|
12168
|
+
* `null` for a key the list doesn't handle. As navigation no year is
|
|
12169
|
+
* disabled, so only the year is used.
|
|
12013
12170
|
*/
|
|
12014
12171
|
function yearStep(key, focusedYear, years) {
|
|
12015
12172
|
switch (key) {
|
|
@@ -12029,7 +12186,7 @@ function yearStep(key, focusedYear, years) {
|
|
|
12029
12186
|
return null;
|
|
12030
12187
|
}
|
|
12031
12188
|
}
|
|
12032
|
-
const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minProp, max, shouldDisableDate, unselectableDates, firstDayOfWeek = 0, locale, dayNames, monthNames, nearbyMonthDays = true, color, size, className, id, autoFocusCell = false, labels, yearsRange, granularity = 'day', }) => {
|
|
12189
|
+
const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minProp, max, shouldDisableDate, unselectableDates, firstDayOfWeek = 0, locale, dayNames, monthNames, nearbyMonthDays = true, color, size, className, id, autoFocusCell = false, labels, yearsRange, granularity = 'day', range, onRangeSelect, allowDisabledInRange = false, }) => {
|
|
12033
12190
|
const gridRef = useRef(null);
|
|
12034
12191
|
const monthGridRef = useRef(null);
|
|
12035
12192
|
const yearGridRef = useRef(null);
|
|
@@ -12086,6 +12243,80 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12086
12243
|
const i = nearestEnabled(focusedDate.getDate() - 1, days.map(isDateUnselectable));
|
|
12087
12244
|
return days[i];
|
|
12088
12245
|
}, [cells, focusedDate, isDateUnselectable]);
|
|
12246
|
+
// ----- Range mode -----
|
|
12247
|
+
// Range props come only with the day grid.
|
|
12248
|
+
const rangeMode = !!range;
|
|
12249
|
+
const valuePending = pendingStartOf(range);
|
|
12250
|
+
// The start of a range being picked, which nothing outside the calendar
|
|
12251
|
+
// sees until its end is picked too. It starts as the range's own pending
|
|
12252
|
+
// start, and starts there again whenever the range changes from outside.
|
|
12253
|
+
const [anchor, setAnchor] = useState(valuePending);
|
|
12254
|
+
const rangeKey = `${timeOf(range?.[0])}|${timeOf(range?.[1])}`;
|
|
12255
|
+
const [anchorRangeKey, setAnchorRangeKey] = useState(rangeKey);
|
|
12256
|
+
if (anchorRangeKey !== rangeKey) {
|
|
12257
|
+
setAnchorRangeKey(rangeKey);
|
|
12258
|
+
setAnchor(valuePending);
|
|
12259
|
+
}
|
|
12260
|
+
// The day under the pointer while a start is pending, which the preview
|
|
12261
|
+
// runs to ahead of the focused day.
|
|
12262
|
+
const [hoverDate, setHoverDate] = useState(null);
|
|
12263
|
+
// Whether `day` can end a range that starts on `start`. The start is
|
|
12264
|
+
// checked as well as the end: one the range brought, or one picked before
|
|
12265
|
+
// the bounds or the disabled days changed, can rule itself out.
|
|
12266
|
+
const canEndAt = useCallback((start, day) => !isDateUnselectable(start) &&
|
|
12267
|
+
!isDateUnselectable(day) &&
|
|
12268
|
+
canCloseRange(start, day, { shouldDisableDate, unselectableDates }, allowDisabledInRange), [
|
|
12269
|
+
isDateUnselectable,
|
|
12270
|
+
shouldDisableDate,
|
|
12271
|
+
unselectableDates,
|
|
12272
|
+
allowDisabledInRange,
|
|
12273
|
+
]);
|
|
12274
|
+
// A pick in range mode: the end of the pending range when it can be one,
|
|
12275
|
+
// and otherwise the start of a new one.
|
|
12276
|
+
const pickRangeDay = useCallback((picked) => {
|
|
12277
|
+
const day = startOfDay(picked);
|
|
12278
|
+
// The pointer is on the picked day, which the focused day now is too.
|
|
12279
|
+
setHoverDate(null);
|
|
12280
|
+
if (anchor && canEndAt(anchor, day)) {
|
|
12281
|
+
setAnchor(null);
|
|
12282
|
+
onRangeSelect?.(anchor, day);
|
|
12283
|
+
}
|
|
12284
|
+
else {
|
|
12285
|
+
setAnchor(day);
|
|
12286
|
+
}
|
|
12287
|
+
}, [anchor, canEndAt, onRangeSelect]);
|
|
12288
|
+
// What the grid shows: while a start is pending, the range from it to the
|
|
12289
|
+
// hovered or focused day, when that day can end it; otherwise the
|
|
12290
|
+
// committed range.
|
|
12291
|
+
let shownStart;
|
|
12292
|
+
let shownEnd;
|
|
12293
|
+
if (anchor) {
|
|
12294
|
+
shownStart = anchor;
|
|
12295
|
+
const previewEnd = startOfDay(hoverDate ?? focusedDate);
|
|
12296
|
+
shownEnd = canEndAt(anchor, previewEnd) ? previewEnd : null;
|
|
12297
|
+
}
|
|
12298
|
+
else {
|
|
12299
|
+
shownStart = range?.[0] ? startOfDay(range[0]) : null;
|
|
12300
|
+
shownEnd = range?.[1] ? startOfDay(range[1]) : null;
|
|
12301
|
+
}
|
|
12302
|
+
const previewing = !!anchor;
|
|
12303
|
+
// Ids for the words that name a range's ends and state the whole range.
|
|
12304
|
+
const reactId = useId();
|
|
12305
|
+
const rangeIdBase = id ?? `calendar-${reactId}`;
|
|
12306
|
+
const rangeStartId = `${rangeIdBase}-range-start`;
|
|
12307
|
+
const rangeEndId = `${rangeIdBase}-range-end`;
|
|
12308
|
+
const rangePreviewEndId = `${rangeIdBase}-range-preview-end`;
|
|
12309
|
+
const rangeSummaryId = `${rangeIdBase}-range-summary`;
|
|
12310
|
+
// The selection in words: the pending start alone, or the committed ends.
|
|
12311
|
+
const selectedEnd = previewing ? null : shownEnd;
|
|
12312
|
+
const rangeSummary = [
|
|
12313
|
+
shownStart &&
|
|
12314
|
+
`${t.rangeStart}: ${formatDate(shownStart, LONG_DATE, locale)}`,
|
|
12315
|
+
selectedEnd &&
|
|
12316
|
+
`${t.rangeEnd}: ${formatDate(selectedEnd, LONG_DATE, locale)}`,
|
|
12317
|
+
]
|
|
12318
|
+
.filter(Boolean)
|
|
12319
|
+
.join(', ');
|
|
12089
12320
|
// Whether the grid on show holds focus, or held it last before focus went
|
|
12090
12321
|
// nowhere: the cell that had it left the page or became disabled. A blur
|
|
12091
12322
|
// that takes focus out of the grid, to nowhere included, is the user
|
|
@@ -12123,6 +12354,8 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12123
12354
|
}
|
|
12124
12355
|
}, [min, max, isDateUnselectable, onFocusedDateChange]);
|
|
12125
12356
|
const handleKeyDown = useCallback((e) => {
|
|
12357
|
+
// A key takes the range preview back from the pointer.
|
|
12358
|
+
setHoverDate(null);
|
|
12126
12359
|
switch (e.key) {
|
|
12127
12360
|
case 'ArrowLeft':
|
|
12128
12361
|
e.preventDefault();
|
|
@@ -12165,11 +12398,35 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12165
12398
|
case 'Enter':
|
|
12166
12399
|
case ' ':
|
|
12167
12400
|
e.preventDefault();
|
|
12168
|
-
if (
|
|
12169
|
-
|
|
12401
|
+
if (isDateUnselectable(focusedDate))
|
|
12402
|
+
break;
|
|
12403
|
+
if (rangeMode)
|
|
12404
|
+
pickRangeDay(focusedDate);
|
|
12405
|
+
else
|
|
12406
|
+
onSelect?.(focusedDate);
|
|
12407
|
+
break;
|
|
12408
|
+
case 'Escape':
|
|
12409
|
+
// A pending pick is taken back, and the key stops there so a
|
|
12410
|
+
// popover around the calendar stays open. The range's own pending
|
|
12411
|
+
// start is no pick, so Escape goes on to close the popover.
|
|
12412
|
+
if (anchor && !(valuePending && isSameDay(anchor, valuePending))) {
|
|
12413
|
+
e.preventDefault();
|
|
12414
|
+
e.stopPropagation();
|
|
12415
|
+
setAnchor(valuePending);
|
|
12416
|
+
}
|
|
12170
12417
|
break;
|
|
12171
12418
|
}
|
|
12172
|
-
}, [
|
|
12419
|
+
}, [
|
|
12420
|
+
focusedDate,
|
|
12421
|
+
firstDayOfWeek,
|
|
12422
|
+
isDateUnselectable,
|
|
12423
|
+
moveFocus,
|
|
12424
|
+
onSelect,
|
|
12425
|
+
rangeMode,
|
|
12426
|
+
pickRangeDay,
|
|
12427
|
+
anchor,
|
|
12428
|
+
valuePending,
|
|
12429
|
+
]);
|
|
12173
12430
|
// The month grid's counterpart to `moveFocus`: step by `direction` months
|
|
12174
12431
|
// until a month with a selectable day turns up, stopping at the min/max
|
|
12175
12432
|
// months. The cap guards against a range with nothing selectable.
|
|
@@ -12192,7 +12449,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12192
12449
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
12193
12450
|
e.preventDefault();
|
|
12194
12451
|
if (!isMonthUnselectable(focusedDate)) {
|
|
12195
|
-
onSelect(startOfMonth(focusedDate));
|
|
12452
|
+
onSelect?.(startOfMonth(focusedDate));
|
|
12196
12453
|
}
|
|
12197
12454
|
return;
|
|
12198
12455
|
}
|
|
@@ -12218,6 +12475,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12218
12475
|
const dayNamesRowClass = usePrefixedClassNames('dateinput-day-names');
|
|
12219
12476
|
const dayNameClass = usePrefixedClassNames('dateinput-day-name');
|
|
12220
12477
|
const gridClass = usePrefixedClassNames('dateinput-grid');
|
|
12478
|
+
const weekClass = usePrefixedClassNames('dateinput-week');
|
|
12221
12479
|
const monthsGridClass = usePrefixedClassNames('dateinput-months-grid');
|
|
12222
12480
|
const monthsRowClass = usePrefixedClassNames('dateinput-months-row');
|
|
12223
12481
|
const yearsGridClass = usePrefixedClassNames('dateinput-years-grid');
|
|
@@ -12270,8 +12528,8 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12270
12528
|
// year is disabled the stop moves to the nearest enabled year in the list,
|
|
12271
12529
|
// and focusing it makes it the focused year. As the selection surface a
|
|
12272
12530
|
// year outside a non-empty list starts from the end it fell past, so the
|
|
12273
|
-
// list keeps a stop. As navigation
|
|
12274
|
-
//
|
|
12531
|
+
// list keeps a stop. As navigation it marks the current year, so a year
|
|
12532
|
+
// outside the list marks no option, and the stop itself follows focus.
|
|
12275
12533
|
const tabStopYear = useMemo(() => {
|
|
12276
12534
|
let i = yearList.indexOf(focusedYear);
|
|
12277
12535
|
if (i < 0) {
|
|
@@ -12281,6 +12539,12 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12281
12539
|
}
|
|
12282
12540
|
return yearList[nearestEnabled(i, yearList.map(y => disabledYears.has(y)))];
|
|
12283
12541
|
}, [yearList, focusedYear, disabledYears, isYearGranularity]);
|
|
12542
|
+
// As navigation the keys move focus and leave the focused year alone, so
|
|
12543
|
+
// the tab stop goes to the year that last had focus while it is listed,
|
|
12544
|
+
// and Tab leaves the list rather than landing on the current year. It
|
|
12545
|
+
// starts over each time the list opens.
|
|
12546
|
+
const [navYear, setNavYear] = useState(null);
|
|
12547
|
+
const listTabStop = navYear !== null && yearList.includes(navYear) ? navYear : tabStopYear;
|
|
12284
12548
|
// Which of the focused year's months have no selectable day.
|
|
12285
12549
|
const disabledMonths = useMemo(() => granularity === 'month'
|
|
12286
12550
|
? Array.from({ length: 12 }, (_, m) => isMonthUnselectable(makeDate(focusedYear, m)))
|
|
@@ -12301,7 +12565,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12301
12565
|
// pick and `onSelect` gets the year's first day.
|
|
12302
12566
|
const handleYearPick = useCallback((year) => {
|
|
12303
12567
|
onFocusedDateChange(clampDate(makeDate(year), min, max));
|
|
12304
|
-
onSelect(makeDate(year));
|
|
12568
|
+
onSelect?.(makeDate(year));
|
|
12305
12569
|
}, [min, max, onFocusedDateChange, onSelect]);
|
|
12306
12570
|
// Step through the listed years from `year` by `direction`, skipping
|
|
12307
12571
|
// disabled ones, and focus the first selectable year found.
|
|
@@ -12316,19 +12580,39 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12316
12580
|
}
|
|
12317
12581
|
}, [yearList, disabledYears, onFocusedDateChange, min, max]);
|
|
12318
12582
|
const handleYearListKeyDown = useCallback((e) => {
|
|
12319
|
-
if (
|
|
12320
|
-
|
|
12321
|
-
if (
|
|
12322
|
-
|
|
12323
|
-
|
|
12324
|
-
|
|
12583
|
+
if (isYearGranularity) {
|
|
12584
|
+
const step = yearStep(e.key, focusedYear, yearList);
|
|
12585
|
+
if (!step)
|
|
12586
|
+
return;
|
|
12587
|
+
e.preventDefault();
|
|
12588
|
+
moveYearFocus(step[0], step[1]);
|
|
12325
12589
|
return;
|
|
12326
12590
|
}
|
|
12327
|
-
|
|
12591
|
+
// As navigation, Escape returns to the grid the list was opened from.
|
|
12592
|
+
// It stops there, so a popover around the calendar stays open and only
|
|
12593
|
+
// a second Escape closes it.
|
|
12594
|
+
if (e.key === 'Escape') {
|
|
12595
|
+
e.preventDefault();
|
|
12596
|
+
e.stopPropagation();
|
|
12597
|
+
setView(baseView);
|
|
12598
|
+
return;
|
|
12599
|
+
}
|
|
12600
|
+
// The other keys move DOM focus from the year that has it and leave
|
|
12601
|
+
// the focused date alone. The list is centred on that date, so moving
|
|
12602
|
+
// it would reflow the list under the keyboard, and it would turn the
|
|
12603
|
+
// grid behind the list before anything is picked. Enter or Space
|
|
12604
|
+
// clicks the year with focus, which picks it.
|
|
12605
|
+
const options = Array.from(e.currentTarget.querySelectorAll('[role="option"]'));
|
|
12606
|
+
const from = options.indexOf(e.target);
|
|
12607
|
+
if (from < 0)
|
|
12608
|
+
return;
|
|
12609
|
+
const step = yearStep(e.key, yearList[from], yearList);
|
|
12328
12610
|
if (!step)
|
|
12329
12611
|
return;
|
|
12330
12612
|
e.preventDefault();
|
|
12331
|
-
|
|
12613
|
+
// The listed years run one apart, so a year's index is its distance
|
|
12614
|
+
// from the first. A step past either end finds no option.
|
|
12615
|
+
options[step[0] - yearList[0]]?.focus();
|
|
12332
12616
|
}, [isYearGranularity, baseView, focusedYear, yearList, moveYearFocus]);
|
|
12333
12617
|
// The day or month grid takes focus as the popover opens on it, or comes
|
|
12334
12618
|
// back to it from the year list.
|
|
@@ -12399,32 +12683,72 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12399
12683
|
list.querySelector('[data-focused-year="true"]')?.focus();
|
|
12400
12684
|
}, [isYearGranularity, focusedYear]);
|
|
12401
12685
|
const navArrow = (d) => (jsx("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", children: jsx("path", { d: d, fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
12402
|
-
return (jsxs("div", { className: classNames(calendarClass, className), id: id, children: [jsxs("div", { className: headerClass, children: [isYearGranularity ? (jsx("span", { className: monthLabelClass, id: labelId, children: monthLabel })) : (jsxs("button", { type: "button", className: monthTriggerClass, "aria-haspopup": "listbox", "aria-expanded": view === 'years', onClick: () =>
|
|
12686
|
+
return (jsxs("div", { className: classNames(calendarClass, className), id: id, children: [jsxs("div", { className: headerClass, children: [isYearGranularity ? (jsx("span", { className: monthLabelClass, id: labelId, children: monthLabel })) : (jsxs("button", { type: "button", className: monthTriggerClass, "aria-haspopup": "listbox", "aria-expanded": view === 'years', onClick: () => {
|
|
12687
|
+
setNavYear(null);
|
|
12688
|
+
setView(v => (v === 'years' ? baseView : 'years'));
|
|
12689
|
+
}, children: [jsx("span", { className: monthLabelClass, id: labelId, "aria-live": "polite", children: monthLabel }), jsx("svg", { className: monthCaretClass, viewBox: "0 0 12 12", width: "10", height: "10", "aria-hidden": "true", children: jsx("path", { d: "M2 4l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) })] })), !isYearGranularity && (jsxs("div", { className: navGroupClass, children: [jsx("button", { type: "button", className: navPrevClass, "aria-label": isDayGranularity ? t.prevMonth : t.prevYear, disabled: prevDisabled || view !== baseView, onClick: () => onFocusedDateChange(prevAnchor), children: navArrow('M10 3l-5 5 5 5') }), jsx("button", { type: "button", className: navNextClass, "aria-label": isDayGranularity ? t.nextMonth : t.nextYear, disabled: nextDisabled || view !== baseView, onClick: () => onFocusedDateChange(nextAnchor), children: navArrow('M6 3l5 5-5 5') })] }))] }), view === 'days' && (jsxs(Fragment, { children: [jsx("div", { className: dayNamesRowClass, "aria-hidden": "true", children: computedDayNames.map((name, i) => (jsx("div", { className: dayNameClass, children: name }, i))) }), jsx("div", { ref: gridRef, role: "grid", "aria-labelledby": labelId, "aria-multiselectable": rangeMode || undefined, "aria-describedby": rangeMode ? rangeSummaryId : undefined, className: gridClass, onKeyDown: handleKeyDown, onMouseLeave: anchor ? () => setHoverDate(null) : undefined, ...gridFocusHandlers, children: inWeeks(cells.map(cell => {
|
|
12403
12690
|
const disabled = isDateUnselectable(cell.date);
|
|
12404
|
-
const
|
|
12691
|
+
const time = cell.date.getTime();
|
|
12692
|
+
// Range mode. The range on show has its ends filled. Every day
|
|
12693
|
+
// of a committed range is selected, and of a preview only the
|
|
12694
|
+
// start.
|
|
12695
|
+
const isStart = time === timeOf(shownStart);
|
|
12696
|
+
const isEnd = time === timeOf(shownEnd);
|
|
12697
|
+
const inRange = !!shownStart &&
|
|
12698
|
+
!!shownEnd &&
|
|
12699
|
+
time > shownStart.getTime() &&
|
|
12700
|
+
time < shownEnd.getTime();
|
|
12701
|
+
// The day a pending range would end on, which isn't picked
|
|
12702
|
+
// yet, so its description says what picking it does.
|
|
12703
|
+
const isPreviewEnd = previewing && isEnd && !isStart;
|
|
12704
|
+
const isPreview = (previewing && inRange) || isPreviewEnd;
|
|
12705
|
+
const isFilled = rangeMode
|
|
12706
|
+
? isStart || (isEnd && !previewing)
|
|
12707
|
+
: !!value && isSameDay(value, cell.date);
|
|
12708
|
+
const isSelected = isFilled || (rangeMode && !previewing && inRange);
|
|
12709
|
+
const describedBy = rangeMode
|
|
12710
|
+
? [
|
|
12711
|
+
isStart && rangeStartId,
|
|
12712
|
+
isEnd && !previewing && rangeEndId,
|
|
12713
|
+
isPreviewEnd && rangePreviewEndId,
|
|
12714
|
+
]
|
|
12715
|
+
.filter(Boolean)
|
|
12716
|
+
.join(' ') || undefined
|
|
12717
|
+
: undefined;
|
|
12405
12718
|
const isFocused = isSameDay(cell.date, tabStopDay);
|
|
12406
12719
|
const otherMonth = !cell.inCurrentMonth;
|
|
12407
12720
|
const cellClass = prefixedClassNames(classPrefix, 'dateinput-cell', {
|
|
12408
|
-
'is-selected':
|
|
12721
|
+
'is-selected': isFilled,
|
|
12722
|
+
'is-range-start': isStart,
|
|
12723
|
+
'is-range-end': isEnd,
|
|
12724
|
+
'is-in-range': inRange,
|
|
12725
|
+
'is-preview': isPreview,
|
|
12409
12726
|
'is-today': cell.isToday,
|
|
12410
12727
|
'is-disabled': disabled,
|
|
12411
12728
|
'is-other-month': otherMonth,
|
|
12412
12729
|
});
|
|
12413
12730
|
const display = !otherMonth || nearbyMonthDays;
|
|
12414
|
-
return (jsx("button", { type: "button", role: "gridcell", tabIndex: isFocused ? 0 : -1, "aria-selected": isSelected, "aria-disabled": disabled, "aria-current": cell.isToday ? 'date' : undefined, "data-focused": isFocused ? 'true' : undefined, disabled: disabled || !display, className: cellClass, onFocus: () => {
|
|
12731
|
+
return (jsx("button", { type: "button", role: "gridcell", tabIndex: isFocused ? 0 : -1, "aria-selected": isSelected, "aria-disabled": disabled, "aria-current": cell.isToday ? 'date' : undefined, "aria-describedby": describedBy, "data-focused": isFocused ? 'true' : undefined, disabled: disabled || !display, className: cellClass, onFocus: () => {
|
|
12415
12732
|
// Reached by Tab while the focused day is disabled, or
|
|
12416
12733
|
// by pointer: keys move on from here. A nearby month's
|
|
12417
12734
|
// day waits for its click, which turns the grid.
|
|
12418
12735
|
if (!otherMonth && !isSameDay(cell.date, focusedDate)) {
|
|
12419
12736
|
onFocusedDateChange(cell.date);
|
|
12420
12737
|
}
|
|
12421
|
-
},
|
|
12738
|
+
},
|
|
12739
|
+
// Mouseover rather than mouseenter: browsers send it to a
|
|
12740
|
+
// disabled day as well, where React holds mouseenter back,
|
|
12741
|
+
// so the preview leaves the last enabled day behind.
|
|
12742
|
+
onMouseOver: anchor ? () => setHoverDate(cell.date) : undefined, onClick: () => {
|
|
12422
12743
|
if (disabled)
|
|
12423
12744
|
return;
|
|
12424
12745
|
onFocusedDateChange(cell.date);
|
|
12425
|
-
|
|
12746
|
+
if (rangeMode)
|
|
12747
|
+
pickRangeDay(cell.date);
|
|
12748
|
+
else
|
|
12749
|
+
onSelect?.(cell.date);
|
|
12426
12750
|
}, style: !display ? { visibility: 'hidden' } : undefined, children: cell.date.getDate() }, cell.date.toISOString()));
|
|
12427
|
-
}) })] })), view === 'months' && (jsx("div", { ref: monthGridRef, role: "grid", "aria-labelledby": labelId, className: monthsGridClass, onKeyDown: handleMonthKeyDown, ...gridFocusHandlers, children: MONTH_ROWS.map(row => (jsx("div", { role: "row", className: monthsRowClass, children: row.map(month => {
|
|
12751
|
+
}), weekClass) })] })), view === 'months' && (jsx("div", { ref: monthGridRef, role: "grid", "aria-labelledby": labelId, className: monthsGridClass, onKeyDown: handleMonthKeyDown, ...gridFocusHandlers, children: MONTH_ROWS.map(row => (jsx("div", { role: "row", className: monthsRowClass, children: row.map(month => {
|
|
12428
12752
|
const date = makeDate(focusedYear, month);
|
|
12429
12753
|
const disabled = disabledMonths[month];
|
|
12430
12754
|
const isSelected = !!value && isSameMonth(value, date);
|
|
@@ -12443,7 +12767,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12443
12767
|
}
|
|
12444
12768
|
}, onClick: () => {
|
|
12445
12769
|
onFocusedDateChange(clampDate(date, min, max));
|
|
12446
|
-
onSelect(date);
|
|
12770
|
+
onSelect?.(date);
|
|
12447
12771
|
}, children: monthCellNames[month] }, month));
|
|
12448
12772
|
}) }, row[0]))) })), view === 'years' && (jsx("div", { ref: yearGridRef, className: yearsGridClass, role: "listbox", "aria-label": isYearGranularity ? t.chooseYear : monthLabel, onKeyDown: handleYearListKeyDown, children: yearList.map(year => {
|
|
12449
12773
|
const isFocused = year === tabStopYear;
|
|
@@ -12459,16 +12783,22 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12459
12783
|
'is-today': isToday,
|
|
12460
12784
|
'is-disabled': disabled,
|
|
12461
12785
|
});
|
|
12462
|
-
return (jsx("button", { type: "button", role: "option", "aria-selected": isSelected, "aria-disabled": isYearGranularity ? disabled : undefined, "aria-current": isYearGranularity && isToday ? 'date' : undefined, "data-focused-year": isFocused ? 'true' : undefined, tabIndex:
|
|
12463
|
-
//
|
|
12464
|
-
//
|
|
12465
|
-
|
|
12786
|
+
return (jsx("button", { type: "button", role: "option", "aria-selected": isSelected, "aria-disabled": isYearGranularity ? disabled : undefined, "aria-current": isYearGranularity && isToday ? 'date' : undefined, "data-focused-year": isFocused ? 'true' : undefined, tabIndex: year === listTabStop ? 0 : -1, disabled: disabled, className: cellCls, onFocus: () => {
|
|
12787
|
+
// As navigation, focusing a year by key or pointer only
|
|
12788
|
+
// moves the tab stop: nothing is committed until a year is
|
|
12789
|
+
// picked. As the selection surface it is reached by Tab
|
|
12790
|
+
// while the focused year is disabled, or by pointer, and
|
|
12791
|
+
// keys move on from here.
|
|
12792
|
+
if (!isYearGranularity) {
|
|
12793
|
+
setNavYear(year);
|
|
12794
|
+
}
|
|
12795
|
+
else if (year !== focusedYear) {
|
|
12466
12796
|
onFocusedDateChange(clampDate(makeDate(year), min, max));
|
|
12467
12797
|
}
|
|
12468
12798
|
}, onClick: () => isYearGranularity
|
|
12469
12799
|
? handleYearPick(year)
|
|
12470
12800
|
: handleYearSelect(year), children: year }, year));
|
|
12471
|
-
}) }))] }));
|
|
12801
|
+
}) })), rangeMode && (jsxs(Fragment, { children: [jsx("span", { id: rangeStartId, hidden: true, children: t.rangeStart }), jsx("span", { id: rangeEndId, hidden: true, children: t.rangeEnd }), jsx("span", { id: rangePreviewEndId, hidden: true, children: t.rangePreviewEnd }), jsx("div", { id: rangeSummaryId, "aria-live": "polite", style: visuallyHidden, children: rangeSummary })] }))] }));
|
|
12472
12802
|
};
|
|
12473
12803
|
Calendar.displayName = 'Calendar';
|
|
12474
12804
|
|
|
@@ -13608,7 +13938,8 @@ DateInputBase.displayName = 'DateInputBase';
|
|
|
13608
13938
|
* shouldDisableDate={d => d.getDay() === 0 || d.getDay() === 6}
|
|
13609
13939
|
* />
|
|
13610
13940
|
*/
|
|
13611
|
-
const DateInput = forwardRef((
|
|
13941
|
+
const DateInput = forwardRef((props, ref) => {
|
|
13942
|
+
const { label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'calendar', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps } = props;
|
|
13612
13943
|
const insideField = useInsideField();
|
|
13613
13944
|
const insideControl = useInsideControl();
|
|
13614
13945
|
const ownField = rendersOwnField('DateInput', {
|
|
@@ -13619,6 +13950,27 @@ const DateInput = forwardRef(({ label, labelSize, labelProps, horizontal, iconLe
|
|
|
13619
13950
|
horizontal,
|
|
13620
13951
|
fieldClassName,
|
|
13621
13952
|
});
|
|
13953
|
+
// Inline mode renders a bare calendar with no input, so the Control's
|
|
13954
|
+
// icon-left container has nothing to anchor to, and it renders no
|
|
13955
|
+
// Control inside one or out. The left icon is checked as the caller
|
|
13956
|
+
// passed it: the default glyph is this component's own choice, not a
|
|
13957
|
+
// prop the caller set.
|
|
13958
|
+
const ownControl = rendersOwnControl('DateInput', {
|
|
13959
|
+
insideControl,
|
|
13960
|
+
inline: baseProps.inline,
|
|
13961
|
+
isLoading,
|
|
13962
|
+
iconLeft,
|
|
13963
|
+
iconLeftName: props.iconLeftName,
|
|
13964
|
+
iconLeftSize,
|
|
13965
|
+
iconRight,
|
|
13966
|
+
iconRightName,
|
|
13967
|
+
iconRightSize,
|
|
13968
|
+
hasIconsLeft,
|
|
13969
|
+
hasIconsRight,
|
|
13970
|
+
isExpanded,
|
|
13971
|
+
controlSize,
|
|
13972
|
+
controlClassName,
|
|
13973
|
+
});
|
|
13622
13974
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
13623
13975
|
label,
|
|
13624
13976
|
id: baseProps.id,
|
|
@@ -13632,9 +13984,7 @@ const DateInput = forwardRef(({ label, labelSize, labelProps, horizontal, iconLe
|
|
|
13632
13984
|
// The base hides its launcher while the Control it sits in is loading,
|
|
13633
13985
|
// whether that is the one rendered below or an enclosing one.
|
|
13634
13986
|
let content = (jsx(DateInputBase, { ref: ref, id: controlId, ...baseProps }));
|
|
13635
|
-
|
|
13636
|
-
// icon-left container has nothing to anchor to. Skip the Control wrap.
|
|
13637
|
-
if (!insideControl && !baseProps.inline) {
|
|
13987
|
+
if (ownControl) {
|
|
13638
13988
|
content = (jsx(Control, { iconLeft: iconLeft, iconRight: iconRight, iconLeftName: iconLeftName, iconRightName: iconRightName, iconLeftSize: iconLeftSize, iconRightSize: iconRightSize, hasIconsLeft: hasIconsLeft || !!iconLeftName, hasIconsRight: hasIconsRight, isLoading: isLoading, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
|
|
13639
13989
|
}
|
|
13640
13990
|
const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;
|
|
@@ -14716,7 +15066,8 @@ TimeInputBase.displayName = 'TimeInputBase';
|
|
|
14716
15066
|
* @example
|
|
14717
15067
|
* <TimeInput label="Slot" hourFormat="12" incrementMinutes={15} />
|
|
14718
15068
|
*/
|
|
14719
|
-
const TimeInput = forwardRef((
|
|
15069
|
+
const TimeInput = forwardRef((props, ref) => {
|
|
15070
|
+
const { label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'clock', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps } = props;
|
|
14720
15071
|
const insideField = useInsideField();
|
|
14721
15072
|
const insideControl = useInsideControl();
|
|
14722
15073
|
const ownField = rendersOwnField('TimeInput', {
|
|
@@ -14727,6 +15078,27 @@ const TimeInput = forwardRef(({ label, labelSize, labelProps, horizontal, iconLe
|
|
|
14727
15078
|
horizontal,
|
|
14728
15079
|
fieldClassName,
|
|
14729
15080
|
});
|
|
15081
|
+
// Inline mode renders a bare panel with no input, so the Control's
|
|
15082
|
+
// icon-left container has nothing to anchor to, and it renders no
|
|
15083
|
+
// Control inside one or out. The left icon is checked as the caller
|
|
15084
|
+
// passed it: the default glyph is this component's own choice, not a
|
|
15085
|
+
// prop the caller set.
|
|
15086
|
+
const ownControl = rendersOwnControl('TimeInput', {
|
|
15087
|
+
insideControl,
|
|
15088
|
+
inline: baseProps.inline,
|
|
15089
|
+
isLoading,
|
|
15090
|
+
iconLeft,
|
|
15091
|
+
iconLeftName: props.iconLeftName,
|
|
15092
|
+
iconLeftSize,
|
|
15093
|
+
iconRight,
|
|
15094
|
+
iconRightName,
|
|
15095
|
+
iconRightSize,
|
|
15096
|
+
hasIconsLeft,
|
|
15097
|
+
hasIconsRight,
|
|
15098
|
+
isExpanded,
|
|
15099
|
+
controlSize,
|
|
15100
|
+
controlClassName,
|
|
15101
|
+
});
|
|
14730
15102
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
14731
15103
|
label,
|
|
14732
15104
|
id: baseProps.id,
|
|
@@ -14740,9 +15112,7 @@ const TimeInput = forwardRef(({ label, labelSize, labelProps, horizontal, iconLe
|
|
|
14740
15112
|
// The base hides its launcher while the Control it sits in is loading,
|
|
14741
15113
|
// whether that is the one rendered below or an enclosing one.
|
|
14742
15114
|
let content = (jsx(TimeInputBase, { ref: ref, id: controlId, ...baseProps }));
|
|
14743
|
-
|
|
14744
|
-
// icon-left container has nothing to anchor to. Skip the Control wrap.
|
|
14745
|
-
if (!insideControl && !baseProps.inline) {
|
|
15115
|
+
if (ownControl) {
|
|
14746
15116
|
content = (jsx(Control, { iconLeft: iconLeft, iconRight: iconRight, iconLeftName: iconLeftName, iconRightName: iconRightName, iconLeftSize: iconLeftSize, iconRightSize: iconRightSize, hasIconsLeft: hasIconsLeft || !!iconLeftName, hasIconsRight: hasIconsRight, isLoading: isLoading, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
|
|
14747
15117
|
}
|
|
14748
15118
|
const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;
|
|
@@ -15037,7 +15407,8 @@ DateTimeInputBase.displayName = 'DateTimeInputBase';
|
|
|
15037
15407
|
* shouldDisableDate={d => d.getDay() === 0 || d.getDay() === 6}
|
|
15038
15408
|
* />
|
|
15039
15409
|
*/
|
|
15040
|
-
const DateTimeInput = forwardRef((
|
|
15410
|
+
const DateTimeInput = forwardRef((props, ref) => {
|
|
15411
|
+
const { label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'calendar-alt', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps } = props;
|
|
15041
15412
|
const insideField = useInsideField();
|
|
15042
15413
|
const insideControl = useInsideControl();
|
|
15043
15414
|
const ownField = rendersOwnField('DateTimeInput', {
|
|
@@ -15048,6 +15419,27 @@ const DateTimeInput = forwardRef(({ label, labelSize, labelProps, horizontal, ic
|
|
|
15048
15419
|
horizontal,
|
|
15049
15420
|
fieldClassName,
|
|
15050
15421
|
});
|
|
15422
|
+
// Inline mode renders a bare picker with no input, so the Control's
|
|
15423
|
+
// icon-left container has nothing to anchor to, and it renders no
|
|
15424
|
+
// Control inside one or out. The left icon is checked as the caller
|
|
15425
|
+
// passed it: the default glyph is this component's own choice, not a
|
|
15426
|
+
// prop the caller set.
|
|
15427
|
+
const ownControl = rendersOwnControl('DateTimeInput', {
|
|
15428
|
+
insideControl,
|
|
15429
|
+
inline: baseProps.inline,
|
|
15430
|
+
isLoading,
|
|
15431
|
+
iconLeft,
|
|
15432
|
+
iconLeftName: props.iconLeftName,
|
|
15433
|
+
iconLeftSize,
|
|
15434
|
+
iconRight,
|
|
15435
|
+
iconRightName,
|
|
15436
|
+
iconRightSize,
|
|
15437
|
+
hasIconsLeft,
|
|
15438
|
+
hasIconsRight,
|
|
15439
|
+
isExpanded,
|
|
15440
|
+
controlSize,
|
|
15441
|
+
controlClassName,
|
|
15442
|
+
});
|
|
15051
15443
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
15052
15444
|
label,
|
|
15053
15445
|
id: baseProps.id,
|
|
@@ -15061,9 +15453,7 @@ const DateTimeInput = forwardRef(({ label, labelSize, labelProps, horizontal, ic
|
|
|
15061
15453
|
// The base hides its launcher while the Control it sits in is loading,
|
|
15062
15454
|
// whether that is the one rendered below or an enclosing one.
|
|
15063
15455
|
let content = (jsx(DateTimeInputBase, { ref: ref, id: controlId, ...baseProps }));
|
|
15064
|
-
|
|
15065
|
-
// icon-left container has nothing to anchor to. Skip the Control wrap.
|
|
15066
|
-
if (!insideControl && !baseProps.inline) {
|
|
15456
|
+
if (ownControl) {
|
|
15067
15457
|
content = (jsx(Control, { iconLeft: iconLeft, iconRight: iconRight, iconLeftName: iconLeftName, iconRightName: iconRightName, iconLeftSize: iconLeftSize, iconRightSize: iconRightSize, hasIconsLeft: hasIconsLeft || !!iconLeftName, hasIconsRight: hasIconsRight, isLoading: isLoading, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
|
|
15068
15458
|
}
|
|
15069
15459
|
const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;
|