@allxsmith/bestax-bulma 5.26.4 → 5.26.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) 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 +224 -39
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.esm.js +224 -39
  8. package/dist/index.esm.js.map +1 -1
  9. package/dist/types/form/Autocomplete.d.ts +6 -1
  10. package/dist/types/form/DateInput.d.ts +8 -4
  11. package/dist/types/form/DateTimeInput.d.ts +8 -4
  12. package/dist/types/form/FormContext.d.ts +47 -0
  13. package/dist/types/form/Input.d.ts +4 -3
  14. package/dist/types/form/Numberinput.d.ts +3 -0
  15. package/dist/types/form/Select.d.ts +4 -3
  16. package/dist/types/form/TextArea.d.ts +4 -3
  17. package/dist/types/form/TimeInput.d.ts +8 -4
  18. package/dist/types-cjs/form/Autocomplete.d.ts +6 -1
  19. package/dist/types-cjs/form/DateInput.d.ts +8 -4
  20. package/dist/types-cjs/form/DateTimeInput.d.ts +8 -4
  21. package/dist/types-cjs/form/FormContext.d.ts +47 -0
  22. package/dist/types-cjs/form/Input.d.ts +4 -3
  23. package/dist/types-cjs/form/Numberinput.d.ts +3 -0
  24. package/dist/types-cjs/form/Select.d.ts +4 -3
  25. package/dist/types-cjs/form/TextArea.d.ts +4 -3
  26. package/dist/types-cjs/form/TimeInput.d.ts +8 -4
  27. package/dist/versions/bestax-no-dark-mode.css +1 -1
  28. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  29. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  30. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  31. package/dist/versions/bestax-no-helpers.css +1 -1
  32. package/dist/versions/bestax-no-helpers.css.map +1 -1
  33. package/dist/versions/bestax-prefixed.css +1 -1
  34. package/dist/versions/bestax-prefixed.css.map +1 -1
  35. package/package.json +1 -1
  36. package/src/scss/form/_taginput.scss +8 -0
package/dist/index.esm.js CHANGED
@@ -8819,6 +8819,58 @@ const rendersOwnField = (component, options) => {
8819
8819
  (moved.length > 0 ? `, and set ${listOf(moved)} on that <Field>.` : '.'));
8820
8820
  return true;
8821
8821
  };
8822
+ // Each Control-level prop, with what takes its place on the `Control` the
8823
+ // caller wrapped around the wrapper. `bare-control.test.tsx` holds this to
8824
+ // what each wrapper hands its own `Control`, so a new Control-level prop
8825
+ // fails there until it is added here and passed to `rendersOwnControl`.
8826
+ const ON_CONTROL = {
8827
+ isLoading: 'isLoading',
8828
+ iconLeft: 'iconLeft',
8829
+ iconLeftName: 'iconLeftName',
8830
+ iconLeftSize: 'iconLeftSize',
8831
+ iconRight: 'iconRight',
8832
+ iconRightName: 'iconRightName',
8833
+ iconRightSize: 'iconRightSize',
8834
+ hasIconsLeft: 'hasIconsLeft',
8835
+ hasIconsRight: 'hasIconsRight',
8836
+ isExpanded: 'isExpanded',
8837
+ controlSize: 'size (for controlSize)',
8838
+ controlClassName: 'className (for controlClassName)',
8839
+ };
8840
+ /**
8841
+ * Whether a convenience form wrapper renders a `Control` of its own (#921):
8842
+ * only outside one. Inside a `Control` it renders none, so the props it
8843
+ * would have handed its own do nothing there, and it warns in development,
8844
+ * naming them and saying to set them on that `Control`. An `inline` picker
8845
+ * renders none in either place, so it drops them inside a `Control` or not,
8846
+ * and its warning says so without pointing at a `Control`. A wrapper passes
8847
+ * only the props it hands its own `Control`, as the caller gave them, so a
8848
+ * default the wrapper fills in itself never warns. A prop counts when it is
8849
+ * truthy, since a falsy one would change nothing on a `Control` either.
8850
+ * Internal; not part of the public API.
8851
+ */
8852
+ const rendersOwnControl = (component, options) => {
8853
+ if (!options.inline && !options.insideControl)
8854
+ return true;
8855
+ const held = Object.keys(ON_CONTROL).filter(prop => options[prop]);
8856
+ if (held.length === 0)
8857
+ return false;
8858
+ const names = held.join(' ');
8859
+ const doNothing = held.length > 1 ? 'those props do' : 'that prop does';
8860
+ if (options.inline) {
8861
+ warnOnce(`${component}:Control-props-inline:${held.join('+')}`, `[bestax-bulma] <${component} inline ${names}> renders no <Control> ` +
8862
+ `in inline mode, inside a <Control> or not, so ${doNothing} ` +
8863
+ `nothing. Leave ${held.length > 1 ? 'them' : 'it'} out of an ` +
8864
+ `inline picker.`);
8865
+ }
8866
+ else {
8867
+ warnOnce(`${component}:Control-props-in-Control:${held.join('+')}`, `[bestax-bulma] <${component} ${names}> inside a <Control> renders ` +
8868
+ `no <Control> of its own, so ${doNothing} nothing there. Set ` +
8869
+ `${listOf(held.map(prop => ON_CONTROL[prop]))} on that <Control> ` +
8870
+ `instead.`);
8871
+ }
8872
+ return false;
8873
+ };
8822
8874
  const FieldLabelIdContext = createContext(undefined);
