@allxsmith/bestax-bulma 5.25.0 → 5.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -12192,7 +12192,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
12192
12192
  Calendar.displayName = 'Calendar';
12193
12193
 
12194
12194
  const isBrowser = typeof window !== 'undefined';
12195
- const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'bottom-left', appendToBody = false, className, trapFocus = true, closeOnClickOutside = true, closeOnEscape = true, ariaLabel, ariaLabelledBy, role = 'dialog', id, }) => {
12195
+ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'bottom-left', appendToBody = false, className, trapFocus = true, closeOnClickOutside = true, closeOnEscape = true, ariaLabel, ariaLabelledBy, role = 'dialog', id, restoreFocusRef, }) => {
12196
12196
  const panelRef = React.useRef(null);
12197
12197
  const resolved = useAnchoredPosition(anchorRef, panelRef, {
12198
12198
  active: isOpen,
@@ -12229,7 +12229,10 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
12229
12229
  document.addEventListener('keydown', handler);
12230
12230
  return () => document.removeEventListener('keydown', handler);
12231
12231
  }, [isOpen, closeOnEscape, onClose]);
12232
- useFocusTrap(panelRef, { active: isOpen && trapFocus });
12232
+ useFocusTrap(panelRef, {
12233
+ active: isOpen && trapFocus,
12234
+ restoreFocus: restoreFocusRef ?? true,
12235
+ });
12233
12236
  const panelClass = usePrefixedClassNames('picker-popover', {
12234
12237
  'is-active': isOpen,
12235
12238
  [`is-${resolved.position}`]: true,
@@ -12634,6 +12637,37 @@ function useSegmentedEntry(params) {
12634
12637
  // change. Kept in a ref because key handlers read+update synchronously
12635
12638
  // within the same event tick.
12636
12639
  const typedDigitsRef = React.useRef('');
12640
+ // The text focus seeded an empty picker with, until the user edits it or
12641
+ // leaves. While the text still matches it, nothing has been typed.
12642
+ const seedRef = React.useRef(null);
12643
+ // Whether the input's current focus is the one a closing popover handed
12644
+ // back, until focus leaves the picker.
12645
+ const handedBackRef = React.useRef(false);
12646
+ // A seed the popover never turned into a value closes with it, wherever
12647
+ // focus goes, so a dismissed empty field stays empty.
12648
+ //
12649
+ // As the popover closes, its focus trap hands focus back to the input. That
12650
+ // focus is not the user arriving: it must not open the popover again under
12651
+ // `openOnFocus`, or seed an empty picker that leaving would then commit.
12652
+ // `closingRef` is set in the layout phase of the commit that closes it and
12653
+ // cleared by that commit's effects, and React runs a commit's effect
12654
+ // cleanups, the trap's among them, before any of its effects, so a focus
12655
+ // in between is the one handed back.
12656
+ const closingRef = React.useRef(false);
12657
+ const wasOpenRef = React.useRef(isOpen);
12658
+ React.useLayoutEffect(() => {
12659
+ if (wasOpenRef.current && !isOpen) {
12660
+ closingRef.current = true;
12661
+ if (!value && seedRef.current !== null && text === seedRef.current) {
12662
+ setText('');
12663
+ }
12664
+ seedRef.current = null;
12665
+ }
12666
+ wasOpenRef.current = isOpen;
12667
+ }, [isOpen, value, text, setText]);
12668
+ React.useEffect(() => {
12669
+ closingRef.current = false;
12670
+ }, [isOpen]);
12637
12671
  // Segment mode is unavailable for disabled / read-only / non-editable
12638
12672
  // pickers and for Intl-options / variable-width formats (segmentMap null).
12639
12673
  const segmentEditable = !!segmentMap && !!editable && !disabled && !readOnly;
@@ -12690,6 +12724,7 @@ function useSegmentedEntry(params) {
12690
12724
  // segment mode is inactive.
12691
12725
  if (activeSegmentIdx !== null && segmentEditable)
12692
12726
  return;
12727
+ seedRef.current = null;
12693
12728
  setText(e.target.value);
12694
12729
  }, [activeSegmentIdx, segmentEditable, setText]);
12695
12730
  const handleBlur = React.useCallback((e) => {
@@ -12707,6 +12742,16 @@ function useSegmentedEntry(params) {
12707
12742
  }
12708
12743
  setActiveSegmentIdx(null);
12709
12744
  typedDigitsRef.current = '';
12745
+ seedRef.current = null;
12746
+ const handedBack = handedBackRef.current;
12747
+ handedBackRef.current = false;
12748
+ // Leaving after a dismiss commits only what was typed since. Text that
12749
+ // still shows the value wasn't, and re-parsing it could drop what the
12750
+ // format leaves out, such as seconds.
12751
+ if (handedBack && value && text === formatFn(value, format, locale)) {
12752
+ onBlur?.(e);
12753
+ return;
12754
+ }
12710
12755
  const parsed = tryParse(text);
12711
12756
  if (parsed && isAllowed(parsed)) {
12712
12757
  commitValue(parsed);
@@ -12729,13 +12774,19 @@ function useSegmentedEntry(params) {
12729
12774
  onBlur,
12730
12775
  ]);
12731
12776
  const handleFocus = React.useCallback((e) => {
12732
- if (openOnFocus && popover && !disabled && !readOnly)
12777
+ const handedBack = closingRef.current;
12778
+ handedBackRef.current = handedBack;
12779
+ if (!handedBack && openOnFocus && popover && !disabled && !readOnly) {
12733
12780
  setOpen(true);
12781
+ }
12734
12782
  // Enter segment mode: prime an initial value (so editing works even when
12735
- // empty), seed the text, and highlight the first editable segment.
12783
+ // empty), seed the text, and highlight the first editable segment. The
12784
+ // segment base doesn't need the seed, so focus handed back skips it.
12736
12785
  if (segmentEditable && segmentMap) {
12737
- if (!value) {
12738
- setText(formatFn(makeBaseDate(), format, locale));
12786
+ if (!value && !handedBack) {
12787
+ const seed = formatFn(makeBaseDate(), format, locale);
12788
+ seedRef.current = seed;
12789
+ setText(seed);
12739
12790
  }
12740
12791
  typedDigitsRef.current = '';
12741
12792
  setActiveSegmentIdx(segmentMap.editable[0]);
@@ -13202,7 +13253,7 @@ const DateInputBase = React.forwardRef((props, ref) => {
13202
13253
  return (jsxRuntime.jsxs("div", { ref: containerRef, className: classNames(containerClass, bulmaHelperClasses, className), ...cleanRest, children: [jsxRuntime.jsx("input", { ref: combinedRef, type: "text", role: "combobox", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": popoverId, autoComplete: "off", className: inputClass, value: text, placeholder: placeholder, disabled: disabled, readOnly: inputReadOnlyAttr, name: name, form: form, required: required, id: id, ...inputHandlers }), popover && triggerIcon && (jsxRuntime.jsx("button", { type: "button", className: triggerClass, onClick: () => {
13203
13254
  if (canOpen)
13204
13255
  setOpen(!open);
13205
- }, disabled: !canOpen, "aria-label": chooseLabel, "aria-haspopup": "dialog", "aria-controls": popoverId, "aria-expanded": open, tabIndex: canOpen ? 0 : -1, children: jsxRuntime.jsx(Icon, { name: triggerIconName, size: size }) })), popover && (jsxRuntime.jsx(PickerPopover, { isOpen: open, onClose: handlePopoverClose, anchorRef: containerRef, position: position, appendToBody: appendToBody, ariaLabel: chooseLabel, id: popoverId, children: calendar }))] }));
13256
+ }, disabled: !canOpen, "aria-label": chooseLabel, "aria-haspopup": "dialog", "aria-controls": popoverId, "aria-expanded": open, tabIndex: canOpen ? 0 : -1, children: jsxRuntime.jsx(Icon, { name: triggerIconName, size: size }) })), popover && (jsxRuntime.jsx(PickerPopover, { isOpen: open, onClose: handlePopoverClose, anchorRef: containerRef, position: position, appendToBody: appendToBody, ariaLabel: chooseLabel, id: popoverId, restoreFocusRef: inputRef, children: calendar }))] }));
13206
13257
  });
