@allxsmith/bestax-bulma 5.26.3 → 5.26.4
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 +385 -123
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +385 -123
- package/dist/index.esm.js.map +1 -1
- package/dist/types/form/Checkboxes.d.ts +7 -0
- package/dist/types/form/DateInput.d.ts +18 -3
- package/dist/types/form/DateInputBase.d.ts +17 -3
- package/dist/types/form/DateTimeInput.d.ts +18 -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 +39 -7
- package/dist/types/form/Input.d.ts +7 -2
- 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 +7 -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 +7 -2
- package/dist/types/form/TimeInput.d.ts +18 -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/Checkboxes.d.ts +7 -0
- package/dist/types-cjs/form/DateInput.d.ts +18 -3
- package/dist/types-cjs/form/DateInputBase.d.ts +17 -3
- package/dist/types-cjs/form/DateTimeInput.d.ts +18 -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 +39 -7
- package/dist/types-cjs/form/Input.d.ts +7 -2
- 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 +7 -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 +7 -2
- package/dist/types-cjs/form/TimeInput.d.ts +18 -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/_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,56 @@ 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
|
+
};
|
|
8740
8824
|
const FieldLabelIdContext = React.createContext(undefined);
|
|
8741
8825
|
/**
|
|
8742
8826
|
* The id a labeled Field wants its single composed control to adopt (#495).
|
|
@@ -8867,6 +8951,18 @@ const Checkbox = React.forwardRef(({ color, size, className, children, textColor
|
|
|
8867
8951
|
});
|
|
8868
8952
|
Checkbox.displayName = 'Checkbox';
|
|
8869
8953
|
|
|
8954
|
+
// Whether the nearest enclosing `Control` shows its `isLoading` spinner. The
|
|
8955
|
+
// picker bases read it so their right-side launcher gives way to that
|
|
8956
|
+
// spinner, which sits at the same edge, whether the `Control` is the one a
|
|
8957
|
+
// convenience input renders or one the caller wrapped around the input.
|
|
8958
|
+
// Kept apart from `FormContext`, whose contexts only report whether a `Field`
|
|
8959
|
+
// or `Control` is present. Internal; not part of the public API.
|
|
8960
|
+
const ControlLoadingContext = React.createContext(false);
|
|
8961
|
+
/** Provider for the loading state, used internally by `Control`. */
|
|
8962
|
+
const ControlLoadingProvider = ControlLoadingContext.Provider;
|
|
8963
|
+
/** Whether the nearest enclosing `Control` is loading; `false` outside one. */
|
|
8964
|
+
const useControlLoading = () => React.useContext(ControlLoadingContext);
|
|
8965
|
+
|
|
8870
8966
|
const allowedColors = [...validColors, 'inherit', 'current'];
|
|
8871
8967
|
/**
|
|
8872
8968
|
* 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 +9022,7 @@ const Control = React.forwardRef(({ as = 'div', hasIconsLeft, hasIconsRight, isL
|
|
|
8926
9022
|
});
|
|
8927
9023
|
const controlClass = classNames(mainClass, bulmaHelperClasses, className);
|
|
8928
9024
|
// --- 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 &&
|
|
9025
|
+
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
9026
|
(isIconProps(leftIconValue) ? (jsxRuntime.jsx(Icon, { ...leftIconValue, className: prefixedClassNames(classPrefix, 'is-left') })) : (jsxRuntime.jsx(Icon, { className: prefixedClassNames(classPrefix, 'is-left'), children: leftIconValue }))), rightIconValue &&
|
|
8931
9027
|
(isIconProps(rightIconValue) ? (jsxRuntime.jsx(Icon, { ...rightIconValue, className: prefixedClassNames(classPrefix, 'is-right') })) : (jsxRuntime.jsx(Icon, { className: prefixedClassNames(classPrefix, 'is-right'), children: rightIconValue })))] }) }));
|
|
8932
9028
|
});
|
|
@@ -9151,10 +9247,18 @@ function useAutoLabelledBy({ label, labelProps, rendersLabel, }) {
|
|
|
9151
9247
|
const CheckboxesComponent = ({ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, name, value, defaultValue, onChange, children, className, ...props }) => {
|
|
9152
9248
|
const insideField = useInsideField();
|
|
9153
9249
|
const insideControl = useInsideControl();
|
|
9250
|
+
const ownField = rendersOwnField('Checkboxes', {
|
|
9251
|
+
insideField,
|
|
9252
|
+
insideControl,
|
|
9253
|
+
label,
|
|
9254
|
+
message,
|
|
9255
|
+
horizontal,
|
|
9256
|
+
fieldClassName,
|
|
9257
|
+
});
|
|
9154
9258
|
const { ariaLabelledBy, fieldLabelProps } = useAutoLabelledBy({
|
|
9155
9259
|
label,
|
|
9156
9260
|
labelProps,
|
|
9157
|
-
rendersLabel:
|
|
9261
|
+
rendersLabel: ownField,
|
|
9158
9262
|
});
|
|
9159
9263
|
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9160
9264
|
...props,
|
|
@@ -9183,7 +9287,7 @@ const CheckboxesComponent = ({ label, labelSize, labelProps, horizontal, message
|
|
|
9183
9287
|
if (!insideControl) {
|
|
9184
9288
|
content = jsxRuntime.jsx(Control, { children: content });
|
|
9185
9289
|
}
|
|
9186
|
-
if (
|
|
9290
|
+
if (ownField) {
|
|
9187
9291
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
9188
9292
|
}
|
|
9189
9293
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -9202,11 +9306,20 @@ const File = React.forwardRef(({
|
|
|
9202
9306
|
// Field props
|
|
9203
9307
|
label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, color, size, isBoxed, isFullwidth, isFullWidth, isRight, isCentered, hasName, buttonLabel, iconLeft, iconRight, className, inputClassName, fileName, ...props }, ref) => {
|
|
9204
9308
|
const insideField = useInsideField();
|
|
9309
|
+
const insideControl = useInsideControl();
|
|
9310
|
+
const ownField = rendersOwnField('File', {
|
|
9311
|
+
insideField,
|
|
9312
|
+
insideControl,
|
|
9313
|
+
label,
|
|
9314
|
+
message,
|
|
9315
|
+
horizontal,
|
|
9316
|
+
fieldClassName,
|
|
9317
|
+
});
|
|
9205
9318
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
9206
9319
|
label,
|
|
9207
9320
|
id: props.id,
|
|
9208
9321
|
labelProps,
|
|
9209
|
-
rendersLabel:
|
|
9322
|
+
rendersLabel: ownField,
|
|
9210
9323
|
});
|
|
9211
9324
|
const { classPrefix } = useConfig();
|
|
9212
9325
|
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
@@ -9238,7 +9351,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
9238
9351
|
});
|
|
9239
9352
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
9240
9353
|
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 (
|
|
9354
|
+
if (ownField) {
|
|
9242
9355
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [fileElement, messageEl] }));
|
|
9243
9356
|
}
|
|
9244
9357
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [fileElement, messageEl] }));
|
|
@@ -9371,10 +9484,18 @@ Radio.displayName = 'Radio';
|
|
|
9371
9484
|
const RadiosComponent = ({ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, name, value, defaultValue, onChange, children, className, ...props }) => {
|
|
9372
9485
|
const insideField = useInsideField();
|
|
9373
9486
|
const insideControl = useInsideControl();
|
|
9487
|
+
const ownField = rendersOwnField('Radios', {
|
|
9488
|
+
insideField,
|
|
9489
|
+
insideControl,
|
|
9490
|
+
label,
|
|
9491
|
+
message,
|
|
9492
|
+
horizontal,
|
|
9493
|
+
fieldClassName,
|
|
9494
|
+
});
|
|
9374
9495
|
const { ariaLabelledBy, fieldLabelProps } = useAutoLabelledBy({
|
|
9375
9496
|
label,
|
|
9376
9497
|
labelProps,
|
|
9377
|
-
rendersLabel:
|
|
9498
|
+
rendersLabel: ownField,
|
|
9378
9499
|
});
|
|
9379
9500
|
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9380
9501
|
...props,
|
|
@@ -9406,7 +9527,7 @@ const RadiosComponent = ({ label, labelSize, labelProps, horizontal, message, me
|
|
|
9406
9527
|
if (!insideControl) {
|
|
9407
9528
|
content = jsxRuntime.jsx(Control, { children: content });
|
|
9408
9529
|
}
|
|
9409
|
-
if (
|
|
9530
|
+
if (ownField) {
|
|
9410
9531
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
9411
9532
|
}
|
|
9412
9533
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -9586,6 +9707,14 @@ const Slider = React.forwardRef((props, ref) => {
|
|
|
9586
9707
|
range: _range, value: _value, defaultValue: _defaultValue, onChange: _onChange, ariaLabel: _ariaLabel, minDistance: _minDistance, nameLow: _nameLow, nameHigh: _nameHigh, ...restProps } = props;
|
|
9587
9708
|
const insideField = useInsideField();
|
|
9588
9709
|
const insideControl = useInsideControl();
|
|
9710
|
+
const ownField = rendersOwnField('Slider', {
|
|
9711
|
+
insideField,
|
|
9712
|
+
insideControl,
|
|
9713
|
+
label,
|
|
9714
|
+
message,
|
|
9715
|
+
horizontal,
|
|
9716
|
+
fieldClassName,
|
|
9717
|
+
});
|
|
9589
9718
|
const { bulmaHelperClasses, rest } = useBulmaClasses(restProps);
|
|
9590
9719
|
// In range mode the low thumb carries `rest` (and any user id), so the
|
|
9591
9720
|
// label targets it; each thumb keeps its own aria-label for AT.
|
|
@@ -9593,7 +9722,7 @@ const Slider = React.forwardRef((props, ref) => {
|
|
|
9593
9722
|
label,
|
|
9594
9723
|
id: restProps.id,
|
|
9595
9724
|
labelProps,
|
|
9596
|
-
rendersLabel:
|
|
9725
|
+
rendersLabel: ownField,
|
|
9597
9726
|
});
|
|
9598
9727
|
// Resolve tooltip mode: explicit tooltip prop takes precedence, else showOutput maps to 'auto'
|
|
9599
9728
|
const tooltipMode = tooltip ?? (showOutput ? 'auto' : 'hidden');
|
|
@@ -9932,7 +10061,7 @@ const Slider = React.forwardRef((props, ref) => {
|
|
|
9932
10061
|
if (!insideControl) {
|
|
9933
10062
|
content = jsxRuntime.jsx(Control, { children: content });
|
|
9934
10063
|
}
|
|
9935
|
-
if (
|
|
10064
|
+
if (ownField) {
|
|
9936
10065
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
9937
10066
|
}
|
|
9938
10067
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -10162,10 +10291,18 @@ function getFillPercent(iconIndex, value) {
|
|
|
10162
10291
|
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
10292
|
const insideField = useInsideField();
|
|
10164
10293
|
const insideControl = useInsideControl();
|
|
10294
|
+
const ownField = rendersOwnField('Rate', {
|
|
10295
|
+
insideField,
|
|
10296
|
+
insideControl,
|
|
10297
|
+
label,
|
|
10298
|
+
message,
|
|
10299
|
+
horizontal,
|
|
10300
|
+
fieldClassName,
|
|
10301
|
+
});
|
|
10165
10302
|
const { ariaLabelledBy, fieldLabelProps } = useAutoLabelledBy({
|
|
10166
10303
|
label,
|
|
10167
10304
|
labelProps,
|
|
10168
|
-
rendersLabel:
|
|
10305
|
+
rendersLabel: ownField,
|
|
10169
10306
|
});
|
|
10170
10307
|
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
10171
10308
|
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
@@ -10368,7 +10505,7 @@ const Rate = React.forwardRef(({ label, labelSize, labelProps, horizontal, messa
|
|
|
10368
10505
|
if (!insideControl) {
|
|
10369
10506
|
content = jsxRuntime.jsx(Control, { children: content });
|
|
10370
10507
|
}
|
|
10371
|
-
if (
|
|
10508
|
+
if (ownField) {
|
|
10372
10509
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
10373
10510
|
}
|
|
10374
10511
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -10649,6 +10786,15 @@ const Taginput = React.forwardRef(({
|
|
|
10649
10786
|
// Field props
|
|
10650
10787
|
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
10788
|
const insideField = useInsideField();
|
|
10789
|
+
const insideControl = useInsideControl();
|
|
10790
|
+
const ownField = rendersOwnField('Taginput', {
|
|
10791
|
+
insideField,
|
|
10792
|
+
insideControl,
|
|
10793
|
+
label,
|
|
10794
|
+
message,
|
|
10795
|
+
horizontal,
|
|
10796
|
+
fieldClassName,
|
|
10797
|
+
});
|
|
10652
10798
|
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
10653
10799
|
const { classPrefix } = useConfig();
|
|
10654
10800
|
const defaultIconLibrary = useIconLibrary();
|
|
@@ -10670,7 +10816,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
10670
10816
|
label,
|
|
10671
10817
|
id,
|
|
10672
10818
|
labelProps,
|
|
10673
|
-
rendersLabel:
|
|
10819
|
+
rendersLabel: ownField && !isMaxReached,
|
|
10674
10820
|
});
|
|
10675
10821
|
// Get display value from tag
|
|
10676
10822
|
const getDisplayValue = (tag) => {
|
|
@@ -10942,7 +11088,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
10942
11088
|
'is-active': index === highlightedIndex,
|
|
10943
11089
|
}), 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
11090
|
tags.map((tag, i) => (jsxRuntime.jsx("input", { type: "hidden", name: name, value: getDisplayValue(tag), form: form }, `tag-input-${i}`)))] }));
|
|
10945
|
-
if (
|
|
11091
|
+
if (ownField) {
|
|
10946
11092
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [taginputElement, messageEl] }));
|
|
10947
11093
|
}
|
|
10948
11094
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [taginputElement, messageEl] }));
|
|
@@ -11095,11 +11241,19 @@ fieldClassName, controlClassName,
|
|
|
11095
11241
|
...inputProps }, ref) => {
|
|
11096
11242
|
const insideField = useInsideField();
|
|
11097
11243
|
const insideControl = useInsideControl();
|
|
11244
|
+
const ownField = rendersOwnField('Input', {
|
|
11245
|
+
insideField,
|
|
11246
|
+
insideControl,
|
|
11247
|
+
label,
|
|
11248
|
+
message,
|
|
11249
|
+
horizontal,
|
|
11250
|
+
fieldClassName,
|
|
11251
|
+
});
|
|
11098
11252
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
11099
11253
|
label,
|
|
11100
11254
|
id: inputProps.id,
|
|
11101
11255
|
labelProps,
|
|
11102
|
-
rendersLabel:
|
|
11256
|
+
rendersLabel: ownField,
|
|
11103
11257
|
});
|
|
11104
11258
|
const helpClass = usePrefixedClassNames('help', {
|
|
11105
11259
|
[`is-${messageColor}`]: !!messageColor,
|
|
@@ -11109,7 +11263,7 @@ fieldClassName, controlClassName,
|
|
|
11109
11263
|
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
11264
|
}
|
|
11111
11265
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
11112
|
-
if (
|
|
11266
|
+
if (ownField) {
|
|
11113
11267
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
11114
11268
|
}
|
|
11115
11269
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -11145,11 +11299,19 @@ fieldClassName, controlClassName,
|
|
|
11145
11299
|
...selectProps }, ref) => {
|
|
11146
11300
|
const insideField = useInsideField();
|
|
11147
11301
|
const insideControl = useInsideControl();
|
|
11302
|
+
const ownField = rendersOwnField('Select', {
|
|
11303
|
+
insideField,
|
|
11304
|
+
insideControl,
|
|
11305
|
+
label,
|
|
11306
|
+
message,
|
|
11307
|
+
horizontal,
|
|
11308
|
+
fieldClassName,
|
|
11309
|
+
});
|
|
11148
11310
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
11149
11311
|
label,
|
|
11150
11312
|
id: selectProps.id,
|
|
11151
11313
|
labelProps,
|
|
11152
|
-
rendersLabel:
|
|
11314
|
+
rendersLabel: ownField,
|
|
11153
11315
|
});
|
|
11154
11316
|
const helpClass = usePrefixedClassNames('help', {
|
|
11155
11317
|
[`is-${messageColor}`]: !!messageColor,
|
|
@@ -11159,7 +11321,7 @@ fieldClassName, controlClassName,
|
|
|
11159
11321
|
content = (jsxRuntime.jsx(Control, { iconLeft: iconLeft, iconLeftName: iconLeftName, iconLeftSize: iconLeftSize, hasIconsLeft: hasIconsLeft, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
|
|
11160
11322
|
}
|
|
11161
11323
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
11162
|
-
if (
|
|
11324
|
+
if (ownField) {
|
|
11163
11325
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
11164
11326
|
}
|
|
11165
11327
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -11196,11 +11358,19 @@ fieldClassName, controlClassName,
|
|
|
11196
11358
|
...textAreaProps }, ref) => {
|
|
11197
11359
|
const insideField = useInsideField();
|
|
11198
11360
|
const insideControl = useInsideControl();
|
|
11361
|
+
const ownField = rendersOwnField('TextArea', {
|
|
11362
|
+
insideField,
|
|
11363
|
+
insideControl,
|
|
11364
|
+
label,
|
|
11365
|
+
message,
|
|
11366
|
+
horizontal,
|
|
11367
|
+
fieldClassName,
|
|
11368
|
+
});
|
|
11199
11369
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
11200
11370
|
label,
|
|
11201
11371
|
id: textAreaProps.id,
|
|
11202
11372
|
labelProps,
|
|
11203
|
-
rendersLabel:
|
|
11373
|
+
rendersLabel: ownField,
|
|
11204
11374
|
});
|
|
11205
11375
|
const helpClass = usePrefixedClassNames('help', {
|
|
11206
11376
|
[`is-${messageColor}`]: !!messageColor,
|
|
@@ -11210,7 +11380,7 @@ fieldClassName, controlClassName,
|
|
|
11210
11380
|
content = (jsxRuntime.jsx(Control, { isLoading: controlIsLoading, size: controlSize, className: controlClassName, children: content }));
|
|
11211
11381
|
}
|
|
11212
11382
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
11213
|
-
if (
|
|
11383
|
+
if (ownField) {
|
|
11214
11384
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
11215
11385
|
}
|
|
11216
11386
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -11297,6 +11467,21 @@ function makeDate(year, month = 0, day = 1) {
|
|
|
11297
11467
|
d.setFullYear(year, month, day);
|
|
11298
11468
|
return d;
|
|
11299
11469
|
}
|
|
11470
|
+
/**
|
|
11471
|
+
* The earliest year the pickers accept. HTML's date, month and datetime-local
|
|
11472
|
+
* inputs hold no year below 1, so the calendar, typing and parsing stop there
|
|
11473
|
+
* too.
|
|
11474
|
+
*/
|
|
11475
|
+
const FIRST_YEAR = 1;
|
|
11476
|
+
/**
|
|
11477
|
+
* The lower bound a picker applies: `min`, raised to local midnight on
|
|
11478
|
+
* 1 January of {@link FIRST_YEAR} when it is earlier or absent. A `min` in
|
|
11479
|
+
* range comes back as the same object.
|
|
11480
|
+
*/
|
|
11481
|
+
function floorMin(min) {
|
|
11482
|
+
const floor = makeDate(FIRST_YEAR);
|
|
11483
|
+
return min && min.getTime() >= floor.getTime() ? min : floor;
|
|
11484
|
+
}
|
|
11300
11485
|
/**
|
|
11301
11486
|
* First instant of the day, month or year containing `d`. Works by setters on
|
|
11302
11487
|
* a copy rather than the `Date(y, m, d)` constructor, which reads years 0–99
|
|
@@ -11390,12 +11575,14 @@ function setTimeOfDay(d, parts) {
|
|
|
11390
11575
|
r.setSeconds(parts.seconds);
|
|
11391
11576
|
return r;
|
|
11392
11577
|
}
|
|
11578
|
+
/**
|
|
11579
|
+
* A copy of `d` moved into `[min, max]`. When `min` is after `max` nothing is
|
|
11580
|
+
* in range and `min` wins, so clamping twice gives the same date and a
|
|
11581
|
+
* picker's focus stays off the years before 1 under a `max` before then.
|
|
11582
|
+
*/
|
|
11393
11583
|
function clampDate(d, min, max) {
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
if (max && isAfter(d, max))
|
|
11397
|
-
return new Date(max);
|
|
11398
|
-
return new Date(d);
|
|
11584
|
+
const capped = max && isAfter(d, max) ? max : d;
|
|
11585
|
+
return new Date(min && isBefore(capped, min) ? min : capped);
|
|
11399
11586
|
}
|
|
11400
11587
|
/**
|
|
11401
11588
|
* Snap a Date's time-of-day to the nearest grid defined by the given
|
|
@@ -11468,7 +11655,9 @@ function formatToken(d, token) {
|
|
|
11468
11655
|
case 'YYYY':
|
|
11469
11656
|
// Zero-pad to a fixed 4 chars so the rendered width matches the
|
|
11470
11657
|
// segment-map width during digit-by-digit keyboard entry (and to match
|
|
11471
|
-
// ISO 8601)
|
|
11658
|
+
// ISO 8601), so year 19 shows as `0019`. Years >9999 keep their natural
|
|
11659
|
+
// width. The pickers commit no year below 1, so a minus sign reaches
|
|
11660
|
+
// this only from a `value` passed in.
|
|
11472
11661
|
return pad(d.getFullYear(), 4);
|
|
11473
11662
|
case 'YY':
|
|
11474
11663
|
return pad(d.getFullYear() % 100);
|
|
@@ -11742,9 +11931,10 @@ function getMonthNames(locale, length = 'long') {
|
|
|
11742
11931
|
|
|
11743
11932
|
/**
|
|
11744
11933
|
* 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.
|
|
11934
|
+
* year list is on show, for a popover to put focus on as it opens. It runs
|
|
11935
|
+
* against the whole panel, so it matches only inside the grid or the list.
|
|
11746
11936
|
*/
|
|
11747
|
-
const CALENDAR_FOCUSED_CELL = '[data-focused="true"], [data-focused-year="true"]';
|
|
11937
|
+
const CALENDAR_FOCUSED_CELL = '[role="grid"] [data-focused="true"], [role="listbox"] [data-focused-year="true"]';
|
|
11748
11938
|
const BASE_VIEW = {
|
|
11749
11939
|
day: 'days',
|
|
11750
11940
|
month: 'months',
|
|
@@ -11841,7 +12031,7 @@ function yearStep(key, focusedYear, years) {
|
|
|
11841
12031
|
return null;
|
|
11842
12032
|
}
|
|
11843
12033
|
}
|
|
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', }) => {
|
|
12034
|
+
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
12035
|
const gridRef = React.useRef(null);
|
|
11846
12036
|
const monthGridRef = React.useRef(null);
|
|
11847
12037
|
const yearGridRef = React.useRef(null);
|
|
@@ -11851,6 +12041,8 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
11851
12041
|
const isDayGranularity = granularity === 'day';
|
|
11852
12042
|
const isYearGranularity = granularity === 'year';
|
|
11853
12043
|
const [view, setView] = React.useState(baseView);
|
|
12044
|
+
// Nothing before year 1 can be picked or reached, as in HTML's date inputs.
|
|
12045
|
+
const min = React.useMemo(() => floorMin(minProp), [minProp]);
|
|
11854
12046
|
const computedDayNames = React.useMemo(() => {
|
|
11855
12047
|
if (dayNames && dayNames.length === 7)
|
|
11856
12048
|
return dayNames;
|
|
@@ -11921,7 +12113,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
11921
12113
|
const moveFocus = React.useCallback((next, direction) => {
|
|
11922
12114
|
let candidate = next;
|
|
11923
12115
|
for (let i = 0; i < 366; i++) {
|
|
11924
|
-
if (
|
|
12116
|
+
if (candidate.getTime() < startOfDay(min).getTime())
|
|
11925
12117
|
return;
|
|
11926
12118
|
if (max && candidate.getTime() > startOfDay(max).getTime())
|
|
11927
12119
|
return;
|
|
@@ -11987,7 +12179,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
11987
12179
|
let candidate = next;
|
|
11988
12180
|
for (let i = 0; i < 120; i++) {
|
|
11989
12181
|
const month = startOfMonth(candidate).getTime();
|
|
11990
|
-
if (
|
|
12182
|
+
if (month < startOfMonth(min).getTime())
|
|
11991
12183
|
return;
|
|
11992
12184
|
if (max && month > startOfMonth(max).getTime())
|
|
11993
12185
|
return;
|
|
@@ -12035,25 +12227,22 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12035
12227
|
const today = new Date();
|
|
12036
12228
|
const todayYear = today.getFullYear();
|
|
12037
12229
|
// 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
|
-
|
|
12230
|
+
// time over the month grid, kept inside min/max. A step is off once the
|
|
12231
|
+
// month or year on show holds the bound or lies past it, as picking a year
|
|
12232
|
+
// from the list can leave it.
|
|
12041
12233
|
const prevAnchor = isDayGranularity
|
|
12042
|
-
?
|
|
12234
|
+
? addMonths(focusedDate, -1)
|
|
12043
12235
|
: clampDate(addYears(focusedDate, -1), min, max);
|
|
12044
12236
|
const nextAnchor = isDayGranularity
|
|
12045
|
-
?
|
|
12237
|
+
? addMonths(focusedDate, 1)
|
|
12046
12238
|
: clampDate(addYears(focusedDate, 1), min, max);
|
|
12239
|
+
const focusedMonth = startOfMonth(focusedDate).getTime();
|
|
12047
12240
|
const prevDisabled = isDayGranularity
|
|
12048
|
-
?
|
|
12049
|
-
|
|
12050
|
-
isSameMonth(focusedDate, min))
|
|
12051
|
-
: !!(min && focusedYear <= min.getFullYear());
|
|
12241
|
+
? focusedMonth <= startOfMonth(min).getTime()
|
|
12242
|
+
: focusedYear <= min.getFullYear();
|
|
12052
12243
|
const nextDisabled = isDayGranularity
|
|
12053
|
-
? !!
|
|
12054
|
-
|
|
12055
|
-
isSameMonth(focusedDate, max))
|
|
12056
|
-
: !!(max && focusedYear >= max.getFullYear());
|
|
12244
|
+
? !!max && focusedMonth >= startOfMonth(max).getTime()
|
|
12245
|
+
: !!max && focusedYear >= max.getFullYear();
|
|
12057
12246
|
const labelId = id ? `${id}-label` : undefined;
|
|
12058
12247
|
const monthLabel = isDayGranularity
|
|
12059
12248
|
? `${computedMonthNames[focusedDate.getMonth()]} ${focusedYear}`
|
|
@@ -12064,7 +12253,9 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12064
12253
|
const [mountYear] = React.useState(focusedYear);
|
|
12065
12254
|
const windowYear = isYearGranularity ? mountYear : focusedYear;
|
|
12066
12255
|
const yearList = React.useMemo(() => {
|
|
12067
|
-
const minYear =
|
|
12256
|
+
const minYear = minProp
|
|
12257
|
+
? min.getFullYear()
|
|
12258
|
+
: Math.max(FIRST_YEAR, windowYear - 100);
|
|
12068
12259
|
const maxYear = max ? max.getFullYear() : windowYear + 100;
|
|
12069
12260
|
const [lo, hi] = yearsRange ?? [minYear, maxYear];
|
|
12070
12261
|
const start = Math.max(lo, minYear);
|
|
@@ -12073,20 +12264,25 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12073
12264
|
for (let y = start; y <= end; y++)
|
|
12074
12265
|
out.push(y);
|
|
12075
12266
|
return out;
|
|
12076
|
-
}, [windowYear, min, max, yearsRange]);
|
|
12267
|
+
}, [windowYear, minProp, min, max, yearsRange]);
|
|
12077
12268
|
// Only the selection surface disables years; as navigation every listed
|
|
12078
12269
|
// year can be visited.
|
|
12079
12270
|
const disabledYears = React.useMemo(() => new Set(isYearGranularity ? yearList.filter(y => isYearUnselectable(y)) : []), [isYearGranularity, yearList, isYearUnselectable]);
|
|
12080
12271
|
// The year list's one tab stop, after the month grid's: when the focused
|
|
12081
12272
|
// 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
|
|
12273
|
+
// and focusing it makes it the focused year. As the selection surface a
|
|
12274
|
+
// year outside a non-empty list starts from the end it fell past, so the
|
|
12275
|
+
// list keeps a stop. As navigation the stop marks the current year, so a
|
|
12276
|
+
// year outside the list marks no option.
|
|
12084
12277
|
const tabStopYear = React.useMemo(() => {
|
|
12085
|
-
|
|
12086
|
-
if (i < 0)
|
|
12087
|
-
|
|
12278
|
+
let i = yearList.indexOf(focusedYear);
|
|
12279
|
+
if (i < 0) {
|
|
12280
|
+
if (!isYearGranularity || yearList.length === 0)
|
|
12281
|
+
return focusedYear;
|
|
12282
|
+
i = focusedYear < yearList[0] ? 0 : yearList.length - 1;
|
|
12283
|
+
}
|
|
12088
12284
|
return yearList[nearestEnabled(i, yearList.map(y => disabledYears.has(y)))];
|
|
12089
|
-
}, [yearList, focusedYear, disabledYears]);
|
|
12285
|
+
}, [yearList, focusedYear, disabledYears, isYearGranularity]);
|
|
12090
12286
|
// Which of the focused year's months have no selectable day.
|
|
12091
12287
|
const disabledMonths = React.useMemo(() => granularity === 'month'
|
|
12092
12288
|
? Array.from({ length: 12 }, (_, m) => isMonthUnselectable(makeDate(focusedYear, m)))
|
|
@@ -12293,11 +12489,10 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
|
|
|
12293
12489
|
active: isOpen,
|
|
12294
12490
|
position,
|
|
12295
12491
|
fixed: appendToBody,
|
|
12296
|
-
//
|
|
12297
|
-
//
|
|
12298
|
-
// the panel.
|
|
12299
|
-
|
|
12300
|
-
offset: appendToBody ? 0 : undefined,
|
|
12492
|
+
// The gap comes from `--bulma-picker-popover-offset` as a margin, in place
|
|
12493
|
+
// or portaled, so the coordinates leave it out and `auto` reads it off
|
|
12494
|
+
// the panel.
|
|
12495
|
+
offset: 0,
|
|
12301
12496
|
styledGap: marginGap,
|
|
12302
12497
|
});
|
|
12303
12498
|
React.useEffect(() => {
|
|
@@ -12743,33 +12938,35 @@ function useSegmentedEntry(params) {
|
|
|
12743
12938
|
// within the same event tick.
|
|
12744
12939
|
const typedDigitsRef = React.useRef('');
|
|
12745
12940
|
// The text focus seeded an empty picker with, until the user edits it or
|
|
12746
|
-
// leaves.
|
|
12941
|
+
// leaves.
|
|
12747
12942
|
const seedRef = React.useRef(null);
|
|
12748
|
-
// Whether the
|
|
12749
|
-
//
|
|
12750
|
-
|
|
12943
|
+
// Whether the text is still what the picker put there, so nothing in it was
|
|
12944
|
+
// typed: the value's display, or an empty picker's seed until an edit drops
|
|
12945
|
+
// it. Against a value it compares text alone, so typing that ends back on
|
|
12946
|
+
// the display counts as untouched.
|
|
12947
|
+
const isUntouched = React.useCallback((s) => value
|
|
12948
|
+
? s === formatFn(value, format, locale)
|
|
12949
|
+
: seedRef.current !== null && s === seedRef.current, [value, formatFn, format, locale]);
|
|
12751
12950
|
// A seed the popover never turned into a value closes with it, wherever
|
|
12752
12951
|
// focus goes, so a dismissed empty field stays empty.
|
|
12753
12952
|
//
|
|
12754
12953
|
// As the popover closes, its focus trap hands focus back to the input. That
|
|
12755
12954
|
// 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.
|
|
12955
|
+
// `openOnFocus`, or seed an empty picker. `closingRef` is set in the layout
|
|
12956
|
+
// phase of the commit that closes it and cleared by that commit's effects,
|
|
12957
|
+
// and React runs a commit's effect cleanups, the trap's among them, before
|
|
12958
|
+
// any of its effects, so a focus in between is the one handed back.
|
|
12761
12959
|
const closingRef = React.useRef(false);
|
|
12762
12960
|
const wasOpenRef = React.useRef(isOpen);
|
|
12763
12961
|
React.useLayoutEffect(() => {
|
|
12764
12962
|
if (wasOpenRef.current && !isOpen) {
|
|
12765
12963
|
closingRef.current = true;
|
|
12766
|
-
if (!value &&
|
|
12964
|
+
if (!value && isUntouched(text))
|
|
12767
12965
|
setText('');
|
|
12768
|
-
}
|
|
12769
12966
|
seedRef.current = null;
|
|
12770
12967
|
}
|
|
12771
12968
|
wasOpenRef.current = isOpen;
|
|
12772
|
-
}, [isOpen, value, text, setText]);
|
|
12969
|
+
}, [isOpen, value, isUntouched, text, setText]);
|
|
12773
12970
|
React.useEffect(() => {
|
|
12774
12971
|
closingRef.current = false;
|
|
12775
12972
|
}, [isOpen]);
|
|
@@ -12847,13 +13044,18 @@ function useSegmentedEntry(params) {
|
|
|
12847
13044
|
}
|
|
12848
13045
|
setActiveSegmentIdx(null);
|
|
12849
13046
|
typedDigitsRef.current = '';
|
|
12850
|
-
|
|
12851
|
-
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
//
|
|
12855
|
-
//
|
|
12856
|
-
|
|
13047
|
+
// Leaving commits only what was typed. Re-parsing the value's display
|
|
13048
|
+
// could drop what the format leaves out, such as seconds, and a seed
|
|
13049
|
+
// nobody typed over is not the user's date.
|
|
13050
|
+
const untouched = isUntouched(text);
|
|
13051
|
+
// An open popover keeps an unused seed until it closes, so one portaled
|
|
13052
|
+
// outside the picker, whose focus reads as leaving, keeps it as one
|
|
13053
|
+
// inside does.
|
|
13054
|
+
if (!isOpen)
|
|
13055
|
+
seedRef.current = null;
|
|
13056
|
+
if (untouched) {
|
|
13057
|
+
if (!value && !isOpen)
|
|
13058
|
+
setText('');
|
|
12857
13059
|
onBlur?.(e);
|
|
12858
13060
|
return;
|
|
12859
13061
|
}
|
|
@@ -12867,6 +13069,8 @@ function useSegmentedEntry(params) {
|
|
|
12867
13069
|
onBlur?.(e);
|
|
12868
13070
|
}, [
|
|
12869
13071
|
containerRef,
|
|
13072
|
+
isUntouched,
|
|
13073
|
+
isOpen,
|
|
12870
13074
|
tryParse,
|
|
12871
13075
|
text,
|
|
12872
13076
|
isAllowed,
|
|
@@ -12880,7 +13084,6 @@ function useSegmentedEntry(params) {
|
|
|
12880
13084
|
]);
|
|
12881
13085
|
const handleFocus = React.useCallback((e) => {
|
|
12882
13086
|
const handedBack = closingRef.current;
|
|
12883
|
-
handedBackRef.current = handedBack;
|
|
12884
13087
|
if (!handedBack && openOnFocus && popover && !disabled && !readOnly) {
|
|
12885
13088
|
setOpen(true);
|
|
12886
13089
|
}
|
|
@@ -13048,12 +13251,20 @@ function useSegmentedEntry(params) {
|
|
|
13048
13251
|
}
|
|
13049
13252
|
if (e.key === 'Enter') {
|
|
13050
13253
|
e.preventDefault();
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
|
|
13254
|
+
// Untouched text has nothing to commit, as on leaving, but Enter
|
|
13255
|
+
// still confirms what the field shows.
|
|
13256
|
+
if (isUntouched(text)) {
|
|
13054
13257
|
if (closeOnSelect)
|
|
13055
13258
|
setOpen(false);
|
|
13056
13259
|
}
|
|
13260
|
+
else {
|
|
13261
|
+
const parsed = tryParse(text);
|
|
13262
|
+
if (parsed && isAllowed(parsed)) {
|
|
13263
|
+
commitValue(parsed);
|
|
13264
|
+
if (closeOnSelect)
|
|
13265
|
+
setOpen(false);
|
|
13266
|
+
}
|
|
13267
|
+
}
|
|
13057
13268
|
}
|
|
13058
13269
|
onKeyDown?.(e);
|
|
13059
13270
|
}, [
|
|
@@ -13067,6 +13278,7 @@ function useSegmentedEntry(params) {
|
|
|
13067
13278
|
setOpen,
|
|
13068
13279
|
closeOnSelect,
|
|
13069
13280
|
popover,
|
|
13281
|
+
isUntouched,
|
|
13070
13282
|
tryParse,
|
|
13071
13283
|
text,
|
|
13072
13284
|
isAllowed,
|
|
@@ -13115,9 +13327,12 @@ const pad2$1 = (n) => String(n).padStart(2, '0');
|
|
|
13115
13327
|
* The value as the native input and the hidden form input carry it:
|
|
13116
13328
|
* `YYYY-MM-DD`, `YYYY-MM` or `YYYY`, the shapes `<input type="date">` and
|
|
13117
13329
|
* `<input type="month">` use. The year is padded to four digits, as those
|
|
13118
|
-
* inputs require and as the `YYYY` token displays it.
|
|
13330
|
+
* inputs require and as the `YYYY` token displays it. HTML has no such shape
|
|
13331
|
+
* for a year before 1, so a date then is empty, as those inputs would make it.
|
|
13119
13332
|
*/
|
|
13120
13333
|
const toIsoValue = (d, granularity) => {
|
|
13334
|
+
if (d.getFullYear() < FIRST_YEAR)
|
|
13335
|
+
return '';
|
|
13121
13336
|
const year = String(d.getFullYear()).padStart(4, '0');
|
|
13122
13337
|
if (granularity === 'year')
|
|
13123
13338
|
return year;
|
|
@@ -13158,14 +13373,19 @@ const SKIPPED_SEGMENTS = {
|
|
|
13158
13373
|
* @returns {JSX.Element}
|
|
13159
13374
|
*/
|
|
13160
13375
|
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
|
|
13376
|
+
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;
|
|
13377
|
+
// The launcher gives way to the loading spinner of a Control this sits in.
|
|
13378
|
+
const controlLoading = useControlLoading();
|
|
13379
|
+
const triggerIcon = triggerIconProp ?? !controlLoading;
|
|
13162
13380
|
const t = mergeLabels(labels);
|
|
13163
13381
|
const isDayGranularity = granularity === 'day';
|
|
13164
13382
|
const resolvedFormat = format ?? DEFAULT_FORMATS[granularity];
|
|
13165
13383
|
const isControlled = controlledValue !== undefined;
|
|
13166
13384
|
const [internalValue, setInternalValue] = React.useState(defaultValue ?? null);
|
|
13167
13385
|
const value = isControlled ? (controlledValue ?? null) : internalValue;
|
|
13168
|
-
|
|
13386
|
+
// Nothing before year 1 is in range, as in HTML's date inputs.
|
|
13387
|
+
const lowerBound = React.useMemo(() => floorMin(min), [min]);
|
|
13388
|
+
const initialFocused = React.useMemo(() => clampDate(value ?? new Date(), lowerBound, max),
|
|
13169
13389
|
// intentionally only on mount: keep focusedDate stable until value/open change
|
|
13170
13390
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
13171
13391
|
[]);
|
|
@@ -13173,8 +13393,8 @@ const DateInputBase = React.forwardRef((props, ref) => {
|
|
|
13173
13393
|
// Re-clamp focusedDate if min/max change after mount so the focused cell
|
|
13174
13394
|
// never disappears outside the displayable range.
|
|
13175
13395
|
React.useEffect(() => {
|
|
13176
|
-
setFocusedDate(prev => clampDate(prev,
|
|
13177
|
-
}, [
|
|
13396
|
+
setFocusedDate(prev => clampDate(prev, lowerBound, max));
|
|
13397
|
+
}, [lowerBound, max]);
|
|
13178
13398
|
const [open, setOpenState] = React.useState(false);
|
|
13179
13399
|
const [text, setText] = React.useState(value ? formatDate(value, resolvedFormat, locale) : '');
|
|
13180
13400
|
const inputRef = React.useRef(null);
|
|
@@ -13232,7 +13452,7 @@ const DateInputBase = React.forwardRef((props, ref) => {
|
|
|
13232
13452
|
}, [isControlled, onChange, isDayGranularity, granularity]);
|
|
13233
13453
|
// At month or year granularity the bounds widen to the periods holding
|
|
13234
13454
|
// them, so the month or year containing `min` stays reachable.
|
|
13235
|
-
const periodMin = React.useMemo(() =>
|
|
13455
|
+
const periodMin = React.useMemo(() => isDayGranularity ? lowerBound : startOfPeriod(lowerBound, granularity), [lowerBound, isDayGranularity, granularity]);
|
|
13236
13456
|
const periodMax = React.useMemo(() => (max && !isDayGranularity ? endOfPeriod(max, granularity) : max), [max, isDayGranularity, granularity]);
|
|
13237
13457
|
const handleSelect = React.useCallback((d) => {
|
|
13238
13458
|
if (!isWithin(d, periodMin, periodMax))
|
|
@@ -13283,7 +13503,7 @@ const DateInputBase = React.forwardRef((props, ref) => {
|
|
|
13283
13503
|
]);
|
|
13284
13504
|
// Typing, and the re-parse on blur or Enter, that lands in the period the
|
|
13285
13505
|
// value already holds commits nothing, so a `value` elsewhere in its month
|
|
13286
|
-
// or year
|
|
13506
|
+
// or year changes only when the user picks or types a different period.
|
|
13287
13507
|
const commitTyped = React.useCallback((next) => {
|
|
13288
13508
|
if (next &&
|
|
13289
13509
|
value &&
|
|
@@ -13349,7 +13569,7 @@ const DateInputBase = React.forwardRef((props, ref) => {
|
|
|
13349
13569
|
? fromIsoValue(e.target.value, granularity)
|
|
13350
13570
|
: null;
|
|
13351
13571
|
commitValue(parsed);
|
|
13352
|
-
}, min: min ? toIsoValue(
|
|
13572
|
+
}, 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
13573
|
}
|
|
13354
13574
|
const chooseLabel = granularity === 'month'
|
|
13355
13575
|
? t.chooseMonth
|
|
@@ -13358,7 +13578,11 @@ const DateInputBase = React.forwardRef((props, ref) => {
|
|
|
13358
13578
|
: t.chooseDate;
|
|
13359
13579
|
const calendar = (jsxRuntime.jsx(Calendar
|
|
13360
13580
|
// 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,
|
|
13581
|
+
, { 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,
|
|
13582
|
+
// The popover panel takes `popoverId`, so the calendar inside it
|
|
13583
|
+
// takes one of its own. Inline there is no panel, and the calendar
|
|
13584
|
+
// keeps the id it released with.
|
|
13585
|
+
id: inline ? popoverId : `${popoverId}-cal`, autoFocusCell: open, labels: labels }, granularity));
|
|
13362
13586
|
if (inline) {
|
|
13363
13587
|
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
13588
|
}
|
|
@@ -13389,26 +13613,34 @@ DateInputBase.displayName = 'DateInputBase';
|
|
|
13389
13613
|
const DateInput = React.forwardRef(({ label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'calendar', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps }, ref) => {
|
|
13390
13614
|
const insideField = useInsideField();
|
|
13391
13615
|
const insideControl = useInsideControl();
|
|
13616
|
+
const ownField = rendersOwnField('DateInput', {
|
|
13617
|
+
insideField,
|
|
13618
|
+
insideControl,
|
|
13619
|
+
label,
|
|
13620
|
+
message,
|
|
13621
|
+
horizontal,
|
|
13622
|
+
fieldClassName,
|
|
13623
|
+
});
|
|
13392
13624
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
13393
13625
|
label,
|
|
13394
13626
|
id: baseProps.id,
|
|
13395
13627
|
labelProps,
|
|
13396
13628
|
// Inline mode renders a bare calendar with no input to label.
|
|
13397
|
-
rendersLabel:
|
|
13629
|
+
rendersLabel: ownField && !baseProps.inline,
|
|
13398
13630
|
});
|
|
13399
13631
|
const helpClass = usePrefixedClassNames('help', {
|
|
13400
13632
|
[`is-${messageColor}`]: !!messageColor,
|
|
13401
13633
|
});
|
|
13402
|
-
// The
|
|
13403
|
-
//
|
|
13404
|
-
let content = (jsxRuntime.jsx(DateInputBase, { ref: ref, id: controlId, ...baseProps
|
|
13634
|
+
// The base hides its launcher while the Control it sits in is loading,
|
|
13635
|
+
// whether that is the one rendered below or an enclosing one.
|
|
13636
|
+
let content = (jsxRuntime.jsx(DateInputBase, { ref: ref, id: controlId, ...baseProps }));
|
|
13405
13637
|
// Inline mode renders a bare calendar with no input, so the Control's
|
|
13406
13638
|
// icon-left container has nothing to anchor to. Skip the Control wrap.
|
|
13407
13639
|
if (!insideControl && !baseProps.inline) {
|
|
13408
13640
|
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
13641
|
}
|
|
13410
13642
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
13411
|
-
if (
|
|
13643
|
+
if (ownField) {
|
|
13412
13644
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
13413
13645
|
}
|
|
13414
13646
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
@@ -14282,7 +14514,10 @@ const fromIsoTime = (s) => {
|
|
|
14282
14514
|
* @returns {JSX.Element}
|
|
14283
14515
|
*/
|
|
14284
14516
|
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
|
|
14517
|
+
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;
|
|
14518
|
+
// The launcher gives way to the loading spinner of a Control this sits in.
|
|
14519
|
+
const controlLoading = useControlLoading();
|
|
14520
|
+
const triggerIcon = triggerIconProp ?? !controlLoading;
|
|
14286
14521
|
// Platform-appropriate feedback routing: when `haptics` is opted in and
|
|
14287
14522
|
// the runtime has no Vibration API (notably iOS Safari), enable the
|
|
14288
14523
|
// audio thunk so the user still gets *some* tactile cue. When vibrate
|
|
@@ -14359,6 +14594,8 @@ const TimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14359
14594
|
React.useEffect(() => {
|
|
14360
14595
|
setText(value ? formatTime(value, defaultFormat, locale) : '');
|
|
14361
14596
|
}, [value, defaultFormat, locale]);
|
|
14597
|
+
// A value's date before year 1 is out of range, as in the date pickers.
|
|
14598
|
+
const lowerBound = React.useMemo(() => floorMin(min), [min]);
|
|
14362
14599
|
const commitValue = React.useCallback((next) => {
|
|
14363
14600
|
if (!isControlled)
|
|
14364
14601
|
setInternalValue(next);
|
|
@@ -14367,10 +14604,10 @@ const TimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14367
14604
|
const handleSpinnerChange = React.useCallback((parts) => {
|
|
14368
14605
|
const base = value ?? new Date();
|
|
14369
14606
|
const next = setTimeOfDay(base, parts);
|
|
14370
|
-
if (!isWithin(next,
|
|
14607
|
+
if (!isWithin(next, lowerBound, max))
|
|
14371
14608
|
return;
|
|
14372
14609
|
commitValue(next);
|
|
14373
|
-
}, [value,
|
|
14610
|
+
}, [value, lowerBound, max, commitValue]);
|
|
14374
14611
|
const tryParse = React.useCallback((s) => {
|
|
14375
14612
|
const trimmed = s.trim();
|
|
14376
14613
|
if (!trimmed)
|
|
@@ -14402,7 +14639,7 @@ const TimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14402
14639
|
setText,
|
|
14403
14640
|
makeBaseDate,
|
|
14404
14641
|
locale,
|
|
14405
|
-
min,
|
|
14642
|
+
min: lowerBound,
|
|
14406
14643
|
max,
|
|
14407
14644
|
isBlocked: unselectableTimes,
|
|
14408
14645
|
disabled,
|
|
@@ -14484,34 +14721,46 @@ TimeInputBase.displayName = 'TimeInputBase';
|
|
|
14484
14721
|
const TimeInput = React.forwardRef(({ label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'clock', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps }, ref) => {
|
|
14485
14722
|
const insideField = useInsideField();
|
|
14486
14723
|
const insideControl = useInsideControl();
|
|
14724
|
+
const ownField = rendersOwnField('TimeInput', {
|
|
14725
|
+
insideField,
|
|
14726
|
+
insideControl,
|
|
14727
|
+
label,
|
|
14728
|
+
message,
|
|
14729
|
+
horizontal,
|
|
14730
|
+
fieldClassName,
|
|
14731
|
+
});
|
|
14487
14732
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
14488
14733
|
label,
|
|
14489
14734
|
id: baseProps.id,
|
|
14490
14735
|
labelProps,
|
|
14491
14736
|
// Inline mode renders a bare panel with no input to label.
|
|
14492
|
-
rendersLabel:
|
|
14737
|
+
rendersLabel: ownField && !baseProps.inline,
|
|
14493
14738
|
});
|
|
14494
14739
|
const helpClass = usePrefixedClassNames('help', {
|
|
14495
14740
|
[`is-${messageColor}`]: !!messageColor,
|
|
14496
14741
|
});
|
|
14497
|
-
// The
|
|
14498
|
-
//
|
|
14499
|
-
let content = (jsxRuntime.jsx(TimeInputBase, { ref: ref, id: controlId, ...baseProps
|
|
14742
|
+
// The base hides its launcher while the Control it sits in is loading,
|
|
14743
|
+
// whether that is the one rendered below or an enclosing one.
|
|
14744
|
+
let content = (jsxRuntime.jsx(TimeInputBase, { ref: ref, id: controlId, ...baseProps }));
|
|
14500
14745
|
// Inline mode renders a bare panel with no input, so the Control's
|
|
14501
14746
|
// icon-left container has nothing to anchor to. Skip the Control wrap.
|
|
14502
14747
|
if (!insideControl && !baseProps.inline) {
|
|
14503
14748
|
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
14749
|
}
|
|
14505
14750
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
14506
|
-
if (
|
|
14751
|
+
if (ownField) {
|
|
14507
14752
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
14508
14753
|
}
|
|
14509
14754
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
14510
14755
|
});
|
|
14511
14756
|
TimeInput.displayName = 'TimeInput';
|
|
14512
14757
|
|
|
14513
|
-
// The year is padded to four digits, as `datetime-local` requires.
|
|
14758
|
+
// The year is padded to four digits, as `datetime-local` requires. HTML has
|
|
14759
|
+
// no such shape for a year before 1, so a date then is empty, as that input
|
|
14760
|
+
// would make it.
|
|
14514
14761
|
const toIsoDateTime = (d, withSeconds) => {
|
|
14762
|
+
if (d.getFullYear() < FIRST_YEAR)
|
|
14763
|
+
return '';
|
|
14515
14764
|
const yyyy = String(d.getFullYear()).padStart(4, '0');
|
|
14516
14765
|
const mo = String(d.getMonth() + 1).padStart(2, '0');
|
|
14517
14766
|
const dd = String(d.getDate()).padStart(2, '0');
|
|
@@ -14544,7 +14793,10 @@ const fromIsoDateTime = (s) => {
|
|
|
14544
14793
|
const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
14545
14794
|
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
14795
|
// 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
|
|
14796
|
+
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;
|
|
14797
|
+
// The launcher gives way to the loading spinner of a Control this sits in.
|
|
14798
|
+
const controlLoading = useControlLoading();
|
|
14799
|
+
const triggerIcon = triggerIconProp ?? !controlLoading;
|
|
14548
14800
|
const t = mergeLabels(labels);
|
|
14549
14801
|
// Platform-appropriate feedback routing for the time wheel: on iOS (no
|
|
14550
14802
|
// navigator.vibrate) the audio thunk fills in; on Android the real haptic
|
|
@@ -14554,14 +14806,16 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14554
14806
|
const isControlled = controlledValue !== undefined;
|
|
14555
14807
|
const [internalValue, setInternalValue] = React.useState(defaultValue ?? null);
|
|
14556
14808
|
const value = isControlled ? (controlledValue ?? null) : internalValue;
|
|
14557
|
-
|
|
14809
|
+
// Nothing before year 1 is in range, as in HTML's date inputs.
|
|
14810
|
+
const lowerBound = React.useMemo(() => floorMin(min), [min]);
|
|
14811
|
+
const initialFocused = React.useMemo(() => clampDate(value ?? new Date(), lowerBound, max),
|
|
14558
14812
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
14559
14813
|
[]);
|
|
14560
14814
|
const [focusedDate, setFocusedDate] = React.useState(initialFocused);
|
|
14561
14815
|
// Re-clamp focusedDate when min/max change so the focused cell stays valid.
|
|
14562
14816
|
React.useEffect(() => {
|
|
14563
|
-
setFocusedDate(prev => clampDate(prev,
|
|
14564
|
-
}, [
|
|
14817
|
+
setFocusedDate(prev => clampDate(prev, lowerBound, max));
|
|
14818
|
+
}, [lowerBound, max]);
|
|
14565
14819
|
const defaultFormat = format ??
|
|
14566
14820
|
(hourFormat === '12'
|
|
14567
14821
|
? enableSeconds
|
|
@@ -14658,18 +14912,18 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14658
14912
|
minutes: value?.getMinutes() ?? 0,
|
|
14659
14913
|
seconds: enableSeconds ? (value?.getSeconds() ?? 0) : undefined,
|
|
14660
14914
|
});
|
|
14661
|
-
if (!isWithin(merged,
|
|
14915
|
+
if (!isWithin(merged, lowerBound, max))
|
|
14662
14916
|
return;
|
|
14663
14917
|
commitValue(merged);
|
|
14664
14918
|
setFocusedDate(d);
|
|
14665
|
-
}, [value, enableSeconds,
|
|
14919
|
+
}, [value, enableSeconds, lowerBound, max, commitValue]);
|
|
14666
14920
|
const handleTimeChange = React.useCallback((parts) => {
|
|
14667
14921
|
const base = value ?? focusedDate;
|
|
14668
14922
|
const next = setTimeOfDay(base, parts);
|
|
14669
|
-
if (!isWithin(next,
|
|
14923
|
+
if (!isWithin(next, lowerBound, max))
|
|
14670
14924
|
return;
|
|
14671
14925
|
commitValue(next);
|
|
14672
|
-
}, [value, focusedDate,
|
|
14926
|
+
}, [value, focusedDate, lowerBound, max, commitValue]);
|
|
14673
14927
|
const tryParse = React.useCallback((s) => {
|
|
14674
14928
|
const trimmed = s.trim();
|
|
14675
14929
|
if (!trimmed)
|
|
@@ -14704,7 +14958,7 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14704
14958
|
setText,
|
|
14705
14959
|
makeBaseDate,
|
|
14706
14960
|
locale,
|
|
14707
|
-
min,
|
|
14961
|
+
min: lowerBound,
|
|
14708
14962
|
max,
|
|
14709
14963
|
isBlocked,
|
|
14710
14964
|
disabled,
|
|
@@ -14737,7 +14991,7 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14737
14991
|
? fromIsoDateTime(e.target.value)
|
|
14738
14992
|
: null;
|
|
14739
14993
|
commitValue(parsed);
|
|
14740
|
-
}, min: min ? toIsoDateTime(
|
|
14994
|
+
}, 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
14995
|
}
|
|
14742
14996
|
const spinnerValue = {
|
|
14743
14997
|
hours: value?.getHours() ?? 0,
|
|
@@ -14788,26 +15042,34 @@ DateTimeInputBase.displayName = 'DateTimeInputBase';
|
|
|
14788
15042
|
const DateTimeInput = React.forwardRef(({ label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'calendar-alt', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps }, ref) => {
|
|
14789
15043
|
const insideField = useInsideField();
|
|
14790
15044
|
const insideControl = useInsideControl();
|
|
15045
|
+
const ownField = rendersOwnField('DateTimeInput', {
|
|
15046
|
+
insideField,
|
|
15047
|
+
insideControl,
|
|
15048
|
+
label,
|
|
15049
|
+
message,
|
|
15050
|
+
horizontal,
|
|
15051
|
+
fieldClassName,
|
|
15052
|
+
});
|
|
14791
15053
|
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
14792
15054
|
label,
|
|
14793
15055
|
id: baseProps.id,
|
|
14794
15056
|
labelProps,
|
|
14795
15057
|
// Inline mode renders a bare picker with no input to label.
|
|
14796
|
-
rendersLabel:
|
|
15058
|
+
rendersLabel: ownField && !baseProps.inline,
|
|
14797
15059
|
});
|
|
14798
15060
|
const helpClass = usePrefixedClassNames('help', {
|
|
14799
15061
|
[`is-${messageColor}`]: !!messageColor,
|
|
14800
15062
|
});
|
|
14801
|
-
// The
|
|
14802
|
-
//
|
|
14803
|
-
let content = (jsxRuntime.jsx(DateTimeInputBase, { ref: ref, id: controlId, ...baseProps
|
|
15063
|
+
// The base hides its launcher while the Control it sits in is loading,
|
|
15064
|
+
// whether that is the one rendered below or an enclosing one.
|
|
15065
|
+
let content = (jsxRuntime.jsx(DateTimeInputBase, { ref: ref, id: controlId, ...baseProps }));
|
|
14804
15066
|
// Inline mode renders a bare picker with no input, so the Control's
|
|
14805
15067
|
// icon-left container has nothing to anchor to. Skip the Control wrap.
|
|
14806
15068
|
if (!insideControl && !baseProps.inline) {
|
|
14807
15069
|
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
15070
|
}
|
|
14809
15071
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
14810
|
-
if (
|
|
15072
|
+
if (ownField) {
|
|
14811
15073
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
14812
15074
|
}
|
|
14813
15075
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|