8823
8875
  /**
8824
8876
  * The id a labeled Field wants its single composed control to adopt (#495).
@@ -8953,6 +9005,7 @@ Checkbox.displayName = 'Checkbox';
8953
9005
  // picker bases read it so their right-side launcher gives way to that
8954
9006
  // spinner, which sits at the same edge, whether the `Control` is the one a
8955
9007
  // convenience input renders or one the caller wrapped around the input.
9008
+ // Autocomplete reads it for its clear button, which sits there too.
8956
9009
  // Kept apart from `FormContext`, whose contexts only report whether a `Field`
8957
9010
  // or `Control` is present. Internal; not part of the public API.
8958
9011
  const ControlLoadingContext = createContext(false);
@@ -9282,7 +9335,7 @@ const CheckboxesComponent = ({ label, labelSize, labelProps, horizontal, message
9282
9335
  }), [name, groupActive, currentValue, handleChange]);
9283
9336
  const checkboxesElement = (jsx("div", { className: wrapperClass, role: "group", "aria-labelledby": ariaLabelledBy, ...rest, children: jsx(CheckboxesProvider, { value: ctx, children: children }) }));
9284
9337
  let content = checkboxesElement;
9285
- if (!insideControl) {
9338
+ if (rendersOwnControl('Checkboxes', { insideControl })) {
9286
9339
  content = jsx(Control, { children: content });
9287
9340
  }
9288
9341
  if (ownField) {
@@ -9522,7 +9575,7 @@ const RadiosComponent = ({ label, labelSize, labelProps, horizontal, message, me
9522
9575
  }), [name, groupActive, currentValue, handleChange]);
9523
9576
  const radiosElement = (jsx("div", { className: wrapperClass, role: "radiogroup", "aria-labelledby": ariaLabelledBy, ...rest, children: jsx(RadiosProvider, { value: ctx, children: children }) }));
9524
9577
  let content = radiosElement;
9525
- if (!insideControl) {
9578
+ if (rendersOwnControl('Radios', { insideControl })) {
9526
9579
  content = jsx(Control, { children: content });
9527
9580
  }
9528
9581
  if (ownField) {
@@ -10056,7 +10109,7 @@ const Slider = forwardRef((props, ref) => {
10056
10109
  '--slider-output-arrow-offset': `calc(50% - ${nudgeXHigh}px)`,
10057
10110
  }, children: formatValue(currentRange[1]) })), renderTicks()] }));
10058
10111
  let content = sliderElement;
10059
- if (!insideControl) {
10112
+ if (rendersOwnControl('Slider', { insideControl })) {
10060
10113
  content = jsx(Control, { children: content });
10061
10114
  }
10062
10115
  if (ownField) {
@@ -10500,7 +10553,7 @@ const Rate = forwardRef(({ label, labelSize, labelProps, horizontal, message, me
10500
10553
  const rateCustomTextClass = usePrefixedClassNames('rate-custom-text');
10501
10554
  const rateElement = (jsxs("div", { ref: ref, className: combinedClasses, role: "radiogroup", "aria-label": ariaLabelledBy ? undefined : 'Rating', "aria-labelledby": ariaLabelledBy, "aria-valuenow": currentValue, "aria-valuemin": 0, "aria-valuemax": max, "aria-valuetext": ariaValueText, tabIndex: disabled ? -1 : 0, onKeyDown: handleKeyDown, ...rest, children: [jsx("div", { className: rateItemsClass, children: renderIcons() }), showScore && (jsx("span", { className: rateScoreClass, children: getScoreDisplay() })), text && jsx("span", { className: rateTextClass, children: text }), customText && (jsx("span", { className: rateCustomTextClass, children: customText })), name && (jsx("input", { type: "hidden", name: name, value: currentValue, form: form }))] }));
10502
10555
  let content = rateElement;
10503
- if (!insideControl) {
10556
+ if (rendersOwnControl('Rate', { insideControl })) {
10504
10557
  content = jsx(Control, { children: content });
10505
10558
  }
10506
10559
  if (ownField) {
@@ -10739,7 +10792,11 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
10739
10792
  [`is-${messageColor}`]: !!messageColor,
10740
10793
  });
10741
10794
  const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;
10742
- const autocompleteElement = (jsxs("div", { ref: containerRef, className: combinedClasses, ...rest, children: [jsxs("div", { className: controlClasses, children: [jsx("input", { ref: combinedRef, type: "text", className: inputClasses, id: controlId, value: inputValue, placeholder: placeholder, disabled: disabled, name: name, form: form, required: required, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, role: "combobox", "aria-expanded": isActive, "aria-haspopup": "listbox", "aria-autocomplete": "list", autoComplete: "off" }), clearable && inputValue && !disabled && (jsx("span", { className: iconRightClickableClass, onClick: handleClear, role: "button", "aria-label": "Clear", children: jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", width: "16", height: "16", children: [jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })), loading && (jsx("span", { className: iconRightClass, children: jsx("span", { className: loaderClass }) }))] }), isActive && (filteredData.length > 0 || empty) && (jsx("div", { className: dropdownMenuClasses, children: jsxs("div", { ref: dropdownRef, className: dropdownContentClass, style: { maxHeight: `${maxHeight}px`, overflowY: 'auto' }, role: "listbox", onScroll: handleDropdownScroll, children: [header && jsx("div", { className: dropdownHeaderClass, children: header }), filteredData.length > 0 ? (filteredData.map((item, index) => {
10795
+ // The clear button gives way to a spinner at its spot: the `loading` one,
10796
+ // or that of a loading Control this sits in.
10797
+ const controlLoading = useControlLoading();
10798
+ const showClear = clearable && !!inputValue && !disabled && !loading && !controlLoading;
10799
+ const autocompleteElement = (jsxs("div", { ref: containerRef, className: combinedClasses, ...rest, children: [jsxs("div", { className: controlClasses, children: [jsx("input", { ref: combinedRef, type: "text", className: inputClasses, id: controlId, value: inputValue, placeholder: placeholder, disabled: disabled, name: name, form: form, required: required, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, role: "combobox", "aria-expanded": isActive, "aria-haspopup": "listbox", "aria-autocomplete": "list", autoComplete: "off" }), showClear && (jsx("button", { type: "button", className: iconRightClickableClass, onClick: handleClear, "aria-label": "Clear", children: jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", width: "16", height: "16", "aria-hidden": "true", children: [jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })), loading && (jsx("span", { className: iconRightClass, children: jsx("span", { className: loaderClass }) }))] }), isActive && (filteredData.length > 0 || empty) && (jsx("div", { className: dropdownMenuClasses, children: jsxs("div", { ref: dropdownRef, className: dropdownContentClass, style: { maxHeight: `${maxHeight}px`, overflowY: 'auto' }, role: "listbox", onScroll: handleDropdownScroll, children: [header && jsx("div", { className: dropdownHeaderClass, children: header }), filteredData.length > 0 ? (filteredData.map((item, index) => {
10743
10800
  const isDisabled = typeof item !== 'string' && item.disabled;
10744
10801
  const isHighlighted = index === highlightedIndex;
10745
10802
  const itemClasses = prefixedClassNames(classPrefix, 'dropdown-item', {
@@ -11247,6 +11304,21 @@ fieldClassName, controlClassName,
11247
11304
  horizontal,
11248
11305
  fieldClassName,
11249
11306
  });
11307
+ const ownControl = rendersOwnControl('Input', {
11308
+ insideControl,
11309
+ isLoading,
11310
+ iconLeft,
11311
+ iconLeftName,
11312
+ iconLeftSize,
11313
+ iconRight,
11314
+ iconRightName,
11315
+ iconRightSize,
11316
+ hasIconsLeft,
11317
+ hasIconsRight,
11318
+ isExpanded,
11319
+ controlSize,
11320
+ controlClassName,
11321
+ });
11250
11322
  const { controlId, fieldLabelProps } = useAutoLabelId({
11251
11323
  label,
11252
11324
  id: inputProps.id,
@@ -11257,7 +11329,7 @@ fieldClassName, controlClassName,
11257
11329
  [`is-${messageColor}`]: !!messageColor,
11258
11330
  });
11259
11331
  let content = jsx(InputBase, { ref: ref, id: controlId, ...inputProps });
11260
- if (!insideControl) {
11332
+ if (ownControl) {
11261
11333
  content = (jsx(Control, { iconLeft: iconLeft, iconRight: iconRight, iconLeftName: iconLeftName, iconRightName: iconRightName, iconLeftSize: iconLeftSize, iconRightSize: iconRightSize, hasIconsLeft: hasIconsLeft, hasIconsRight: hasIconsRight, isLoading: isLoading, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
11262
11334
  }
11263
11335
  const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;
@@ -11305,6 +11377,17 @@ fieldClassName, controlClassName,
11305
11377
  horizontal,
11306
11378
  fieldClassName,
11307
11379
  });
11380
+ // Not isLoading, which the select draws itself and so keeps in a Control.
11381
+ const ownControl = rendersOwnControl('Select', {
11382
+ insideControl,
11383
+ iconLeft,
11384
+ iconLeftName,
11385
+ iconLeftSize,
11386
+ hasIconsLeft,
11387
+ isExpanded,
11388
+ controlSize,
11389
+ controlClassName,
11390
+ });
11308
11391
  const { controlId, fieldLabelProps } = useAutoLabelId({
11309
11392
  label,
11310
11393
  id: selectProps.id,
@@ -11315,7 +11398,7 @@ fieldClassName, controlClassName,
11315
11398
  [`is-${messageColor}`]: !!messageColor,
11316
11399
  });
11317
11400
  let content = jsx(SelectBase, { ref: ref, id: controlId, ...selectProps });
11318
- if (!insideControl) {
11401
+ if (ownControl) {
11319
11402
  content = (jsx(Control, { iconLeft: iconLeft, iconLeftName: iconLeftName, iconLeftSize: iconLeftSize, hasIconsLeft: hasIconsLeft, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
11320
11403
  }
11321
11404
  const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;
@@ -11364,6 +11447,12 @@ fieldClassName, controlClassName,
11364
11447
  horizontal,
11365
11448
  fieldClassName,
11366
11449
  });
11450
+ const ownControl = rendersOwnControl('TextArea', {
11451
+ insideControl,
11452
+ isLoading: controlIsLoading,
11453
+ controlSize,
11454
+ controlClassName,
11455
+ });
11367
11456
  const { controlId, fieldLabelProps } = useAutoLabelId({
11368
11457
  label,
11369
11458
  id: textAreaProps.id,
@@ -11374,7 +11463,7 @@ fieldClassName, controlClassName,
11374
11463
  [`is-${messageColor}`]: !!messageColor,
11375
11464
  });
11376
11465
  let content = jsx(TextAreaBase, { ref: ref, id: controlId, ...textAreaProps });
11377
- if (!insideControl) {
11466
+ if (ownControl) {
11378
11467
  content = (jsx(Control, { isLoading: controlIsLoading, size: controlSize, className: controlClassName, children: content }));
11379
11468
  }
11380
11469
  const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;
@@ -12006,10 +12095,11 @@ function nearestEnabled(i, disabled) {
12006
12095
  return i;
12007
12096
  }
12008
12097
  /**
12009
- * The year list's keyboard when it is the selection surface: arrows move a
12010
- * year or a row, Home/End go to the list's ends. Returns the year to start
12011
- * from and the direction to search past disabled years, or `null` for a key
12012
- * the list doesn't handle.
12098
+ * The year list's keyboard, as the selection surface and as navigation:
12099
+ * arrows move a year or a row, Home/End go to the list's ends. Returns the
12100
+ * year to start from and the direction to search past disabled years, or
12101
+ * `null` for a key the list doesn't handle. As navigation no year is
12102
+ * disabled, so only the year is used.
12013
12103
  */