13207
13258
  DateInputBase.displayName = 'DateInputBase';
13208
13259
 
@@ -14175,9 +14226,6 @@ const TimeInputBase = React.forwardRef((props, ref) => {
14175
14226
  const mobileFooterClass = usePrefixedClassNames('timeinput-footer-mobile');
14176
14227
  const mobileFooterResetClass = usePrefixedClassNames('timeinput-footer-reset');
14177
14228
  const mobileFooterDoneClass = usePrefixedClassNames('timeinput-footer-done');
14178
- // Set from the moment the popover starts to close until the close has
14179
- // committed. See `setOpenFromInput`.
14180
- const closingRef = React.useRef(false);
14181
14229
  const setOpen = React.useCallback((next) => {
14182
14230
  setOpenState(prev => {
14183
14231
  if (prev === next)
@@ -14187,26 +14235,11 @@ const TimeInputBase = React.forwardRef((props, ref) => {
14187
14235
  onOpen?.();
14188
14236
  }
14189
14237
  else {
14190
- closingRef.current = true;
14191
14238
  onClose?.();
14192
14239
  }
14193
14240
  return next;
14194
14241
  });
14195
14242
  }, [onOpen, onClose, value]);
14196
- // React runs a commit's effect cleanups before any of its effects, so on a
14197
- // close this runs after the popover's focus trap has handed focus back.
14198
- React.useEffect(() => {
14199
- closingRef.current = false;
14200
- }, [open]);
14201
- // As the popover closes, its focus trap hands focus back to the input.
14202
- // That focus is not the user arriving, so under `openOnFocus` it must not
14203
- // open the popover again. The input asks to open only on focus, click or
14204
- // ArrowDown, so a request while a close commits can only be that one.
14205
- const setOpenFromInput = React.useCallback((next) => {
14206
- if (next && closingRef.current)
14207
- return;
14208
- setOpen(next);
14209
- }, [setOpen]);
14210
14243
  React.useEffect(() => {
14211
14244
  setText(value ? formatTime(value, defaultFormat, locale) : '');
14212
14245
  }, [value, defaultFormat, locale]);
@@ -14263,7 +14296,7 @@ const TimeInputBase = React.forwardRef((props, ref) => {
14263
14296
  openOnFocus,
14264
14297
  closeOnSelect,
14265
14298
  isOpen: open,
14266
- setOpen: setOpenFromInput,
14299
+ setOpen,
14267
14300
  inputRef,
14268
14301
  containerRef,
14269
14302
  onFocus,
@@ -14315,7 +14348,7 @@ const TimeInputBase = React.forwardRef((props, ref) => {
14315
14348
  return (jsxRuntime.jsxs("div", { ref: containerRef, className: classNames(containerClass, bulmaHelperClasses, className), ...cleanRest, children: [jsxRuntime.jsx("input", { ref: combinedRef, type: "text", role: "combobox", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": popoverId, autoComplete: "off", className: inputClass, value: text, placeholder: placeholder, disabled: disabled, readOnly: inputReadOnlyAttr, name: name, form: form, required: required, id: id, ...inputHandlers }), popover && triggerIcon && (jsxRuntime.jsx("button", { type: "button", className: triggerClass, onClick: () => {
14316
14349
  if (canOpen)
14317
14350
  setOpen(!open);
14318
- }, disabled: !canOpen, "aria-label": t.chooseTime, "aria-haspopup": "dialog", "aria-controls": popoverId, "aria-expanded": open, tabIndex: canOpen ? 0 : -1, children: jsxRuntime.jsx(Icon, { name: triggerIconName, size: size }) })), popover && (jsxRuntime.jsx(PickerPopover, { isOpen: open, onClose: () => setOpen(false), anchorRef: containerRef, position: position, appendToBody: appendToBody, ariaLabel: t.chooseTime, id: popoverId, children: panel }))] }));
14351
+ }, disabled: !canOpen, "aria-label": t.chooseTime, "aria-haspopup": "dialog", "aria-controls": popoverId, "aria-expanded": open, tabIndex: canOpen ? 0 : -1, children: jsxRuntime.jsx(Icon, { name: triggerIconName, size: size }) })), popover && (jsxRuntime.jsx(PickerPopover, { isOpen: open, onClose: () => setOpen(false), anchorRef: containerRef, position: position, appendToBody: appendToBody, ariaLabel: t.chooseTime, id: popoverId, restoreFocusRef: inputRef, children: panel }))] }));
14319
14352
  });
