@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.
Files changed (63) hide show
  1. package/dist/bestax.css +1 -1
  2. package/dist/bestax.css.map +1 -1
  3. package/dist/extras.css +1 -1
  4. package/dist/extras.css.map +1 -1
  5. package/dist/index.cjs +385 -123
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.esm.js +385 -123
  8. package/dist/index.esm.js.map +1 -1
  9. package/dist/types/form/Checkboxes.d.ts +7 -0
  10. package/dist/types/form/DateInput.d.ts +18 -3
  11. package/dist/types/form/DateInputBase.d.ts +17 -3
  12. package/dist/types/form/DateTimeInput.d.ts +18 -3
  13. package/dist/types/form/DateTimeInputBase.d.ts +17 -3
  14. package/dist/types/form/File.d.ts +6 -0
  15. package/dist/types/form/FormContext.d.ts +39 -7
  16. package/dist/types/form/Input.d.ts +7 -2
  17. package/dist/types/form/Radios.d.ts +7 -0
  18. package/dist/types/form/Rate.d.ts +7 -0
  19. package/dist/types/form/Select.d.ts +7 -2
  20. package/dist/types/form/Slider.d.ts +7 -0
  21. package/dist/types/form/Taginput.d.ts +6 -0
  22. package/dist/types/form/TextArea.d.ts +7 -2
  23. package/dist/types/form/TimeInput.d.ts +18 -3
  24. package/dist/types/form/TimeInputBase.d.ts +16 -3
  25. package/dist/types/form/_pickerInternals/Calendar.d.ts +9 -3
  26. package/dist/types/form/_pickerInternals/dateUtils.d.ts +17 -0
  27. package/dist/types/form/controlLoading.d.ts +4 -0
  28. package/dist/types/helpers/useAnchoredPosition.d.ts +4 -1
  29. package/dist/types/helpers/useFocusTrap.d.ts +11 -4
  30. package/dist/types-cjs/form/Checkboxes.d.ts +7 -0
  31. package/dist/types-cjs/form/DateInput.d.ts +18 -3
  32. package/dist/types-cjs/form/DateInputBase.d.ts +17 -3
  33. package/dist/types-cjs/form/DateTimeInput.d.ts +18 -3
  34. package/dist/types-cjs/form/DateTimeInputBase.d.ts +17 -3
  35. package/dist/types-cjs/form/File.d.ts +6 -0
  36. package/dist/types-cjs/form/FormContext.d.ts +39 -7
  37. package/dist/types-cjs/form/Input.d.ts +7 -2
  38. package/dist/types-cjs/form/Radios.d.ts +7 -0
  39. package/dist/types-cjs/form/Rate.d.ts +7 -0
  40. package/dist/types-cjs/form/Select.d.ts +7 -2
  41. package/dist/types-cjs/form/Slider.d.ts +7 -0
  42. package/dist/types-cjs/form/Taginput.d.ts +6 -0
  43. package/dist/types-cjs/form/TextArea.d.ts +7 -2
  44. package/dist/types-cjs/form/TimeInput.d.ts +18 -3
  45. package/dist/types-cjs/form/TimeInputBase.d.ts +16 -3
  46. package/dist/types-cjs/form/_pickerInternals/Calendar.d.ts +9 -3
  47. package/dist/types-cjs/form/_pickerInternals/dateUtils.d.ts +17 -0
  48. package/dist/types-cjs/form/controlLoading.d.ts +4 -0
  49. package/dist/types-cjs/helpers/useAnchoredPosition.d.ts +4 -1
  50. package/dist/types-cjs/helpers/useFocusTrap.d.ts +11 -4
  51. package/dist/versions/bestax-no-dark-mode.css +1 -1
  52. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  53. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  54. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  55. package/dist/versions/bestax-no-helpers.css +1 -1
  56. package/dist/versions/bestax-no-helpers.css.map +1 -1
  57. package/dist/versions/bestax-prefixed.css +1 -1
  58. package/dist/versions/bestax-prefixed.css.map +1 -1
  59. package/package.json +1 -1
  60. package/src/scss/form/_datetimeinput.scss +9 -0
  61. package/src/scss/form/_picker-popover.scss +12 -9
  62. package/src/scss/form/_rate.scss +3 -1
  63. 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 container has to be in the DOM when the trap turns on. The trap waits