12014
12104
  function yearStep(key, focusedYear, years) {
12015
12105
  switch (key) {
@@ -12270,8 +12360,8 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
12270
12360
  // year is disabled the stop moves to the nearest enabled year in the list,
12271
12361
  // and focusing it makes it the focused year. As the selection surface a
12272
12362
  // year outside a non-empty list starts from the end it fell past, so the
12273
- // list keeps a stop. As navigation the stop marks the current year, so a
12274
- // year outside the list marks no option.
12363
+ // list keeps a stop. As navigation it marks the current year, so a year
12364
+ // outside the list marks no option, and the stop itself follows focus.
12275
12365
  const tabStopYear = useMemo(() => {
12276
12366
  let i = yearList.indexOf(focusedYear);
12277
12367
  if (i < 0) {
@@ -12281,6 +12371,12 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
12281
12371
  }
12282
12372
  return yearList[nearestEnabled(i, yearList.map(y => disabledYears.has(y)))];
12283
12373
  }, [yearList, focusedYear, disabledYears, isYearGranularity]);
12374
+ // As navigation the keys move focus and leave the focused year alone, so
12375
+ // the tab stop goes to the year that last had focus while it is listed,
12376
+ // and Tab leaves the list rather than landing on the current year. It
12377
+ // starts over each time the list opens.
12378
+ const [navYear, setNavYear] = useState(null);
12379
+ const listTabStop = navYear !== null && yearList.includes(navYear) ? navYear : tabStopYear;
12284
12380
  // Which of the focused year's months have no selectable day.
12285
12381
  const disabledMonths = useMemo(() => granularity === 'month'
12286
12382
  ? Array.from({ length: 12 }, (_, m) => isMonthUnselectable(makeDate(focusedYear, m)))
@@ -12316,19 +12412,39 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
12316
12412
  }
12317
12413
  }, [yearList, disabledYears, onFocusedDateChange, min, max]);
