@allxsmith/bestax-bulma 5.8.3 → 5.9.0

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.esm.js CHANGED
@@ -5558,6 +5558,17 @@ const useInsideControl = () => useContext(ControlContext);
5558
5558
  const FieldProvider = FieldContext.Provider;
5559
5559
  /** Provider for Control context — used internally by Control component. */
5560
5560
  const ControlProvider = ControlContext.Provider;
5561
+ const FieldLabelIdContext = createContext(undefined);
5562
+ /**
5563
+ * The id a labeled Field wants its single composed control to adopt (#495).
5564
+ * `undefined` outside a Field, in unlabeled/grouped/addons Fields, or when the
5565
+ * user took over the association with an explicit `labelProps.htmlFor`.
5566
+ * Consumed only by the single-control bases (InputBase, SelectBase,
5567
+ * TextAreaBase). Internal; not part of the public API.
5568
+ */
5569
+ const useFieldLabelId = () => useContext(FieldLabelIdContext);
5570
+ /** Provider for the Field label-target id — used internally by Field. */
5571
+ const FieldLabelIdProvider = FieldLabelIdContext.Provider;
5561
5572
  const RadiosContext = createContext(undefined);
5562
5573
  const CheckboxesContext = createContext(undefined);
5563
5574
  /**
@@ -5765,15 +5776,19 @@ const FieldBody = ({ textColor, bgColor, className, children, ...props }) => {
5765
5776
  * @see {@link https://bulma.io/documentation/form/general/#field | Bulma Field documentation}
5766
5777
  *
5767
5778
  * @example
5768
- * // Labelled field
5779
+ * // Labelled field — the label auto-associates with the composed base
5769
5780
  * <Field label="Email">
5770
- * <input className="input" type="email" />
5781
+ * <Control>
5782
+ * <InputBase type="email" />
5783
+ * </Control>
5771
5784
  * </Field>
5772
5785
  *
5773
5786
  * @example
5774
5787
  * // Horizontal field
5775
5788
  * <Field horizontal label="Name">
5776
- * <input className="input" />
5789
+ * <Control>
5790
+ * <InputBase />
5791
+ * </Control>
5777
5792
  * </Field>
5778
5793
  */