5205
- * for hydration, so a container that only appears once the page has
5206
- * hydrated, the way portaled content does, still has it attach in the commit
5207
- * where the container appears.
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
- }, [active, hydrated, containerRef, initialFocusRef, restoreFocus]);
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: !insideField,
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 (!insideField) {
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: !insideField,
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 (!insideField) {
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: !insideField,
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 (!insideField) {
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: !insideField,
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 (!insideField) {
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: !insideField,
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 (!insideField) {
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: !insideField && !isMaxReached,
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 (!insideField) {
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: !insideField,
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 (!insideField) {
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: !insideField,
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 (!insideField) {
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: !insideField,
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 (!insideField) {
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
- if (min && isBefore(d, min))
11395
- return new Date(min);
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). Years >9999 keep their natural width.
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 (min && candidate.getTime() < startOfDay(min).getTime())
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 (min && month < startOfMonth(min).getTime())
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
- const prevMonthAnchor = addMonths(focusedDate, -1);
12040
- const nextMonthAnchor = addMonths(focusedDate, 1);
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
- ? prevMonthAnchor
12234
+ ? addMonths(focusedDate, -1)
12043
12235
  : clampDate(addYears(focusedDate, -1), min, max);
12044
12236
  const nextAnchor = isDayGranularity
12045
- ? nextMonthAnchor
12237
+ ? addMonths(focusedDate, 1)
12046
12238
  : clampDate(addYears(focusedDate, 1), min, max);
12239
+ const focusedMonth = startOfMonth(focusedDate).getTime();
12047
12240
  const prevDisabled = isDayGranularity
12048
- ? !!(min &&
12049
- prevMonthAnchor.getTime() < startOfDay(min).getTime() &&
12050
- isSameMonth(focusedDate, min))
12051
- : !!(min && focusedYear <= min.getFullYear());
12241
+ ? focusedMonth <= startOfMonth(min).getTime()
12242
+ : focusedYear <= min.getFullYear();
12052
12243
  const nextDisabled = isDayGranularity
12053
- ? !!(max &&
12054
- nextMonthAnchor.getTime() > startOfDay(max).getTime() &&
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 = min ? min.getFullYear() : windowYear - 100;
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. A year outside the list keeps
12083
- // the stop, which then marks no option.
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
- const i = yearList.indexOf(focusedYear);
12086
- if (i < 0)
12087
- return focusedYear;
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
- // A portaled panel takes its gap from `--bulma-picker-popover-offset` as
12297
- // a margin, so its coordinates leave the gap out and `auto` reads it off
12298
- // the panel. An in-place panel's gap is in its `top` or `bottom`, which
12299
- // `auto` cannot read, so it keeps the hook's default estimate.
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. While the text still matches it, nothing has been typed.
12941
+ // leaves.
12747
12942
  const seedRef = React.useRef(null);
12748
- // Whether the input's current focus is the one a closing popover handed
12749
- // back, until focus leaves the picker.
12750
- const handedBackRef = React.useRef(false);
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 that leaving would then commit.
12757
- // `closingRef` is set in the layout phase of the commit that closes it and
12758
- // cleared by that commit's effects, and React runs a commit's effect
12759
- // cleanups, the trap's among them, before any of its effects, so a focus
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 && seedRef.current !== null && text === seedRef.current) {
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
- seedRef.current = null;
12851
- const handedBack = handedBackRef.current;
12852
- handedBackRef.current = false;
12853
- // Leaving after a dismiss commits only what was typed since. Text that
12854
- // still shows the value wasn't, and re-parsing it could drop what the
12855
- // format leaves out, such as seconds.
12856
- if (handedBack && value && text === formatFn(value, format, locale)) {
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
- const parsed = tryParse(text);
13052
- if (parsed && isAllowed(parsed)) {
13053
- commitValue(parsed);
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 = true, triggerIconName = 'chevron-down', labels, ...rest } = props;
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
- const initialFocused = React.useMemo(() => clampDate(value ?? new Date(), min, max),
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, min, max));
13177
- }, [min, max]);
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(() => (min && !isDayGranularity ? startOfPeriod(min, granularity) : min), [min, isDayGranularity, granularity]);
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 isn't rewritten by focus passing through the field.
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(min, granularity) : undefined, max: max ? toIsoValue(max, granularity) : undefined, disabled: disabled, readOnly: readOnly, placeholder: placeholder, name: name, form: form, required: required, id: id }));
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, id: `${popoverId}-cal`, autoFocusCell: open, labels: labels }, granularity));
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: !insideField && !baseProps.inline,
13629
+ rendersLabel: ownField && !baseProps.inline,
13398
13630
  });