12318
12414
  const handleYearListKeyDown = useCallback((e) => {
12319
- if (!isYearGranularity) {
12320
- // As navigation, Escape returns to the grid the list was opened from.
12321
- if (e.key === 'Escape') {
12322
- e.preventDefault();
12323
- setView(baseView);
12324
- }
12415
+ if (isYearGranularity) {
12416
+ const step = yearStep(e.key, focusedYear, yearList);
12417
+ if (!step)
12418
+ return;
12419
+ e.preventDefault();
12420
+ moveYearFocus(step[0], step[1]);
12325
12421
  return;
12326
12422
  }
12327
- const step = yearStep(e.key, focusedYear, yearList);
12423
+ // As navigation, Escape returns to the grid the list was opened from.
12424
+ // It stops there, so a popover around the calendar stays open and only
12425
+ // a second Escape closes it.
12426
+ if (e.key === 'Escape') {
12427
+ e.preventDefault();
12428
+ e.stopPropagation();
12429
+ setView(baseView);
12430
+ return;
12431
+ }
12432
+ // The other keys move DOM focus from the year that has it and leave
12433
+ // the focused date alone. The list is centred on that date, so moving
12434
+ // it would reflow the list under the keyboard, and it would turn the
12435
+ // grid behind the list before anything is picked. Enter or Space
12436
+ // clicks the year with focus, which picks it.
12437
+ const options = Array.from(e.currentTarget.querySelectorAll('[role="option"]'));
12438
+ const from = options.indexOf(e.target);
12439
+ if (from < 0)
12440
+ return;
12441
+ const step = yearStep(e.key, yearList[from], yearList);
12328
12442
  if (!step)
12329
12443
  return;
12330
12444
  e.preventDefault();
12331
- moveYearFocus(step[0], step[1]);
12445
+ // The listed years run one apart, so a year's index is its distance
12446
+ // from the first. A step past either end finds no option.
12447
+ options[step[0] - yearList[0]]?.focus();
12332
12448
  }, [isYearGranularity, baseView, focusedYear, yearList, moveYearFocus]);
12333
12449
  // The day or month grid takes focus as the popover opens on it, or comes
12334
12450
  // back to it from the year list.
@@ -12399,7 +12515,10 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
12399
12515
  list.querySelector('[data-focused-year="true"]')?.focus();
12400
12516
  }, [isYearGranularity, focusedYear]);
