@allxsmith/bestax-bulma 5.26.3 → 5.26.5
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 +607 -160
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +607 -160
- package/dist/index.esm.js.map +1 -1
- package/dist/types/form/Autocomplete.d.ts +6 -1
- package/dist/types/form/Checkboxes.d.ts +7 -0
- package/dist/types/form/DateInput.d.ts +22 -3
- package/dist/types/form/DateInputBase.d.ts +17 -3
- package/dist/types/form/DateTimeInput.d.ts +22 -3
- package/dist/types/form/DateTimeInputBase.d.ts +17 -3
- package/dist/types/form/File.d.ts +6 -0
- package/dist/types/form/FormContext.d.ts +86 -7
- package/dist/types/form/Input.d.ts +8 -2
- package/dist/types/form/Numberinput.d.ts +3 -0
- package/dist/types/form/Radios.d.ts +7 -0
- package/dist/types/form/Rate.d.ts +7 -0
- package/dist/types/form/Select.d.ts +8 -2
- package/dist/types/form/Slider.d.ts +7 -0
- package/dist/types/form/Taginput.d.ts +6 -0
- package/dist/types/form/TextArea.d.ts +8 -2
- package/dist/types/form/TimeInput.d.ts +22 -3
- package/dist/types/form/TimeInputBase.d.ts +16 -3
- package/dist/types/form/_pickerInternals/Calendar.d.ts +9 -3
- package/dist/types/form/_pickerInternals/dateUtils.d.ts +17 -0
- package/dist/types/form/controlLoading.d.ts +4 -0
- package/dist/types/helpers/useAnchoredPosition.d.ts +4 -1
- package/dist/types/helpers/useFocusTrap.d.ts +11 -4
- package/dist/types-cjs/form/Autocomplete.d.ts +6 -1
- package/dist/types-cjs/form/Checkboxes.d.ts +7 -0
- package/dist/types-cjs/form/DateInput.d.ts +22 -3
- package/dist/types-cjs/form/DateInputBase.d.ts +17 -3
- package/dist/types-cjs/form/DateTimeInput.d.ts +22 -3
- package/dist/types-cjs/form/DateTimeInputBase.d.ts +17 -3
- package/dist/types-cjs/form/File.d.ts +6 -0
- package/dist/types-cjs/form/FormContext.d.ts +86 -7
- package/dist/types-cjs/form/Input.d.ts +8 -2
- package/dist/types-cjs/form/Numberinput.d.ts +3 -0
- package/dist/types-cjs/form/Radios.d.ts +7 -0
- package/dist/types-cjs/form/Rate.d.ts +7 -0
- package/dist/types-cjs/form/Select.d.ts +8 -2
- package/dist/types-cjs/form/Slider.d.ts +7 -0
- package/dist/types-cjs/form/Taginput.d.ts +6 -0
- package/dist/types-cjs/form/TextArea.d.ts +8 -2
- package/dist/types-cjs/form/TimeInput.d.ts +22 -3
- package/dist/types-cjs/form/TimeInputBase.d.ts +16 -3
- package/dist/types-cjs/form/_pickerInternals/Calendar.d.ts +9 -3
- package/dist/types-cjs/form/_pickerInternals/dateUtils.d.ts +17 -0
- package/dist/types-cjs/form/controlLoading.d.ts +4 -0
- package/dist/types-cjs/helpers/useAnchoredPosition.d.ts +4 -1
- package/dist/types-cjs/helpers/useFocusTrap.d.ts +11 -4
- 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/_datetimeinput.scss +9 -0
- package/src/scss/form/_picker-popover.scss +12 -9
- package/src/scss/form/_rate.scss +3 -1
- package/src/scss/form/_taginput.scss +8 -0
- package/src/scss/form/_timeinput.scss +5 -0
package/dist/index.cjs
CHANGED
|
@@ -5201,10 +5201,17 @@ const focusedElement = (doc) => getDeepestActiveElement(doc) ?? doc.body;
|
|
|
5201
5201
|
* container, or give them a trap of their own. To trap content a `Portal`
|
|
5202
5202
|
* renders, pass a ref to the element inside the `Portal` as `containerRef`.
|
|
5203
5203
|
*
|
|
5204
|
-
* The
|
|
5205
|
-
*
|
|
5206
|
-
*
|
|
5207
|
-
*
|
|
5204
|
+
* The trap looks at `containerRef` again each time the component calling it
|
|
5205
|
+
* renders. A container that mounts after the trap turns on, such as a panel
|
|
5206
|
+
* waiting for a `Portal` target held in state, has it attach once that
|
|
5207
|
+
* component renders with the container in the DOM. When the container leaves
|
|
5208
|
+
* or is replaced, the trap lets go of it as it would turning off, handing
|
|
5209
|
+
* focus back, and attaches to the next one.
|
|
5210
|
+
* A child that mounts the container in a render of its own (from its own
|
|
5211
|
+
* state, or as a `Suspense` boundary resolves) goes unseen until the
|
|
5212
|
+
* component calling the hook renders again. The trap waits for hydration, so
|
|
5213
|
+
* that component renders again when the page hydrates, and portaled content,
|
|
5214
|
+
* which first appears then, is found.
|
|
5208
5215
|
*
|
|
5209
5216
|
* @function useFocusTrap
|
|
5210
5217
|
* @param containerRef - Ref to the element focus stays inside.
|
|
@@ -5224,12 +5231,18 @@ function useFocusTrap(containerRef, options = {}) {
|
|
|
5224
5231
|
// Portaled content has no server-rendered counterpart, so a portaled
|
|
5225
5232
|
// container only exists from the commit after hydration.
|
|
5226
5233
|
const hydrated = useIsHydrated();
|
|
5234
|
+
// The container the trap is attached to, or `null` while it has none.
|
|
5235
|
+
const heldRef = React.useRef(null);
|
|
5236
|
+
// Bumped to run the trap again when the container in the ref changes.
|
|
5237
|
+
const [refChanges, setRefChanges] = React.useState(0);
|
|
5227
5238
|
React.useEffect(() => {
|
|
5239
|
+
heldRef.current = null;
|
|
5228
5240
|
if (!active || !hydrated)
|
|
5229
5241
|
return undefined;
|
|
5230
5242
|
const container = containerRef.current;
|
|
5231
5243
|
if (!container)
|
|
5232
5244
|
return undefined;
|
|
5245
|
+
heldRef.current = container;
|
|
5233
5246
|
const doc = container.ownerDocument;
|
|
5234
5247
|
const opener = focusedElement(doc);
|
|
5235
5248
|
// `initialFocusRef` counts only when focusing it puts focus inside. Focus
|
|
@@ -5304,7 +5317,28 @@ function useFocusTrap(containerRef, options = {}) {
|
|
|
5304
5317
|
target.focus();
|
|
5305
5318
|
}
|
|
5306
5319
|
};
|
|
5307
|
-
}, [
|
|
5320
|
+
}, [
|
|
5321
|
+
active,
|
|
5322
|
+
hydrated,
|
|
5323
|
+
containerRef,
|
|
5324
|
+
initialFocusRef,
|
|
5325
|
+
restoreFocus,
|
|
5326
|
+
refChanges,
|
|
5327
|
+
]);
|
|
5328
|
+
// Nothing says when a ref is set, so after every render, a ref that no
|
|
5329
|
+
// longer matches what the trap holds (a container that mounted, left or was
|
|
5330
|
+
// replaced) runs it again. Effects run in order, so whenever the effect
|
|
5331
|
+
// above ran in this commit, it read the ref just before this does, and a
|
|
5332
|
+
// mismatch means the ref changed in a render it didn't run for. The render
|
|
5333
|
+
// a bump asks for runs it, so the two match and the trap settles. An empty
|
|
5334
|
+
// ref matches a trap holding nothing, so a container that never mounts
|
|
5335
|
+
// costs no render.
|
|
5336
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- after every render on purpose
|
|
5337
|
+
React.useEffect(() => {
|
|
5338
|
+
if (active && hydrated && containerRef.current !== heldRef.current) {
|
|
5339
|
+
setRefChanges(n => n + 1);
|
|
5340
|
+
}
|
|
5341
|
+
});
|
|
5308
5342
|
}
|
|
5309
5343
|
|
|
5310
5344
|
// INTERNAL: deliberately not exported from src/index.ts.
|
|
@@ -8737,6 +8771,108 @@ const useInsideControl = () => React.useContext(ControlContext);
|
|
|
8737
8771
|
const FieldProvider = FieldContext.Provider;
|
|
8738
8772
|
/** Provider for Control context — used internally by Control component. */
|
|
8739
8773
|
const ControlProvider = ControlContext.Provider;
|
|
8774
|
+
// Each Field-shaping prop, with what takes its place on a `Field` the caller
|
|
8775
|
+
// wraps around the `Control`. `message` needs nothing there, because the
|
|
8776
|
+
// wrapper still renders it inside `Field > Control`. The other Field-level
|
|
8777
|
+
// props (`labelSize`, `labelProps`, `messageColor`) change nothing unless a
|
|
8778
|
+
// `label` or `message` renders, so on their own they keep no `Field`.
|
|
8779
|
+
// `bare-control.test.tsx` holds this list to what each wrapper hands its
|
|
8780
|
+
// `Field` and to which props change its markup, so a new Field-level prop
|
|
8781
|
+
// fails there until it is sorted into one group or the other.
|
|
8782
|
+
const FIELD_SHAPING = [
|
|
8783
|
+
['label', 'label'],
|
|
8784
|
+
['message', undefined],
|
|
8785
|
+
['horizontal', 'horizontal'],
|
|
8786
|
+
['fieldClassName', 'className (for fieldClassName)'],
|
|
8787
|
+
];
|
|
8788
|
+
/** `a`, `a and b`, `a, b and c`. */
|
|
8789
|
+
const listOf = (items) => items.length > 1
|
|
8790
|
+
? `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`
|
|
8791
|
+
: items[0];
|
|
8792
|
+
/**
|
|
8793
|
+
* Whether a convenience form wrapper renders a `Field` of its own (#905).
|
|
8794
|
+
* Never inside an outer `Field`. Inside a `Control` with no `Field` around
|
|
8795
|
+
* it, a `Field` of its own would put a `.field` inside the `.control`, where
|
|
8796
|
+
* Bulma's control styles break (its icon rules are sibling selectors on the
|
|
8797
|
+
* input). So it renders one there only when a prop that shapes the `Field`'s
|
|
8798
|
+
* markup is set (`label`, `message`, `horizontal`, `fieldClassName`), keeping
|
|
8799
|
+
* the markup it always had, and warns in development that the fix is a
|
|
8800
|
+
* `Field` around that `Control`. A prop counts when it is truthy, the same
|
|
8801
|
+
* test that decides whether it changes the `Field`'s output.
|
|
8802
|
+
* Internal; not part of the public API.
|
|
8803
|
+
*/
|
|
8804
|
+
const rendersOwnField = (component, options) => {
|
|
8805
|
+
if (options.insideField)
|
|
8806
|
+
return false;
|
|
8807
|
+
if (!options.insideControl)
|
|
8808
|
+
return true;
|
|
8809
|
+
const held = FIELD_SHAPING.filter(([prop]) => options[prop]);
|
|
8810
|
+
if (held.length === 0)
|
|
8811
|
+
return false;
|
|
8812
|
+
const names = held.map(([prop]) => prop);
|
|
8813
|
+
const moved = held
|
|
8814
|
+
.map(([, onField]) => onField)
|
|
8815
|
+
.filter((onField) => onField !== undefined);
|
|
8816
|
+
warnOnce(`${component}:Field-in-bare-Control:${names.join('+')}`, `[bestax-bulma] <${component} ${names.join(' ')}> inside a <Control> ` +
|
|
8817
|
+
`with no <Field> around it renders its own <Field> for ` +
|
|
8818
|
+
`${names.length > 1 ? 'those props' : 'that prop'}, which puts a ` +
|
|
8819
|
+
`.field inside the .control, a nesting Bulma's styles are not ` +
|
|
8820
|
+
`written for. Wrap the <Control> in a <Field> instead` +
|
|
8821
|
+
(moved.length > 0 ? `, and set ${listOf(moved)} on that <Field>.` : '.'));
|
|
8822
|
+
return true;
|
|
8823
|
+
};
|
|
8824
|
+
// Each Control-level prop, with what takes its place on the `Control` the
|
|
8825
|
+
// caller wrapped around the wrapper. `bare-control.test.tsx` holds this to
|
|
8826
|
+
// what each wrapper hands its own `Control`, so a new Control-level prop
|
|
8827
|
+
// fails there until it is added here and passed to `rendersOwnControl`.
|
|
8828
|
+
const ON_CONTROL = {
|
|
8829
|
+
isLoading: 'isLoading',
|
|
8830
|
+
iconLeft: 'iconLeft',
|
|
8831
|
+
iconLeftName: 'iconLeftName',
|
|
8832
|
+
iconLeftSize: 'iconLeftSize',
|
|
8833
|
+
iconRight: 'iconRight',
|
|
8834
|
+
iconRightName: 'iconRightName',
|
|
8835
|
+
iconRightSize: 'iconRightSize',
|
|
8836
|
+
hasIconsLeft: 'hasIconsLeft',
|
|
8837
|
+
hasIconsRight: 'hasIconsRight',
|
|
8838
|
+
isExpanded: 'isExpanded',
|
|
8839
|
+
controlSize: 'size (for controlSize)',
|
|
8840
|
+
controlClassName: 'className (for controlClassName)',
|
|
8841
|
+
};
|
|
8842
|
+
/**
|
|
8843
|
+
* Whether a convenience form wrapper renders a `Control` of its own (#921):
|
|
8844
|
+
* only outside one. Inside a `Control` it renders none, so the props it
|
|
8845
|
+
* would have handed its own do nothing there, and it warns in development,
|
|
8846
|
+
* naming them and saying to set them on that `Control`. An `inline` picker
|
|
8847
|
+
* renders none in either place, so it drops them inside a `Control` or not,
|
|
8848
|
+
* and its warning says so without pointing at a `Control`. A wrapper passes
|
|
8849
|
+
* only the props it hands its own `Control`, as the caller gave them, so a
|
|
8850
|
+
* default the wrapper fills in itself never warns. A prop counts when it is
|
|
8851
|
+
* truthy, since a falsy one would change nothing on a `Control` either.
|
|
8852
|
+
* Internal; not part of the public API.
|
|
8853
|
+
*/
|
|
8854
|
+
const rendersOwnControl = (component, options) => {
|
|
8855
|
+
if (!options.inline && !options.insideControl)
|
|
8856
|
+
return true;
|
|
8857
|
+
const held = Object.keys(ON_CONTROL).filter(prop => options[prop]);
|
|
8858
|
+
if (held.length === 0)
|
|
8859
|
+
return false;
|
|
8860
|
+
const names = held.join(' ');
|
|
8861
|
+
const doNothing = held.length > 1 ? 'those props do' : 'that prop does';
|
|
8862
|
+
if (options.inline) {
|
|
8863
|
+
warnOnce(`${component}:Control-props-inline:${held.join('+')}`, `[bestax-bulma] <${component} inline ${names}> renders no <Control> ` +
|
|
8864
|
+
`in inline mode, inside a <Control> or not, so ${doNothing} ` +
|
|
8865
|
+
`nothing. Leave ${held.length > 1 ? 'them' : 'it'} out of an ` +
|
|
8866
|
+
`inline picker.`);
|
|
8867
|
+
}
|
|
8868
|
+
else {
|
|
8869
|
+
warnOnce(`${component}:Control-props-in-Control:${held.join('+')}`, `[bestax-bulma] <${component} ${names}> inside a <Control> renders ` +
|
|
8870
|
+
`no <Control> of its own, so ${doNothing} nothing there. Set ` +
|
|
8871
|
+
`${listOf(held.map(prop => ON_CONTROL[prop]))} on that <Control> ` +
|
|
8872
|
+
`instead.`);
|
|
8873
|
+
}
|
|
8874
|
+
return false;
|
|
8875
|
+
};
|
|
8740
8876
|
const FieldLabelIdContext = React.createContext(undefined);
|
|
8741
8877
|
/**
|
|
8742
8878
|
* The id a labeled Field wants its single composed control to adopt (#495).
|
|
@@ -8867,6 +9003,19 @@ const Checkbox = React.forwardRef(({ color, size, className, children, textColor
|
|
|
8867
9003
|
});
|
|
8868
9004
|
Checkbox.displayName = 'Checkbox';
|
|
8869
9005
|
|
|
9006
|
+
// Whether the nearest enclosing `Control` shows its `isLoading` spinner. The
|
|
9007
|
+
// picker bases read it so their right-side launcher gives way to that
|
|
9008
|
+
// spinner, which sits at the same edge, whether the `Control` is the one a
|
|
9009
|
+
// convenience input renders or one the caller wrapped around the input.
|
|
9010
|
+
// Autocomplete reads it for its clear button, which sits there too.
|
|
9011
|
+
// Kept apart from `FormContext`, whose contexts only report whether a `Field`
|
|
9012
|
+
// or `Control` is present. Internal; not part of the public API.
|
|
9013
|
+
const ControlLoadingContext = React.createContext(false);
|
|
9014
|
+
/** Provider for the loading state, used internally by `Control`. */
|
|
9015
|
+
const ControlLoadingProvider = ControlLoadingContext.Provider;
|
|
9016
|
+
/** Whether the nearest enclosing `Control` is loading; `false` outside one. */
|
|
9017
|
+
const useControlLoading = () => React.useContext(ControlLoadingContext);
|
|
9018
|
+
|
|
8870
9019
|
const allowedColors = [...validColors, 'inherit', 'current'];
|
|
8871
9020
|
/**
|
|
8872
9021
|
* The `Control` component is a Bulma-styled wrapper for form controls (`Input`, `Select`, `TextArea`, etc.), supporting icons (left/right), loading state, expansion, size, and Bulma helper props for layout and color.
|
|
@@ -8926,7 +9075,7 @@ const Control = React.forwardRef(({ as = 'div', hasIconsLeft, hasIconsRight, isL
|
|
|
8926
9075
|
});
|
|
8927
9076
|
const controlClass = classNames(mainClass, bulmaHelperClasses, className);
|
|
8928
9077
|
// --- FIX: Spread both restProps (for data-testid, etc) AND rest (from useBulmaClasses) ---
|
|
8929
|
-
return (jsxRuntime.jsx(ControlProvider, { value: true, children: jsxRuntime.jsxs(Component, { className: controlClass, ref: ref, ...restProps, ...rest, children: [children, leftIconValue &&
|
|
9078
|
+
return (jsxRuntime.jsx(ControlProvider, { value: true, children: jsxRuntime.jsxs(Component, { className: controlClass, ref: ref, ...restProps, ...rest, children: [jsxRuntime.jsx(ControlLoadingProvider, { value: !!isLoading, children: children }), leftIconValue &&
|
|
8930
9079
|
(isIconProps(leftIconValue) ? (jsxRuntime.jsx(Icon, { ...leftIconValue, className: prefixedClassNames(classPrefix, 'is-left') })) : (jsxRuntime.jsx(Icon, { className: prefixedClassNames(classPrefix, 'is-left'), children: leftIconValue }))), rightIconValue &&
|
|
8931
9080
|
(isIconProps(rightIconValue) ? (jsxRuntime.jsx(Icon, { ...rightIconValue, className: prefixedClassNames(classPrefix, 'is-right') })) : (jsxRuntime.jsx(Icon, { className: prefixedClassNames(classPrefix, 'is-right'), children: rightIconValue })))] }) }));
|
|
8932
9081
|
});
|
|
@@ -9151,10 +9300,18 @@ function useAutoLabelledBy({ label, labelProps, rendersLabel, }) {
|
|
|
9151
9300
|
const CheckboxesComponent = ({ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, name, value, defaultValue, onChange, children, className, ...props }) => {
|
|
9152
9301
|
const insideField = useInsideField();
|
|
9153
9302
|
const insideControl = useInsideControl();
|
|
9303
|
+
const ownField = rendersOwnField('Checkboxes', {
|
|
9304
|
+
insideField,
|
|
9305
|
+
insideControl,
|
|
9306
|
+
label,
|
|
9307
|
+
message,
|
|
9308
|
+
horizontal,
|
|
9309
|
+
fieldClassName,
|
|
9310
|
+
});
|
|
9154
9311
|
const { ariaLabelledBy, fieldLabelProps } = useAutoLabelledBy({
|
|
9155
9312
|
label,
|
|
9156
9313
|
labelProps,
|
|
9157
|
-
rendersLabel:
|
|
9314
|
+
rendersLabel: ownField,
|
|
9158
9315
|
});
|
|
9159
9316
|
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9160
9317
|
...props,
|
|
@@ -9180,10 +9337,10 @@ const CheckboxesComponent = ({ label, labelSize, labelProps, horizontal, message
|
|
|
9180
9337
|
}), [name, groupActive, currentValue, handleChange]);
|
|
9181
9338
|
const checkboxesElement = (jsxRuntime.jsx("div", { className: wrapperClass, role: "group", "aria-labelledby": ariaLabelledBy, ...rest, children: jsxRuntime.jsx(CheckboxesProvider, { value: ctx, children: children }) }));
|
|
9182
9339
|
let content = checkboxesElement;
|
|
9183
|
-
if (
|
|
9340
|
+
if (rendersOwnControl('Checkboxes', { insideControl })) {
|
|
9184
9341
|
content = jsxRuntime.jsx(Control, { children: content });
|
|
9185
9342
|
}
|
|
9186
|
-
if (
|
|
9343
|
+
if (ownField) {
|
|
9187
9344
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
9188
9345
|
}
|
|
9189
9346
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -9202,11 +9359,20 @@ const File = React.forwardRef(({
|
|
|
9202
9359
|
// Field props
|
|
9203
9360
|
label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, color, size, isBoxed, isFullwidth, isFullWidth, isRight, isCentered, hasName, buttonLabel, iconLeft, iconRight, className, inputClassName, fileName, ...props }, ref) => {
|
|
9204
9361
|
const insideField = useInsideField();
|
|
9362
|
+
const insideControl = useInsideControl();
|
|
9363
|
+
const ownField = rendersOwnField('File', {
|
|
9364
|
+
insideField,
|
|
9365
|
+
insideControl,
|
|
9366
|
+
label,
|
|
9367
|
+
message,
|
|
9368
|
+
horizontal,
|
|
9369
|
+
fieldClassName,
|
|
9370
|
+
});
|
|
9205
9371
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
9206
9372
|
label,
|
|
9207
9373
|
id: props.id,
|
|
9208
9374
|
labelProps,
|
|
9209
|
-
rendersLabel:
|
|
9375
|
+
rendersLabel: ownField,
|
|
9210
9376
|
});
|
|
9211
9377
|
const { classPrefix } = useConfig();
|
|
9212
9378
|
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
@@ -9238,7 +9404,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
9238
9404
|
});
|
|
9239
9405
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
9240
9406
|
const fileElement = (jsxRuntime.jsx("div", { className: fileClass, children: jsxRuntime.jsxs("label", { className: usePrefixedClassNames('file-label'), children: [jsxRuntime.jsx("input", { ref: ref, className: classNames(usePrefixedClassNames('file-input'), inputClassName), type: "file", id: controlId, ...rest }), jsxRuntime.jsxs("span", { className: usePrefixedClassNames('file-cta'), children: [iconLeft && (jsxRuntime.jsx("span", { className: prefixedClassNames(classPrefix, 'file-icon'), children: iconLeft })), jsxRuntime.jsx("span", { className: usePrefixedClassNames('file-label'), children: buttonLabel || 'Choose a file\u2026' }), iconRight && (jsxRuntime.jsx("span", { className: prefixedClassNames(classPrefix, 'file-icon'), children: iconRight }))] }), hasName && fileName && (jsxRuntime.jsx("span", { className: prefixedClassNames(classPrefix, 'file-name'), children: fileName }))] }) }));
|
|
9241
|
-
if (
|
|
9407
|
+
if (ownField) {
|
|
9242
9408
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [fileElement, messageEl] }));
|
|
9243
9409
|
}
|
|
9244
9410
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [fileElement, messageEl] }));
|
|
@@ -9371,10 +9537,18 @@ Radio.displayName = 'Radio';
|
|
|
9371
9537
|
const RadiosComponent = ({ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, name, value, defaultValue, onChange, children, className, ...props }) => {
|
|
9372
9538
|
const insideField = useInsideField();
|
|
9373
9539
|
const insideControl = useInsideControl();
|
|
9540
|
+
const ownField = rendersOwnField('Radios', {
|
|
9541
|
+
insideField,
|
|
9542
|
+
insideControl,
|
|
9543
|
+
label,
|
|
9544
|
+
message,
|
|
9545
|
+
horizontal,
|
|
9546
|
+
fieldClassName,
|
|
9547
|
+
});
|
|
9374
9548
|
const { ariaLabelledBy, fieldLabelProps } = useAutoLabelledBy({
|
|
9375
9549
|
label,
|
|
9376
9550
|
labelProps,
|
|
9377
|
-
rendersLabel:
|
|
9551
|
+
rendersLabel: ownField,
|
|
9378
9552
|
});
|
|
9379
9553
|
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9380
9554
|
...props,
|
|
@@ -9403,10 +9577,10 @@ const RadiosComponent = ({ label, labelSize, labelProps, horizontal, message, me
|
|
|
9403
9577
|
}), [name, groupActive, currentValue, handleChange]);
|
|
9404
9578
|
const radiosElement = (jsxRuntime.jsx("div", { className: wrapperClass, role: "radiogroup", "aria-labelledby": ariaLabelledBy, ...rest, children: jsxRuntime.jsx(RadiosProvider, { value: ctx, children: children }) }));
|
|
9405
9579
|
let content = radiosElement;
|
|
9406
|
-
if (
|
|
9580
|
+
if (rendersOwnControl('Radios', { insideControl })) {
|
|
9407
9581
|
content = jsxRuntime.jsx(Control, { children: content });
|
|
9408
9582
|
}
|
|
9409
|
-
if (
|
|
9583
|
+
if (ownField) {
|
|
9410
9584
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
9411
9585
|
}
|
|
9412
9586
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -9586,6 +9760,14 @@ const Slider = React.forwardRef((props, ref) => {
|
|
|
9586
9760
|
range: _range, value: _value, defaultValue: _defaultValue, onChange: _onChange, ariaLabel: _ariaLabel, minDistance: _minDistance, nameLow: _nameLow, nameHigh: _nameHigh, ...restProps } = props;
|
|
9587
9761
|
const insideField = useInsideField();
|
|
9588
9762
|
const insideControl = useInsideControl();
|
|
9763
|
+
const ownField = rendersOwnField('Slider', {
|
|
9764
|
+
insideField,
|
|
9765
|
+
insideControl,
|
|
9766
|
+
label,
|
|
9767
|
+
message,
|
|
9768
|
+
horizontal,
|
|
9769
|
+
fieldClassName,
|
|
9770
|
+
});
|
|
9589
9771
|
const { bulmaHelperClasses, rest } = useBulmaClasses(restProps);
|
|
9590
9772
|
// In range mode the low thumb carries `rest` (and any user id), so the
|
|
9591
9773
|
// label targets it; each thumb keeps its own aria-label for AT.
|
|
@@ -9593,7 +9775,7 @@ const Slider = React.forwardRef((props, ref) => {
|
|
|
9593
9775
|
label,
|
|
9594
9776
|
id: restProps.id,
|
|
9595
9777
|
labelProps,
|
|
9596
|
-
rendersLabel:
|
|
9778
|
+
rendersLabel: ownField,
|
|
9597
9779
|
});
|
|
9598
9780
|
// Resolve tooltip mode: explicit tooltip prop takes precedence, else showOutput maps to 'auto'
|
|
9599
9781
|
const tooltipMode = tooltip ?? (showOutput ? 'auto' : 'hidden');
|
|
@@ -9929,10 +10111,10 @@ const Slider = React.forwardRef((props, ref) => {
|
|
|
9929
10111
|
'--slider-output-arrow-offset': `calc(50% - ${nudgeXHigh}px)`,
|
|
9930
10112
|
}, children: formatValue(currentRange[1]) })), renderTicks()] }));
|
|
9931
10113
|
let content = sliderElement;
|
|
9932
|
-
if (
|
|
10114
|
+
if (rendersOwnControl('Slider', { insideControl })) {
|
|
9933
10115
|
content = jsxRuntime.jsx(Control, { children: content });
|
|
9934
10116
|
}
|
|
9935
|
-
if (
|
|
10117
|
+
if (ownField) {
|
|
9936
10118
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
9937
10119
|
}
|
|
9938
10120
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -10162,10 +10344,18 @@ function getFillPercent(iconIndex, value) {
|
|
|
10162
10344
|
const Rate = React.forwardRef(({ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, value: controlledValue, defaultValue = 0, max = 5, size, disabled = false, showScore = false, showText = false, texts, onChange, customIcon, spaced = false, rtl = false, iconName, iconLibrary: iconLibraryProp, iconVariant, iconFeatures, color, precision = 1, customText, name, form, className, ...props }, ref) => {
|
|
10163
10345
|
const insideField = useInsideField();
|
|
10164
10346
|
const insideControl = useInsideControl();
|
|
10347
|
+
const ownField = rendersOwnField('Rate', {
|
|
10348
|
+
insideField,
|
|
10349
|
+
insideControl,
|
|
10350
|
+
label,
|
|
10351
|
+
message,
|
|
10352
|
+
horizontal,
|
|
10353
|
+
fieldClassName,
|
|
10354
|
+
});
|
|
10165
10355
|
const { ariaLabelledBy, fieldLabelProps } = useAutoLabelledBy({
|
|
10166
10356
|
label,
|
|
10167
10357
|
labelProps,
|
|
10168
|
-
rendersLabel:
|
|
10358
|
+
rendersLabel: ownField,
|
|
10169
10359
|
});
|
|
10170
10360
|
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
10171
10361
|
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
@@ -10365,10 +10555,10 @@ const Rate = React.forwardRef(({ label, labelSize, labelProps, horizontal, messa
|
|
|
10365
10555
|
const rateCustomTextClass = usePrefixedClassNames('rate-custom-text');
|
|
10366
10556
|
const rateElement = (jsxRuntime.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: [jsxRuntime.jsx("div", { className: rateItemsClass, children: renderIcons() }), showScore && (jsxRuntime.jsx("span", { className: rateScoreClass, children: getScoreDisplay() })), text && jsxRuntime.jsx("span", { className: rateTextClass, children: text }), customText && (jsxRuntime.jsx("span", { className: rateCustomTextClass, children: customText })), name && (jsxRuntime.jsx("input", { type: "hidden", name: name, value: currentValue, form: form }))] }));
|
|
10367
10557
|
let content = rateElement;
|
|
10368
|
-
if (
|
|
10558
|
+
if (rendersOwnControl('Rate', { insideControl })) {
|
|
10369
10559
|
content = jsxRuntime.jsx(Control, { children: content });
|
|
10370
10560
|
}
|
|
10371
|
-
if (
|
|
10561
|
+
if (ownField) {
|
|
10372
10562
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
10373
10563
|
}
|
|
10374
10564
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -10604,7 +10794,11 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
10604
10794
|
[`is-${messageColor}`]: !!messageColor,
|
|
10605
10795
|
});
|
|
10606
10796
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
10607
|
-
|
|
10797
|
+
// The clear button gives way to a spinner at its spot: the `loading` one,
|
|
10798
|
+
// or that of a loading Control this sits in.
|
|
10799
|
+
const controlLoading = useControlLoading();
|
|
10800
|
+
const showClear = clearable && !!inputValue && !disabled && !loading && !controlLoading;
|
|
10801
|
+
const autocompleteElement = (jsxRuntime.jsxs("div", { ref: containerRef, className: combinedClasses, ...rest, children: [jsxRuntime.jsxs("div", { className: controlClasses, children: [jsxRuntime.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 && (jsxRuntime.jsx("button", { type: "button", className: iconRightClickableClass, onClick: handleClear, "aria-label": "Clear", children: jsxRuntime.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: [jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })), loading && (jsxRuntime.jsx("span", { className: iconRightClass, children: jsxRuntime.jsx("span", { className: loaderClass }) }))] }), isActive && (filteredData.length > 0 || empty) && (jsxRuntime.jsx("div", { className: dropdownMenuClasses, children: jsxRuntime.jsxs("div", { ref: dropdownRef, className: dropdownContentClass, style: { maxHeight: `${maxHeight}px`, overflowY: 'auto' }, role: "listbox", onScroll: handleDropdownScroll, children: [header && jsxRuntime.jsx("div", { className: dropdownHeaderClass, children: header }), filteredData.length > 0 ? (filteredData.map((item, index) => {
|
|
10608
10802
|
const isDisabled = typeof item !== 'string' && item.disabled;
|
|
10609
10803
|
const isHighlighted = index === highlightedIndex;
|
|
10610
10804
|
const itemClasses = prefixedClassNames(classPrefix, 'dropdown-item', {
|
|
@@ -10649,6 +10843,15 @@ const Taginput = React.forwardRef(({
|
|
|
10649
10843
|
// Field props
|
|
10650
10844
|
label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, value: controlledValue, defaultValue = [], data = [], placeholder, field = 'label', allowNew = true, allowDuplicates = false, openOnFocus = false, removeOnKeys = true, confirmKeys = ['Enter', ','], closable = true, attached = false, maxTags, maxlength, disabled = false, readonly = false, rounded = false, ellipsis = false, hasCounter = true, onPasteSeparators = [','], beforeAdding, createTag, keepFirst = false, keepOpen = true, loading = false, ariaCloseLabel, icon, iconLibrary: iconLibraryProp, iconVariant, iconFeatures, color, tagColor, size, onChange, onAdd, onRemove, onTyping, tagTemplate, name, form, id, className, ...props }, ref) => {
|
|
10651
10845
|
const insideField = useInsideField();
|
|
10846
|
+
const insideControl = useInsideControl();
|
|
10847
|
+
const ownField = rendersOwnField('Taginput', {
|
|
10848
|
+
insideField,
|
|
10849
|
+
insideControl,
|
|
10850
|
+
label,
|
|
10851
|
+
message,
|
|
10852
|
+
horizontal,
|
|
10853
|
+
fieldClassName,
|
|
10854
|
+
});
|
|
10652
10855
|
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
10653
10856
|
const { classPrefix } = useConfig();
|
|
10654
10857
|
const defaultIconLibrary = useIconLibrary();
|
|
@@ -10670,7 +10873,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
10670
10873
|
label,
|
|
10671
10874
|
id,
|
|
10672
10875
|
labelProps,
|
|
10673
|
-
rendersLabel:
|
|
10876
|
+
rendersLabel: ownField && !isMaxReached,
|
|
10674
10877
|
});
|
|
10675
10878
|
// Get display value from tag
|
|
10676
10879
|
const getDisplayValue = (tag) => {
|
|
@@ -10942,7 +11145,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
10942
11145
|
'is-active': index === highlightedIndex,
|
|
10943
11146
|
}), onClick: () => addTag(item), onMouseEnter: () => setHighlightedIndex(index), role: "option", "aria-selected": index === highlightedIndex, children: item }, index))) }) })), showCounter && jsxRuntime.jsx("small", { className: counterClasses, children: counterText }), name &&
|
|
10944
11147
|
tags.map((tag, i) => (jsxRuntime.jsx("input", { type: "hidden", name: name, value: getDisplayValue(tag), form: form }, `tag-input-${i}`)))] }));
|
|
10945
|
-
if (
|
|
11148
|
+
if (ownField) {
|
|
10946
11149
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [taginputElement, messageEl] }));
|
|
10947
11150
|
}
|
|
10948
11151
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [taginputElement, messageEl] }));
|
|
@@ -11095,21 +11298,44 @@ fieldClassName, controlClassName,
|
|
|
11095
11298
|
...inputProps }, ref) => {
|
|
11096
11299
|
const insideField = useInsideField();
|
|
11097
11300
|
const insideControl = useInsideControl();
|
|
11301
|
+
const ownField = rendersOwnField('Input', {
|
|
11302
|
+
insideField,
|
|
11303
|
+
insideControl,
|
|
11304
|
+
label,
|
|
11305
|
+
message,
|
|
11306
|
+
horizontal,
|
|
11307
|
+
fieldClassName,
|
|
11308
|
+
});
|
|
11309
|
+
const ownControl = rendersOwnControl('Input', {
|
|
11310
|
+
insideControl,
|
|
11311
|
+
isLoading,
|
|
11312
|
+
iconLeft,
|
|
11313
|
+
iconLeftName,
|
|
11314
|
+
iconLeftSize,
|
|
11315
|
+
iconRight,
|
|
11316
|
+
iconRightName,
|
|
11317
|
+
iconRightSize,
|
|
11318
|
+
hasIconsLeft,
|
|
11319
|
+
hasIconsRight,
|
|
11320
|
+
isExpanded,
|
|
11321
|
+
controlSize,
|
|
11322
|
+
controlClassName,
|
|
11323
|
+
});
|
|
11098
11324
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
11099
11325
|
label,
|
|
11100
11326
|
id: inputProps.id,
|
|
11101
11327
|
labelProps,
|
|
11102
|
-
rendersLabel:
|
|
11328
|
+
rendersLabel: ownField,
|
|
11103
11329
|
});
|
|
11104
11330
|
const helpClass = usePrefixedClassNames('help', {
|
|
11105
11331
|
[`is-${messageColor}`]: !!messageColor,
|
|
11106
11332
|
});
|
|
11107
11333
|
let content = jsxRuntime.jsx(InputBase, { ref: ref, id: controlId, ...inputProps });
|
|
11108
|
-
if (
|
|
11334
|
+
if (ownControl) {
|
|
11109
11335
|
content = (jsxRuntime.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 }));
|
|
11110
11336
|
}
|
|
11111
11337
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
11112
|
-
if (
|
|
11338
|
+
if (ownField) {
|
|
11113
11339
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
11114
11340
|
}
|
|
11115
11341
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -11145,21 +11371,40 @@ fieldClassName, controlClassName,
|
|
|
11145
11371
|
...selectProps }, ref) => {
|
|
11146
11372
|
const insideField = useInsideField();
|
|
11147
11373
|
const insideControl = useInsideControl();
|
|
11374
|
+
const ownField = rendersOwnField('Select', {
|
|
11375
|
+
insideField,
|
|
11376
|
+
insideControl,
|
|
11377
|
+
label,
|
|
11378
|
+
message,
|
|
11379
|
+
horizontal,
|
|
11380
|
+
fieldClassName,
|
|
11381
|
+
});
|
|
11382
|
+
// Not isLoading, which the select draws itself and so keeps in a Control.
|
|
11383
|
+
const ownControl = rendersOwnControl('Select', {
|
|
11384
|
+
insideControl,
|
|
11385
|
+
iconLeft,
|
|
11386
|
+
iconLeftName,
|
|
11387
|
+
iconLeftSize,
|
|
11388
|
+
hasIconsLeft,
|
|
11389
|
+
isExpanded,
|
|
11390
|
+
controlSize,
|
|
11391
|
+
controlClassName,
|
|
11392
|
+
});
|
|
11148
11393
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
11149
11394
|
label,
|
|
11150
11395
|
id: selectProps.id,
|
|
11151
11396
|
labelProps,
|
|
11152
|
-
rendersLabel:
|
|
11397
|
+
rendersLabel: ownField,
|
|
11153
11398
|
});
|
|
11154
11399
|
const helpClass = usePrefixedClassNames('help', {
|
|
11155
11400
|
[`is-${messageColor}`]: !!messageColor,
|
|
11156
11401
|
});
|
|
11157
11402
|
let content = jsxRuntime.jsx(SelectBase, { ref: ref, id: controlId, ...selectProps });
|
|
11158
|
-
if (
|
|
11403
|
+
if (ownControl) {
|
|
11159
11404
|
content = (jsxRuntime.jsx(Control, { iconLeft: iconLeft, iconLeftName: iconLeftName, iconLeftSize: iconLeftSize, hasIconsLeft: hasIconsLeft, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
|
|
11160
11405
|
}
|
|
11161
11406
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
11162
|
-
if (
|
|
11407
|
+
if (ownField) {
|
|
11163
11408
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
11164
11409
|
}
|
|
11165
11410
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -11196,21 +11441,35 @@ fieldClassName, controlClassName,
|
|
|
11196
11441
|
...textAreaProps }, ref) => {
|
|
11197
11442
|
const insideField = useInsideField();
|
|
11198
11443
|
const insideControl = useInsideControl();
|
|
11444
|
+
const ownField = rendersOwnField('TextArea', {
|
|
11445
|
+
insideField,
|
|
11446
|
+
insideControl,
|
|
11447
|
+
label,
|
|
11448
|
+
message,
|
|
11449
|
+
horizontal,
|
|
11450
|
+
fieldClassName,
|
|
11451
|
+
});
|
|
11452
|
+
const ownControl = rendersOwnControl('TextArea', {
|
|
11453
|
+
insideControl,
|
|
11454
|
+
isLoading: controlIsLoading,
|
|
11455
|
+
controlSize,
|
|
11456
|
+
controlClassName,
|
|
11457
|
+
});
|
|
11199
11458
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
11200
11459
|
label,
|
|
11201
11460
|
id: textAreaProps.id,
|
|
11202
11461
|
labelProps,
|
|
11203
|
-
rendersLabel:
|
|
11462
|
+
rendersLabel: ownField,
|
|
11204
11463
|
});
|
|
11205
11464
|
const helpClass = usePrefixedClassNames('help', {
|
|
11206
11465
|
[`is-${messageColor}`]: !!messageColor,
|
|
11207
11466
|
});
|
|
11208
11467
|
let content = jsxRuntime.jsx(TextAreaBase, { ref: ref, id: controlId, ...textAreaProps });
|
|
11209
|
-
if (
|
|
11468
|
+
if (ownControl) {
|
|
11210
11469
|
content = (jsxRuntime.jsx(Control, { isLoading: controlIsLoading, size: controlSize, className: controlClassName, children: content }));
|
|
11211
11470
|
}
|
|
11212
11471
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
11213
|
-
if (
|
|
11472
|
+
if (ownField) {
|
|
11214
11473
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
11215
11474
|
}
|
|
11216
11475
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -11297,6 +11556,21 @@ function makeDate(year, month = 0, day = 1) {
|
|
|
11297
11556
|
d.setFullYear(year, month, day);
|
|
11298
11557
|
return d;
|
|
11299
11558
|
}
|
|
11559
|
+
/**
|
|
11560
|
+
* The earliest year the pickers accept. HTML's date, month and datetime-local
|
|
11561
|
+
* inputs hold no year below 1, so the calendar, typing and parsing stop there
|
|
11562
|
+
* too.
|
|
11563
|
+
*/
|
|
11564
|
+
const FIRST_YEAR = 1;
|
|
11565
|
+
/**
|
|
11566
|
+
* The lower bound a picker applies: `min`, raised to local midnight on
|
|
11567
|
+
* 1 January of {@link FIRST_YEAR} when it is earlier or absent. A `min` in
|
|
11568
|
+
* range comes back as the same object.
|
|
11569
|
+
*/
|
|
11570
|
+
function floorMin(min) {
|
|
11571
|
+
const floor = makeDate(FIRST_YEAR);
|
|
11572
|
+
return min && min.getTime() >= floor.getTime() ? min : floor;
|
|
11573
|
+
}
|
|
11300
11574
|
/**
|
|
11301
11575
|
* First instant of the day, month or year containing `d`. Works by setters on
|
|
11302
11576
|
* a copy rather than the `Date(y, m, d)` constructor, which reads years 0–99
|
|
@@ -11390,12 +11664,14 @@ function setTimeOfDay(d, parts) {
|
|
|
11390
11664
|
r.setSeconds(parts.seconds);
|
|
11391
11665
|
return r;
|
|
11392
11666
|
}
|
|
11667
|
+
/**
|
|
11668
|
+
* A copy of `d` moved into `[min, max]`. When `min` is after `max` nothing is
|
|
11669
|
+
* in range and `min` wins, so clamping twice gives the same date and a
|
|
11670
|
+
* picker's focus stays off the years before 1 under a `max` before then.
|
|
11671
|
+
*/
|
|
11393
11672
|
function clampDate(d, min, max) {
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
if (max && isAfter(d, max))
|
|
11397
|
-
return new Date(max);
|
|
11398
|
-
return new Date(d);
|
|
11673
|
+
const capped = max && isAfter(d, max) ? max : d;
|
|
11674
|
+
return new Date(min && isBefore(capped, min) ? min : capped);
|
|
11399
11675
|
}
|
|
11400
11676
|
/**
|
|
11401
11677
|
* Snap a Date's time-of-day to the nearest grid defined by the given
|
|
@@ -11468,7 +11744,9 @@ function formatToken(d, token) {
|
|
|
11468
11744
|
case 'YYYY':
|
|
11469
11745
|
// Zero-pad to a fixed 4 chars so the rendered width matches the
|
|
11470
11746
|
// segment-map width during digit-by-digit keyboard entry (and to match
|
|
11471
|
-
// ISO 8601)
|
|
11747
|
+
// ISO 8601), so year 19 shows as `0019`. Years >9999 keep their natural
|
|
11748
|
+
// width. The pickers commit no year below 1, so a minus sign reaches
|
|
11749
|
+
// this only from a `value` passed in.
|
|
11472
11750
|
return pad(d.getFullYear(), 4);
|
|
11473
11751
|
case 'YY':
|
|
11474
11752
|
return pad(d.getFullYear() % 100);
|
|
@@ -11742,9 +12020,10 @@ function getMonthNames(locale, length = 'long') {
|
|
|
11742
12020
|
|
|
11743
12021
|
/**
|
|
11744
12022
|
* Selects the calendar's focused cell, the one tab stop of whichever grid or
|
|
11745
|
-
* year list is on show, for a popover to put focus on as it opens.
|
|
12023
|
+
* year list is on show, for a popover to put focus on as it opens. It runs
|
|
12024
|
+
* against the whole panel, so it matches only inside the grid or the list.
|
|
11746
12025
|
*/
|
|
11747
|
-
const CALENDAR_FOCUSED_CELL = '[data-focused="true"], [data-focused-year="true"]';
|
|
12026
|
+
const CALENDAR_FOCUSED_CELL = '[role="grid"] [data-focused="true"], [role="listbox"] [data-focused-year="true"]';
|
|
11748
12027
|
const BASE_VIEW = {
|
|
11749
12028
|
day: 'days',
|
|
11750
12029
|
month: 'months',
|
|
@@ -11818,10 +12097,11 @@ function nearestEnabled(i, disabled) {
|
|
|
11818
12097
|
return i;
|
|
11819
12098
|
}
|
|
11820
12099
|
/**
|
|
11821
|
-
* The year list's keyboard
|
|
11822
|
-
* year or a row, Home/End go to the list's ends. Returns the
|
|
11823
|
-
* from and the direction to search past disabled years, or
|
|
11824
|
-
* the list doesn't handle.
|
|
12100
|
+
* The year list's keyboard, as the selection surface and as navigation:
|
|
12101
|
+
* arrows move a year or a row, Home/End go to the list's ends. Returns the
|
|
12102
|
+
* year to start from and the direction to search past disabled years, or
|
|
12103
|
+
* `null` for a key the list doesn't handle. As navigation no year is
|
|
12104
|
+
* disabled, so only the year is used.
|
|
11825
12105
|
*/
|
|
11826
12106
|
function yearStep(key, focusedYear, years) {
|
|
11827
12107
|
switch (key) {
|
|
@@ -11841,7 +12121,7 @@ function yearStep(key, focusedYear, years) {
|
|
|
11841
12121
|
return null;
|
|
11842
12122
|
}
|
|
11843
12123
|
}
|
|
11844
|
-
const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max, shouldDisableDate, unselectableDates, firstDayOfWeek = 0, locale, dayNames, monthNames, nearbyMonthDays = true, color, size, className, id, autoFocusCell = false, labels, yearsRange, granularity = 'day', }) => {
|
|
12124
|
+
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', }) => {
|
|
11845
12125
|
const gridRef = React.useRef(null);
|
|
11846
12126
|
const monthGridRef = React.useRef(null);
|
|
11847
12127
|
const yearGridRef = React.useRef(null);
|
|
@@ -11851,6 +12131,8 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
11851
12131
|
const isDayGranularity = granularity === 'day';
|
|
11852
12132
|
const isYearGranularity = granularity === 'year';
|
|
11853
12133
|
const [view, setView] = React.useState(baseView);
|
|
12134
|
+
// Nothing before year 1 can be picked or reached, as in HTML's date inputs.
|
|
12135
|
+
const min = React.useMemo(() => floorMin(minProp), [minProp]);
|
|
11854
12136
|
const computedDayNames = React.useMemo(() => {
|
|
11855
12137
|
if (dayNames && dayNames.length === 7)
|
|
11856
12138
|
return dayNames;
|
|
@@ -11921,7 +12203,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
11921
12203
|
const moveFocus = React.useCallback((next, direction) => {
|
|
11922
12204
|
let candidate = next;
|
|
11923
12205
|
for (let i = 0; i < 366; i++) {
|
|
11924
|
-
if (
|
|
12206
|
+
if (candidate.getTime() < startOfDay(min).getTime())
|
|
11925
12207
|
return;
|
|
11926
12208
|
if (max && candidate.getTime() > startOfDay(max).getTime())
|
|
11927
12209
|
return;
|
|
@@ -11987,7 +12269,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
11987
12269
|
let candidate = next;
|
|
11988
12270
|
for (let i = 0; i < 120; i++) {
|
|
11989
12271
|
const month = startOfMonth(candidate).getTime();
|
|
11990
|
-
if (
|
|
12272
|
+
if (month < startOfMonth(min).getTime())
|
|
11991
12273
|
return;
|
|
11992
12274
|
if (max && month > startOfMonth(max).getTime())
|
|
11993
12275
|
return;
|
|
@@ -12035,25 +12317,22 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12035
12317
|
const today = new Date();
|
|
12036
12318
|
const todayYear = today.getFullYear();
|
|
12037
12319
|
// The header steps a month at a time over the day grid and a year at a
|
|
12038
|
-
// time over the month grid, kept inside min/max.
|
|
12039
|
-
|
|
12040
|
-
|
|
12320
|
+
// time over the month grid, kept inside min/max. A step is off once the
|
|
12321
|
+
// month or year on show holds the bound or lies past it, as picking a year
|
|
12322
|
+
// from the list can leave it.
|
|
12041
12323
|
const prevAnchor = isDayGranularity
|
|
12042
|
-
?
|
|
12324
|
+
? addMonths(focusedDate, -1)
|
|
12043
12325
|
: clampDate(addYears(focusedDate, -1), min, max);
|
|
12044
12326
|
const nextAnchor = isDayGranularity
|
|
12045
|
-
?
|
|
12327
|
+
? addMonths(focusedDate, 1)
|
|
12046
12328
|
: clampDate(addYears(focusedDate, 1), min, max);
|
|
12329
|
+
const focusedMonth = startOfMonth(focusedDate).getTime();
|
|
12047
12330
|
const prevDisabled = isDayGranularity
|
|
12048
|
-
?
|
|
12049
|
-
|
|
12050
|
-
isSameMonth(focusedDate, min))
|
|
12051
|
-
: !!(min && focusedYear <= min.getFullYear());
|
|
12331
|
+
? focusedMonth <= startOfMonth(min).getTime()
|
|
12332
|
+
: focusedYear <= min.getFullYear();
|
|
12052
12333
|
const nextDisabled = isDayGranularity
|
|
12053
|
-
? !!
|
|
12054
|
-
|
|
12055
|
-
isSameMonth(focusedDate, max))
|
|
12056
|
-
: !!(max && focusedYear >= max.getFullYear());
|
|
12334
|
+
? !!max && focusedMonth >= startOfMonth(max).getTime()
|
|
12335
|
+
: !!max && focusedYear >= max.getFullYear();
|
|
12057
12336
|
const labelId = id ? `${id}-label` : undefined;
|
|
12058
12337
|
const monthLabel = isDayGranularity
|
|
12059
12338
|
? `${computedMonthNames[focusedDate.getMonth()]} ${focusedYear}`
|
|
@@ -12064,7 +12343,9 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12064
12343
|
const [mountYear] = React.useState(focusedYear);
|
|
12065
12344
|
const windowYear = isYearGranularity ? mountYear : focusedYear;
|
|
12066
12345
|
const yearList = React.useMemo(() => {
|
|
12067
|
-
const minYear =
|
|
12346
|
+
const minYear = minProp
|
|
12347
|
+
? min.getFullYear()
|
|
12348
|
+
: Math.max(FIRST_YEAR, windowYear - 100);
|
|
12068
12349
|
const maxYear = max ? max.getFullYear() : windowYear + 100;
|
|
12069
12350
|
const [lo, hi] = yearsRange ?? [minYear, maxYear];
|
|
12070
12351
|
const start = Math.max(lo, minYear);
|
|
@@ -12073,20 +12354,31 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12073
12354
|
for (let y = start; y <= end; y++)
|
|
12074
12355
|
out.push(y);
|
|
12075
12356
|
return out;
|
|
12076
|
-
}, [windowYear, min, max, yearsRange]);
|
|
12357
|
+
}, [windowYear, minProp, min, max, yearsRange]);
|
|
12077
12358
|
// Only the selection surface disables years; as navigation every listed
|
|
12078
12359
|
// year can be visited.
|
|
12079
12360
|
const disabledYears = React.useMemo(() => new Set(isYearGranularity ? yearList.filter(y => isYearUnselectable(y)) : []), [isYearGranularity, yearList, isYearUnselectable]);
|
|
12080
12361
|
// The year list's one tab stop, after the month grid's: when the focused
|
|
12081
12362
|
// year is disabled the stop moves to the nearest enabled year in the list,
|
|
12082
|
-
// and focusing it makes it the focused year.
|
|
12083
|
-
// the
|
|
12363
|
+
// and focusing it makes it the focused year. As the selection surface a
|
|
12364
|
+
// year outside a non-empty list starts from the end it fell past, so the
|
|
12365
|
+
// list keeps a stop. As navigation it marks the current year, so a year
|
|
12366
|
+
// outside the list marks no option, and the stop itself follows focus.
|
|
12084
12367
|
const tabStopYear = React.useMemo(() => {
|
|
12085
|
-
|
|
12086
|
-
if (i < 0)
|
|
12087
|
-
|
|
12368
|
+
let i = yearList.indexOf(focusedYear);
|
|
12369
|
+
if (i < 0) {
|
|
12370
|
+
if (!isYearGranularity || yearList.length === 0)
|
|
12371
|
+
return focusedYear;
|
|
12372
|
+
i = focusedYear < yearList[0] ? 0 : yearList.length - 1;
|
|
12373
|
+
}
|
|
12088
12374
|
return yearList[nearestEnabled(i, yearList.map(y => disabledYears.has(y)))];
|
|
12089
|
-
}, [yearList, focusedYear, disabledYears]);
|
|
12375
|
+
}, [yearList, focusedYear, disabledYears, isYearGranularity]);
|
|
12376
|
+
// As navigation the keys move focus and leave the focused year alone, so
|
|
12377
|
+
// the tab stop goes to the year that last had focus while it is listed,
|
|
12378
|
+
// and Tab leaves the list rather than landing on the current year. It
|
|
12379
|
+
// starts over each time the list opens.
|
|
12380
|
+
const [navYear, setNavYear] = React.useState(null);
|
|
12381
|
+
const listTabStop = navYear !== null && yearList.includes(navYear) ? navYear : tabStopYear;
|
|
12090
12382
|
// Which of the focused year's months have no selectable day.
|
|
12091
12383
|
const disabledMonths = React.useMemo(() => granularity === 'month'
|
|
12092
12384
|
? Array.from({ length: 12 }, (_, m) => isMonthUnselectable(makeDate(focusedYear, m)))
|
|
@@ -12122,19 +12414,39 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12122
12414
|
}
|
|
12123
12415
|
}, [yearList, disabledYears, onFocusedDateChange, min, max]);
|
|
12124
12416
|
const handleYearListKeyDown = React.useCallback((e) => {
|
|
12125
|
-
if (
|
|
12126
|
-
|
|
12127
|
-
if (
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12417
|
+
if (isYearGranularity) {
|
|
12418
|
+
const step = yearStep(e.key, focusedYear, yearList);
|
|
12419
|
+
if (!step)
|
|
12420
|
+
return;
|
|
12421
|
+
e.preventDefault();
|
|
12422
|
+
moveYearFocus(step[0], step[1]);
|
|
12423
|
+
return;
|
|
12424
|
+
}
|
|
12425
|
+
// As navigation, Escape returns to the grid the list was opened from.
|
|
12426
|
+
// It stops there, so a popover around the calendar stays open and only
|
|
12427
|
+
// a second Escape closes it.
|
|
12428
|
+
if (e.key === 'Escape') {
|
|
12429
|
+
e.preventDefault();
|
|
12430
|
+
e.stopPropagation();
|
|
12431
|
+
setView(baseView);
|
|
12131
12432
|
return;
|
|
12132
12433
|
}
|
|
12133
|
-
|
|
12434
|
+
// The other keys move DOM focus from the year that has it and leave
|
|
12435
|
+
// the focused date alone. The list is centred on that date, so moving
|
|
12436
|
+
// it would reflow the list under the keyboard, and it would turn the
|
|
12437
|
+
// grid behind the list before anything is picked. Enter or Space
|
|
12438
|
+
// clicks the year with focus, which picks it.
|
|
12439
|
+
const options = Array.from(e.currentTarget.querySelectorAll('[role="option"]'));
|
|
12440
|
+
const from = options.indexOf(e.target);
|
|
12441
|
+
if (from < 0)
|
|
12442
|
+
return;
|
|
12443
|
+
const step = yearStep(e.key, yearList[from], yearList);
|
|
12134
12444
|
if (!step)
|
|
12135
12445
|
return;
|
|
12136
12446
|
e.preventDefault();
|
|
12137
|
-
|
|
12447
|
+
// The listed years run one apart, so a year's index is its distance
|
|
12448
|
+
// from the first. A step past either end finds no option.
|
|
12449
|
+
options[step[0] - yearList[0]]?.focus();
|
|
12138
12450
|
}, [isYearGranularity, baseView, focusedYear, yearList, moveYearFocus]);
|
|
12139
12451
|
// The day or month grid takes focus as the popover opens on it, or comes
|
|
12140
12452
|
// back to it from the year list.
|
|
@@ -12205,7 +12517,10 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12205
12517
|
list.querySelector('[data-focused-year="true"]')?.focus();
|
|
12206
12518
|
}, [isYearGranularity, focusedYear]);
|
|
12207
12519
|
const navArrow = (d) => (jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", children: jsxRuntime.jsx("path", { d: d, fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
12208
|
-
return (jsxRuntime.jsxs("div", { className: classNames(calendarClass, className), id: id, children: [jsxRuntime.jsxs("div", { className: headerClass, children: [isYearGranularity ? (jsxRuntime.jsx("span", { className: monthLabelClass, id: labelId, children: monthLabel })) : (jsxRuntime.jsxs("button", { type: "button", className: monthTriggerClass, "aria-haspopup": "listbox", "aria-expanded": view === 'years', onClick: () =>
|
|
12520
|
+
return (jsxRuntime.jsxs("div", { className: classNames(calendarClass, className), id: id, children: [jsxRuntime.jsxs("div", { className: headerClass, children: [isYearGranularity ? (jsxRuntime.jsx("span", { className: monthLabelClass, id: labelId, children: monthLabel })) : (jsxRuntime.jsxs("button", { type: "button", className: monthTriggerClass, "aria-haspopup": "listbox", "aria-expanded": view === 'years', onClick: () => {
|
|
12521
|
+
setNavYear(null);
|
|
12522
|
+
setView(v => (v === 'years' ? baseView : 'years'));
|
|
12523
|
+
}, children: [jsxRuntime.jsx("span", { className: monthLabelClass, id: labelId, "aria-live": "polite", children: monthLabel }), jsxRuntime.jsx("svg", { className: monthCaretClass, viewBox: "0 0 12 12", width: "10", height: "10", "aria-hidden": "true", children: jsxRuntime.jsx("path", { d: "M2 4l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) })] })), !isYearGranularity && (jsxRuntime.jsxs("div", { className: navGroupClass, children: [jsxRuntime.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') }), jsxRuntime.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' && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: dayNamesRowClass, "aria-hidden": "true", children: computedDayNames.map((name, i) => (jsxRuntime.jsx("div", { className: dayNameClass, children: name }, i))) }), jsxRuntime.jsx("div", { ref: gridRef, role: "grid", "aria-labelledby": labelId, className: gridClass, onKeyDown: handleKeyDown, ...gridFocusHandlers, children: cells.map(cell => {
|
|
12209
12524
|
const disabled = isDateUnselectable(cell.date);
|
|
12210
12525
|
const isSelected = !!value && isSameDay(value, cell.date);
|
|
12211
12526
|
const isFocused = isSameDay(cell.date, tabStopDay);
|
|
@@ -12265,10 +12580,16 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12265
12580
|
'is-today': isToday,
|
|
12266
12581
|
'is-disabled': disabled,
|
|
12267
12582
|
});
|
|
12268
|
-
return (jsxRuntime.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:
|
|
12269
|
-
//
|
|
12270
|
-
//
|
|
12271
|
-
|
|
12583
|
+
return (jsxRuntime.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: () => {
|
|
12584
|
+
// As navigation, focusing a year by key or pointer only
|
|
12585
|
+
// moves the tab stop: nothing is committed until a year is
|
|
12586
|
+
// picked. As the selection surface it is reached by Tab
|
|
12587
|
+
// while the focused year is disabled, or by pointer, and
|
|
12588
|
+
// keys move on from here.
|
|
12589
|
+
if (!isYearGranularity) {
|
|
12590
|
+
setNavYear(year);
|
|
12591
|
+
}
|
|
12592
|
+
else if (year !== focusedYear) {
|
|
12272
12593
|
onFocusedDateChange(clampDate(makeDate(year), min, max));
|
|
12273
12594
|
}
|
|
12274
12595
|
}, onClick: () => isYearGranularity
|
|
@@ -12293,11 +12614,10 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
|
|
|
12293
12614
|
active: isOpen,
|
|
12294
12615
|
position,
|
|
12295
12616
|
fixed: appendToBody,
|
|
12296
|
-
//
|
|
12297
|
-
//
|
|
12298
|
-
// the panel.
|
|
12299
|
-
|
|
12300
|
-
offset: appendToBody ? 0 : undefined,
|
|
12617
|
+
// The gap comes from `--bulma-picker-popover-offset` as a margin, in place
|
|
12618
|
+
// or portaled, so the coordinates leave it out and `auto` reads it off
|
|
12619
|
+
// the panel.
|
|
12620
|
+
offset: 0,
|
|
12301
12621
|
styledGap: marginGap,
|
|
12302
12622
|
});
|
|
12303
12623
|
React.useEffect(() => {
|
|
@@ -12743,33 +13063,35 @@ function useSegmentedEntry(params) {
|
|
|
12743
13063
|
// within the same event tick.
|
|
12744
13064
|
const typedDigitsRef = React.useRef('');
|
|
12745
13065
|
// The text focus seeded an empty picker with, until the user edits it or
|
|
12746
|
-
// leaves.
|
|
13066
|
+
// leaves.
|
|
12747
13067
|
const seedRef = React.useRef(null);
|
|
12748
|
-
// Whether the
|
|
12749
|
-
//
|
|
12750
|
-
|
|
13068
|
+
// Whether the text is still what the picker put there, so nothing in it was
|
|
13069
|
+
// typed: the value's display, or an empty picker's seed until an edit drops
|
|
13070
|
+
// it. Against a value it compares text alone, so typing that ends back on
|
|
13071
|
+
// the display counts as untouched.
|
|
13072
|
+
const isUntouched = React.useCallback((s) => value
|
|
13073
|
+
? s === formatFn(value, format, locale)
|
|
13074
|
+
: seedRef.current !== null && s === seedRef.current, [value, formatFn, format, locale]);
|
|
12751
13075
|
// A seed the popover never turned into a value closes with it, wherever
|
|
12752
13076
|
// focus goes, so a dismissed empty field stays empty.
|
|
12753
13077
|
//
|
|
12754
13078
|
// As the popover closes, its focus trap hands focus back to the input. That
|
|
12755
13079
|
// focus is not the user arriving: it must not open the popover again under
|
|
12756
|
-
// `openOnFocus`, or seed an empty picker
|
|
12757
|
-
//
|
|
12758
|
-
//
|
|
12759
|
-
//
|
|
12760
|
-
// in between is the one handed back.
|
|
13080
|
+
// `openOnFocus`, or seed an empty picker. `closingRef` is set in the layout
|
|
13081
|
+
// phase of the commit that closes it and cleared by that commit's effects,
|
|
13082
|
+
// and React runs a commit's effect cleanups, the trap's among them, before
|
|
13083
|
+
// any of its effects, so a focus in between is the one handed back.
|
|
12761
13084
|
const closingRef = React.useRef(false);
|
|
12762
13085
|
const wasOpenRef = React.useRef(isOpen);
|
|
12763
13086
|
React.useLayoutEffect(() => {
|
|
12764
13087
|
if (wasOpenRef.current && !isOpen) {
|
|
12765
13088
|
closingRef.current = true;
|
|
12766
|
-
if (!value &&
|
|
13089
|
+
if (!value && isUntouched(text))
|
|
12767
13090
|
setText('');
|
|
12768
|
-
}
|
|
12769
13091
|
seedRef.current = null;
|
|
12770
13092
|
}
|
|
12771
13093
|
wasOpenRef.current = isOpen;
|
|
12772
|
-
}, [isOpen, value, text, setText]);
|
|
13094
|
+
}, [isOpen, value, isUntouched, text, setText]);
|
|
12773
13095
|
React.useEffect(() => {
|
|
12774
13096
|
closingRef.current = false;
|
|
12775
13097
|
}, [isOpen]);
|
|
@@ -12847,13 +13169,18 @@ function useSegmentedEntry(params) {
|
|
|
12847
13169
|
}
|
|
12848
13170
|
setActiveSegmentIdx(null);
|
|
12849
13171
|
typedDigitsRef.current = '';
|
|
12850
|
-
|
|
12851
|
-
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
//
|
|
12855
|
-
//
|
|
12856
|
-
|
|
13172
|
+
// Leaving commits only what was typed. Re-parsing the value's display
|
|
13173
|
+
// could drop what the format leaves out, such as seconds, and a seed
|
|
13174
|
+
// nobody typed over is not the user's date.
|
|
13175
|
+
const untouched = isUntouched(text);
|
|
13176
|
+
// An open popover keeps an unused seed until it closes, so one portaled
|
|
13177
|
+
// outside the picker, whose focus reads as leaving, keeps it as one
|
|
13178
|
+
// inside does.
|
|
13179
|
+
if (!isOpen)
|
|
13180
|
+
seedRef.current = null;
|
|
13181
|
+
if (untouched) {
|
|
13182
|
+
if (!value && !isOpen)
|
|
13183
|
+
setText('');
|
|
12857
13184
|
onBlur?.(e);
|
|
12858
13185
|
return;
|
|
12859
13186
|
}
|
|
@@ -12867,6 +13194,8 @@ function useSegmentedEntry(params) {
|
|
|
12867
13194
|
onBlur?.(e);
|
|
12868
13195
|
}, [
|
|
12869
13196
|
containerRef,
|
|
13197
|
+
isUntouched,
|
|
13198
|
+
isOpen,
|
|
12870
13199
|
tryParse,
|
|
12871
13200
|
text,
|
|
12872
13201
|
isAllowed,
|
|
@@ -12880,7 +13209,6 @@ function useSegmentedEntry(params) {
|
|
|
12880
13209
|
]);
|
|
12881
13210
|
const handleFocus = React.useCallback((e) => {
|
|
12882
13211
|
const handedBack = closingRef.current;
|
|
12883
|
-
handedBackRef.current = handedBack;
|
|
12884
13212
|
if (!handedBack && openOnFocus && popover && !disabled && !readOnly) {
|
|
12885
13213
|
setOpen(true);
|
|
12886
13214
|
}
|
|
@@ -13048,12 +13376,20 @@ function useSegmentedEntry(params) {
|
|
|
13048
13376
|
}
|
|
13049
13377
|
if (e.key === 'Enter') {
|
|
13050
13378
|
e.preventDefault();
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
|
|
13379
|
+
// Untouched text has nothing to commit, as on leaving, but Enter
|
|
13380
|
+
// still confirms what the field shows.
|
|
13381
|
+
if (isUntouched(text)) {
|
|
13054
13382
|
if (closeOnSelect)
|
|
13055
13383
|
setOpen(false);
|
|
13056
13384
|
}
|
|
13385
|
+
else {
|
|
13386
|
+
const parsed = tryParse(text);
|
|
13387
|
+
if (parsed && isAllowed(parsed)) {
|
|
13388
|
+
commitValue(parsed);
|
|
13389
|
+
if (closeOnSelect)
|
|
13390
|
+
setOpen(false);
|
|
13391
|
+
}
|
|
13392
|
+
}
|
|
13057
13393
|
}
|
|
13058
13394
|
onKeyDown?.(e);
|
|
13059
13395
|
}, [
|
|
@@ -13067,6 +13403,7 @@ function useSegmentedEntry(params) {
|
|
|
13067
13403
|
setOpen,
|
|
13068
13404
|
closeOnSelect,
|
|
13069
13405
|
popover,
|
|
13406
|
+
isUntouched,
|
|
13070
13407
|
tryParse,
|
|
13071
13408
|
text,
|
|
13072
13409
|
isAllowed,
|
|
@@ -13115,9 +13452,12 @@ const pad2$1 = (n) => String(n).padStart(2, '0');
|
|
|
13115
13452
|
* The value as the native input and the hidden form input carry it:
|
|
13116
13453
|
* `YYYY-MM-DD`, `YYYY-MM` or `YYYY`, the shapes `<input type="date">` and
|
|
13117
13454
|
* `<input type="month">` use. The year is padded to four digits, as those
|
|
13118
|
-
* inputs require and as the `YYYY` token displays it.
|
|
13455
|
+
* inputs require and as the `YYYY` token displays it. HTML has no such shape
|
|
13456
|
+
* for a year before 1, so a date then is empty, as those inputs would make it.
|
|
13119
13457
|
*/
|
|
13120
13458
|
const toIsoValue = (d, granularity) => {
|
|
13459
|
+
if (d.getFullYear() < FIRST_YEAR)
|
|
13460
|
+
return '';
|
|
13121
13461
|
const year = String(d.getFullYear()).padStart(4, '0');
|
|
13122
13462
|
if (granularity === 'year')
|
|
13123
13463
|
return year;
|
|
@@ -13158,14 +13498,19 @@ const SKIPPED_SEGMENTS = {
|
|
|
13158
13498
|
* @returns {JSX.Element}
|
|
13159
13499
|
*/
|
|
13160
13500
|
const DateInputBase = React.forwardRef((props, ref) => {
|
|
13161
|
-
const { value: controlledValue, defaultValue, onChange, onOpen, onClose, min, max, disabled, readOnly, placeholder, granularity = 'day', format, parse, locale, inline = false, mobileNative = 'auto', editable = true, popover = true, openOnFocus = true, closeOnSelect = true, position = 'bottom-left', appendToBody = false, color, size, isRounded, shouldDisableDate, unselectableDates, firstDayOfWeek = 0, dayNames, monthNames, nearbyMonthDays = true, className, name, form, required, id, onFocus, onClick, onKeyDown, onBlur, iconLeftName: _iconLeftName, triggerIcon
|
|
13501
|
+
const { value: controlledValue, defaultValue, onChange, onOpen, onClose, min, max, disabled, readOnly, placeholder, granularity = 'day', format, parse, locale, inline = false, mobileNative = 'auto', editable = true, popover = true, openOnFocus = true, closeOnSelect = true, position = 'bottom-left', appendToBody = false, color, size, isRounded, shouldDisableDate, unselectableDates, firstDayOfWeek = 0, dayNames, monthNames, nearbyMonthDays = true, className, name, form, required, id, onFocus, onClick, onKeyDown, onBlur, iconLeftName: _iconLeftName, triggerIcon: triggerIconProp, triggerIconName = 'chevron-down', labels, ...rest } = props;
|
|
13502
|
+
// The launcher gives way to the loading spinner of a Control this sits in.
|
|
13503
|
+
const controlLoading = useControlLoading();
|
|
13504
|
+
const triggerIcon = triggerIconProp ?? !controlLoading;
|
|
13162
13505
|
const t = mergeLabels(labels);
|
|
13163
13506
|
const isDayGranularity = granularity === 'day';
|
|
13164
13507
|
const resolvedFormat = format ?? DEFAULT_FORMATS[granularity];
|
|
13165
13508
|
const isControlled = controlledValue !== undefined;
|
|
13166
13509
|
const [internalValue, setInternalValue] = React.useState(defaultValue ?? null);
|
|
13167
13510
|
const value = isControlled ? (controlledValue ?? null) : internalValue;
|
|
13168
|
-
|
|
13511
|
+
// Nothing before year 1 is in range, as in HTML's date inputs.
|
|
13512
|
+
const lowerBound = React.useMemo(() => floorMin(min), [min]);
|
|
13513
|
+
const initialFocused = React.useMemo(() => clampDate(value ?? new Date(), lowerBound, max),
|
|
13169
13514
|
// intentionally only on mount: keep focusedDate stable until value/open change
|
|
13170
13515
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
13171
13516
|
[]);
|
|
@@ -13173,8 +13518,8 @@ const DateInputBase = React.forwardRef((props, ref) => {
|
|
|
13173
13518
|
// Re-clamp focusedDate if min/max change after mount so the focused cell
|
|
13174
13519
|
// never disappears outside the displayable range.
|
|
13175
13520
|
React.useEffect(() => {
|
|
13176
|
-
setFocusedDate(prev => clampDate(prev,
|
|
13177
|
-
}, [
|
|
13521
|
+
setFocusedDate(prev => clampDate(prev, lowerBound, max));
|
|
13522
|
+
}, [lowerBound, max]);
|
|
13178
13523
|
const [open, setOpenState] = React.useState(false);
|
|
13179
13524
|
const [text, setText] = React.useState(value ? formatDate(value, resolvedFormat, locale) : '');
|
|
13180
13525
|
const inputRef = React.useRef(null);
|
|
@@ -13232,7 +13577,7 @@ const DateInputBase = React.forwardRef((props, ref) => {
|
|
|
13232
13577
|
}, [isControlled, onChange, isDayGranularity, granularity]);
|
|
13233
13578
|
// At month or year granularity the bounds widen to the periods holding
|
|
13234
13579
|
// them, so the month or year containing `min` stays reachable.
|
|
13235
|
-
const periodMin = React.useMemo(() =>
|
|
13580
|
+
const periodMin = React.useMemo(() => isDayGranularity ? lowerBound : startOfPeriod(lowerBound, granularity), [lowerBound, isDayGranularity, granularity]);
|
|
13236
13581
|
const periodMax = React.useMemo(() => (max && !isDayGranularity ? endOfPeriod(max, granularity) : max), [max, isDayGranularity, granularity]);
|
|
13237
13582
|
const handleSelect = React.useCallback((d) => {
|
|
13238
13583
|
if (!isWithin(d, periodMin, periodMax))
|
|
@@ -13283,7 +13628,7 @@ const DateInputBase = React.forwardRef((props, ref) => {
|
|
|
13283
13628
|
]);
|
|
13284
13629
|
// Typing, and the re-parse on blur or Enter, that lands in the period the
|
|
13285
13630
|
// value already holds commits nothing, so a `value` elsewhere in its month
|
|
13286
|
-
// or year
|
|
13631
|
+
// or year changes only when the user picks or types a different period.
|
|
13287
13632
|
const commitTyped = React.useCallback((next) => {
|
|
13288
13633
|
if (next &&
|
|
13289
13634
|
value &&
|
|
@@ -13349,7 +13694,7 @@ const DateInputBase = React.forwardRef((props, ref) => {
|
|
|
13349
13694
|
? fromIsoValue(e.target.value, granularity)
|
|
13350
13695
|
: null;
|
|
13351
13696
|
commitValue(parsed);
|
|
13352
|
-
}, min: min ? toIsoValue(
|
|
13697
|
+
}, min: min ? toIsoValue(lowerBound, granularity) : undefined, max: max ? toIsoValue(max, granularity) : undefined, disabled: disabled, readOnly: readOnly, placeholder: placeholder, name: name, form: form, required: required, id: id }));
|
|
13353
13698
|
}
|
|
13354
13699
|
const chooseLabel = granularity === 'month'
|
|
13355
13700
|
? t.chooseMonth
|
|
@@ -13358,7 +13703,11 @@ const DateInputBase = React.forwardRef((props, ref) => {
|
|
|
13358
13703
|
: t.chooseDate;
|
|
13359
13704
|
const calendar = (jsxRuntime.jsx(Calendar
|
|
13360
13705
|
// A new granularity starts the calendar over on its own view.
|
|
13361
|
-
, { granularity: granularity, value: value, focusedDate: focusedDate, onSelect: handleSelect, onFocusedDateChange: setFocusedDate, min: min, max: max, shouldDisableDate: shouldDisableDate, unselectableDates: unselectableDates, firstDayOfWeek: firstDayOfWeek, locale: locale, dayNames: dayNames, monthNames: monthNames, nearbyMonthDays: nearbyMonthDays, color: color, size: size,
|
|
13706
|
+
, { granularity: granularity, value: value, focusedDate: focusedDate, onSelect: handleSelect, onFocusedDateChange: setFocusedDate, min: min, max: max, shouldDisableDate: shouldDisableDate, unselectableDates: unselectableDates, firstDayOfWeek: firstDayOfWeek, locale: locale, dayNames: dayNames, monthNames: monthNames, nearbyMonthDays: nearbyMonthDays, color: color, size: size,
|
|
13707
|
+
// The popover panel takes `popoverId`, so the calendar inside it
|
|
13708
|
+
// takes one of its own. Inline there is no panel, and the calendar
|
|
13709
|
+
// keeps the id it released with.
|
|
13710
|
+
id: inline ? popoverId : `${popoverId}-cal`, autoFocusCell: open, labels: labels }, granularity));
|
|
13362
13711
|
if (inline) {
|
|
13363
13712
|
return (jsxRuntime.jsxs("div", { ...cleanRest, ref: containerRef, className: classNames(containerClass, bulmaHelperClasses, className), children: [calendar, name && (jsxRuntime.jsx("input", { type: "hidden", name: name, form: form, value: value ? toIsoValue(value, granularity) : '', required: required }))] }));
|
|
13364
13713
|
}
|
|
@@ -13386,29 +13735,57 @@ DateInputBase.displayName = 'DateInputBase';
|
|
|
13386
13735
|
* shouldDisableDate={d => d.getDay() === 0 || d.getDay() === 6}
|
|
13387
13736
|
* />
|
|
13388
13737
|
*/
|
|
13389
|
-
const DateInput = React.forwardRef((
|
|
13738
|
+
const DateInput = React.forwardRef((props, ref) => {
|
|
13739
|
+
const { label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'calendar', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps } = props;
|
|
13390
13740
|
const insideField = useInsideField();
|
|
13391
13741
|
const insideControl = useInsideControl();
|
|
13742
|
+
const ownField = rendersOwnField('DateInput', {
|
|
13743
|
+
insideField,
|
|
13744
|
+
insideControl,
|
|
13745
|
+
label,
|
|
13746
|
+
message,
|
|
13747
|
+
horizontal,
|
|
13748
|
+
fieldClassName,
|
|
13749
|
+
});
|
|
13750
|
+
// Inline mode renders a bare calendar with no input, so the Control's
|
|
13751
|
+
// icon-left container has nothing to anchor to, and it renders no
|
|
13752
|
+
// Control inside one or out. The left icon is checked as the caller
|
|
13753
|
+
// passed it: the default glyph is this component's own choice, not a
|
|
13754
|
+
// prop the caller set.
|
|
13755
|
+
const ownControl = rendersOwnControl('DateInput', {
|
|
13756
|
+
insideControl,
|
|
13757
|
+
inline: baseProps.inline,
|
|
13758
|
+
isLoading,
|
|
13759
|
+
iconLeft,
|
|
13760
|
+
iconLeftName: props.iconLeftName,
|
|
13761
|
+
iconLeftSize,
|
|
13762
|
+
iconRight,
|
|
13763
|
+
iconRightName,
|
|
13764
|
+
iconRightSize,
|
|
13765
|
+
hasIconsLeft,
|
|
13766
|
+
hasIconsRight,
|
|
13767
|
+
isExpanded,
|
|
13768
|
+
controlSize,
|
|
13769
|
+
controlClassName,
|
|
13770
|
+
});
|
|
13392
13771
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
13393
13772
|
label,
|
|
13394
13773
|
id: baseProps.id,
|
|
13395
13774
|
labelProps,
|
|
13396
13775
|
// Inline mode renders a bare calendar with no input to label.
|
|
13397
|
-
rendersLabel:
|
|
13776
|
+
rendersLabel: ownField && !baseProps.inline,
|
|
13398
13777
|
});
|
|
13399
13778
|
const helpClass = usePrefixedClassNames('help', {
|
|
13400
13779
|
[`is-${messageColor}`]: !!messageColor,
|
|
13401
13780
|
});
|
|
13402
|
-
// The
|
|
13403
|
-
//
|
|
13404
|
-
let content = (jsxRuntime.jsx(DateInputBase, { ref: ref, id: controlId, ...baseProps
|
|
13405
|
-
|
|
13406
|
-
// icon-left container has nothing to anchor to. Skip the Control wrap.
|
|
13407
|
-
if (!insideControl && !baseProps.inline) {
|
|
13781
|
+
// The base hides its launcher while the Control it sits in is loading,
|
|
13782
|
+
// whether that is the one rendered below or an enclosing one.
|
|
13783
|
+
let content = (jsxRuntime.jsx(DateInputBase, { ref: ref, id: controlId, ...baseProps }));
|
|
13784
|
+
if (ownControl) {
|
|
13408
13785
|
content = (jsxRuntime.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 }));
|
|
13409
13786
|
}
|
|
13410
13787
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
13411
|
-
if (
|
|
13788
|
+
if (ownField) {
|
|
13412
13789
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
13413
13790
|
}
|
|
13414
13791
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -14282,7 +14659,10 @@ const fromIsoTime = (s) => {
|
|
|
14282
14659
|
* @returns {JSX.Element}
|
|
14283
14660
|
*/
|
|
14284
14661
|
const TimeInputBase = React.forwardRef((props, ref) => {
|
|
14285
|
-
const { value: controlledValue, defaultValue, onChange, onOpen, onClose, min, max, disabled, readOnly, placeholder, format, parse, locale, inline = false, mobileNative = 'auto', editable = true, popover = true, openOnFocus = true, closeOnSelect = false, position = 'bottom-left', appendToBody = false, color, size, isRounded, hourFormat = '24', enableSeconds = false, incrementHours = 1, incrementMinutes = 1, incrementSeconds = 1, unselectableTimes, className, name, form, required, id, onFocus, onClick, onKeyDown, onBlur, iconLeftName: _iconLeftName, triggerIcon
|
|
14662
|
+
const { value: controlledValue, defaultValue, onChange, onOpen, onClose, min, max, disabled, readOnly, placeholder, format, parse, locale, inline = false, mobileNative = 'auto', editable = true, popover = true, openOnFocus = true, closeOnSelect = false, position = 'bottom-left', appendToBody = false, color, size, isRounded, hourFormat = '24', enableSeconds = false, incrementHours = 1, incrementMinutes = 1, incrementSeconds = 1, unselectableTimes, className, name, form, required, id, onFocus, onClick, onKeyDown, onBlur, iconLeftName: _iconLeftName, triggerIcon: triggerIconProp, triggerIconName = 'chevron-down', labels, audioTick = false, haptics = false, ...rest } = props;
|
|
14663
|
+
// The launcher gives way to the loading spinner of a Control this sits in.
|
|
14664
|
+
const controlLoading = useControlLoading();
|
|
14665
|
+
const triggerIcon = triggerIconProp ?? !controlLoading;
|
|
14286
14666
|
// Platform-appropriate feedback routing: when `haptics` is opted in and
|
|
14287
14667
|
// the runtime has no Vibration API (notably iOS Safari), enable the
|
|
14288
14668
|
// audio thunk so the user still gets *some* tactile cue. When vibrate
|
|
@@ -14359,6 +14739,8 @@ const TimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14359
14739
|
React.useEffect(() => {
|
|
14360
14740
|
setText(value ? formatTime(value, defaultFormat, locale) : '');
|
|
14361
14741
|
}, [value, defaultFormat, locale]);
|
|
14742
|
+
// A value's date before year 1 is out of range, as in the date pickers.
|
|
14743
|
+
const lowerBound = React.useMemo(() => floorMin(min), [min]);
|
|
14362
14744
|
const commitValue = React.useCallback((next) => {
|
|
14363
14745
|
if (!isControlled)
|
|
14364
14746
|
setInternalValue(next);
|
|
@@ -14367,10 +14749,10 @@ const TimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14367
14749
|
const handleSpinnerChange = React.useCallback((parts) => {
|
|
14368
14750
|
const base = value ?? new Date();
|
|
14369
14751
|
const next = setTimeOfDay(base, parts);
|
|
14370
|
-
if (!isWithin(next,
|
|
14752
|
+
if (!isWithin(next, lowerBound, max))
|
|
14371
14753
|
return;
|
|
14372
14754
|
commitValue(next);
|
|
14373
|
-
}, [value,
|
|
14755
|
+
}, [value, lowerBound, max, commitValue]);
|
|
14374
14756
|
const tryParse = React.useCallback((s) => {
|
|
14375
14757
|
const trimmed = s.trim();
|
|
14376
14758
|
if (!trimmed)
|
|
@@ -14402,7 +14784,7 @@ const TimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14402
14784
|
setText,
|
|
14403
14785
|
makeBaseDate,
|
|
14404
14786
|
locale,
|
|
14405
|
-
min,
|
|
14787
|
+
min: lowerBound,
|
|
14406
14788
|
max,
|
|
14407
14789
|
isBlocked: unselectableTimes,
|
|
14408
14790
|
disabled,
|
|
@@ -14481,37 +14863,69 @@ TimeInputBase.displayName = 'TimeInputBase';
|
|
|
14481
14863
|
* @example
|
|
14482
14864
|
* <TimeInput label="Slot" hourFormat="12" incrementMinutes={15} />
|
|
14483
14865
|
*/
|
|
14484
|
-
const TimeInput = React.forwardRef((
|
|
14866
|
+
const TimeInput = React.forwardRef((props, ref) => {
|
|
14867
|
+
const { label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'clock', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps } = props;
|
|
14485
14868
|
const insideField = useInsideField();
|
|
14486
14869
|
const insideControl = useInsideControl();
|
|
14870
|
+
const ownField = rendersOwnField('TimeInput', {
|
|
14871
|
+
insideField,
|
|
14872
|
+
insideControl,
|
|
14873
|
+
label,
|
|
14874
|
+
message,
|
|
14875
|
+
horizontal,
|
|
14876
|
+
fieldClassName,
|
|
14877
|
+
});
|
|
14878
|
+
// Inline mode renders a bare panel with no input, so the Control's
|
|
14879
|
+
// icon-left container has nothing to anchor to, and it renders no
|
|
14880
|
+
// Control inside one or out. The left icon is checked as the caller
|
|
14881
|
+
// passed it: the default glyph is this component's own choice, not a
|
|
14882
|
+
// prop the caller set.
|
|
14883
|
+
const ownControl = rendersOwnControl('TimeInput', {
|
|
14884
|
+
insideControl,
|
|
14885
|
+
inline: baseProps.inline,
|
|
14886
|
+
isLoading,
|
|
14887
|
+
iconLeft,
|
|
14888
|
+
iconLeftName: props.iconLeftName,
|
|
14889
|
+
iconLeftSize,
|
|
14890
|
+
iconRight,
|
|
14891
|
+
iconRightName,
|
|
14892
|
+
iconRightSize,
|
|
14893
|
+
hasIconsLeft,
|
|
14894
|
+
hasIconsRight,
|
|
14895
|
+
isExpanded,
|
|
14896
|
+
controlSize,
|
|
14897
|
+
controlClassName,
|
|
14898
|
+
});
|
|
14487
14899
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
14488
14900
|
label,
|
|
14489
14901
|
id: baseProps.id,
|
|
14490
14902
|
labelProps,
|
|
14491
14903
|
// Inline mode renders a bare panel with no input to label.
|
|
14492
|
-
rendersLabel:
|
|
14904
|
+
rendersLabel: ownField && !baseProps.inline,
|
|
14493
14905
|
});
|
|
14494
14906
|
const helpClass = usePrefixedClassNames('help', {
|
|
14495
14907
|
[`is-${messageColor}`]: !!messageColor,
|
|
14496
14908
|
});
|
|
14497
|
-
// The
|
|
14498
|
-
//
|
|
14499
|
-
let content = (jsxRuntime.jsx(TimeInputBase, { ref: ref, id: controlId, ...baseProps
|
|
14500
|
-
|
|
14501
|
-
// icon-left container has nothing to anchor to. Skip the Control wrap.
|
|
14502
|
-
if (!insideControl && !baseProps.inline) {
|
|
14909
|
+
// The base hides its launcher while the Control it sits in is loading,
|
|
14910
|
+
// whether that is the one rendered below or an enclosing one.
|
|
14911
|
+
let content = (jsxRuntime.jsx(TimeInputBase, { ref: ref, id: controlId, ...baseProps }));
|
|
14912
|
+
if (ownControl) {
|
|
14503
14913
|
content = (jsxRuntime.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 }));
|
|
14504
14914
|
}
|
|
14505
14915
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
14506
|
-
if (
|
|
14916
|
+
if (ownField) {
|
|
14507
14917
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
14508
14918
|
}
|
|
14509
14919
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
14510
14920
|
});
|
|
14511
14921
|
TimeInput.displayName = 'TimeInput';
|
|
14512
14922
|
|
|
14513
|
-
// The year is padded to four digits, as `datetime-local` requires.
|
|
14923
|
+
// The year is padded to four digits, as `datetime-local` requires. HTML has
|
|
14924
|
+
// no such shape for a year before 1, so a date then is empty, as that input
|
|
14925
|
+
// would make it.
|
|
14514
14926
|
const toIsoDateTime = (d, withSeconds) => {
|
|
14927
|
+
if (d.getFullYear() < FIRST_YEAR)
|
|
14928
|
+
return '';
|
|
14515
14929
|
const yyyy = String(d.getFullYear()).padStart(4, '0');
|
|
14516
14930
|
const mo = String(d.getMonth() + 1).padStart(2, '0');
|
|
14517
14931
|
const dd = String(d.getDate()).padStart(2, '0');
|
|
@@ -14544,7 +14958,10 @@ const fromIsoDateTime = (s) => {
|
|
|
14544
14958
|
const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
14545
14959
|
const { value: controlledValue, defaultValue, onChange, onOpen, onClose, min, max, disabled, readOnly, placeholder, format, parse, locale, inline = false, mobileNative = 'auto', editable = true, popover = true, openOnFocus = true, closeOnSelect = false, position = 'bottom-left', appendToBody = false, color, size, isRounded, shouldDisableDate, unselectableDates, firstDayOfWeek = 0, dayNames, monthNames, nearbyMonthDays = true, hourFormat = '24', enableSeconds = false, incrementHours = 1,
|
|
14546
14960
|
// Step between visible wheel values; 1 gives the iOS-style every-minute wheel.
|
|
14547
|
-
incrementMinutes = 1, incrementSeconds = 1, unselectableTimes, className, name, form, required, id, onFocus, onClick, onKeyDown, onBlur, iconLeftName: _iconLeftName, triggerIcon
|
|
14961
|
+
incrementMinutes = 1, incrementSeconds = 1, unselectableTimes, className, name, form, required, id, onFocus, onClick, onKeyDown, onBlur, iconLeftName: _iconLeftName, triggerIcon: triggerIconProp, triggerIconName = 'chevron-down', audioTick = false, haptics = false, labels, ...rest } = props;
|
|
14962
|
+
// The launcher gives way to the loading spinner of a Control this sits in.
|
|
14963
|
+
const controlLoading = useControlLoading();
|
|
14964
|
+
const triggerIcon = triggerIconProp ?? !controlLoading;
|
|
14548
14965
|
const t = mergeLabels(labels);
|
|
14549
14966
|
// Platform-appropriate feedback routing for the time wheel: on iOS (no
|
|
14550
14967
|
// navigator.vibrate) the audio thunk fills in; on Android the real haptic
|
|
@@ -14554,14 +14971,16 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14554
14971
|
const isControlled = controlledValue !== undefined;
|
|
14555
14972
|
const [internalValue, setInternalValue] = React.useState(defaultValue ?? null);
|
|
14556
14973
|
const value = isControlled ? (controlledValue ?? null) : internalValue;
|
|
14557
|
-
|
|
14974
|
+
// Nothing before year 1 is in range, as in HTML's date inputs.
|
|
14975
|
+
const lowerBound = React.useMemo(() => floorMin(min), [min]);
|
|
14976
|
+
const initialFocused = React.useMemo(() => clampDate(value ?? new Date(), lowerBound, max),
|
|
14558
14977
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
14559
14978
|
[]);
|
|
14560
14979
|
const [focusedDate, setFocusedDate] = React.useState(initialFocused);
|
|
14561
14980
|
// Re-clamp focusedDate when min/max change so the focused cell stays valid.
|
|
14562
14981
|
React.useEffect(() => {
|
|
14563
|
-
setFocusedDate(prev => clampDate(prev,
|
|
14564
|
-
}, [
|
|
14982
|
+
setFocusedDate(prev => clampDate(prev, lowerBound, max));
|
|
14983
|
+
}, [lowerBound, max]);
|
|
14565
14984
|
const defaultFormat = format ??
|
|
14566
14985
|
(hourFormat === '12'
|
|
14567
14986
|
? enableSeconds
|
|
@@ -14658,18 +15077,18 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14658
15077
|
minutes: value?.getMinutes() ?? 0,
|
|
14659
15078
|
seconds: enableSeconds ? (value?.getSeconds() ?? 0) : undefined,
|
|
14660
15079
|
});
|
|
14661
|
-
if (!isWithin(merged,
|
|
15080
|
+
if (!isWithin(merged, lowerBound, max))
|
|
14662
15081
|
return;
|
|
14663
15082
|
commitValue(merged);
|
|
14664
15083
|
setFocusedDate(d);
|
|
14665
|
-
}, [value, enableSeconds,
|
|
15084
|
+
}, [value, enableSeconds, lowerBound, max, commitValue]);
|
|
14666
15085
|
const handleTimeChange = React.useCallback((parts) => {
|
|
14667
15086
|
const base = value ?? focusedDate;
|
|
14668
15087
|
const next = setTimeOfDay(base, parts);
|
|
14669
|
-
if (!isWithin(next,
|
|
15088
|
+
if (!isWithin(next, lowerBound, max))
|
|
14670
15089
|
return;
|
|
14671
15090
|
commitValue(next);
|
|
14672
|
-
}, [value, focusedDate,
|
|
15091
|
+
}, [value, focusedDate, lowerBound, max, commitValue]);
|
|
14673
15092
|
const tryParse = React.useCallback((s) => {
|
|
14674
15093
|
const trimmed = s.trim();
|
|
14675
15094
|
if (!trimmed)
|
|
@@ -14704,7 +15123,7 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14704
15123
|
setText,
|
|
14705
15124
|
makeBaseDate,
|
|
14706
15125
|
locale,
|
|
14707
|
-
min,
|
|
15126
|
+
min: lowerBound,
|
|
14708
15127
|
max,
|
|
14709
15128
|
isBlocked,
|
|
14710
15129
|
disabled,
|
|
@@ -14737,7 +15156,7 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14737
15156
|
? fromIsoDateTime(e.target.value)
|
|
14738
15157
|
: null;
|
|
14739
15158
|
commitValue(parsed);
|
|
14740
|
-
}, min: min ? toIsoDateTime(
|
|
15159
|
+
}, min: min ? toIsoDateTime(lowerBound, enableSeconds) : undefined, max: max ? toIsoDateTime(max, enableSeconds) : undefined, disabled: disabled, readOnly: readOnly, placeholder: placeholder, name: name, form: form, required: required, id: id }));
|
|
14741
15160
|
}
|
|
14742
15161
|
const spinnerValue = {
|
|
14743
15162
|
hours: value?.getHours() ?? 0,
|
|
@@ -14785,29 +15204,57 @@ DateTimeInputBase.displayName = 'DateTimeInputBase';
|
|
|
14785
15204
|
* shouldDisableDate={d => d.getDay() === 0 || d.getDay() === 6}
|
|
14786
15205
|
* />
|
|
14787
15206
|
*/
|
|
14788
|
-
const DateTimeInput = React.forwardRef((
|
|
15207
|
+
const DateTimeInput = React.forwardRef((props, ref) => {
|
|
15208
|
+
const { label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'calendar-alt', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps } = props;
|
|
14789
15209
|
const insideField = useInsideField();
|
|
14790
15210
|
const insideControl = useInsideControl();
|
|
15211
|
+
const ownField = rendersOwnField('DateTimeInput', {
|
|
15212
|
+
insideField,
|
|
15213
|
+
insideControl,
|
|
15214
|
+
label,
|
|
15215
|
+
message,
|
|
15216
|
+
horizontal,
|
|
15217
|
+
fieldClassName,
|
|
15218
|
+
});
|
|
15219
|
+
// Inline mode renders a bare picker with no input, so the Control's
|
|
15220
|
+
// icon-left container has nothing to anchor to, and it renders no
|
|
15221
|
+
// Control inside one or out. The left icon is checked as the caller
|
|
15222
|
+
// passed it: the default glyph is this component's own choice, not a
|
|
15223
|
+
// prop the caller set.
|
|
15224
|
+
const ownControl = rendersOwnControl('DateTimeInput', {
|
|
15225
|
+
insideControl,
|
|
15226
|
+
inline: baseProps.inline,
|
|
15227
|
+
isLoading,
|
|
15228
|
+
iconLeft,
|
|
15229
|
+
iconLeftName: props.iconLeftName,
|
|
15230
|
+
iconLeftSize,
|
|
15231
|
+
iconRight,
|
|
15232
|
+
iconRightName,
|
|
15233
|
+
iconRightSize,
|
|
15234
|
+
hasIconsLeft,
|
|
15235
|
+
hasIconsRight,
|
|
15236
|
+
isExpanded,
|
|
15237
|
+
controlSize,
|
|
15238
|
+
controlClassName,
|
|
15239
|
+
});
|
|
14791
15240
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
14792
15241
|
label,
|
|
14793
15242
|
id: baseProps.id,
|
|
14794
15243
|
labelProps,
|
|
14795
15244
|
// Inline mode renders a bare picker with no input to label.
|
|
14796
|
-
rendersLabel:
|
|
15245
|
+
rendersLabel: ownField && !baseProps.inline,
|
|
14797
15246
|
});
|
|
14798
15247
|
const helpClass = usePrefixedClassNames('help', {
|
|
14799
15248
|
[`is-${messageColor}`]: !!messageColor,
|
|
14800
15249
|
});
|
|
14801
|
-
// The
|
|
14802
|
-
//
|
|
14803
|
-
let content = (jsxRuntime.jsx(DateTimeInputBase, { ref: ref, id: controlId, ...baseProps
|
|
14804
|
-
|
|
14805
|
-
// icon-left container has nothing to anchor to. Skip the Control wrap.
|
|
14806
|
-
if (!insideControl && !baseProps.inline) {
|
|
15250
|
+
// The base hides its launcher while the Control it sits in is loading,
|
|
15251
|
+
// whether that is the one rendered below or an enclosing one.
|
|
15252
|
+
let content = (jsxRuntime.jsx(DateTimeInputBase, { ref: ref, id: controlId, ...baseProps }));
|
|
15253
|
+
if (ownControl) {
|
|
14807
15254
|
content = (jsxRuntime.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 }));
|
|
14808
15255
|
}
|
|
14809
15256
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
14810
|
-
if (
|
|
15257
|
+
if (ownField) {
|
|
14811
15258
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
14812
15259
|
}
|
|
14813
15260
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|