13399
13631
  const helpClass = usePrefixedClassNames('help', {
13400
13632
  [`is-${messageColor}`]: !!messageColor,
13401
13633
  });
13402
- // The right-side launcher is on by default; suppress it while the Control
13403
- // shows its loading spinner (also on the right) unless explicitly set.
13404
- let content = (jsxRuntime.jsx(DateInputBase, { ref: ref, id: controlId, ...baseProps, triggerIcon: baseProps.triggerIcon ?? !isLoading }));
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 (!insideField) {
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 = true, triggerIconName = 'chevron-down', labels, audioTick = false, haptics = false, ...rest } = props;
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, min, max))
14607
+ if (!isWithin(next, lowerBound, max))
14371
14608
  return;
14372
14609
  commitValue(next);
14373
- }, [value, min, max, commitValue]);
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: !insideField && !baseProps.inline,
14737
+ rendersLabel: ownField && !baseProps.inline,
14493
14738
  });
14494
14739
  const helpClass = usePrefixedClassNames('help', {
14495
14740
  [`is-${messageColor}`]: !!messageColor,
14496
14741
  });
14497
- // The right-side launcher is on by default; suppress it while the Control
14498
- // shows its loading spinner (also on the right) unless explicitly set.
14499
- let content = (jsxRuntime.jsx(TimeInputBase, { ref: ref, id: controlId, ...baseProps, triggerIcon: baseProps.triggerIcon ?? !isLoading }));
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 (!insideField) {
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 = true, triggerIconName = 'chevron-down', audioTick = false, haptics = false, labels, ...rest } = props;
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
- const initialFocused = React.useMemo(() => clampDate(value ?? new Date(), min, max),
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, min, max));
14564
- }, [min, max]);
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, min, max))
14915
+ if (!isWithin(merged, lowerBound, max))
14662
14916
  return;
14663
14917
  commitValue(merged);
14664
14918
  setFocusedDate(d);
14665
- }, [value, enableSeconds, min, max, commitValue]);
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, min, max))
14923
+ if (!isWithin(next, lowerBound, max))
14670
14924
  return;
14671
14925
  commitValue(next);
14672
- }, [value, focusedDate, min, max, commitValue]);
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(min, enableSeconds) : undefined, max: max ? toIsoDateTime(max, enableSeconds) : undefined, disabled: disabled, readOnly: readOnly, placeholder: placeholder, name: name, form: form, required: required, id: id }));
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: !insideField && !baseProps.inline,
15058
+ rendersLabel: ownField && !baseProps.inline,
14797
15059
  });
14798
15060
  const helpClass = usePrefixedClassNames('help', {
14799
15061
  [`is-${messageColor}`]: !!messageColor,
14800
15062
  });
14801
- // The right-side launcher is on by default; suppress it while the Control
14802
- // shows its loading spinner (also on the right) unless explicitly set.
14803
- let content = (jsxRuntime.jsx(DateTimeInputBase, { ref: ref, id: controlId, ...baseProps, triggerIcon: baseProps.triggerIcon ?? !isLoading }));
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 (!insideField) {
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] }));