12401
12517
  const navArrow = (d) => (jsx("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", children: jsx("path", { d: d, fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) }));
12402
- return (jsxs("div", { className: classNames(calendarClass, className), id: id, children: [jsxs("div", { className: headerClass, children: [isYearGranularity ? (jsx("span", { className: monthLabelClass, id: labelId, children: monthLabel })) : (jsxs("button", { type: "button", className: monthTriggerClass, "aria-haspopup": "listbox", "aria-expanded": view === 'years', onClick: () => setView(v => (v === 'years' ? baseView : 'years')), children: [jsx("span", { className: monthLabelClass, id: labelId, "aria-live": "polite", children: monthLabel }), jsx("svg", { className: monthCaretClass, viewBox: "0 0 12 12", width: "10", height: "10", "aria-hidden": "true", children: jsx("path", { d: "M2 4l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) })] })), !isYearGranularity && (jsxs("div", { className: navGroupClass, children: [jsx("button", { type: "button", className: navPrevClass, "aria-label": isDayGranularity ? t.prevMonth : t.prevYear, disabled: prevDisabled || view !== baseView, onClick: () => onFocusedDateChange(prevAnchor), children: navArrow('M10 3l-5 5 5 5') }), jsx("button", { type: "button", className: navNextClass, "aria-label": isDayGranularity ? t.nextMonth : t.nextYear, disabled: nextDisabled || view !== baseView, onClick: () => onFocusedDateChange(nextAnchor), children: navArrow('M6 3l5 5-5 5') })] }))] }), view === 'days' && (jsxs(Fragment, { children: [jsx("div", { className: dayNamesRowClass, "aria-hidden": "true", children: computedDayNames.map((name, i) => (jsx("div", { className: dayNameClass, children: name }, i))) }), jsx("div", { ref: gridRef, role: "grid", "aria-labelledby": labelId, className: gridClass, onKeyDown: handleKeyDown, ...gridFocusHandlers, children: cells.map(cell => {
12518
+ return (jsxs("div", { className: classNames(calendarClass, className), id: id, children: [jsxs("div", { className: headerClass, children: [isYearGranularity ? (jsx("span", { className: monthLabelClass, id: labelId, children: monthLabel })) : (jsxs("button", { type: "button", className: monthTriggerClass, "aria-haspopup": "listbox", "aria-expanded": view === 'years', onClick: () => {
12519
+ setNavYear(null);
12520
+ setView(v => (v === 'years' ? baseView : 'years'));
12521
+ }, children: [jsx("span", { className: monthLabelClass, id: labelId, "aria-live": "polite", children: monthLabel }), jsx("svg", { className: monthCaretClass, viewBox: "0 0 12 12", width: "10", height: "10", "aria-hidden": "true", children: jsx("path", { d: "M2 4l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) })] })), !isYearGranularity && (jsxs("div", { className: navGroupClass, children: [jsx("button", { type: "button", className: navPrevClass, "aria-label": isDayGranularity ? t.prevMonth : t.prevYear, disabled: prevDisabled || view !== baseView, onClick: () => onFocusedDateChange(prevAnchor), children: navArrow('M10 3l-5 5 5 5') }), jsx("button", { type: "button", className: navNextClass, "aria-label": isDayGranularity ? t.nextMonth : t.nextYear, disabled: nextDisabled || view !== baseView, onClick: () => onFocusedDateChange(nextAnchor), children: navArrow('M6 3l5 5-5 5') })] }))] }), view === 'days' && (jsxs(Fragment, { children: [jsx("div", { className: dayNamesRowClass, "aria-hidden": "true", children: computedDayNames.map((name, i) => (jsx("div", { className: dayNameClass, children: name }, i))) }), jsx("div", { ref: gridRef, role: "grid", "aria-labelledby": labelId, className: gridClass, onKeyDown: handleKeyDown, ...gridFocusHandlers, children: cells.map(cell => {
12403
12522
  const disabled = isDateUnselectable(cell.date);
12404
12523
  const isSelected = !!value && isSameDay(value, cell.date);
12405
12524
  const isFocused = isSameDay(cell.date, tabStopDay);
@@ -12459,10 +12578,16 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
12459
12578
  'is-today': isToday,
12460
12579
  'is-disabled': disabled,
12461
12580
  });
12462
- return (jsx("button", { type: "button", role: "option", "aria-selected": isSelected, "aria-disabled": isYearGranularity ? disabled : undefined, "aria-current": isYearGranularity && isToday ? 'date' : undefined, "data-focused-year": isFocused ? 'true' : undefined, tabIndex: isFocused ? 0 : -1, disabled: disabled, className: cellCls, onFocus: () => {
12463
- // Reached by Tab while the focused year is disabled, or by
12464
- // pointer: keys move on from here.
12465
- if (isYearGranularity && year !== focusedYear) {
12581
+ return (jsx("button", { type: "button", role: "option", "aria-selected": isSelected, "aria-disabled": isYearGranularity ? disabled : undefined, "aria-current": isYearGranularity && isToday ? 'date' : undefined, "data-focused-year": isFocused ? 'true' : undefined, tabIndex: year === listTabStop ? 0 : -1, disabled: disabled, className: cellCls, onFocus: () => {
12582
+ // As navigation, focusing a year by key or pointer only
12583
+ // moves the tab stop: nothing is committed until a year is
12584
+ // picked. As the selection surface it is reached by Tab
12585
+ // while the focused year is disabled, or by pointer, and
12586
+ // keys move on from here.
12587
+ if (!isYearGranularity) {
12588
+ setNavYear(year);
12589
+ }
12590
+ else if (year !== focusedYear) {
12466
12591
  onFocusedDateChange(clampDate(makeDate(year), min, max));
12467
12592
  }
12468
12593
  }, onClick: () => isYearGranularity
@@ -13608,7 +13733,8 @@ DateInputBase.displayName = 'DateInputBase';
13608
13733
  * shouldDisableDate={d => d.getDay() === 0 || d.getDay() === 6}
13609
13734
  * />
13610
13735
  */
13611
- const DateInput = forwardRef(({ label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'calendar', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps }, ref) => {
13736
+ const DateInput = forwardRef((props, ref) => {
13737
+ const { label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'calendar', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps } = props;
13612
13738
  const insideField = useInsideField();
13613
13739
  const insideControl = useInsideControl();
13614
13740
  const ownField = rendersOwnField('DateInput', {
@@ -13619,6 +13745,27 @@ const DateInput = forwardRef(({ label, labelSize, labelProps, horizontal, iconLe
13619
13745
  horizontal,
13620
13746
  fieldClassName,
13621
13747
  });
13748
+ // Inline mode renders a bare calendar with no input, so the Control's
13749
+ // icon-left container has nothing to anchor to, and it renders no
13750
+ // Control inside one or out. The left icon is checked as the caller
13751
+ // passed it: the default glyph is this component's own choice, not a
13752
+ // prop the caller set.
13753
+ const ownControl = rendersOwnControl('DateInput', {
13754
+ insideControl,
13755
+ inline: baseProps.inline,
13756
+ isLoading,
13757
+ iconLeft,
13758
+ iconLeftName: props.iconLeftName,
13759
+ iconLeftSize,
13760
+ iconRight,
13761
+ iconRightName,
13762
+ iconRightSize,
13763
+ hasIconsLeft,
13764
+ hasIconsRight,
13765
+ isExpanded,
13766
+ controlSize,
13767
+ controlClassName,
13768
+ });
13622
13769
  const { controlId, fieldLabelProps } = useAutoLabelId({
13623
13770
  label,
13624
13771
  id: baseProps.id,
@@ -13632,9 +13779,7 @@ const DateInput = forwardRef(({ label, labelSize, labelProps, horizontal, iconLe
13632
13779
  // The base hides its launcher while the Control it sits in is loading,
13633
13780
  // whether that is the one rendered below or an enclosing one.
13634
13781
  let content = (jsx(DateInputBase, { ref: ref, id: controlId, ...baseProps }));
13635
- // Inline mode renders a bare calendar with no input, so the Control's
13636
- // icon-left container has nothing to anchor to. Skip the Control wrap.
13637
- if (!insideControl && !baseProps.inline) {
13782
+ if (ownControl) {
13638
13783
  content = (jsx(Control, { iconLeft: iconLeft, iconRight: iconRight, iconLeftName: iconLeftName, iconRightName: iconRightName, iconLeftSize: iconLeftSize, iconRightSize: iconRightSize, hasIconsLeft: hasIconsLeft || !!iconLeftName, hasIconsRight: hasIconsRight, isLoading: isLoading, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
13639
13784
  }
13640
13785
  const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;
@@ -14716,7 +14861,8 @@ TimeInputBase.displayName = 'TimeInputBase';
14716
14861
  * @example
14717
14862
  * <TimeInput label="Slot" hourFormat="12" incrementMinutes={15} />
14718
14863
  */
14719
- const TimeInput = forwardRef(({ label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'clock', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps }, ref) => {
14864
+ const TimeInput = forwardRef((props, ref) => {
14865
+ const { label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'clock', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps } = props;
14720
14866
  const insideField = useInsideField();
14721
14867
  const insideControl = useInsideControl();
14722
14868
  const ownField = rendersOwnField('TimeInput', {
@@ -14727,6 +14873,27 @@ const TimeInput = forwardRef(({ label, labelSize, labelProps, horizontal, iconLe
14727
14873
  horizontal,
14728
14874
  fieldClassName,
14729
14875
  });
14876
+ // Inline mode renders a bare panel with no input, so the Control's
14877
+ // icon-left container has nothing to anchor to, and it renders no
14878
+ // Control inside one or out. The left icon is checked as the caller
14879
+ // passed it: the default glyph is this component's own choice, not a
14880
+ // prop the caller set.
14881
+ const ownControl = rendersOwnControl('TimeInput', {
14882
+ insideControl,
14883
+ inline: baseProps.inline,
14884
+ isLoading,
14885
+ iconLeft,
14886
+ iconLeftName: props.iconLeftName,
14887
+ iconLeftSize,
14888
+ iconRight,
14889
+ iconRightName,
14890
+ iconRightSize,
14891
+ hasIconsLeft,
14892
+ hasIconsRight,
14893
+ isExpanded,
14894
+ controlSize,
14895
+ controlClassName,
14896
+ });
14730
14897
  const { controlId, fieldLabelProps } = useAutoLabelId({
14731
14898
  label,
14732
14899
  id: baseProps.id,
@@ -14740,9 +14907,7 @@ const TimeInput = forwardRef(({ label, labelSize, labelProps, horizontal, iconLe
14740
14907
  // The base hides its launcher while the Control it sits in is loading,
14741
14908
  // whether that is the one rendered below or an enclosing one.
14742
14909
  let content = (jsx(TimeInputBase, { ref: ref, id: controlId, ...baseProps }));
14743
- // Inline mode renders a bare panel with no input, so the Control's
14744
- // icon-left container has nothing to anchor to. Skip the Control wrap.
14745
- if (!insideControl && !baseProps.inline) {
14910
+ if (ownControl) {
14746
14911
  content = (jsx(Control, { iconLeft: iconLeft, iconRight: iconRight, iconLeftName: iconLeftName, iconRightName: iconRightName, iconLeftSize: iconLeftSize, iconRightSize: iconRightSize, hasIconsLeft: hasIconsLeft || !!iconLeftName, hasIconsRight: hasIconsRight, isLoading: isLoading, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
14747
14912
  }
14748
14913
  const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;
@@ -15037,7 +15202,8 @@ DateTimeInputBase.displayName = 'DateTimeInputBase';
15037
15202
  * shouldDisableDate={d => d.getDay() === 0 || d.getDay() === 6}
15038
15203
  * />
15039
15204
  */
15040
- const DateTimeInput = forwardRef(({ label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'calendar-alt', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps }, ref) => {
15205
+ const DateTimeInput = forwardRef((props, ref) => {
15206
+ const { label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'calendar-alt', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps } = props;
15041
15207
  const insideField = useInsideField();
15042
15208
  const insideControl = useInsideControl();
15043
15209
  const ownField = rendersOwnField('DateTimeInput', {
@@ -15048,6 +15214,27 @@ const DateTimeInput = forwardRef(({ label, labelSize, labelProps, horizontal, ic
15048
15214
  horizontal,
15049
15215
  fieldClassName,
15050
15216
  });
15217
+ // Inline mode renders a bare picker with no input, so the Control's
15218
+ // icon-left container has nothing to anchor to, and it renders no
15219
+ // Control inside one or out. The left icon is checked as the caller
15220
+ // passed it: the default glyph is this component's own choice, not a
15221
+ // prop the caller set.
15222
+ const ownControl = rendersOwnControl('DateTimeInput', {
15223
+ insideControl,
15224
+ inline: baseProps.inline,
15225
+ isLoading,
15226
+ iconLeft,
15227
+ iconLeftName: props.iconLeftName,
15228
+ iconLeftSize,
15229
+ iconRight,
15230
+ iconRightName,
15231
+ iconRightSize,
15232
+ hasIconsLeft,
15233
+ hasIconsRight,
15234
+ isExpanded,
15235
+ controlSize,
15236
+ controlClassName,
15237
+ });
15051
15238
  const { controlId, fieldLabelProps } = useAutoLabelId({
15052
15239
  label,
15053
15240
  id: baseProps.id,
@@ -15061,9 +15248,7 @@ const DateTimeInput = forwardRef(({ label, labelSize, labelProps, horizontal, ic
15061
15248
  // The base hides its launcher while the Control it sits in is loading,
15062
15249
  // whether that is the one rendered below or an enclosing one.
15063
15250
  let content = (jsx(DateTimeInputBase, { ref: ref, id: controlId, ...baseProps }));
15064
- // Inline mode renders a bare picker with no input, so the Control's
15065
- // icon-left container has nothing to anchor to. Skip the Control wrap.
15066
- if (!insideControl && !baseProps.inline) {
15251
+ if (ownControl) {
15067
15252
  content = (jsx(Control, { iconLeft: iconLeft, iconRight: iconRight, iconLeftName: iconLeftName, iconRightName: iconRightName, iconLeftSize: iconLeftSize, iconRightSize: iconRightSize, hasIconsLeft: hasIconsLeft || !!iconLeftName, hasIconsRight: hasIconsRight, isLoading: isLoading, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
15068
15253
  }
15069
15254
  const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;