5779
5794
  const FieldComponent = ({ horizontal, grouped, hasAddons, narrow, label, labelSize, labelProps, textColor, color: _fieldColor, bgColor, className, children, ...props }) => {
@@ -5800,13 +5815,24 @@ const FieldComponent = ({ horizontal, grouped, hasAddons, narrow, label, labelSi
5800
5815
  // Default labelSize to 'normal' when horizontal for proper baseline alignment
5801
5816
  const effectiveLabelSize = labelSize ?? (horizontal ? 'normal' : undefined);
5802
5817
  const labelClass = usePrefixedClassNames('label');
5818
+ // Auto-associate the label with a single composed base control (#495): the
5819
+ // label points at a generated id shared via context, which InputBase/
5820
+ // SelectBase/TextAreaBase adopt when the user supplied no id of their own.
5821
+ // Presence semantics on htmlFor — even an explicit `htmlFor: undefined`
5822
+ // means the caller owns the association. Grouped/addons fields hold several
5823
+ // controls, so no single association is generated for them.
5824
+ const generatedId = useId();
5825
+ const userWiredLabel = !!labelProps && 'htmlFor' in labelProps;
5826
+ const targetId = label && !userWiredLabel && !grouped && !hasAddons
5827
+ ? generatedId
5828
+ : undefined;
5803
5829
  let renderedLabel = null;
5804
5830
  if (label) {
5805
5831
  if (horizontal) {
5806
- renderedLabel = (jsx(FieldLabel, { size: effectiveLabelSize, children: jsx("label", { ...labelProps, className: classNames(labelClass, labelProps?.className), style: labelProps?.style, children: label }) }));
5832
+ renderedLabel = (jsx(FieldLabel, { size: effectiveLabelSize, children: jsx("label", { htmlFor: targetId, ...labelProps, className: classNames(labelClass, labelProps?.className), style: labelProps?.style, children: label }) }));
5807
5833
  }
5808
5834
  else {
5809
- renderedLabel = (jsx("label", { ...labelProps, className: classNames(labelClass, labelProps?.className), style: { display: 'block', ...(labelProps?.style || {}) }, children: label }));
5835
+ renderedLabel = (jsx("label", { htmlFor: targetId, ...labelProps, className: classNames(labelClass, labelProps?.className), style: { display: 'block', ...(labelProps?.style || {}) }, children: label }));
5810
5836
  }
5811
5837
  }
5812
5838
  // If horizontal, wrap children in FieldBody (unless the user already provided
@@ -5829,7 +5855,7 @@ const FieldComponent = ({ horizontal, grouped, hasAddons, narrow, label, labelSi
5829
5855
  content = jsx(FieldBody, { children: children });
5830
5856
  }
5831
5857
  }
5832
- return (jsx(FieldProvider, { value: true, children: jsxs("div", { className: fieldClass, ...rest, children: [renderedLabel, content] }) }));
5858
+ return (jsx(FieldProvider, { value: true, children: jsx(FieldLabelIdProvider, { value: targetId, children: jsxs("div", { className: fieldClass, ...rest, children: [renderedLabel, content] }) }) }));
5833
5859
  };
5834
5860
  FieldLabel.displayName = 'FieldLabel';
5835
5861
  FieldBody.displayName = 'FieldBody';
@@ -5839,6 +5865,55 @@ const Field = withSubComponents(FieldComponent, {
5839
5865
  Control,
5840
5866
  }, 'Field');
5841
5867
 
5868
+ /**
5869
+ * Associates the convenience `label` prop with its control (#368): generates
5870
+ * an id for the control and returns labelProps carrying a matching `htmlFor`.
5871
+ * A user-supplied `id` is used as the target instead of the generated one, and
5872
+ * an explicit `htmlFor` key in labelProps — even set to `undefined` — disables
5873
+ * generation entirely: the user has taken over the association (#495 presence
5874
+ * semantics). Internal; not part of the public API.
5875
+ */
5876
+ function useAutoLabelId({ label, id, labelProps, rendersLabel, }) {
5877
+ // Called unconditionally per the rules of hooks; SSR-safe on React 18 and 19.
5878
+ const generatedId = useId();
5879
+ // Truthiness mirrors Field's own `if (label)` render gate.
5880
+ const active = !!label && rendersLabel;
5881
+ // Presence, not truthiness: `htmlFor: undefined` is an explicit opt-out and
5882
+ // must not leave an orphan generated id on the control.
5883
+ const userWired = !!labelProps && 'htmlFor' in labelProps;
5884
+ const controlId = id ?? (active && !userWired ? generatedId : undefined);
5885
+ // Inactive with a label still means an own Field may render it (pickers'
5886
+ // inline mode, Taginput at maxTags) — the explicit `htmlFor: undefined`
5887
+ // tells Field the association is owned here, so it must not generate one
5888
+ // that would dangle (#495 presence semantics).
5889
+ const fieldLabelProps = active
5890
+ ? { htmlFor: controlId, ...labelProps }
5891
+ : label
5892
+ ? { htmlFor: undefined, ...labelProps }
5893
+ : labelProps;
5894
+ return { controlId, fieldLabelProps };
5895
+ }
5896
+ /**
5897
+ * Group-input counterpart of {@link useAutoLabelId} (#494): a group of
5898
+ * controls cannot take a single `htmlFor`, so instead the rendered `<label>`
5899
+ * gets a generated id and the group container points at it with
5900
+ * `aria-labelledby`. A user-supplied `labelProps.id` is used as the target
5901
+ * instead of generating one. Any caller `htmlFor` is stripped — a group label
5902
+ * names the group, never a single control — so the merged labelProps always
5903
+ * carry an explicit `htmlFor: undefined`.
5904
+ * Internal; not part of the public API.
5905
+ */
5906
+ function useAutoLabelledBy({ label, labelProps, rendersLabel, }) {
5907
+ // Called unconditionally per the rules of hooks; SSR-safe on React 18 and 19.
5908
+ const generatedId = useId();
5909
+ const active = !!label && rendersLabel;
5910
+ const labelId = labelProps?.id ?? (active ? generatedId : undefined);
5911
+ const fieldLabelProps = active
5912
+ ? { ...labelProps, id: labelId, htmlFor: undefined }
5913
+ : labelProps;
5914
+ return { ariaLabelledBy: active ? labelId : undefined, fieldLabelProps };
5915
+ }
5916
+
5842
5917
  /**
5843
5918
  * The `Checkboxes` component wraps multiple `Checkbox` components in a Bulma-styled group.
5844
5919
  *
@@ -5867,6 +5942,11 @@ const Field = withSubComponents(FieldComponent, {
5867
5942
  const CheckboxesComponent = ({ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, name, value, defaultValue, onChange, children, className, ...props }) => {
5868
5943
  const insideField = useInsideField();
5869
5944
  const insideControl = useInsideControl();
5945
+ const { ariaLabelledBy, fieldLabelProps } = useAutoLabelledBy({
5946
+ label,
5947
+ labelProps,
5948
+ rendersLabel: !insideField,
5949
+ });
5870
5950
  const { bulmaHelperClasses, rest } = useBulmaClasses({
5871
5951
  ...props,
5872
5952
  });
@@ -5889,37 +5969,18 @@ const CheckboxesComponent = ({ label, labelSize, labelProps, horizontal, message
5889
5969
  name,
5890
5970
  ...(groupActive ? { value: currentValue, onChange: handleChange } : {}),
5891
5971
  }), [name, groupActive, currentValue, handleChange]);
5892
- const checkboxesElement = (jsx("div", { className: wrapperClass, ...rest, children: jsx(CheckboxesProvider, { value: ctx, children: children }) }));
5972
+ const checkboxesElement = (jsx("div", { className: wrapperClass, role: "group", "aria-labelledby": ariaLabelledBy, ...rest, children: jsx(CheckboxesProvider, { value: ctx, children: children }) }));
5893
5973
  let content = checkboxesElement;
5894
5974
  if (!insideControl) {
5895
5975
  content = jsx(Control, { children: content });
5896
5976
  }
5897
5977
  if (!insideField) {
5898
- return (jsxs(Field, { label: label, labelSize: labelSize, labelProps: labelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
5978
+ return (jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
5899
5979
  }
5900
5980
  return (jsxs(Fragment, { children: [content, messageEl] }));
5901
5981
  };
5902
5982
  const Checkboxes = withSubComponents(CheckboxesComponent, { Checkbox }, 'Checkboxes');
5903
5983
 
5904
- /**
5905
- * Associates the convenience `label` prop with its control (#368): generates
5906
- * an id for the control and returns labelProps carrying a matching `htmlFor`.
5907
- * A user-supplied `id` is used as the target instead of the generated one, and
5908
- * an explicit `labelProps.htmlFor` disables generation entirely — the user has
5909
- * taken over the association. Internal; not part of the public API.
5910
- */
5911
- function useAutoLabelId({ label, id, labelProps, rendersLabel, }) {
5912
- // Called unconditionally per the rules of hooks; SSR-safe on React 18 and 19.
5913
- const generatedId = useId();
5914
- // Truthiness mirrors Field's own `if (label)` render gate.
5915
- const active = !!label && rendersLabel;
5916
- const controlId = id ?? (active && !labelProps?.htmlFor ? generatedId : undefined);
5917
- const fieldLabelProps = active
5918
- ? { htmlFor: controlId, ...labelProps }
5919
- : labelProps;
5920
- return { controlId, fieldLabelProps };
5921
- }
5922
-
5923
5984
  /**
5924
5985
  * The `File` component provides a Bulma-styled file input, supporting color, size, boxed/fullwidth/align styles, icons, "has name", and filename display.
5925
5986
  *
@@ -6075,6 +6136,11 @@ Radio.displayName = 'Radio';
6075
6136
  const RadiosComponent = ({ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, name, value, defaultValue, onChange, children, className, ...props }) => {
6076
6137
  const insideField = useInsideField();
6077
6138
  const insideControl = useInsideControl();
6139
+ const { ariaLabelledBy, fieldLabelProps } = useAutoLabelledBy({
6140
+ label,
6141
+ labelProps,
6142
+ rendersLabel: !insideField,
6143
+ });
6078
6144
  const { bulmaHelperClasses, rest } = useBulmaClasses({
6079
6145
  ...props,
6080
6146
  });
@@ -6100,13 +6166,13 @@ const RadiosComponent = ({ label, labelSize, labelProps, horizontal, message, me
6100
6166
  name,
6101
6167
  ...(groupActive ? { value: currentValue, onChange: handleChange } : {}),
6102
6168
  }), [name, groupActive, currentValue, handleChange]);
6103
- const radiosElement = (jsx("div", { className: wrapperClass, ...rest, children: jsx(RadiosProvider, { value: ctx, children: children }) }));
6169
+ const radiosElement = (jsx("div", { className: wrapperClass, role: "radiogroup", "aria-labelledby": ariaLabelledBy, ...rest, children: jsx(RadiosProvider, { value: ctx, children: children }) }));
6104
6170
  let content = radiosElement;
6105
6171
  if (!insideControl) {
6106
6172
  content = jsx(Control, { children: content });
6107
6173
  }
6108
6174
  if (!insideField) {
6109
- return (jsxs(Field, { label: label, labelSize: labelSize, labelProps: labelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
6175
+ return (jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
6110
6176
  }
6111
6177
  return (jsxs(Fragment, { children: [content, messageEl] }));
6112
6178
  };
@@ -6835,6 +6901,11 @@ function getFillPercent(iconIndex, value) {
6835
6901
  const Rate = 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) => {
6836
6902
  const insideField = useInsideField();
6837
6903
  const insideControl = useInsideControl();
6904
+ const { ariaLabelledBy, fieldLabelProps } = useAutoLabelledBy({
6905
+ label,
6906
+ labelProps,
6907
+ rendersLabel: !insideField,
6908
+ });
6838
6909
  const { bulmaHelperClasses, rest } = useBulmaClasses(props);
6839
6910
  const [internalValue, setInternalValue] = useState(defaultValue);
6840
6911
  const [hoverValue, setHoverValue] = useState(null);
@@ -7031,13 +7102,13 @@ const Rate = forwardRef(({ label, labelSize, labelProps, horizontal, message, me
7031
7102
  const rateScoreClass = usePrefixedClassNames('rate-score');
7032
7103
  const rateTextClass = usePrefixedClassNames('rate-text');
7033
7104
  const rateCustomTextClass = usePrefixedClassNames('rate-custom-text');
7034
- const rateElement = (jsxs("div", { ref: ref, className: combinedClasses, role: "radiogroup", "aria-label": "Rating", "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 }))] }));
7105
+ 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 }))] }));
7035
7106
  let content = rateElement;
7036
7107
  if (!insideControl) {
7037
7108
  content = jsx(Control, { children: content });
7038
7109
  }
7039
7110
  if (!insideField) {
7040
- return (jsxs(Field, { label: label, labelSize: labelSize, labelProps: labelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
7111
+ return (jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
7041
7112
  }
7042
7113
  return (jsxs(Fragment, { children: [content, messageEl] }));
7043
7114
  });
@@ -7631,6 +7702,7 @@ const InputBase = forwardRef(({ color, size, isRounded, isStatic, isHovered, isF
7631
7702
  color,
7632
7703
  ...props,
7633
7704
  });
7705
+ const fieldLabelId = useFieldLabelId();
7634
7706
  const mainClass = usePrefixedClassNames('input', {
7635
7707
  [`is-${color}`]: !!color,
7636
7708
  [`is-${size}`]: !!size,
@@ -7641,7 +7713,10 @@ const InputBase = forwardRef(({ color, size, isRounded, isStatic, isHovered, isF
7641
7713
  'is-loading': isLoading,
7642
7714
  });
7643
7715
  const inputClass = classNames(mainClass, bulmaHelperClasses, className);
7644
- return (jsx("input", { ref: ref, className: inputClass, disabled: disabled, readOnly: readOnly, ...rest }));
7716
+ return (jsx("input", { ref: ref, className: inputClass, disabled: disabled, readOnly: readOnly, ...rest,
7717
+ // After the spread: a labeled Field's generated id is adopted only
7718
+ // when no user id arrived (rest.id may be an undefined own key).
7719
+ id: rest.id ?? fieldLabelId }));
7645
7720
  });
7646
7721
  InputBase.displayName = 'InputBase';
7647
7722
 
@@ -7658,6 +7733,7 @@ const SelectBase = forwardRef(({ color, size, isRounded, isLoading, isActive, is
7658
7733
  color,
7659
7734
  ...props,
7660
7735
  });
7736
+ const fieldLabelId = useFieldLabelId();
7661
7737
  const mainClass = usePrefixedClassNames('select', {
7662
7738
  [`is-${color}`]: !!color,
7663
7739
  [`is-${size}`]: !!size,
@@ -7682,7 +7758,10 @@ const SelectBase = forwardRef(({ color, size, isRounded, isLoading, isActive, is
7682
7758
  if (multiple && typeof multipleSize === 'number') {
7683
7759
  selectProps.size = multipleSize;
7684
7760
  }
7685
- return (jsx("div", { className: selectClass, children: jsx("select", { ref: ref, className: innerSelectClass || undefined, ...selectProps, children: children }) }));
7761
+ return (jsx("div", { className: selectClass, children: jsx("select", { ref: ref, className: innerSelectClass || undefined, ...selectProps,
7762
+ // After the spread: a labeled Field's generated id is adopted only
7763
+ // when no user id arrived; the id belongs on the inner <select>.
7764
+ id: selectProps.id ?? fieldLabelId, children: children }) }));
7686
7765
  });
7687
7766
  SelectBase.displayName = 'SelectBase';
7688
7767
 
@@ -7699,6 +7778,7 @@ const TextAreaBase = forwardRef(({ color, size, isRounded, isStatic, isHovered,
7699
7778
  color,
7700
7779
  ...props,
7701
7780
  });
7781
+ const fieldLabelId = useFieldLabelId();
7702
7782
  // Note: `is-loading` is intentionally NOT applied to the <textarea> itself —
7703
7783
  // Bulma documents `<div class="control is-loading">` as the loading pattern
7704
7784
  // for textareas, not `<textarea class="textarea is-loading">`. The convenience
@@ -7714,7 +7794,10 @@ const TextAreaBase = forwardRef(({ color, size, isRounded, isStatic, isHovered,
7714
7794
  'has-fixed-size': hasFixedSize,
7715
7795
  });
7716
7796
  const textareaClass = classNames(mainClass, bulmaHelperClasses, className);
7717
- return (jsx("textarea", { ref: ref, className: textareaClass, disabled: disabled, readOnly: readOnly, rows: rows, ...rest }));
7797
+ return (jsx("textarea", { ref: ref, className: textareaClass, disabled: disabled, readOnly: readOnly, rows: rows, ...rest,
7798
+ // After the spread: a labeled Field's generated id is adopted only
7799
+ // when no user id arrived (rest.id may be an undefined own key).
7800
+ id: rest.id ?? fieldLabelId }));
7718
7801
  });
7719
7802
  TextAreaBase.displayName = 'TextAreaBase';
7720
7803