14320
14353
  TimeInputBase.displayName = 'TimeInputBase';
14321
14354
 
@@ -14607,7 +14640,7 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
14607
14640
  return (jsxRuntime.jsxs("div", { ref: containerRef, className: classNames(containerClass, bulmaHelperClasses, className), ...cleanRest, children: [jsxRuntime.jsx("input", { ref: combinedRef, type: "text", role: "combobox", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": popoverId, autoComplete: "off", className: inputClass, value: text, placeholder: placeholder, disabled: disabled, readOnly: inputReadOnlyAttr, name: name, form: form, required: required, id: id, ...inputHandlers }), popover && triggerIcon && (jsxRuntime.jsx("button", { type: "button", className: triggerClass, onClick: () => {
14608
14641
  if (canOpen)
14609
14642
  setOpen(!open);
14610
- }, disabled: !canOpen, "aria-label": t.chooseDateTime, "aria-haspopup": "dialog", "aria-controls": popoverId, "aria-expanded": open, tabIndex: canOpen ? 0 : -1, children: jsxRuntime.jsx(Icon, { name: triggerIconName, size: size }) })), popover && (jsxRuntime.jsx(PickerPopover, { isOpen: open, onClose: () => setOpen(false), anchorRef: containerRef, position: position, appendToBody: appendToBody, ariaLabel: t.chooseDateTime, id: popoverId, children: panel }))] }));
14643
+ }, disabled: !canOpen, "aria-label": t.chooseDateTime, "aria-haspopup": "dialog", "aria-controls": popoverId, "aria-expanded": open, tabIndex: canOpen ? 0 : -1, children: jsxRuntime.jsx(Icon, { name: triggerIconName, size: size }) })), popover && (jsxRuntime.jsx(PickerPopover, { isOpen: open, onClose: () => setOpen(false), anchorRef: containerRef, position: position, appendToBody: appendToBody, ariaLabel: t.chooseDateTime, id: popoverId, restoreFocusRef: inputRef, children: panel }))] }));
14611
14644
  });
