@allxsmith/bestax-bulma 5.31.0 → 5.32.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/esm/components/SegmentedControl.js +230 -0
- package/dist/esm/components/SegmentedControl.js.map +1 -0
- package/dist/esm/elements/ToggleButton.js +2 -1
- package/dist/esm/elements/ToggleButton.js.map +1 -1
- package/dist/index.cjs +358 -138
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +1 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/SegmentedControl.d.ts +116 -0
- package/dist/types/elements/ToggleButton.d.ts +2 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types-cjs/components/SegmentedControl.d.ts +116 -0
- package/dist/types-cjs/elements/ToggleButton.d.ts +2 -1
- package/dist/types-cjs/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4645,6 +4645,361 @@ const Tabs = withSubComponents(TabsComponent, {
|
|
|
4645
4645
|
Content: TabsContent,
|
|
4646
4646
|
}, 'Tabs');
|
|
4647
4647
|
|
|
4648
|
+
const validButtonColors = [...validColors, 'text', 'ghost'];
|
|
4649
|
+
/**
|
|
4650
|
+
* The `Button` component provides a flexible and highly customizable button for your Bulma React UI.
|
|
4651
|
+
*
|
|
4652
|
+
* @function
|
|
4653
|
+
* @param {ButtonProps} props - Props for the Button component.
|
|
4654
|
+
* @param {React.Ref} ref - Forwarded ref to the element `as` renders.
|
|
4655
|
+
* @returns {JSX.Element} The rendered button, anchor, or custom element.
|
|
4656
|
+
* @see {@link https://bulma.io/documentation/elements/button/ | Bulma Button documentation}
|
|
4657
|
+
*/
|
|
4658
|
+
const Button = React.forwardRef(function Button(props, ref) {
|
|
4659
|
+
const { color, size, isLight, isRounded, isLoading, isStatic, isFullwidth, isFullWidth, isOutlined, isInverted, isFocused, isActive, isHovered, isDisabled, className, children, textColor, bgColor, as: Component = 'button', href, onClick, target, rel, ...bulmaProps } = props;
|
|
4660
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
4661
|
+
color: textColor,
|
|
4662
|
+
backgroundColor: bgColor,
|
|
4663
|
+
...bulmaProps,
|
|
4664
|
+
});
|
|
4665
|
+
// Generate Bulma classes with prefix
|
|
4666
|
+
const bulmaClasses = usePrefixedClassNames('button', {
|
|
4667
|
+
[`is-${color}`]: color &&
|
|
4668
|
+
validButtonColors.includes(color),
|
|
4669
|
+
[`is-${size}`]: size,
|
|
4670
|
+
'is-outlined': isOutlined,
|
|
4671
|
+
'is-light': isLight,
|
|
4672
|
+
'is-loading': isLoading,
|
|
4673
|
+
'is-static': isStatic,
|
|
4674
|
+
'is-disabled': isDisabled,
|
|
4675
|
+
'is-rounded': isRounded,
|
|
4676
|
+
'is-hovered': isHovered,
|
|
4677
|
+
'is-focused': isFocused,
|
|
4678
|
+
'is-active': isActive,
|
|
4679
|
+
'is-inverted': isInverted,
|
|
4680
|
+
'is-fullwidth': isFullwidth ?? isFullWidth,
|
|
4681
|
+
});
|
|
4682
|
+
// Combine prefixed Bulma classes with unprefixed user className and prefixed helper classes
|
|
4683
|
+
const buttonClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
4684
|
+
if (Component !== 'button') {
|
|
4685
|
+
// Strip the form-control attributes a link-like element cannot carry, so a
|
|
4686
|
+
// JavaScript consumer or a spread object doesn't put them on the DOM. The
|
|
4687
|
+
// types no longer allow them through; this is the runtime backstop.
|
|
4688
|
+
//
|
|
4689
|
+
// Only for INTRINSIC tags. A custom component (a router Link, ...) owns its
|
|
4690
|
+
// prop contract, and `ComponentPropsWithoutRef<T>` promises the caller that
|
|
4691
|
+
// its props reach it. Stripping names by their spelling alone broke that
|
|
4692
|
+
// promise silently: `<Button as={Custom} name="x" />` type-checked while
|
|
4693
|
+
// `Custom` never received the `name` it requires.
|
|
4694
|
+
//
|
|
4695
|
+
// `autoFocus` and `type` are deliberately NOT stripped: both are valid on
|
|
4696
|
+
// targets this list was filtering them from. `autoFocus` is a global
|
|
4697
|
+
// attribute (React decides per element whether to honour it, which is not
|
|
4698
|
+
// ours to pre-empt), and `type` on an anchor is the MIME hint that
|
|
4699
|
+
// `ButtonProps<'a'>` now types it as.
|
|
4700
|
+
//
|
|
4701
|
+
// The types already answer this for a TypeScript caller: `ButtonProps<T>`
|
|
4702
|
+
// derives from `ComponentPropsWithoutRef<T>`, so only what the target
|
|
4703
|
+
// accepts can be passed. What is left is a runtime backstop for JavaScript
|
|
4704
|
+
// consumers and spread objects.
|
|
4705
|
+
//
|
|
4706
|
+
// Keep it to the two cases where a stray attribute does something. The
|
|
4707
|
+
// submit-overrides belong to a submit control and never make sense on the
|
|
4708
|
+
// anchor path. `disabled` is the one that is VISIBLE: Bulma styles
|
|
4709
|
+
// `.button[disabled]` (background, border, shadow, opacity), so letting it
|
|
4710
|
+
// through to a `<span>` greys the element out — main stripped it, and not
|
|
4711
|
+
// stripping it would be a silent visual change on a types-only release.
|
|
4712
|
+
//
|
|
4713
|
+
// Everything else (`name`, `value`, `form`) is inert where it does not
|
|
4714
|
+
// belong, and enumerating which tags own it is what kept going wrong:
|
|
4715
|
+
// first all nine names on every non-button tag (so `<Button as="input"
|
|
4716
|
+
// name="query" value="Search">` submitted nothing), then a form-control
|
|
4717
|
+
// allowlist that still lost `formAction` on an input and `value` on an
|
|
4718
|
+
// option. Those now ride on the types instead.
|
|
4719
|
+
//
|
|
4720
|
+
// A custom component is exempt entirely — it owns its prop contract, and
|
|
4721
|
+
// `ComponentPropsWithoutRef<T>` promises the caller its props arrive.
|
|
4722
|
+
//
|
|
4723
|
+
// The elements that own `disabled`, per the HTML spec. A closed set, unlike
|
|
4724
|
+
// the open-ended question of which tag owns which attribute.
|
|
4725
|
+
const SUBMIT_OVERRIDES = [
|
|
4726
|
+
'formAction',
|
|
4727
|
+
'formEncType',
|
|
4728
|
+
'formMethod',
|
|
4729
|
+
'formNoValidate',
|
|
4730
|
+
'formTarget',
|
|
4731
|
+
];
|
|
4732
|
+
const OWNS_DISABLED = [
|
|
4733
|
+
'button',
|
|
4734
|
+
'fieldset',
|
|
4735
|
+
'input',
|
|
4736
|
+
'optgroup',
|
|
4737
|
+
'option',
|
|
4738
|
+
'select',
|
|
4739
|
+
'textarea',
|
|
4740
|
+
];
|
|
4741
|
+
// Two independent filters. They are not exclusive — an `<a>` owns neither
|
|
4742
|
+
// the submit-overrides nor `disabled`, so it takes both — and deriving them
|
|
4743
|
+
// from one destructure, or from one if/else, is what broke each of them in
|
|
4744
|
+
// turn: first the overrides came off every intrinsic (an `as="input"` lost
|
|
4745
|
+
// `formAction`), then scoping them to the anchor let `disabled` back onto
|
|
4746
|
+
// it.
|
|
4747
|
+
const forwardedRest = { ...rest };
|
|
4748
|
+
if (typeof Component === 'string' && !isCustomElement(Component)) {
|
|
4749
|
+
// Meaningless anywhere but a submit control, and an anchor is not one.
|
|
4750
|
+
if (Component === 'a') {
|
|
4751
|
+
for (const key of SUBMIT_OVERRIDES)
|
|
4752
|
+
delete forwardedRest[key];
|
|
4753
|
+
}
|
|
4754
|
+
// Visible via Bulma's `.button[disabled]`, so withheld from any element
|
|
4755
|
+
// that does not own it.
|
|
4756
|
+
if (!OWNS_DISABLED.includes(Component))
|
|
4757
|
+
delete forwardedRest.disabled;
|
|
4758
|
+
}
|
|
4759
|
+
return (jsxRuntime.jsx(Component, { ref: ref, className: buttonClasses, href: href, target: target, rel: rel, "aria-disabled": isDisabled, tabIndex: isDisabled ? -1 : undefined, onClick: isDisabled
|
|
4760
|
+
? // Guarded, because this handler is installed into whatever `as`
|
|
4761
|
+
// renders and a custom component's `onClick` need not be a DOM
|
|
4762
|
+
// event handler at all. One declaring `onClick: (v: string) =>
|
|
4763
|
+
// void` calls this with a string, and an unguarded
|
|
4764
|
+
// `e.preventDefault()` throws `is not a function`. Blocking the
|
|
4765
|
+
// real event is the point; a foreign payload is simply not ours
|
|
4766
|
+
// to act on.
|
|
4767
|
+
(e) => {
|
|
4768
|
+
if (typeof e?.preventDefault === 'function')
|
|
4769
|
+
e.preventDefault();
|
|
4770
|
+
}
|
|
4771
|
+
: onClick, ...forwardedRest, children: children }));
|
|
4772
|
+
}
|
|
4773
|
+
return (jsxRuntime.jsx("button", { ref: ref, className: buttonClasses, disabled: isDisabled, onClick: onClick, ...rest, children: children }));
|
|
4774
|
+
});
|
|
4775
|
+
Button.displayName = 'Button';
|
|
4776
|
+
|
|
4777
|
+
// INTERNAL: deliberately not exported from src/index.ts.
|
|
4778
|
+
/**
|
|
4779
|
+
* `useLayoutEffect` in the browser, `useEffect` on the server. A server
|
|
4780
|
+
* render runs neither, and React 18 warns about a layout effect there, so the
|
|
4781
|
+
* server is handed the plain one.
|
|
4782
|
+
*/
|
|
4783
|
+
const useClientLayoutEffect = typeof document === 'undefined' ? React.useEffect : React.useLayoutEffect;
|
|
4784
|
+
|
|
4785
|
+
// Not exported, so it stays out of the public API that `index.ts` re-exports.
|
|
4786
|
+
const SegmentedControlContext = React.createContext(null);
|
|
4787
|
+
/** Sorts mounted segments into the order they sit in the page. */
|
|
4788
|
+
const inDocumentOrder = (a, b) => a.node.compareDocumentPosition(b.node) & Node.DOCUMENT_POSITION_FOLLOWING
|
|
4789
|
+
? -1
|
|
4790
|
+
: 1;
|
|
4791
|
+
/** How far each arrow key moves along the row. */
|
|
4792
|
+
const STEPS = {
|
|
4793
|
+
ArrowRight: 1,
|
|
4794
|
+
ArrowDown: 1,
|
|
4795
|
+
ArrowLeft: -1,
|
|
4796
|
+
ArrowUp: -1,
|
|
4797
|
+
};
|
|
4798
|
+
/**
|
|
4799
|
+
* The segment a key moves to, found among the segments of the same group in
|
|
4800
|
+
* DOM order, skipping disabled ones and wrapping at either end. `undefined`
|
|
4801
|
+
* for a key the group leaves alone, when no enabled segment is left, or when
|
|
4802
|
+
* the segment isn't inside its row in the page, as a portaled one isn't.
|
|
4803
|
+
*/
|
|
4804
|
+
function segmentFor(current, key) {
|
|
4805
|
+
const group = current.closest('[role="radiogroup"]');
|
|
4806
|
+
if (!group)
|
|
4807
|
+
return undefined;
|
|
4808
|
+
const segments = Array.from(group.querySelectorAll('[role="radio"]'));
|
|
4809
|
+
const enabled = segments.filter(segment => !segment.disabled);
|
|
4810
|
+
if (key === 'Home')
|
|
4811
|
+
return enabled[0];
|
|
4812
|
+
if (key === 'End')
|
|
4813
|
+
return enabled[enabled.length - 1];
|
|
4814
|
+
const step = STEPS[key];
|
|
4815
|
+
if (!step)
|
|
4816
|
+
return undefined;
|
|
4817
|
+
const at = segments.indexOf(current);
|
|
4818
|
+
for (let i = 1; i <= segments.length; i += 1) {
|
|
4819
|
+
const candidate = segments[(at + step * i + segments.length) % segments.length];
|
|
4820
|
+
if (!candidate.disabled)
|
|
4821
|
+
return candidate;
|
|
4822
|
+
}
|
|
4823
|
+
return undefined;
|
|
4824
|
+
}
|
|
4825
|
+
/**
|
|
4826
|
+
* The `SegmentedControl` component renders a row of buttons where only one can be on, and tells a screen reader it's a radio group.
|
|
4827
|
+
*
|
|
4828
|
+
* It's Bulma's `.buttons.has-addons` row, with the checked segment filled and
|
|
4829
|
+
* marked `is-selected`, the way Bulma's button docs show it. To a screen
|
|
4830
|
+
* reader it's `role="radiogroup"`, with `role="radio"` and `aria-checked` on
|
|
4831
|
+
* each segment, which is how the APG toolbar example marks its alignment
|
|
4832
|
+
* buttons. The keys follow the APG radio group: the row is one tab stop, the
|
|
4833
|
+
* arrow keys move to the next or previous enabled segment and check it,
|
|
4834
|
+
* wrapping at either end, Home and End go to the first and last, and Space
|
|
4835
|
+
* checks the focused segment. Once a segment is checked, one always is.
|
|
4836
|
+
*
|
|
4837
|
+
* It carries no form value. A choice that's sent with a form is `Radios`.
|
|
4838
|
+
*
|
|
4839
|
+
* @function
|
|
4840
|
+
* @param {SegmentedControlProps} props - Props for the SegmentedControl component.
|
|
4841
|
+
* @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the row's `<div>`.
|
|
4842
|
+
* @returns {JSX.Element} The rendered segmented control.
|
|
4843
|
+
* @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/radio/ | APG radio group pattern}
|
|
4844
|
+
* @see {@link https://bulma.io/documentation/elements/button/#list-of-buttons | Bulma list of buttons}
|
|
4845
|
+
*
|
|
4846
|
+
* @example
|
|
4847
|
+
* <SegmentedControl aria-label="Text alignment" defaultValue="left">
|
|
4848
|
+
* <SegmentedControl.Segment value="left">Left</SegmentedControl.Segment>
|
|
4849
|
+
* <SegmentedControl.Segment value="center">Center</SegmentedControl.Segment>
|
|
4850
|
+
* <SegmentedControl.Segment value="right">Right</SegmentedControl.Segment>
|
|
4851
|
+
* </SegmentedControl>
|
|
4852
|
+
*/
|
|
4853
|
+
const SegmentedControlRoot = React.forwardRef(function SegmentedControl({ value, defaultValue, onChange, color = 'link', size, hasAddons = true, isCentered, isRight, className, children, ...props }, ref) {
|
|
4854
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
|
|
4855
|
+
// Controlled vs uncontrolled, as in Tabs and Collapses.
|
|
4856
|
+
const isControlled = value !== undefined;
|
|
4857
|
+
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
4858
|
+
const checked = isControlled ? (value ?? undefined) : internalValue;
|
|
4859
|
+
const select = (next) => {
|
|
4860
|
+
if (next === checked)
|
|
4861
|
+
return;
|
|
4862
|
+
if (!isControlled)
|
|
4863
|
+
setInternalValue(next);
|
|
4864
|
+
onChange?.(next);
|
|
4865
|
+
};
|
|
4866
|
+
// The segments mount and register from an effect, so the group knows
|
|
4867
|
+
// which can hold the tab stop wherever in the tree they sit.
|
|
4868
|
+
const [segments, setSegments] = React.useState([]);
|
|
4869
|
+
const register = React.useCallback((record) => {
|
|
4870
|
+
setSegments(prev => [...prev, record]);
|
|
4871
|
+
return () => setSegments(prev => prev.filter(r => r !== record));
|
|
4872
|
+
}, []);
|
|
4873
|
+
const [focused, setFocused] = React.useState();
|
|
4874
|
+
// The first enabled segment in the page, sorted when it's read rather
|
|
4875
|
+
// than when a segment registers, so reordered segments are seen too. It's
|
|
4876
|
+
// read after the commit: a render runs before React moves the nodes, so
|
|
4877
|
+
// sorting there would find the old order.
|
|
4878
|
+
const [firstEnabled, setFirstEnabled] = React.useState();
|
|
4879
|
+
useClientLayoutEffect(() => {
|
|
4880
|
+
const inPage = segments.filter(s => !s.disabled).sort(inDocumentOrder);
|
|
4881
|
+
setFirstEnabled(inPage[0]?.value);
|
|
4882
|
+
}, [segments, children]);
|
|
4883
|
+
// One tab stop for the row (roving tabindex). While focus is in the row
|
|
4884
|
+
// the stop follows it, so Tab leaves from wherever focus is even when a
|
|
4885
|
+
// controlled group hasn't taken the new value. Otherwise it's the checked
|
|
4886
|
+
// segment, or the first enabled one when none is checked or the checked
|
|
4887
|
+
// one is disabled. Before the segments register (the server render and
|
|
4888
|
+
// the first client render) the checked one holds it, so a row with
|
|
4889
|
+
// nothing checked has no stop until the client has rendered.
|
|
4890
|
+
const canHoldStop = (candidate) => segments.some(s => !s.disabled && s.value === candidate);
|
|
4891
|
+
let tabStop;
|
|
4892
|
+
if (canHoldStop(focused))
|
|
4893
|
+
tabStop = focused;
|
|
4894
|
+
else if (segments.length === 0 || canHoldStop(checked))
|
|
4895
|
+
tabStop = checked;
|
|
4896
|
+
else
|
|
4897
|
+
tabStop = firstEnabled;
|
|
4898
|
+
const named = Boolean(rest['aria-label'] || rest['aria-labelledby']);
|
|
4899
|
+
React.useEffect(() => {
|
|
4900
|
+
if (!named) {
|
|
4901
|
+
warnOnce('SegmentedControl:unnamed', '[bestax-bulma] <SegmentedControl> has no accessible name. Pass ' +
|
|
4902
|
+
'aria-label or aria-labelledby, so a screen reader can say what ' +
|
|
4903
|
+
'the choice is.');
|
|
4904
|
+
}
|
|
4905
|
+
}, [named]);
|
|
4906
|
+
const rowClasses = usePrefixedClassNames('buttons', {
|
|
4907
|
+
'has-addons': hasAddons,
|
|
4908
|
+
'is-centered': isCentered,
|
|
4909
|
+
'is-right': isRight,
|
|
4910
|
+
[`are-${size}`]: size,
|
|
4911
|
+
});
|
|
4912
|
+
// The props are spread first, so the role a screen reader relies on wins
|
|
4913
|
+
// over anything a JavaScript caller or a spread object passes along.
|
|
4914
|
+
return (jsxRuntime.jsx(SegmentedControlContext.Provider, { value: { checked, tabStop, color, select, setFocused, register }, children: jsxRuntime.jsx("div", { ...rest, ref: ref, role: "radiogroup", className: classNames(rowClasses, bulmaHelperClasses, className), children: children }) }));
|
|
4915
|
+
});
|
|
4916
|
+
/**
|
|
4917
|
+
* One option in the row: a `<button role="radio">` that's checked while the group's value is its `value`, filled with the group's `color` and marked `is-selected`.
|
|
4918
|
+
*
|
|
4919
|
+
* @function
|
|
4920
|
+
* @param {SegmentedControlSegmentProps} props - Props for the segment.
|
|
4921
|
+
* @param {React.Ref<HTMLButtonElement>} ref - Forwarded ref to the `<button>`.
|
|
4922
|
+
* @returns {JSX.Element} The rendered segment.
|
|
4923
|
+
*/
|
|
4924
|
+
const Segment = React.forwardRef(function Segment({ value, isDisabled, disabled, className, onClick, onKeyDown, onFocus, onBlur, ...props }, ref) {
|
|
4925
|
+
const ctx = React.useContext(SegmentedControlContext);
|
|
4926
|
+
const checked = ctx !== null && ctx.checked === value;
|
|
4927
|
+
const off = Boolean(isDisabled || disabled);
|
|
4928
|
+
// Prefixed like Button's own classes, which a hand-written
|
|
4929
|
+
// `className="is-selected"` could not be under ConfigProvider classPrefix.
|
|
4930
|
+
// Bulma lifts `is-selected` above its neighbours in `.buttons.has-addons`.
|
|
4931
|
+
const selectedClass = usePrefixedClassNames({ 'is-selected': checked });
|
|
4932
|
+
const nodeRef = React.useRef(null);
|
|
4933
|
+
const setRefs = React.useCallback((node) => {
|
|
4934
|
+
nodeRef.current = node;
|
|
4935
|
+
if (typeof ref === 'function')
|
|
4936
|
+
ref(node);
|
|
4937
|
+
else if (ref)
|
|
4938
|
+
ref.current = node;
|
|
4939
|
+
}, [ref]);
|
|
4940
|
+
const register = ctx?.register;
|
|
4941
|
+
React.useEffect(() => {
|
|
4942
|
+
if (!register) {
|
|
4943
|
+
warnOnce('SegmentedControl.Segment:outside', '[bestax-bulma] <SegmentedControl.Segment> is outside a ' +
|
|
4944
|
+
'<SegmentedControl>, so nothing can check it. Put it inside one.');
|
|
4945
|
+
return undefined;
|
|
4946
|
+
}
|
|
4947
|
+
return register({
|
|
4948
|
+
value,
|
|
4949
|
+
disabled: off,
|
|
4950
|
+
node: nodeRef.current,
|
|
4951
|
+
});
|
|
4952
|
+
}, [register, value, off]);
|
|
4953
|
+
// The caller's onClick runs first, so a `preventDefault()` there keeps
|
|
4954
|
+
// the segment from being checked. A disabled button gets no click.
|
|
4955
|
+
const handleClick = (event) => {
|
|
4956
|
+
onClick?.(event);
|
|
4957
|
+
if (event.defaultPrevented)
|
|
4958
|
+
return;
|
|
4959
|
+
ctx?.select(value);
|
|
4960
|
+
};
|
|
4961
|
+
const handleKeyDown = (event) => {
|
|
4962
|
+
onKeyDown?.(event);
|
|
4963
|
+
if (!ctx ||
|
|
4964
|
+
event.defaultPrevented ||
|
|
4965
|
+
event.altKey ||
|
|
4966
|
+
event.ctrlKey ||
|
|
4967
|
+
event.metaKey) {
|
|
4968
|
+
return;
|
|
4969
|
+
}
|
|
4970
|
+
const target = segmentFor(event.currentTarget, event.key);
|
|
4971
|
+
if (!target)
|
|
4972
|
+
return;
|
|
4973
|
+
event.preventDefault();
|
|
4974
|
+
// The row has taken this key, so nothing above it acts on it too: a
|
|
4975
|
+
// Carousel moves its slide on the arrows from a document listener that
|
|
4976
|
+
// doesn't read `defaultPrevented`. Keys the row leaves alone still bubble.
|
|
4977
|
+
event.stopPropagation();
|
|
4978
|
+
target.focus();
|
|
4979
|
+
// The check follows focus, APG's rule for a radio group outside a
|
|
4980
|
+
// toolbar. It goes through a click on the segment, as a native radio's
|
|
4981
|
+
// does, so its onClick sees it. Inside a toolbar the arrows would move
|
|
4982
|
+
// focus alone; that is the line a toolbar would change.
|
|
4983
|
+
target.click();
|
|
4984
|
+
};
|
|
4985
|
+
const handleFocus = (event) => {
|
|
4986
|
+
onFocus?.(event);
|
|
4987
|
+
ctx?.setFocused(value);
|
|
4988
|
+
};
|
|
4989
|
+
// The segment focus moves to, if any, records itself straight after.
|
|
4990
|
+
const handleBlur = (event) => {
|
|
4991
|
+
onBlur?.(event);
|
|
4992
|
+
ctx?.setFocused(undefined);
|
|
4993
|
+
};
|
|
4994
|
+
// The props are spread first, so what a radio depends on wins over
|
|
4995
|
+
// anything a JavaScript caller or a spread object passes along: it stays
|
|
4996
|
+
// a button, never submits, reports whether it's checked, and the group
|
|
4997
|
+
// decides whether Tab reaches it.
|
|
4998
|
+
return (jsxRuntime.jsx(Button, { ...props, ref: setRefs, as: "button", type: "button", role: "radio", "aria-checked": checked, tabIndex: ctx ? (ctx.tabStop === value ? 0 : -1) : undefined, isDisabled: off, color: checked ? ctx.color : undefined, className: classNames(selectedClass, className), onClick: handleClick, onKeyDown: handleKeyDown, onFocus: handleFocus, onBlur: handleBlur }));
|
|
4999
|
+
});
|
|
5000
|
+
Segment.displayName = 'SegmentedControl.Segment';
|
|
5001
|
+
const SegmentedControl = withSubComponents(SegmentedControlRoot, { Segment }, 'SegmentedControl');
|
|
5002
|
+
|
|
4648
5003
|
/**
|
|
4649
5004
|
* The `Loading` component provides a loading overlay with a spinner animation.
|
|
4650
5005
|
*
|
|
@@ -5022,14 +5377,6 @@ const CollapsesComponent = ({ multiple = false, value, defaultValue, onChange, s
|
|
|
5022
5377
|
};
|
|
5023
5378
|
const Collapses = withSubComponents(CollapsesComponent, { Collapse }, 'Collapses');
|
|
5024
5379
|
|
|
5025
|
-
// INTERNAL: deliberately not exported from src/index.ts.
|
|
5026
|
-
/**
|
|
5027
|
-
* `useLayoutEffect` in the browser, `useEffect` on the server. A server
|
|
5028
|
-
* render runs neither, and React 18 warns about a layout effect there, so the
|
|
5029
|
-
* server is handed the plain one.
|
|
5030
|
-
*/
|
|
5031
|
-
const useClientLayoutEffect = typeof document === 'undefined' ? React.useEffect : React.useLayoutEffect;
|
|
5032
|
-
|
|
5033
5380
|
// INTERNAL: deliberately not exported from src/index.ts.
|
|
5034
5381
|
/**
|
|
5035
5382
|
* Runs the caller's own handler on the element that holds the trigger, then
|
|
@@ -6207,135 +6554,6 @@ function useAnchoredPosition(anchorRef, panelRef, { active, position, fixed, off
|
|
|
6207
6554
|
return resolved;
|
|
6208
6555
|
}
|
|
6209
6556
|
|
|
6210
|
-
const validButtonColors = [...validColors, 'text', 'ghost'];
|
|
6211
|
-
/**
|
|
6212
|
-
* The `Button` component provides a flexible and highly customizable button for your Bulma React UI.
|
|
6213
|
-
*
|
|
6214
|
-
* @function
|
|
6215
|
-
* @param {ButtonProps} props - Props for the Button component.
|
|
6216
|
-
* @param {React.Ref} ref - Forwarded ref to the element `as` renders.
|
|
6217
|
-
* @returns {JSX.Element} The rendered button, anchor, or custom element.
|
|
6218
|
-
* @see {@link https://bulma.io/documentation/elements/button/ | Bulma Button documentation}
|
|
6219
|
-
*/
|
|
6220
|
-
const Button = React.forwardRef(function Button(props, ref) {
|
|
6221
|
-
const { color, size, isLight, isRounded, isLoading, isStatic, isFullwidth, isFullWidth, isOutlined, isInverted, isFocused, isActive, isHovered, isDisabled, className, children, textColor, bgColor, as: Component = 'button', href, onClick, target, rel, ...bulmaProps } = props;
|
|
6222
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
6223
|
-
color: textColor,
|
|
6224
|
-
backgroundColor: bgColor,
|
|
6225
|
-
...bulmaProps,
|
|
6226
|
-
});
|
|
6227
|
-
// Generate Bulma classes with prefix
|
|
6228
|
-
const bulmaClasses = usePrefixedClassNames('button', {
|
|
6229
|
-
[`is-${color}`]: color &&
|
|
6230
|
-
validButtonColors.includes(color),
|
|
6231
|
-
[`is-${size}`]: size,
|
|
6232
|
-
'is-outlined': isOutlined,
|
|
6233
|
-
'is-light': isLight,
|
|
6234
|
-
'is-loading': isLoading,
|
|
6235
|
-
'is-static': isStatic,
|
|
6236
|
-
'is-disabled': isDisabled,
|
|
6237
|
-
'is-rounded': isRounded,
|
|
6238
|
-
'is-hovered': isHovered,
|
|
6239
|
-
'is-focused': isFocused,
|
|
6240
|
-
'is-active': isActive,
|
|
6241
|
-
'is-inverted': isInverted,
|
|
6242
|
-
'is-fullwidth': isFullwidth ?? isFullWidth,
|
|
6243
|
-
});
|
|
6244
|
-
// Combine prefixed Bulma classes with unprefixed user className and prefixed helper classes
|
|
6245
|
-
const buttonClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
6246
|
-
if (Component !== 'button') {
|
|
6247
|
-
// Strip the form-control attributes a link-like element cannot carry, so a
|
|
6248
|
-
// JavaScript consumer or a spread object doesn't put them on the DOM. The
|
|
6249
|
-
// types no longer allow them through; this is the runtime backstop.
|
|
6250
|
-
//
|
|
6251
|
-
// Only for INTRINSIC tags. A custom component (a router Link, ...) owns its
|
|
6252
|
-
// prop contract, and `ComponentPropsWithoutRef<T>` promises the caller that
|
|
6253
|
-
// its props reach it. Stripping names by their spelling alone broke that
|
|
6254
|
-
// promise silently: `<Button as={Custom} name="x" />` type-checked while
|
|
6255
|
-
// `Custom` never received the `name` it requires.
|
|
6256
|
-
//
|
|
6257
|
-
// `autoFocus` and `type` are deliberately NOT stripped: both are valid on
|
|
6258
|
-
// targets this list was filtering them from. `autoFocus` is a global
|
|
6259
|
-
// attribute (React decides per element whether to honour it, which is not
|
|
6260
|
-
// ours to pre-empt), and `type` on an anchor is the MIME hint that
|
|
6261
|
-
// `ButtonProps<'a'>` now types it as.
|
|
6262
|
-
//
|
|
6263
|
-
// The types already answer this for a TypeScript caller: `ButtonProps<T>`
|
|
6264
|
-
// derives from `ComponentPropsWithoutRef<T>`, so only what the target
|
|
6265
|
-
// accepts can be passed. What is left is a runtime backstop for JavaScript
|
|
6266
|
-
// consumers and spread objects.
|
|
6267
|
-
//
|
|
6268
|
-
// Keep it to the two cases where a stray attribute does something. The
|
|
6269
|
-
// submit-overrides belong to a submit control and never make sense on the
|
|
6270
|
-
// anchor path. `disabled` is the one that is VISIBLE: Bulma styles
|
|
6271
|
-
// `.button[disabled]` (background, border, shadow, opacity), so letting it
|
|
6272
|
-
// through to a `<span>` greys the element out — main stripped it, and not
|
|
6273
|
-
// stripping it would be a silent visual change on a types-only release.
|
|
6274
|
-
//
|
|
6275
|
-
// Everything else (`name`, `value`, `form`) is inert where it does not
|
|
6276
|
-
// belong, and enumerating which tags own it is what kept going wrong:
|
|
6277
|
-
// first all nine names on every non-button tag (so `<Button as="input"
|
|
6278
|
-
// name="query" value="Search">` submitted nothing), then a form-control
|
|
6279
|
-
// allowlist that still lost `formAction` on an input and `value` on an
|
|
6280
|
-
// option. Those now ride on the types instead.
|
|
6281
|
-
//
|
|
6282
|
-
// A custom component is exempt entirely — it owns its prop contract, and
|
|
6283
|
-
// `ComponentPropsWithoutRef<T>` promises the caller its props arrive.
|
|
6284
|
-
//
|
|
6285
|
-
// The elements that own `disabled`, per the HTML spec. A closed set, unlike
|
|
6286
|
-
// the open-ended question of which tag owns which attribute.
|
|
6287
|
-
const SUBMIT_OVERRIDES = [
|
|
6288
|
-
'formAction',
|
|
6289
|
-
'formEncType',
|
|
6290
|
-
'formMethod',
|
|
6291
|
-
'formNoValidate',
|
|
6292
|
-
'formTarget',
|
|
6293
|
-
];
|
|
6294
|
-
const OWNS_DISABLED = [
|
|
6295
|
-
'button',
|
|
6296
|
-
'fieldset',
|
|
6297
|
-
'input',
|
|
6298
|
-
'optgroup',
|
|
6299
|
-
'option',
|
|
6300
|
-
'select',
|
|
6301
|
-
'textarea',
|
|
6302
|
-
];
|
|
6303
|
-
// Two independent filters. They are not exclusive — an `<a>` owns neither
|
|
6304
|
-
// the submit-overrides nor `disabled`, so it takes both — and deriving them
|
|
6305
|
-
// from one destructure, or from one if/else, is what broke each of them in
|
|
6306
|
-
// turn: first the overrides came off every intrinsic (an `as="input"` lost
|
|
6307
|
-
// `formAction`), then scoping them to the anchor let `disabled` back onto
|
|
6308
|
-
// it.
|
|
6309
|
-
const forwardedRest = { ...rest };
|
|
6310
|
-
if (typeof Component === 'string' && !isCustomElement(Component)) {
|
|
6311
|
-
// Meaningless anywhere but a submit control, and an anchor is not one.
|
|
6312
|
-
if (Component === 'a') {
|
|
6313
|
-
for (const key of SUBMIT_OVERRIDES)
|
|
6314
|
-
delete forwardedRest[key];
|
|
6315
|
-
}
|
|
6316
|
-
// Visible via Bulma's `.button[disabled]`, so withheld from any element
|
|
6317
|
-
// that does not own it.
|
|
6318
|
-
if (!OWNS_DISABLED.includes(Component))
|
|
6319
|
-
delete forwardedRest.disabled;
|
|
6320
|
-
}
|
|
6321
|
-
return (jsxRuntime.jsx(Component, { ref: ref, className: buttonClasses, href: href, target: target, rel: rel, "aria-disabled": isDisabled, tabIndex: isDisabled ? -1 : undefined, onClick: isDisabled
|
|
6322
|
-
? // Guarded, because this handler is installed into whatever `as`
|
|
6323
|
-
// renders and a custom component's `onClick` need not be a DOM
|
|
6324
|
-
// event handler at all. One declaring `onClick: (v: string) =>
|
|
6325
|
-
// void` calls this with a string, and an unguarded
|
|
6326
|
-
// `e.preventDefault()` throws `is not a function`. Blocking the
|
|
6327
|
-
// real event is the point; a foreign payload is simply not ours
|
|
6328
|
-
// to act on.
|
|
6329
|
-
(e) => {
|
|
6330
|
-
if (typeof e?.preventDefault === 'function')
|
|
6331
|
-
e.preventDefault();
|
|
6332
|
-
}
|
|
6333
|
-
: onClick, ...forwardedRest, children: children }));
|
|
6334
|
-
}
|
|
6335
|
-
return (jsxRuntime.jsx("button", { ref: ref, className: buttonClasses, disabled: isDisabled, onClick: onClick, ...rest, children: children }));
|
|
6336
|
-
});
|
|
6337
|
-
Button.displayName = 'Button';
|
|
6338
|
-
|
|
6339
6557
|
const PopoverContext = React.createContext(null);
|
|
6340
6558
|
/** Intrinsic elements that are controls already, and keep their own role. */
|
|
6341
6559
|
const NATIVE_CONTROLS = new Set([
|
|
@@ -8579,7 +8797,8 @@ LinkButton.displayName = 'LinkButton';
|
|
|
8579
8797
|
* carries no form value: a setting saved with a form is a `Checkbox` or a
|
|
8580
8798
|
* `Switch`. A set of toggles that each turn on and off on their own goes in
|
|
8581
8799
|
* `Buttons hasAddons` with `role="group"` and an `aria-label`. A set where only
|
|
8582
|
-
* one can be on is a radio group rather than a row of these: use
|
|
8800
|
+
* one can be on is a radio group rather than a row of these: use
|
|
8801
|
+
* `SegmentedControl`, or `Radios` for a choice that's sent with a form.
|
|
8583
8802
|
*
|
|
8584
8803
|
* @function
|
|
8585
8804
|
* @param {ToggleButtonProps} props - Props for the ToggleButton component.
|
|
@@ -19206,6 +19425,7 @@ exports.Radios = Radios;
|
|
|
19206
19425
|
exports.Rate = Rate;
|
|
19207
19426
|
exports.Reveal = Reveal;
|
|
19208
19427
|
exports.Section = Section;
|
|
19428
|
+
exports.SegmentedControl = SegmentedControl;
|
|
19209
19429
|
exports.Select = Select;
|
|
19210
19430
|
exports.SelectBase = SelectBase;
|
|
19211
19431
|
exports.Sidebar = Sidebar;
|