14612
14645
  DateTimeInputBase.displayName = 'DateTimeInputBase';
14613
14646
 
@@ -15300,17 +15333,20 @@ function cssVarToProp(varName) {
15300
15333
  .join('');
15301
15334
  }
15302
15335
  /**
15303
- * Prop names `cssVarToProp` would mint that are already `BulmaOtherProps`
15304
- * helper props: `--bulma-shadow` becomes `shadow` and `--bulma-radius` becomes
15305
- * `radius`. They stay out of `bulmaVarPropMap`, so on Theme each is the helper
15306
- * prop it is on every other component, and both variables are still reachable
15307
- * through `bulmaVars`.
15336
+ * Prop names `cssVarToProp` would mint that are already helper props:
15337
+ * `--bulma-shadow` becomes `shadow`, `--bulma-radius` becomes `radius` and
15338
+ * `--bulma-column-gap` becomes `columnGap`. They stay out of
15339
+ * `bulmaVarPropMap`, so on Theme each is the helper prop it is on every other
15340
+ * component, and every one of those variables is still reachable through
15341
+ * `bulmaVars`.
15308
15342
  *
15309
15343
  * `radius` was missed here once and set `--bulma-radius` while typed as the
15310
15344
  * helper (#694). It still writes that variable, so what it did then keeps
15311
- * working; see `themeRadiusVar`.
15345
+ * working; see `themeRadiusVar`. `columnGap` joined when the gap helpers
15346
+ * became shared helper props, and keeps its old variable route the same way;
15347
+ * see `themeColumnGapVar`.
15312
15348
  */
15313
- const helperPropNames = ['shadow', 'radius'];
15349
+ const helperPropNames = ['shadow', 'radius', 'columnGap'];
15314
15350
  /**
15315
15351
  * Mapping of camelCase prop names to their Bulma CSS variable counterparts,
15316
15352
  * minus the `helperPropNames` above.
@@ -15372,6 +15408,36 @@ const themeRadiusVar = (radius) => {
15372
15408
  `bulmaVars={{ '--bulma-radius': '${radius}' }} instead.`);
15373
15409
  return radius;
15374
15410
  };
15411
+ /**
15412
+ * What `columnGap` writes to `--bulma-column-gap` on a Theme, or `undefined`
15413
+ * when it writes nothing.
15414
+ *
15415
+ * Until the gap helpers were shared helper props, `columnGap` on Theme was
15416
+ * the camelCase prop Theme mints for `--bulma-column-gap`. It was never in
15417
+ * `ThemeProps`, so only untyped code reached it, but that code got the
15418
+ * columns gutter it asked for. It is the column gap helper now, as on every
15419
+ * other component, so a gap step goes to the helper and writes nothing here.
15420
+ * Any other non-empty string is still written as given, so a length such as
15421
+ * `'1rem'` keeps working, with a development warning pointing at `bulmaVars`.
15422
+ *
15423
+ * Only a string writes anything, for the reason `themeRadiusVar` gives: a
15424
+ * number or a boolean was never a usable length.
15425
+ */
15426
+ const themeColumnGapVar = (columnGap) => {
15427
+ if (typeof columnGap !== 'string' || columnGap === '') {
15428
+ return undefined;
15429
+ }
15430
+ if (validGaps.includes(columnGap)) {
15431
+ return undefined;
15432
+ }
15433
+ warnOnce('Theme:column-gap-variable', `[bestax-bulma] <Theme columnGap="${columnGap}">: setting ` +
15434
+ '--bulma-column-gap through the columnGap prop is deprecated and will ' +
15435
+ 'stop working in a future major version. On Theme, as on every other ' +
15436
+ 'component, columnGap is the column gap helper ' +
15437
+ `("${validGaps.join('", "')}"). Set the variable with ` +
15438
+ `bulmaVars={{ '--bulma-column-gap': '${columnGap}' }} instead.`);
15439
+ return columnGap;
15440
+ };
15375
15441
  /** The one `<style>` element every `isRoot` Theme writes into. */
15376
15442
  const ROOT_STYLE_ID = 'bestax-bulma-theme-vars';
15377
15443
  /**
@@ -15449,8 +15515,9 @@ const setRootThemeRules = (order, rules) => {
15449
15515
  * <Button color="primary">Themed</Button>
15450
15516
  * </Theme>
15451
15517
  */
15452
- const Theme = ({ bulmaVars = {}, children, className, isRoot = false, colorMode, radius, ...restProps }) => {
15518
+ const Theme = ({ bulmaVars = {}, children, className, isRoot = false, colorMode, radius, columnGap, ...restProps }) => {
15453
15519
  const radiusVar = themeRadiusVar(radius);
15520
+ const columnGapVar = themeColumnGapVar(columnGap);
15454
15521
  const radiusHelper = validRadii.includes(radius)
15455
15522
  ? radius
15456
15523
  : undefined;
@@ -15464,6 +15531,18 @@ const Theme = ({ bulmaVars = {}, children, className, isRoot = false, colorMode,
15464
15531
  `element, put radius="${radiusHelper}" on it. To change the radius ` +
15465
15532
  "across the page, set bulmaVars={{ '--bulma-radius': '…' }} instead.");
15466
15533
  }
15534
+ // The same for a gap step in `columnGap`, which reads like the columns
15535
+ // gutter it once set and, at the root, has no element for its class.
15536
+ const columnGapStep = typeof columnGap === 'number' ? String(columnGap) : columnGap;
15537
+ if (isRoot &&
15538
+ columnGapStep !== undefined &&
15539
+ validGaps.includes(columnGapStep)) {
15540
+ warnOnce('Theme:root-column-gap', `[bestax-bulma] <Theme isRoot columnGap="${columnGapStep}">: a root ` +
15541
+ `Theme has no wrapper element for is-column-gap-${columnGapStep}, ` +
15542
+ 'and a gap step sets no variable, so this does nothing. To change ' +
15543
+ 'the columns gutter across the page, set ' +
15544
+ "bulmaVars={{ '--bulma-column-gap': '…' }} instead.");
15545
+ }
15467
15546
  // Extract Bulma variable props from restProps
15468
15547
  const { bulmaVarProps, otherProps } = React.useMemo(() => {
15469
15548
  const varProps = {};
@@ -15483,6 +15562,7 @@ const Theme = ({ bulmaVars = {}, children, className, isRoot = false, colorMode,
15483
15562
  const { bulmaHelperClasses, rest } = useBulmaClasses({
15484
15563
  ...otherProps,
15485
15564
  radius: radiusHelper,
15565
+ columnGap,
15486
15566
  });
15487
15567
  // Merge bulmaVars and individual props, with props taking precedence
15488
15568
  const mergedVars = React.useMemo(() => {
@@ -15495,8 +15575,11 @@ const Theme = ({ bulmaVars = {}, children, className, isRoot = false, colorMode,
15495
15575
  if (radiusVar !== undefined) {
15496
15576
  vars['--bulma-radius'] = radiusVar;
15497
15577
  }
15578
+ if (columnGapVar !== undefined) {
15579
+ vars['--bulma-column-gap'] = columnGapVar;
15580
+ }
15498
15581
  return vars;
15499
- }, [bulmaVars, bulmaVarProps, radiusVar]);
15582
+ }, [bulmaVars, bulmaVarProps, radiusVar, columnGapVar]);
15500
15583
  // This Theme's place among root Themes; see `rootThemeRules`.
15501
15584
  const [rootOrder] = React.useState(() => nextRootOrder++);
15502
15585
  // The `:root` rules this Theme contributes, or '' when it contributes none.