@allxsmith/bestax-bulma 5.36.0 → 5.37.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.
Files changed (44) hide show
  1. package/dist/bestax.css +1 -1
  2. package/dist/bestax.css.map +1 -1
  3. package/dist/esm/components/EmptyState.js +2 -2
  4. package/dist/esm/components/Menu.js +9 -2
  5. package/dist/esm/components/Menu.js.map +1 -1
  6. package/dist/esm/components/Timeline.js +5 -5
  7. package/dist/esm/components/Timeline.js.map +1 -1
  8. package/dist/esm/components/TreeView.js +192 -0
  9. package/dist/esm/components/TreeView.js.map +1 -0
  10. package/dist/esm/helpers/typeahead.js +82 -0
  11. package/dist/esm/helpers/typeahead.js.map +1 -0
  12. package/dist/esm/helpers/useMenuKeys.js +8 -58
  13. package/dist/esm/helpers/useMenuKeys.js.map +1 -1
  14. package/dist/esm/helpers/useTreeView.js +380 -0
  15. package/dist/esm/helpers/useTreeView.js.map +1 -0
  16. package/dist/extras.css +1 -1
  17. package/dist/extras.css.map +1 -1
  18. package/dist/index.cjs +1954 -1370
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.esm.js +1 -0
  21. package/dist/index.esm.js.map +1 -1
  22. package/dist/types/components/Menu.d.ts +5 -0
  23. package/dist/types/components/TreeView.d.ts +115 -0
  24. package/dist/types/helpers/typeahead.d.ts +41 -0
  25. package/dist/types/helpers/useMenuKeys.d.ts +1 -2
  26. package/dist/types/helpers/useTreeView.d.ts +88 -0
  27. package/dist/types/index.d.ts +1 -0
  28. package/dist/types-cjs/components/Menu.d.ts +5 -0
  29. package/dist/types-cjs/components/TreeView.d.ts +115 -0
  30. package/dist/types-cjs/helpers/typeahead.d.ts +41 -0
  31. package/dist/types-cjs/helpers/useMenuKeys.d.ts +1 -2
  32. package/dist/types-cjs/helpers/useTreeView.d.ts +88 -0
  33. package/dist/types-cjs/index.d.ts +1 -0
  34. package/dist/versions/bestax-no-dark-mode.css +1 -1
  35. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  36. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  37. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  38. package/dist/versions/bestax-no-helpers.css +1 -1
  39. package/dist/versions/bestax-no-helpers.css.map +1 -1
  40. package/dist/versions/bestax-prefixed.css +1 -1
  41. package/dist/versions/bestax-prefixed.css.map +1 -1
  42. package/package.json +1 -1
  43. package/src/scss/components/_index.scss +1 -0
  44. package/src/scss/components/_treeview.scss +252 -0
package/dist/index.cjs CHANGED
@@ -2365,41 +2365,13 @@ function getDeepestActiveElement(doc = document) {
2365
2365
  }
2366
2366
 
2367
2367
  // INTERNAL: deliberately not exported from src/index.ts.
2368
- /**
2369
- * Every menu-item role, so an item a caller gives `menuitemcheckbox` or
2370
- * `menuitemradio` (a filter or sort menu) stays reachable.
2371
- */
2372
- const MENU_ITEM_SELECTOR = '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]';
2373
- /**
2374
- * Whether a menu item can be reached and activated: it isn't disabled, as a
2375
- * native control or through `aria-disabled="true"`.
2376
- */
2377
- function isEnabledMenuItem(item) {
2378
- return (!item.hasAttribute('disabled') &&
2379
- item.getAttribute('aria-disabled') !== 'true');
2380
- }
2381
- /** Whether `node` is one of `menu`'s items, enabled or not. */
2382
- const isItemOf = (menu, node) => Boolean(node && menu.contains(node) && node.matches(MENU_ITEM_SELECTOR));
2383
- /** The items in `menu` the keys reach, in document order. */
2384
- function enabledMenuItems(menu) {
2385
- return Array.from(menu.querySelectorAll(MENU_ITEM_SELECTOR)).filter(isEnabledMenuItem);
2386
- }
2387
2368
  /** How long typed characters keep adding up to one search, in milliseconds. */
2388
2369
  const TYPEAHEAD_TIMEOUT = 500;
2389
2370
  /**
2390
- * Whether the browser activates `item` on this key by itself: a `<button>`
2391
- * answers Enter and Space, and a link with an `href` answers Enter.
2392
- */
2393
- function activatesItself(item, key) {
2394
- if (item.tagName === 'BUTTON')
2395
- return true;
2396
- return key === 'Enter' && item.tagName === 'A' && item.hasAttribute('href');
2397
- }
2398
- /**
2399
- * The text typing finds an item by: what it says, without what's hidden from
2400
- * assistive technology, such as an icon whose glyph is a ligature's name.
2371
+ * The text typing finds an element by: what it says, without what's hidden
2372
+ * from assistive technology, such as an icon whose glyph is a ligature's name.
2401
2373
  */
2402
- function label(item) {
2374
+ function typeaheadText(element) {
2403
2375
  let text = '';
2404
2376
  const read = (node) => {
2405
2377
  node.childNodes.forEach(child => {
@@ -2411,7 +2383,7 @@ function label(item) {
2411
2383
  }
2412
2384
  });
2413
2385
  };
2414
- read(item);
2386
+ read(element);
2415
2387
  return text.trim().toLowerCase();
2416
2388
  }
2417
2389
  /**
@@ -2420,18 +2392,84 @@ function label(item) {
2420
2392
  * through the items that start with it, and so does a run of one letter
2421
2393
  * ("dd"). A longer search looks from the focused item, so typing more of
2422
2394
  * its own text keeps it.
2395
+ *
2396
+ * @param items - The items typing can reach, in the order they're shown.
2397
+ * @param search - What's been typed, lowercased.
2398
+ * @param current - The focused item's index in `items`, or -1 for none.
2399
+ * @param textOf - The text an item is found by. By default it's the item's
2400
+ * own, read by `typeaheadText`.
2423
2401
  */
2424
- function typeaheadMatch(items, search, current) {
2402
+ function typeaheadMatch(items, search, current, textOf = typeaheadText) {
2425
2403
  const repeated = Array.from(search).every(char => char === search[0]);
2426
2404
  const wanted = repeated ? search[0] : search;
2427
2405
  const start = current < 0 ? 0 : current + (wanted.length === 1 ? 1 : 0);
2428
2406
  for (let i = 0; i < items.length; i += 1) {
2429
2407
  const item = items[(start + i) % items.length];
2430
- if (label(item).startsWith(wanted))
2408
+ if (textOf(item).startsWith(wanted))
2431
2409
  return item;
2432
2410
  }
2433
2411
  return undefined;
2434
2412
  }
2413
+ /**
2414
+ * Keeps what has been typed for a type-ahead search, and forgets it once
2415
+ * `TYPEAHEAD_TIMEOUT` passes with nothing typed. Pair it with
2416
+ * `typeaheadMatch` to find the item the search names.
2417
+ *
2418
+ * @function useTypeahead
2419
+ * @returns A way to add a character to the search, and a way to forget it.
2420
+ */
2421
+ function useTypeahead() {
2422
+ const typedRef = React.useRef({ text: '' });
2423
+ React.useEffect(() => {
2424
+ const typed = typedRef.current;
2425
+ return () => clearTimeout(typed.timer);
2426
+ }, []);
2427
+ return React.useMemo(() => {
2428
+ const reset = () => {
2429
+ const typed = typedRef.current;
2430
+ clearTimeout(typed.timer);
2431
+ typed.text = '';
2432
+ };
2433
+ const type = (char) => {
2434
+ const typed = typedRef.current;
2435
+ typed.text += char.toLowerCase();
2436
+ clearTimeout(typed.timer);
2437
+ typed.timer = setTimeout(reset, TYPEAHEAD_TIMEOUT);
2438
+ return typed.text;
2439
+ };
2440
+ return { type, reset };
2441
+ }, []);
2442
+ }
2443
+
2444
+ // INTERNAL: deliberately not exported from src/index.ts.
2445
+ /**
2446
+ * Every menu-item role, so an item a caller gives `menuitemcheckbox` or
2447
+ * `menuitemradio` (a filter or sort menu) stays reachable.
2448
+ */
2449
+ const MENU_ITEM_SELECTOR = '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]';
2450
+ /**
2451
+ * Whether a menu item can be reached and activated: it isn't disabled, as a
2452
+ * native control or through `aria-disabled="true"`.
2453
+ */
2454
+ function isEnabledMenuItem(item) {
2455
+ return (!item.hasAttribute('disabled') &&
2456
+ item.getAttribute('aria-disabled') !== 'true');
2457
+ }
2458
+ /** Whether `node` is one of `menu`'s items, enabled or not. */
2459
+ const isItemOf = (menu, node) => Boolean(node && menu.contains(node) && node.matches(MENU_ITEM_SELECTOR));
2460
+ /** The items in `menu` the keys reach, in document order. */
2461
+ function enabledMenuItems(menu) {
2462
+ return Array.from(menu.querySelectorAll(MENU_ITEM_SELECTOR)).filter(isEnabledMenuItem);
2463
+ }
2464
+ /**
2465
+ * Whether the browser activates `item` on this key by itself: a `<button>`
2466
+ * answers Enter and Space, and a link with an `href` answers Enter.
2467
+ */
2468
+ function activatesItself(item, key) {
2469
+ if (item.tagName === 'BUTTON')
2470
+ return true;
2471
+ return key === 'Enter' && item.tagName === 'A' && item.hasAttribute('href');
2472
+ }
2435
2473
  /**
2436
2474
  * Moves through and activates the items of a `role="menu"` element from the
2437
2475
  * keyboard. Put the `onKeyDown` it returns on that element. It reads the
@@ -2463,17 +2501,8 @@ function typeaheadMatch(items, search, current) {
2463
2501
  * what has been typed.
2464
2502
  */
2465
2503
  function useMenuKeys() {
2466
- const typedRef = React.useRef({ text: '' });
2467
- React.useEffect(() => {
2468
- const typed = typedRef.current;
2469
- return () => clearTimeout(typed.timer);
2470
- }, []);
2504
+ const typeahead = useTypeahead();
2471
2505
  return React.useMemo(() => {
2472
- const reset = () => {
2473
- const typed = typedRef.current;
2474
- clearTimeout(typed.timer);
2475
- typed.text = '';
2476
- };
2477
2506
  const onKeyDown = (event) => {
2478
2507
  const menu = event.currentTarget;
2479
2508
  const focused = getActiveElementInTree(menu);
@@ -2530,14 +2559,10 @@ function useMenuKeys() {
2530
2559
  return;
2531
2560
  }
2532
2561
  event.preventDefault();
2533
- const typed = typedRef.current;
2534
- typed.text += event.key.toLowerCase();
2535
- clearTimeout(typed.timer);
2536
- typed.timer = setTimeout(reset, TYPEAHEAD_TIMEOUT);
2537
- typeaheadMatch(items, typed.text, current)?.focus();
2562
+ typeaheadMatch(items, typeahead.type(event.key), current)?.focus();
2538
2563
  };
2539
- return { onKeyDown, reset };
2540
- }, []);
2564
+ return { onKeyDown, reset: typeahead.reset };
2565
+ }, [typeahead]);
2541
2566
  }
2542
2567
 
2543
2568
  /**
@@ -3473,7 +3498,7 @@ const EmptyStateActions = ({ className, ...props }) => {
3473
3498
  };
3474
3499
  EmptyStateActions.displayName = 'EmptyState.Actions';
3475
3500
  /** Whether a shorthand prop was given something to render. */
3476
- const isGiven$1 = (node) => node !== undefined &&
3501
+ const isGiven$2 = (node) => node !== undefined &&
3477
3502
  node !== null &&
3478
3503
  typeof node !== 'boolean' &&
3479
3504
  node !== '';
@@ -3520,7 +3545,7 @@ const EmptyStateRoot = React.forwardRef(({ className, icon, title, titleAs = 'h2
3520
3545
  });
3521
3546
  // The props' parts first, each only when it's given, then the children
3522
3547
  // exactly as they were written: nothing here looks at them.
3523
- return (jsxRuntime.jsxs("div", { ref: ref, className: classNames(rootClass, bulmaHelperClasses, className), ...rest, children: [isGiven$1(icon) && (jsxRuntime.jsx(EmptyStateIndicator, { children: typeof icon === 'string' ? jsxRuntime.jsx(Icon, { name: icon }) : icon })), isGiven$1(title) && (jsxRuntime.jsx(EmptyStateTitle, { as: titleAs, children: title })), isGiven$1(description) && (jsxRuntime.jsx(EmptyStateDescription, { children: description })), children] }));
3548
+ return (jsxRuntime.jsxs("div", { ref: ref, className: classNames(rootClass, bulmaHelperClasses, className), ...rest, children: [isGiven$2(icon) && (jsxRuntime.jsx(EmptyStateIndicator, { children: typeof icon === 'string' ? jsxRuntime.jsx(Icon, { name: icon }) : icon })), isGiven$2(title) && (jsxRuntime.jsx(EmptyStateTitle, { as: titleAs, children: title })), isGiven$2(description) && (jsxRuntime.jsx(EmptyStateDescription, { children: description })), children] }));
3524
3549
  });
3525
3550
  const EmptyState = withSubComponents(EmptyStateRoot, {
3526
3551
  Indicator: EmptyStateIndicator,
@@ -3559,6 +3584,10 @@ const MenuLabel = ({ className, children, ...props }) => {
3559
3584
  /**
3560
3585
  * MenuList applies `menu-list` class only at the top level (not for nested lists).
3561
3586
  *
3587
+ * Every level renders `role="list"`, which Safari needs: WebKit stops
3588
+ * exposing a `<ul>` as a list once its markers are hidden, and Bulma's reset
3589
+ * hides them.
3590
+ *
3562
3591
  * @function
3563
3592
  * @param {MenuListProps} props - Props for the MenuList component.
3564
3593
  * @returns {JSX.Element} The rendered menu list.
@@ -3569,8 +3598,11 @@ const MenuList = ({ className, children, ...props }) => {
3569
3598
  const ulClass = classNames(className, bulmaHelperClasses, {
3570
3599
  [usePrefixedClassNames('menu-list')]: level === 0,
3571
3600
  });
3572
- // Increment level for nested MenuLists
3573
- return (jsxRuntime.jsx(MenuListLevelContext.Provider, { value: level + 1, children: jsxRuntime.jsx("ul", { className: ulClass || undefined, ...rest, children: children }) }));
3601
+ // Increment level for nested MenuLists. role="list" restates the <ul>'s
3602
+ // own role, for Safari: WebKit stops exposing a list as one once its
3603
+ // markers are hidden, as Bulma's reset hides them, unless the role is set
3604
+ // explicitly. After the spread, so a caller's role can't take it away.
3605
+ return (jsxRuntime.jsx(MenuListLevelContext.Provider, { value: level + 1, children: jsxRuntime.jsx("ul", { className: ulClass || undefined, ...rest, role: "list", children: children }) }));
3574
3606
  };
3575
3607
  /**
3576
3608
  * `href` alone — annotated so a typo is a compile error rather than a silent
@@ -8130,7 +8162,7 @@ const TimelineItemContext = React.createContext({
8130
8162
  titleAs: 'h2',
8131
8163
  });
8132
8164
  /** Whether a prop was given something to render. */
8133
- const isGiven = (node) => node !== undefined &&
8165
+ const isGiven$1 = (node) => node !== undefined &&
8134
8166
  node !== null &&
8135
8167
  typeof node !== 'boolean' &&
8136
8168
  node !== '';
@@ -8163,7 +8195,7 @@ const TimelineItem = React.forwardRef(({ className, title, time, dateTime, icon,
8163
8195
  [`is-${color}`]: isTimelineColor(color),
8164
8196
  });
8165
8197
  const markerClass = usePrefixedClassNames('timeline-marker', {
8166
- 'is-icon': isGiven(icon),
8198
+ 'is-icon': isGiven$1(icon),
8167
8199
  });
8168
8200
  const contentClass = usePrefixedClassNames('timeline-content');
8169
8201
  const titleClass = usePrefixedClassNames('timeline-title');
@@ -8175,8 +8207,8 @@ const TimelineItem = React.forwardRef(({ className, title, time, dateTime, icon,
8175
8207
  const words = label ? (jsxRuntime.jsx("span", { style: visuallyHidden, children: ` ${label}` })) : null;
8176
8208
  return (jsxRuntime.jsxs("li", { ref: ref, className: classNames(itemClass, bulmaHelperClasses, className), ...rest, ...(state && {
8177
8209
  'aria-current': state === 'active' ? 'step' : undefined,
8178
- }), children: [jsxRuntime.jsx("div", { className: markerClass, "aria-hidden": "true", children: isGiven(icon) &&
8179
- (typeof icon === 'string' ? jsxRuntime.jsx(Icon, { name: icon }) : icon) }), jsxRuntime.jsxs("div", { className: contentClass, children: [isGiven(title) ? (jsxRuntime.jsxs(TitleTag, { className: titleClass, children: [title, words] })) : (words), isGiven(time) &&
8210
+ }), children: [jsxRuntime.jsx("div", { className: markerClass, "aria-hidden": "true", children: isGiven$1(icon) &&
8211
+ (typeof icon === 'string' ? jsxRuntime.jsx(Icon, { name: icon }) : icon) }), jsxRuntime.jsxs("div", { className: contentClass, children: [isGiven$1(title) ? (jsxRuntime.jsxs(TitleTag, { className: titleClass, children: [title, words] })) : (words), isGiven$1(time) &&
8180
8212
  (dateTime ? (jsxRuntime.jsx("time", { className: timeClass, dateTime: dateTime, children: time })) : (jsxRuntime.jsx("span", { className: timeClass, children: time }))), children] })] }));
8181
8213
  });
8182
8214
  TimelineItem.displayName = 'Timeline.Item';
@@ -8250,7 +8282,7 @@ const TimelineRoot = React.forwardRef(({ className, children, active, pending, a
8250
8282
  // Each child gets its state through context rather than a clone, so an
8251
8283
  // app's own component around a Timeline.Item still gets it. Fragments
8252
8284
  // and arrays are opened up first, so each item inside one counts.
8253
- return (jsxRuntime.jsxs("ol", { ref: ref, className: classNames(rootClass, bulmaHelperClasses, className), ...rest, role: "list", children: [flattenChildren(children).map((child, index) => (jsxRuntime.jsx(TimelineItemContext.Provider, { value: contextFor(stateAt(index)), children: child }, React.isValidElement(child) ? child.key : index))), isGiven(pending) && (jsxRuntime.jsx(TimelineItemContext.Provider, { value: contextFor('pending'), children: jsxRuntime.jsx(TimelineItem, { title: pending }) }))] }));
8285
+ return (jsxRuntime.jsxs("ol", { ref: ref, className: classNames(rootClass, bulmaHelperClasses, className), ...rest, role: "list", children: [flattenChildren(children).map((child, index) => (jsxRuntime.jsx(TimelineItemContext.Provider, { value: contextFor(stateAt(index)), children: child }, React.isValidElement(child) ? child.key : index))), isGiven$1(pending) && (jsxRuntime.jsx(TimelineItemContext.Provider, { value: contextFor('pending'), children: jsxRuntime.jsx(TimelineItem, { title: pending }) }))] }));
8254
8286
  });
8255
8287
  const Timeline = withSubComponents(TimelineRoot, { Item: TimelineItem }, 'Timeline');
8256
8288
 
@@ -9545,922 +9577,1353 @@ function RevealImpl(revealProps) {
9545
9577
  const Reveal = RevealImpl;
9546
9578
  Reveal.displayName = 'Reveal';
9547
9579
 
9580
+ // INTERNAL: deliberately not exported from src/index.ts.
9548
9581
  /**
9549
- * The `Block` component renders a simple container with Bulma's `.block` class, adding vertical margin between sections of content.
9550
- *
9551
- * @function
9552
- * @param {BlockProps} props - Props for the Block component.
9553
- * @returns {JSX.Element} The rendered block element.
9554
- * @see {@link https://bulma.io/documentation/elements/block/ | Bulma Block documentation}
9582
+ * What Enter, Space and a click on the row do in each mode, from the tables
9583
+ * in #1096. `open` opens or closes a parent and does nothing on a leaf. A
9584
+ * click on the chevron only ever opens or closes.
9555
9585
  */
9556
- const Block = ({ className, textColor, color, bgColor, children, ...props }) => {
9557
- /**
9558
- * Generates Bulma helper classes and separates out remaining props.
9559
- */
9560
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9561
- color: textColor ?? color,
9562
- backgroundColor: bgColor,
9563
- ...props,
9564
- });
9565
- const bulmaClasses = usePrefixedClassNames('block');
9566
- const blockClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9567
- return (jsxRuntime.jsx("div", { className: blockClasses, ...rest, children: children }));
9586
+ const MODES = {
9587
+ none: { enter: ['open'], space: ['open'], click: ['open'] },
9588
+ single: { enter: ['select'], space: ['select'], click: ['select', 'open'] },
9589
+ checkbox: { enter: ['open'], space: ['check'], click: ['check'] },
9568
9590
  };
9569
-
9591
+ /** Left and Right, swapped for a right-to-left page. */
9592
+ const MIRRORED = {
9593
+ ArrowLeft: 'ArrowRight',
9594
+ ArrowRight: 'ArrowLeft',
9595
+ };
9596
+ /** An IME is composing, so the key belongs to it (`keyCode` 229 is Safari's word for it). */
9597
+ const isComposing$2 = (event) => event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;
9598
+ /** The registry's items on the page, in document order. */
9599
+ const shownIn = (tree, { values }) => Array.from(tree.querySelectorAll('[role="treeitem"]')).filter(element => values.has(element));
9600
+ /** The item an item on the page sits in: its group's `<li>`, or none at the top. */
9601
+ function parentItemOf(element) {
9602
+ const group = element.parentElement;
9603
+ return group.getAttribute('role') === 'group' ? group.parentElement : null;
9604
+ }
9605
+ /** The values of the items an item on the page is in, from the top. */
9606
+ function pathOf(element, { values }) {
9607
+ const path = [];
9608
+ for (let at = parentItemOf(element); at; at = parentItemOf(at)) {
9609
+ path.unshift(values.get(at));
9610
+ }
9611
+ return path;
9612
+ }
9570
9613
  /**
9571
- * The `Box` component renders a bordered, padded container with an optional shadow using Bulma's `.box` class.
9572
- *
9573
- * @function
9574
- * @param {BoxProps} props - Props for the Box component.
9575
- * @returns {JSX.Element} The rendered box element.
9576
- * @see {@link https://bulma.io/documentation/elements/box/ | Bulma Box documentation}
9577
- */
9578
- const Box = ({ className, textColor, color, bgColor, hasShadow = true, children, style, ...props }) => {
9579
- /**
9580
- * Generates Bulma helper classes and separates out remaining props.
9581
- */
9582
- const { bulmaHelperClasses, bulmaHelperStyles, rest } = useBulmaClasses({
9583
- color: textColor ?? color,
9584
- backgroundColor: bgColor,
9585
- ...props,
9614
+ * Every known item's check state, by the rules in #1096:
9615
+ *
9616
+ * - a disabled item is checked only when its own value is listed;
9617
+ * - an enabled item with no enabled items anywhere under it is checked when
9618
+ * it, or any item above it, is listed;
9619
+ * - any other enabled item follows the items of that second kind in its
9620
+ * branch: checked when they all are, unchecked when none is, mixed between.
9621
+ *
9622
+ * Also returns the items of the second kind, which are what a toggle sets.
9623
+ */
9624
+ function readChecks(records, listed) {
9625
+ const kids = new Map();
9626
+ records.forEach(({ parent }, value) => {
9627
+ kids.set(parent, [...(kids.get(parent) ?? []), value]);
9628
+ });
9629
+ const states = new Map();
9630
+ const ends = [];
9631
+ // How many enabled ends the branch holds, and how many are checked.
9632
+ const visit = (value, above) => {
9633
+ const marked = above || listed.has(value);
9634
+ let total = 0;
9635
+ let on = 0;
9636
+ for (const kid of kids.get(value) ?? []) {
9637
+ const [t, o] = visit(kid, marked);
9638
+ total += t;
9639
+ on += o;
9640
+ }
9641
+ if (records.get(value).disabled) {
9642
+ states.set(value, listed.has(value));
9643
+ return [total, on];
9644
+ }
9645
+ if (total === 0) {
9646
+ states.set(value, marked);
9647
+ ends.push(value);
9648
+ return [1, marked ? 1 : 0];
9649
+ }
9650
+ states.set(value, on === total ? true : on > 0 && 'mixed');
9651
+ return [total, on];
9652
+ };
9653
+ for (const top of kids.get(null) ?? [])
9654
+ visit(top, false);
9655
+ return { states, ends };
9656
+ }
9657
+ /**
9658
+ * The list a toggle of `value` leaves: it and every enabled item under it
9659
+ * take the opposite of its state (a mixed one checks), and the rest is read
9660
+ * back by the rules. That's every item that shows checked, parents
9661
+ * included, plus any listed value no item has.
9662
+ */
9663
+ function toggledList(records, list, value) {
9664
+ const { states, ends } = readChecks(records, new Set(list));
9665
+ const to = states.get(value) !== true;
9666
+ // An end was reached from the top, so everything above it is known.
9667
+ const under = (end) => {
9668
+ for (let at = end; at !== null;) {
9669
+ if (at === value)
9670
+ return true;
9671
+ at = records.get(at).parent;
9672
+ }
9673
+ return false;
9674
+ };
9675
+ const seed = new Set(ends.filter(end => (under(end) ? to : states.get(end) === true)));
9676
+ records.forEach(({ disabled }, item) => {
9677
+ if (disabled && states.get(item) === true)
9678
+ seed.add(item);
9586
9679
  });
9587
- const bulmaClasses = usePrefixedClassNames('box', {
9588
- 'is-shadowless': !hasShadow,
9680
+ const after = readChecks(records, seed).states;
9681
+ return [
9682
+ ...Array.from(after)
9683
+ .filter(([, state]) => state === true)
9684
+ .map(([item]) => item),
9685
+ ...list.filter(item => !records.has(item)),
9686
+ ];
9687
+ }
9688
+ /** A value the caller holds, or one this hook holds when the caller doesn't. */
9689
+ function useControlled(controlled, initial) {
9690
+ const [own, setOwn] = React.useState(initial);
9691
+ const isControlled = controlled !== undefined;
9692
+ const set = (next) => {
9693
+ if (!isControlled)
9694
+ setOwn(next);
9695
+ };
9696
+ return [isControlled ? controlled : own, set];
9697
+ }
9698
+ /**
9699
+ * The open state, the selection, the keys, the tab stop and the registry of
9700
+ * a TreeView (#1096). Focus moves for real, with a roving tabindex, rather
9701
+ * than through `aria-activedescendant`.
9702
+ *
9703
+ * Every item registers its value, its parent and whether it's disabled from
9704
+ * a layout effect. In a checkbox tree a closed item's children still render
9705
+ * and register while drawing nothing, so the registry knows every item and a
9706
+ * check reaches all of them. The keys read the items on the page, in
9707
+ * document order, each time they need them.
9708
+ *
9709
+ * @function useTreeView
9710
+ * @param options - The TreeView's props.
9711
+ * @returns The state and handlers the root and its items wire up.
9712
+ */
9713
+ function useTreeView(options) {
9714
+ // An own key only, so a name every object inherits, like `toString`, is a
9715
+ // mode the tree doesn't know too.
9716
+ const mode = Object.prototype.hasOwnProperty.call(MODES, options.mode)
9717
+ ? options.mode
9718
+ : 'none';
9719
+ const actions = MODES[mode];
9720
+ const [expanded, setExpanded] = useControlled(options.expanded, options.defaultExpanded);
9721
+ const [selection, setSelection] = useControlled(options.selected, options.defaultSelected);
9722
+ const [checkedList, setChecked] = useControlled(options.checked, options.defaultChecked);
9723
+ // Each is read in its own mode only.
9724
+ const selected = mode === 'single' ? selection : null;
9725
+ const listed = new Set(mode === 'checkbox' ? checkedList : []);
9726
+ const treeRef = React.useRef(null);
9727
+ const registryRef = React.useRef({
9728
+ records: new Map(),
9729
+ elements: new Map(),
9730
+ values: new WeakMap(),
9731
+ });
9732
+ // Items register from their own effects, so they ask for a render to be read.
9733
+ const [, refresh] = React.useReducer((n) => n + 1, 0);
9734
+ const typeahead = useTypeahead();
9735
+ // The item with focus, and where the stop rests while focus is elsewhere:
9736
+ // `undefined` until the tree has mounted. With the values above it, for
9737
+ // when it goes away.
9738
+ const [focused, setFocused] = React.useState(null);
9739
+ const [resting, setResting] = React.useState();
9740
+ const restingPathRef = React.useRef([]);
9741
+ // The values above an item that left the page with focus in it.
9742
+ const lostRef = React.useRef(null);
9743
+ const { records } = registryRef.current;
9744
+ const checks = mode === 'checkbox'
9745
+ ? // eslint-disable-next-line react-hooks/refs -- read in render on purpose: items write the registry from their layout effects and then ask for this render with `refresh`
9746
+ readChecks(records, listed).states
9747
+ : undefined;
9748
+ const tabStop = focused ?? (resting === undefined ? selected : resting);
9749
+ const isOpen = (value) => expanded.includes(value);
9750
+ const stateOf = (value, path, disabled) => {
9751
+ if (mode === 'single') {
9752
+ return disabled ? {} : { selected: value === selected };
9753
+ }
9754
+ if (checks) {
9755
+ // Until the items register, a value stands for its branch, and a
9756
+ // parent can't know it's mixed.
9757
+ const fromList = listed.has(value) || (!disabled && path.some(v => listed.has(v)));
9758
+ return { checked: checks.get(value) ?? fromList };
9759
+ }
9760
+ return {};
9761
+ };
9762
+ const changeOpen = (next) => {
9763
+ setExpanded(next);
9764
+ options.onExpandedChange?.(next);
9765
+ };
9766
+ const run = {
9767
+ open: ({ value, isParent, open }) => {
9768
+ if (isParent) {
9769
+ changeOpen(open ? expanded.filter(v => v !== value) : [...expanded, value]);
9770
+ }
9771
+ },
9772
+ select: ({ value }) => {
9773
+ if (value === selected)
9774
+ return;
9775
+ setSelection(value);
9776
+ options.onSelectedChange?.(value);
9777
+ },
9778
+ check: ({ value }) => {
9779
+ const next = toggledList(records, checkedList, value);
9780
+ setChecked(next);
9781
+ options.onCheckedChange?.(next);
9782
+ },
9783
+ };
9784
+ /** Disabled items are never selected, checked, opened or closed. */
9785
+ const act = (item, list) => {
9786
+ if (!item.disabled)
9787
+ list.forEach(action => run[action](item));
9788
+ };
9789
+ // After every render, put the stop where it belongs. While focus is in
9790
+ // the tree it's the focused item. Otherwise it's the selected item, or the
9791
+ // first checked one on the page, and failing that it stays where it was.
9792
+ // An item that goes away hands it to its nearest ancestor on the page, or
9793
+ // to the first item, and focus goes with it if it had focus.
9794
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- after every render on purpose: it reads the page, and sets state only when what it reads has changed
9795
+ useClientLayoutEffect(() => {
9796
+ const tree = treeRef.current;
9797
+ const registry = registryRef.current;
9798
+ // Each item's element is read off the page with its value, rather than
9799
+ // out of `elements`, which holds one element per value: two items
9800
+ // sharing a value, which they shouldn't, can leave it without one the
9801
+ // page still has.
9802
+ const shown = shownIn(tree, registry);
9803
+ const onPage = shown.map(element => registry.values.get(element));
9804
+ const elementOf = (value) => shown[onPage.indexOf(value)];
9805
+ const isShown = (value) => value != null && onPage.includes(value);
9806
+ const heir = (path) => [...path].reverse().find(isShown) ?? onPage[0] ?? null;
9807
+ const lost = lostRef.current;
9808
+ lostRef.current = null;
9809
+ if (lost && !tree.contains(getActiveElementInTree(tree))) {
9810
+ const to = heir(lost);
9811
+ if (to)
9812
+ elementOf(to).focus();
9813
+ return;
9814
+ }
9815
+ if (focused !== null && !isShown(focused))
9816
+ setFocused(null);
9817
+ const preferred = onPage.find(value => value === selected || checks?.get(value) === true);
9818
+ const next = preferred ?? (isShown(resting) ? resting : heir(restingPathRef.current));
9819
+ if (next !== resting) {
9820
+ setResting(next);
9821
+ restingPathRef.current = next ? pathOf(elementOf(next), registry) : [];
9822
+ }
9589
9823
  });
9590
- const boxClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9591
- return (jsxRuntime.jsx("div", { className: boxClasses, style: mergeBulmaStyles(bulmaHelperStyles, style), ...rest, children: children }));
9592
- };
9824
+ const register = React.useCallback((value, record) => {
9825
+ const { records: known } = registryRef.current;
9826
+ known.set(value, record);
9827
+ refresh();
9828
+ return () => {
9829
+ if (known.get(value) !== record)
9830
+ return;
9831
+ known.delete(value);
9832
+ refresh();
9833
+ };
9834
+ }, []);
9835
+ const setElement = React.useCallback((value, element) => {
9836
+ const registry = registryRef.current;
9837
+ if (element) {
9838
+ registry.elements.set(value, element);
9839
+ registry.values.set(element, value);
9840
+ return;
9841
+ }
9842
+ const gone = registry.elements.get(value);
9843
+ if (!gone)
9844
+ return;
9845
+ // The ref lets go before the element leaves the page, so it can still
9846
+ // say whether focus was in it, and where it sat.
9847
+ if (!lostRef.current && gone.contains(getActiveElementInTree(gone))) {
9848
+ lostRef.current = [...pathOf(gone, registry), value];
9849
+ }
9850
+ registry.elements.delete(value);
9851
+ }, []);
9852
+ const onFocus = (value, element) => {
9853
+ setFocused(value);
9854
+ setResting(value);
9855
+ restingPathRef.current = pathOf(element, registryRef.current);
9856
+ };
9857
+ const onBlur = () => setFocused(null);
9858
+ const onKeyDown = (event, item) => {
9859
+ const element = event.currentTarget;
9860
+ // A key on an item inside this one bubbles here too, and is its own.
9861
+ if (event.target !== element ||
9862
+ event.defaultPrevented ||
9863
+ event.altKey ||
9864
+ event.ctrlKey ||
9865
+ event.metaKey ||
9866
+ isComposing$2(event)) {
9867
+ return;
9868
+ }
9869
+ const registry = registryRef.current;
9870
+ const items = shownIn(treeRef.current, registry);
9871
+ const at = items.indexOf(element);
9872
+ const rtl = getComputedStyle(element).direction === 'rtl';
9873
+ const key = (rtl && MIRRORED[event.key]) || event.key;
9874
+ switch (key) {
9875
+ case 'ArrowDown':
9876
+ items[at + 1]?.focus();
9877
+ break;
9878
+ case 'ArrowUp':
9879
+ items[at - 1]?.focus();
9880
+ break;
9881
+ case 'Home':
9882
+ items[0].focus();
9883
+ break;
9884
+ case 'End':
9885
+ items[items.length - 1].focus();
9886
+ break;
9887
+ case 'ArrowRight': {
9888
+ const next = items[at + 1];
9889
+ if (!item.open)
9890
+ act(item, ['open']);
9891
+ else if (next && parentItemOf(next) === element)
9892
+ next.focus();
9893
+ break;
9894
+ }
9895
+ case 'ArrowLeft':
9896
+ if (item.open && !item.disabled)
9897
+ act(item, ['open']);
9898
+ else
9899
+ parentItemOf(element)?.focus();
9900
+ break;
9901
+ // A held key acts once.
9902
+ case 'Enter':
9903
+ if (!event.repeat)
9904
+ act(item, actions.enter);
9905
+ break;
9906
+ case ' ':
9907
+ if (!event.repeat)
9908
+ act(item, actions.space);
9909
+ break;
9910
+ case '*': {
9911
+ const closed = [];
9912
+ registry.records.forEach((record, value) => {
9913
+ if (record.parent === item.parent &&
9914
+ record.isParent &&
9915
+ !record.disabled &&
9916
+ !expanded.includes(value)) {
9917
+ closed.push(value);
9918
+ }
9919
+ });
9920
+ if (closed.length)
9921
+ changeOpen([...expanded, ...closed]);
9922
+ break;
9923
+ }
9924
+ default:
9925
+ // Type-ahead takes a printable character. Anything else, Escape and
9926
+ // Tab included, is left to whatever is around the tree.
9927
+ if (event.key.length !== 1)
9928
+ return;
9929
+ typeaheadMatch(items, typeahead.type(event.key), at, el => typeaheadText(el.firstElementChild))?.focus();
9930
+ }
9931
+ event.preventDefault();
9932
+ };
9933
+ const onClick = (element, item, onToggle) => {
9934
+ element.focus();
9935
+ act(item, onToggle ? ['open'] : actions.click);
9936
+ };
9937
+ return {
9938
+ treeRef,
9939
+ rendersClosed: mode === 'checkbox',
9940
+ tabStop,
9941
+ isOpen,
9942
+ stateOf,
9943
+ register,
9944
+ setElement,
9945
+ onFocus,
9946
+ onBlur,
9947
+ onKeyDown,
9948
+ onClick,
9949
+ };
9950
+ }
9593
9951
 
9952
+ const FieldContext = React.createContext(false);
9953
+ const ControlContext = React.createContext(false);
9594
9954
  /**
9595
- * The `Code` component renders a styled `<code>` element with Bulma helper class integration.
9596
- *
9597
- * @function
9598
- * @param {CodeProps} props - Props for the Code component.
9599
- * @returns {JSX.Element} The rendered code element.
9955
+ * Hook to detect if the component is inside a Field wrapper.
9956
+ * Form components use this to skip rendering their own Field.
9600
9957
  */
9601
- const Code = ({ className, textColor, bgColor, children, ...props }) => {
9602
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9603
- color: textColor,
9604
- backgroundColor: bgColor,
9605
- ...props,
9606
- });
9607
- const bulmaClasses = usePrefixedClassNames();
9608
- const codeClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9609
- return (jsxRuntime.jsx("code", { className: codeClasses || undefined, ...rest, children: children }));
9610
- };
9611
-
9612
- // Valid size modifiers for the content class
9613
- const validSizes = ['small', 'medium', 'large'];
9958
+ const useInsideField = () => React.useContext(FieldContext);
9614
9959
  /**
9615
- * The `Content` component applies Bulma’s typographic styles to its children, enhancing the appearance of HTML elements like paragraphs, headings, lists, and tables.
9616
- *
9617
- * @function
9618
- * @param {ContentProps} props - Props for the Content component.
9619
- * @returns {JSX.Element} The rendered content block.
9620
- * @see {@link https://bulma.io/documentation/elements/content/ | Bulma Content documentation}
9960
+ * Hook to detect if the component is inside a Control wrapper.
9961
+ * Form components use this to skip rendering their own Control.
9621
9962
  */
9622
- const Content = ({ className, textColor, color, bgColor, size, children, ...props }) => {
9623
- /**
9624
- * Generates Bulma helper classes and separates out remaining props.
9625
- */
9626
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9627
- color: textColor ?? color,
9628
- backgroundColor: bgColor,
9629
- ...props,
9630
- });
9631
- const bulmaClasses = usePrefixedClassNames('content', {
9632
- [`is-${size}`]: size && size !== 'normal' && validSizes.includes(size),
9633
- });
9634
- const contentClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9635
- return (jsxRuntime.jsx("div", { className: contentClasses, ...rest, children: children }));
9636
- };
9637
-
9963
+ const useInsideControl = () => React.useContext(ControlContext);
9964
+ /** Provider for Field context — used internally by Field component. */
9965
+ const FieldProvider = FieldContext.Provider;
9966
+ /** Provider for Control context — used internally by Control component. */
9967
+ const ControlProvider = ControlContext.Provider;
9968
+ // Each Field-shaping prop, with what takes its place on a `Field` the caller
9969
+ // wraps around the `Control`. `message` needs nothing there, because the
9970
+ // wrapper still renders it inside `Field > Control`. The other Field-level
9971
+ // props (`labelSize`, `labelProps`, `messageColor`) change nothing unless a
9972
+ // `label` or `message` renders, so on their own they keep no `Field`.
9973
+ // `bare-control.test.tsx` holds this list to what each wrapper hands its
9974
+ // `Field` and to which props change its markup, so a new Field-level prop
9975
+ // fails there until it is sorted into one group or the other.
9976
+ const FIELD_SHAPING = [
9977
+ ['label', 'label'],
9978
+ ['message', undefined],
9979
+ ['horizontal', 'horizontal'],
9980
+ ['fieldClassName', 'className (for fieldClassName)'],
9981
+ ];
9982
+ /** `a`, `a and b`, `a, b and c`. */
9983
+ const listOf = (items) => items.length > 1
9984
+ ? `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`
9985
+ : items[0];
9638
9986
  /**
9639
- * The `Delete` component provides a Bulma-styled close/delete button for dismissing modals, notifications, tags, messages, and more.
9640
- *
9641
- * It renders `type="button"`, so a delete button inside a form does not submit it.
9642
- *
9643
- * @function
9644
- * @param {DeleteProps} props - Props for the Delete component.
9645
- * @returns {JSX.Element} The rendered delete button.
9646
- * @see {@link https://bulma.io/documentation/elements/delete/ | Bulma Delete documentation}
9987
+ * Whether a convenience form wrapper renders a `Field` of its own (#905).
9988
+ * Never inside an outer `Field`. Inside a `Control` with no `Field` around
9989
+ * it, a `Field` of its own would put a `.field` inside the `.control`, where
9990
+ * Bulma's control styles break (its icon rules are sibling selectors on the
9991
+ * input). So it renders one there only when a prop that shapes the `Field`'s
9992
+ * markup is set (`label`, `message`, `horizontal`, `fieldClassName`), keeping
9993
+ * the markup it always had, and warns in development that the fix is a
9994
+ * `Field` around that `Control`. A prop counts when it is truthy, the same
9995
+ * test that decides whether it changes the `Field`'s output.
9996
+ * Internal; not part of the public API.
9647
9997
  */
9648
- const Delete = ({ className, textColor, color, bgColor, onClick, size, ariaLabel, disabled = false, ...props }) => {
9649
- /**
9650
- * Generates Bulma helper classes and separates out remaining props.
9651
- */
9652
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9653
- color: textColor ?? color,
9654
- backgroundColor: bgColor,
9655
- ...props,
9656
- });
9657
- const bulmaClasses = usePrefixedClassNames('delete', {
9658
- [`is-${size}`]: size,
9659
- 'is-disabled': disabled,
9660
- });
9661
- const classes = classNames(bulmaClasses, bulmaHelperClasses, className);
9662
- return (jsxRuntime.jsx("button", { className: classes, onClick: onClick, disabled: disabled, ...rest, "aria-label": rest['aria-label'] || ariaLabel || 'Close', type: buttonType(rest.type) }));
9998
+ const rendersOwnField = (component, options) => {
9999
+ if (options.insideField)
10000
+ return false;
10001
+ if (!options.insideControl)
10002
+ return true;
10003
+ const held = FIELD_SHAPING.filter(([prop]) => options[prop]);
10004
+ if (held.length === 0)
10005
+ return false;
10006
+ const names = held.map(([prop]) => prop);
10007
+ const moved = held
10008
+ .map(([, onField]) => onField)
10009
+ .filter((onField) => onField !== undefined);
10010
+ warnOnce(`${component}:Field-in-bare-Control:${names.join('+')}`, `[bestax-bulma] <${component} ${names.join(' ')}> inside a <Control> ` +
10011
+ `with no <Field> around it renders its own <Field> for ` +
10012
+ `${names.length > 1 ? 'those props' : 'that prop'}, which puts a ` +
10013
+ `.field inside the .control, a nesting Bulma's styles are not ` +
10014
+ `written for. Wrap the <Control> in a <Field> instead` +
10015
+ (moved.length > 0 ? `, and set ${listOf(moved)} on that <Field>.` : '.'));
10016
+ return true;
10017
+ };
10018
+ // Each Control-level prop, with what takes its place on the `Control` the
10019
+ // caller wrapped around the wrapper. `bare-control.test.tsx` holds this to
10020
+ // what each wrapper hands its own `Control`, so a new Control-level prop
10021
+ // fails there until it is added here and passed to `rendersOwnControl`.
10022
+ const ON_CONTROL = {
10023
+ isLoading: 'isLoading',
10024
+ iconLeft: 'iconLeft',
10025
+ iconLeftName: 'iconLeftName',
10026
+ iconLeftSize: 'iconLeftSize',
10027
+ iconRight: 'iconRight',
10028
+ iconRightName: 'iconRightName',
10029
+ iconRightSize: 'iconRightSize',
10030
+ hasIconsLeft: 'hasIconsLeft',
10031
+ hasIconsRight: 'hasIconsRight',
10032
+ isExpanded: 'isExpanded',
10033
+ controlSize: 'size (for controlSize)',
10034
+ controlClassName: 'className (for controlClassName)',
9663
10035
  };
9664
-
9665
10036
  /**
9666
- * The `Divider` component renders a styled horizontal rule (`<hr>`) element with Bulma helper class integration.
9667
- *
9668
- * @function
9669
- * @param {DividerProps} props - Props for the Divider component.
9670
- * @returns {JSX.Element} The rendered hr element.
10037
+ * Whether a convenience form wrapper renders a `Control` of its own (#921):
10038
+ * only outside one. Inside a `Control` it renders none, so the props it
10039
+ * would have handed its own do nothing there, and it warns in development,
10040
+ * naming them and saying to set them on that `Control`. An `inline` picker
10041
+ * renders none in either place, so it drops them inside a `Control` or not,
10042
+ * and its warning says so without pointing at a `Control`. A wrapper passes
10043
+ * only the props it hands its own `Control`, as the caller gave them, so a
10044
+ * default the wrapper fills in itself never warns. A prop counts when it is
10045
+ * truthy, since a falsy one would change nothing on a `Control` either.
10046
+ * `iconLeftSize` and `hasIconsLeft` show nothing on a `Control` without a
10047
+ * glyph, so when they move and the caller chose no left icon, the advice
10048
+ * names the wrapper's default glyph too, and following it draws what the
10049
+ * wrapper's own `Control` did.
10050
+ * Internal; not part of the public API.
9671
10051
  */
9672
- const Divider = ({ className, bgColor, ...props }) => {
9673
- /**
9674
- * Generates Bulma helper classes and separates out remaining props.
9675
- */
9676
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9677
- backgroundColor: bgColor,
9678
- ...props,
9679
- });
9680
- const bulmaClasses = usePrefixedClassNames();
9681
- const dividerClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9682
- return jsxRuntime.jsx("hr", { className: dividerClasses || undefined, ...rest });
10052
+ const rendersOwnControl = (component, options) => {
10053
+ if (!options.inline && !options.insideControl)
10054
+ return true;
10055
+ const held = Object.keys(ON_CONTROL).filter(prop => options[prop]);
10056
+ if (held.length === 0)
10057
+ return false;
10058
+ const names = held.join(' ');
10059
+ const doNothing = held.length > 1 ? 'those props do' : 'that prop does';
10060
+ let message;
10061
+ if (options.inline) {
10062
+ message =
10063
+ `[bestax-bulma] <${component} inline ${names}> renders no <Control> ` +
10064
+ `in inline mode, inside a <Control> or not, so ${doNothing} ` +
10065
+ `nothing. Leave ${held.length > 1 ? 'them' : 'it'} out of an ` +
10066
+ `inline picker.`;
10067
+ }
10068
+ else {
10069
+ const toSet = held.map(prop => ON_CONTROL[prop]);
10070
+ const needsGlyph = (options.iconLeftSize || options.hasIconsLeft) &&
10071
+ !options.iconLeft &&
10072
+ options.iconLeftName === undefined &&
10073
+ options.defaultIconLeftName;
10074
+ if (needsGlyph) {
10075
+ toSet.push(`iconLeftName="${options.defaultIconLeftName}" (its default icon)`);
10076
+ }
10077
+ message =
10078
+ `[bestax-bulma] <${component} ${names}> inside a <Control> renders ` +
10079
+ `no <Control> of its own, so ${doNothing} nothing there. Set ` +
10080
+ `${listOf(toSet)} on that <Control> instead.`;
10081
+ }
10082
+ // Keyed by the message itself: the props named do not settle the advice
10083
+ // (an empty `iconLeftName` and an unset one name the same props), so two
10084
+ // calls that would advise differently must not share a key.
10085
+ warnOnce(message, message);
10086
+ return false;
9683
10087
  };
9684
-
10088
+ const FieldLabelIdContext = React.createContext(undefined);
9685
10089
  /**
9686
- * The `Emphasis` component renders a styled `<em>` element with Bulma helper class integration.
9687
- *
9688
- * @function
9689
- * @param {EmphasisProps} props - Props for the Emphasis component.
9690
- * @returns {JSX.Element} The rendered em element.
10090
+ * The id a labeled Field wants its single composed control to adopt (#495).
10091
+ * `undefined` outside a Field, in unlabeled/grouped/addons Fields, or when the
10092
+ * user took over the association with an explicit `labelProps.htmlFor`.
10093
+ * Consumed through `useFieldLabelTarget` by the single-control bases
10094
+ * (InputBase, SelectBase, TextAreaBase, and the date and time picker bases
10095
+ * unless inline, #968) and, through `useAutoLabelId`, by the convenience
10096
+ * inputs that render an input of their own (#939). Read it only through
10097
+ * those, which report what they take: a control that read it directly would
10098
+ * report nothing and lose its label (#1004), and
10099
+ * `__tests__/field-label-readers.test.ts` fails on one. Internal; not part of
10100
+ * the public API.
9691
10101
  */
9692
- const Emphasis = ({ className, textColor, bgColor, children, ...props }) => {
9693
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9694
- color: textColor,
9695
- backgroundColor: bgColor,
9696
- ...props,
9697
- });
9698
- const bulmaClasses = usePrefixedClassNames();
9699
- const emphasisClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9700
- return (jsxRuntime.jsx("em", { className: emphasisClasses || undefined, ...rest, children: children }));
9701
- };
9702
-
10102
+ const useFieldLabelId = () => React.useContext(FieldLabelIdContext);
10103
+ /** Provider for the Field label-target id — used internally by Field. */
10104
+ const FieldLabelIdProvider = FieldLabelIdContext.Provider;
10105
+ const FieldLabelElementIdContext = React.createContext(undefined);
9703
10106
  /**
9704
- * FigureCaption component for rendering a styled figcaption element.
9705
- *
9706
- * A FigureCaption wraps the HTML `<figcaption>` element with Bulma helper class integration.
9707
- * Use it inside Figure components to provide captions for images or other media.
9708
- *
9709
- * @function
9710
- * @param {FigureCaptionProps} props - Props for the FigureCaption component.
9711
- * @returns {JSX.Element} The rendered figcaption element.
10107
+ * The id of a labeled Field's own `<label>`, for a group control (Radios,
10108
+ * Checkboxes, Rate, DateRangeInput and DateRangeInputBase) to point
10109
+ * `aria-labelledby` at, since a group cannot take the label's `htmlFor`
10110
+ * (#939, #1005). Set under the same conditions as
10111
+ * {@link useFieldLabelId}. Consumed through `useAutoLabelledBy`.
10112
+ * Internal; not part of the public API.
9712
10113
  */
9713
- const FigureCaption = ({ className, textColor, bgColor, children, ...props }) => {
9714
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9715
- color: textColor,
9716
- backgroundColor: bgColor,
9717
- ...props,
9718
- });
9719
- const bulmaClasses = usePrefixedClassNames();
9720
- const captionClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9721
- return (jsxRuntime.jsx("figcaption", { className: captionClasses || undefined, ...rest, children: children }));
10114
+ const useFieldLabelElementId = () => React.useContext(FieldLabelElementIdContext);
10115
+ /** Provider for the Field label's own id, used internally by Field. */
10116
+ const FieldLabelElementIdProvider = FieldLabelElementIdContext.Provider;
10117
+ const FieldLabelForContext = React.createContext({});
10118
+ /**
10119
+ * The nearest labeled Field's `<label>`: what it points `htmlFor` at, the id
10120
+ * it generated or the caller's own `labelProps.htmlFor`, and the id the label
10121
+ * renders with, if it has one. An unlabeled Field passes both through
10122
+ * together, so a control in the inner Field of a horizontal layout can tell
10123
+ * the outer label names it, and point at that label by its id, as a range
10124
+ * Slider's thumbs (#981) and Autocomplete's and Taginput's suggestion lists
10125
+ * (#998, #1082) do. The
10126
+ * `htmlFor` is only ever compared with a control's own id, never adopted, so
10127
+ * it hands out no id for a control. It keeps the generated id after the
10128
+ * label drops a `for` nothing took (#1004), which no control's id matches
10129
+ * then, so the value does not change under the content that reported it.
10130
+ * The `id` is set only while that label renders with it. Unlike
10131
+ * {@link useFieldLabelId}, it is set in a `grouped` or `hasAddons` Field
10132
+ * too, so a label wired by hand names the control its `for` picks in those
10133
+ * rows as well. Consumed through `useAutoLabelId`.
10134
+ * Internal; not part of the public API.
10135
+ */
10136
+ const useFieldLabelFor = () => React.useContext(FieldLabelForContext);
10137
+ /** Provider for the Field label's `htmlFor` target and id, used internally by Field. */
10138
+ const FieldLabelForProvider = FieldLabelForContext.Provider;
10139
+ const FieldLabelForReportContext = React.createContext(undefined);
10140
+ /**
10141
+ * Tells the nearest labeled Field that this content took its generated id
10142
+ * while `takesFor` is true (#1004). Once the Field's content has mounted, its
10143
+ * label keeps the generated `for` only while something has said so, so the
10144
+ * label renders none over a group, a `Checkbox`, `Radio` or `Switch`, an
10145
+ * inner `Field`, an input with an `id` of its own, markup of your own, or
10146
+ * nothing at all. Anything that puts the id from {@link useFieldLabelId} on
10147
+ * an element has to say so, or the label drops the `for` that names it, and
10148
+ * content that takes no id has nothing to do. Set under the same conditions
10149
+ * as {@link useFieldLabelId}, so a label wired by hand and a `grouped` or
10150
+ * `hasAddons` Field hear nothing. It reports from a layout effect, which a
10151
+ * server never runs and a hydrating client runs only after its first render,
10152
+ * so that render writes the `for` the way the server did.
10153
+ * Internal; not part of the public API.
10154
+ */
10155
+ const useReportFieldLabelFor = (takesFor) => {
10156
+ const report = React.useContext(FieldLabelForReportContext);
10157
+ useClientLayoutEffect(() => (takesFor ? report?.() : undefined), [report, takesFor]);
9722
10158
  };
10159
+ /** Provider for the labeled Field's report function, used internally by Field. */
10160
+ const FieldLabelForReportProvider = FieldLabelForReportContext.Provider;
9723
10161
  /**
9724
- * The `Figure` component renders a styled `<figure>` element with Bulma helper class integration.
9725
- *
9726
- * @function
9727
- * @param {FigureProps} props - Props for the Figure component.
9728
- * @returns {JSX.Element} The rendered figure element.
10162
+ * The id a control with one input of its own renders with: the caller's
10163
+ * `id` when one arrived, otherwise the one a labeled Field offers (#495),
10164
+ * unless it has no input to put it on (`hasInput: false`, as in an `inline`
10165
+ * picker). It also tells that Field when it took the Field's id, through
10166
+ * {@link useReportFieldLabelFor}, so the label keeps its `for` (#1004). Used
10167
+ * by the single-control bases. Internal; not part of the public API.
9729
10168
  */
9730
- const FigureComponent = ({ className, textColor, bgColor, children, ...props }) => {
9731
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9732
- color: textColor,
9733
- backgroundColor: bgColor,
9734
- ...props,
9735
- });
9736
- const bulmaClasses = usePrefixedClassNames();
9737
- const figureClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9738
- return (jsxRuntime.jsx("figure", { className: figureClasses || undefined, ...rest, children: children }));
10169
+ const useFieldLabelTarget = (id, hasInput = true) => {
10170
+ const fieldLabelId = useFieldLabelId();
10171
+ const target = id ?? (hasInput ? fieldLabelId : undefined);
10172
+ useReportFieldLabelFor(fieldLabelId !== undefined && target === fieldLabelId);
10173
+ return target;
9739
10174
  };
10175
+ const RadiosContext = React.createContext(undefined);
10176
+ const CheckboxesContext = React.createContext(undefined);
9740
10177
  /**
9741
- * Figure component with Caption subcomponent.
9742
- *
9743
- * @example
9744
- * <Figure>
9745
- * <img src="image.jpg" alt="Description" />
9746
- * <Figure.Caption>Image caption text</Figure.Caption>
9747
- * </Figure>
10178
+ * Hook to read the full surrounding `<Radios>` group context (name + selection).
10179
+ * Returns `undefined` when not inside a `<Radios>` group.
9748
10180
  */
9749
- const Figure = withSubComponents(FigureComponent, {
9750
- Caption: FigureCaption,
9751
- }, 'Figure');
9752
-
10181
+ const useRadiosGroup = () => React.useContext(RadiosContext);
9753
10182
  /**
9754
- * Distinguishes an `IconProps` object from a plain custom node (an inline SVG, a
9755
- * `react-icons` component, …) in a slot that accepts either — `Control`'s
9756
- * `iconLeft`/`iconRight`, `IconText`'s `iconProps`.
9757
- *
9758
- * One copy, imported by both consumers: two hand-maintained copies, one per
9759
- * consumer, is how the deprecated `icon` path came to crash both (#663).
9760
- *
9761
- * Its own module rather than `Icon.tsx` because `src/index.ts` wildcard-exports
9762
- * that file, and everything published there is public forever. `polymorphic.ts`
9763
- * makes the same call for `isCustomElement`: an internal discriminator is not
9764
- * something to support for the life of the package.
9765
- *
9766
- * It tests each member's VALUE, not merely its key, and only after everything
9767
- * React owns is out of the way. `'icon' in value` was enough
9768
- * to claim a Font Awesome `IconDefinition` — `{ prefix, iconName, icon: [w, h, …,
9769
- * path] }` — whose `icon` is a path array rather than a class string. That object
9770
- * is not a renderable node either, so the honest outcome is the one this slot has
9771
- * always had for it: the node branch, and React's own "Objects are not valid as a
9772
- * React child". Claiming it here instead rendered a blank container that still
9773
- * reserved layout.
9774
- *
9775
- * A member added to `IconProps` needs a line here, and a missed one is a crash
9776
- * rather than a fallthrough. That is the cost of a slot taking either a props
9777
- * object or a node; keeping the guard in one place is what keeps the cost to a
9778
- * single line.
10183
+ * Hook to read the full surrounding `<Checkboxes>` group context.
10184
+ * Returns `undefined` when not inside a `<Checkboxes>` group.
9779
10185
  */
9780
- function isIconProps(value) {
9781
- if (typeof value !== 'object' || value === null)
9782
- return false;
9783
- // Anything React owns. `isValidElement` covers elements only; a PORTAL is a
9784
- // valid `ReactNode` that fails it and carries a `children` key, so the member
9785
- // test below claimed it and `Control` spread it into `<Icon>` as props —
9786
- // rendering the portal's child locally instead of in its container. Every
9787
- // React internal carries `$$typeof`, so testing for it covers lazy, context
9788
- // and whatever React adds next, rather than portals alone.
9789
- if (React.isValidElement(value) || '$$typeof' in value)
9790
- return false;
9791
- const candidate = value;
9792
- return (typeof candidate.name === 'string' ||
9793
- typeof candidate.icon === 'string' ||
9794
- 'children' in candidate);
9795
- }
10186
+ const useCheckboxesGroup = () => React.useContext(CheckboxesContext);
10187
+ /** Provider for the Radios group context — used internally by Radios. */
10188
+ const RadiosProvider = RadiosContext.Provider;
10189
+ /** Provider for the Checkboxes group context — used internally by Checkboxes. */
10190
+ const CheckboxesProvider = CheckboxesContext.Provider;
9796
10191
 
9797
10192
  /**
9798
- * The `IconText` component provides a Bulma-styled horizontal arrangement of one or more `Icon` components and optional text.
10193
+ * The values the Checkbox `color` prop accepts, as a readonly tuple.
9799
10194
  *
9800
- * @function
9801
- * @param {IconTextProps} props - Props for the IconText component.
9802
- * @returns {JSX.Element} The rendered icon text element.
9803
- * @see {@link https://bulma.io/documentation/elements/icon/#icon-text | Bulma IconText documentation}
9804
- */
9805
- const IconTextComponent = ({ className, textColor, color, bgColor, iconProps, children, items, ...props }) => {
9806
- /**
9807
- * Generates Bulma helper classes and separates out remaining props.
9808
- */
9809
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9810
- color: textColor ?? color,
9811
- backgroundColor: bgColor,
9812
- ...props,
9813
- });
9814
- const bulmaClasses = usePrefixedClassNames('icon-text');
9815
- const iconTextClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9816
- return (jsxRuntime.jsx("span", { className: iconTextClasses, ...rest, children: items ? (items.map((item, index) => (jsxRuntime.jsxs(React.Fragment, { children: [item.iconProps &&
9817
- (isIconProps(item.iconProps) ? (jsxRuntime.jsx(Icon, { ...item.iconProps })) : (jsxRuntime.jsx(Icon, { children: item.iconProps }))), item.text && jsxRuntime.jsx("span", { children: item.text })] }, index)))) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [iconProps &&
9818
- (isIconProps(iconProps) ? (jsxRuntime.jsx(Icon, { ...iconProps })) : (jsxRuntime.jsx(Icon, { children: iconProps }))), children && jsxRuntime.jsx("span", { children: children })] })) }));
9819
- };
9820
- const IconText = withSubComponents(IconTextComponent, { Icon }, 'IconText');
9821
-
9822
- /**
9823
- * The `Image` component wraps images, iframes, or custom content in a Bulma-styled container, supporting fixed sizes, aspect ratios, rounded corners, retina images, and all Bulma helper props for color and spacing.
10195
+ * `CheckboxProps['color']` is typed from it, so the two list the same values.
10196
+ * Map over it to build a color picker, or check a value that arrives at
10197
+ * runtime before passing it in: the component adds no color class for a value
10198
+ * outside the tuple.
9824
10199
  *
9825
- * @function
9826
- * @param {ImageProps} props - Props for the Image component.
9827
- * @returns {JSX.Element} The rendered image element.
9828
- * @see {@link https://bulma.io/documentation/elements/image/ | Bulma Image documentation}
10200
+ * @example
10201
+ * import { Checkbox, checkboxColors } from '@allxsmith/bestax-bulma';
10202
+ *
10203
+ * checkboxColors.map(color => (
10204
+ * <Checkbox key={color} color={color}>
10205
+ * {color}
10206
+ * </Checkbox>
10207
+ * ));
9829
10208
  */
9830
- const Image = ({ as, className, textColor, color, bgColor, size, isRounded, isRetina, src, alt, children, ...props }) => {
9831
- /**
9832
- * Generates Bulma helper classes and separates out remaining props.
9833
- */
9834
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9835
- color: textColor ?? color,
9836
- backgroundColor: bgColor,
9837
- ...props,
9838
- });
9839
- const bulmaClasses = usePrefixedClassNames('image', {
9840
- [`is-${size}`]: size,
9841
- 'has-ratio': size && typeof size === 'string' && size.includes('by'),
9842
- });
9843
- const imageClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9844
- // Default tag logic: if "as" is provided, use it.
9845
- // If not, use <figure> for aspect ratios or children, <div> otherwise.
9846
- let Tag;
9847
- if (as) {
9848
- Tag = as;
9849
- }
9850
- else if (size && typeof size === 'string' && size.includes('by')) {
9851
- Tag = 'figure';
9852
- }
9853
- else {
9854
- Tag = 'div';
9855
- }
9856
- const roundedClass = usePrefixedClassNames('is-rounded');
9857
- const content = children ? (children) : (jsxRuntime.jsx("img", { className: classNames({ [roundedClass]: isRounded }) || undefined, src: src, alt: alt, ...(isRetina && src ? { srcSet: `${src} 2x` } : {}) }));
9858
- return (jsxRuntime.jsx(Tag, { className: imageClasses, ...rest, children: content }));
9859
- };
9860
-
10209
+ const checkboxColors = [
10210
+ 'primary',
10211
+ 'link',
10212
+ 'info',
10213
+ 'success',
10214
+ 'warning',
10215
+ 'danger',
10216
+ ];
9861
10217
  /**
9862
- * Keys pressed together or one after another, in the outer `<kbd>` HTML gives a combination, with the separator between each pair as text.
9863
- *
9864
- * It doesn't wrap in the middle and isn't drawn as a key itself. It sets no
9865
- * font size, so the keys in it are the size of a key on its own.
10218
+ * The values the Checkbox `size` prop accepts, as a readonly tuple.
9866
10219
  *
9867
- * @function
9868
- * @param {KbdGroupProps} props - Props for Kbd.Group.
9869
- * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the outer `<kbd>`.
9870
- * @returns {JSX.Element} The rendered combination.
10220
+ * `CheckboxProps['size']` is typed from it, so the two list the same values.
10221
+ * Use it to offer a size choice or to check a value that arrives at runtime:
10222
+ * the component adds no size class for a value outside the tuple. These are
10223
+ * element sizes, not the spacing scale in `validSizes`.
9871
10224
  *
9872
10225
  * @example
9873
- * <Kbd.Group>
9874
- * <Kbd>Ctrl</Kbd>
9875
- * <Kbd>Shift</Kbd>
9876
- * <Kbd>P</Kbd>
9877
- * </Kbd.Group>
10226
+ * import { checkboxSizes } from '@allxsmith/bestax-bulma';
9878
10227
  *
9879
- * @example
9880
- * <Kbd.Group separator="then">
9881
- * <Kbd>G</Kbd>
9882
- * <Kbd>I</Kbd>
9883
- * </Kbd.Group>
10228
+ * type CheckboxSize = (typeof checkboxSizes)[number];
9884
10229
  */
9885
- const KbdGroup = React.forwardRef(({ className, textColor, separator = '+', children, ...props }, ref) => {
9886
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9887
- color: textColor,
9888
- ...props,
9889
- });
9890
- const groupClass = usePrefixedClassNames('kbd-group');
9891
- const separatorClass = usePrefixedClassNames('kbd-separator');
9892
- // Null, undefined and booleans drop out, and Fragments are opened up, so
9893
- // an empty Fragment leaves no separator behind and the keys in one are
9894
- // separated like the rest. The spaces are part of the text, so a screen
9895
- // reader reads "Ctrl plus S" rather than one run-together word, whatever
9896
- // the CSS does.
9897
- const keys = flattenChildren(children);
9898
- return (jsxRuntime.jsx("kbd", { ref: ref, className: classNames(groupClass, bulmaHelperClasses, className), ...rest, children: keys.map((key, index) => (jsxRuntime.jsxs(React.Fragment, { children: [index > 0 && (jsxRuntime.jsx("span", { className: separatorClass, children: ` ${separator} ` })), key] }, React.isValidElement(key) && key.key != null ? key.key : index))) }));
9899
- });
9900
- KbdGroup.displayName = 'Kbd.Group';
10230
+ const checkboxSizes = ['small', 'normal', 'medium', 'large'];
9901
10231
  /**
9902
- * The `Kbd` component renders a `<kbd>` drawn as a keycap, for a key the reader presses.
9903
- *
9904
- * It only shows the key: it doesn't listen for it. The key takes its colors
9905
- * from the text it sits in, and its size in `em`, so it fits a paragraph, a
9906
- * title or a filled button as it is, and `textSize` sizes it outright. For a
9907
- * combination or a sequence, put the keys in a `Kbd.Group`. What tells
9908
- * assistive tech a control has a shortcut is `aria-keyshortcuts` on that
9909
- * control, with `Kbd` as the hint people see next to it.
10232
+ * The `Checkbox` component provides a Bulma-styled checkbox input.
9910
10233
  *
9911
10234
  * @function
9912
- * @param {KbdProps} props - Props for the Kbd component.
9913
- * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<kbd>`.
9914
- * @returns {JSX.Element} The rendered key.
10235
+ * @param {CheckboxProps} props - Props for the Checkbox component.
10236
+ * @returns {JSX.Element} The rendered checkbox element.
9915
10237
  *
9916
10238
  * @example
9917
- * <Kbd>Esc</Kbd>
10239
+ * // Basic checkbox
10240
+ * <Checkbox>Accept terms</Checkbox>
9918
10241
  *
9919
10242
  * @example
9920
- * <Kbd label="Command">⌘</Kbd>
10243
+ * // Inside a group
10244
+ * <Checkboxes name="tags" defaultValue={['react']}>
10245
+ * <Checkbox value="react">React</Checkbox>
10246
+ * <Checkbox value="vue">Vue</Checkbox>
10247
+ * </Checkboxes>
9921
10248
  */
9922
- const KbdRoot = React.forwardRef(({ className, textColor, bgColor, label, children, ...props }, ref) => {
10249
+ const Checkbox = React.forwardRef(({ color, size, className, children, textColor, disabled, name, value, checked, defaultChecked, indeterminate, onChange, ...props }, ref) => {
9923
10250
  const { bulmaHelperClasses, rest } = useBulmaClasses({
9924
10251
  color: textColor,
9925
- backgroundColor: bgColor,
9926
10252
  ...props,
9927
10253
  });
9928
- const kbdClass = usePrefixedClassNames('kbd');
9929
- // The name is text rather than an aria-label, which ARIA in HTML
9930
- // prohibits on kbd. Its styles are inline, so it stays hidden in a
9931
- // modular build that leaves out Bulma's helper classes.
9932
- return (jsxRuntime.jsx("kbd", { ref: ref, className: classNames(kbdClass, bulmaHelperClasses, className), ...rest, children: label ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { "aria-hidden": "true", children: children }), jsxRuntime.jsx("span", { style: visuallyHidden, children: label })] })) : (children) }));
9933
- });
9934
- const Kbd = withSubComponents(KbdRoot, { Group: KbdGroup }, 'Kbd');
9935
-
9936
- /**
9937
- * The `Link` component renders a styled anchor (`<a>`) element with Bulma helper class integration.
9938
- *
9939
- * @function
9940
- * @param {LinkProps} props - Props for the Link component.
9941
- * @param {React.Ref} ref - Forwarded ref to the element `as` renders.
9942
- * @returns {JSX.Element} The rendered anchor, or whatever `as` names.
9943
- * @see {@link https://bulma.io/documentation/elements/content/ | Bulma Content documentation}
9944
- */
9945
- const Link = React.forwardRef(function Link(linkProps, ref) {
9946
- const { className, textColor, bgColor, isActive, as: Component = 'a', children, ...props } = linkProps;
9947
- /**
9948
- * Generates Bulma helper classes and separates out remaining props.
9949
- */
9950
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9951
- color: textColor,
9952
- backgroundColor: bgColor,
9953
- ...props,
10254
+ // The input's own ref, merged with the forwarded one. A callback ref's
10255
+ // return value is handed back, so React 19 still runs a cleanup it
10256
+ // returns, as it did when the ref went straight to the input.
10257
+ const inputRef = React.useRef(null);
10258
+ const setRefs = React.useCallback((node) => {
10259
+ inputRef.current = node;
10260
+ if (typeof ref === 'function')
10261
+ return ref(node);
10262
+ if (ref)
10263
+ ref.current = node;
10264
+ }, [ref]);
10265
+ // `indeterminate` is a DOM property with no attribute, so it's set on the
10266
+ // input rather than rendered. A click clears it, so this runs after every
10267
+ // render, not only when the prop changes. Unset, it's left alone, so a
10268
+ // ref that sets the property itself still works. `ownsIndeterminateRef`
10269
+ // remembers that this component set it, so a prop that goes back to unset
10270
+ // clears the dash once instead of leaving it on.
10271
+ const ownsIndeterminateRef = React.useRef(false);
10272
+ useClientLayoutEffect(() => {
10273
+ if (indeterminate !== undefined) {
10274
+ ownsIndeterminateRef.current = true;
10275
+ inputRef.current.indeterminate = indeterminate;
10276
+ }
10277
+ else if (ownsIndeterminateRef.current) {
10278
+ ownsIndeterminateRef.current = false;
10279
+ inputRef.current.indeterminate = false;
10280
+ }
9954
10281
  });
9955
- const bulmaClasses = usePrefixedClassNames({
9956
- 'is-active': isActive,
10282
+ // Inherit name + selection state from a surrounding <Checkboxes> group.
10283
+ // Local props always win over the group (explicit > implicit).
10284
+ const group = useCheckboxesGroup();
10285
+ const effectiveName = name ?? group?.name;
10286
+ // Group-managed checked state — only when the group is in
10287
+ // controlled/uncontrolled mode (group.value is defined) AND this Checkbox
10288
+ // has a value to compare against. Local `checked` always wins.
10289
+ const groupManaged = group?.value !== undefined && value !== undefined;
10290
+ const groupHas = groupManaged && group.value.includes(String(value));
10291
+ const effectiveChecked = checked !== undefined ? checked : groupManaged ? groupHas : undefined;
10292
+ // When the group manages checked, suppress local defaultChecked to avoid
10293
+ // the controlled/uncontrolled React warning.
10294
+ const effectiveDefaultChecked = groupManaged ? undefined : defaultChecked;
10295
+ const handleChange = React.useCallback((e) => {
10296
+ // The click that got here cleared the mixed state. Put it back now,
10297
+ // since a click that changes no state renders nothing to do it.
10298
+ if (indeterminate !== undefined)
10299
+ e.target.indeterminate = indeterminate;
10300
+ // Local onChange always fires.
10301
+ onChange?.(e);
10302
+ // Dispatch to the group additionally — toggle this value's
10303
+ // membership in the group's array.
10304
+ if (group?.onChange && value !== undefined) {
10305
+ const valStr = String(value);
10306
+ const currentArr = group.value ?? [];
10307
+ const next = e.target.checked
10308
+ ? currentArr.includes(valStr)
10309
+ ? currentArr
10310
+ : [...currentArr, valStr]
10311
+ : currentArr.filter(v => v !== valStr);
10312
+ group.onChange(next);
10313
+ }
10314
+ }, [onChange, group, value, indeterminate]);
10315
+ const mainClass = usePrefixedClassNames('styled-checkbox', 'checkbox', {
10316
+ [`is-${color}`]: color && checkboxColors.includes(color),
10317
+ [`is-${size}`]: size && checkboxSizes.includes(size),
9957
10318
  });
9958
- const linkClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9959
- return (jsxRuntime.jsx(Component, { ref: ref, className: linkClasses || undefined, ...rest, ...(Component === 'button' ? { type: buttonType(rest.type) } : {}), children: children }));
10319
+ const checkboxClass = classNames(mainClass, bulmaHelperClasses, className);
10320
+ const checkClass = usePrefixedClassNames('check');
10321
+ const controlLabelClass = usePrefixedClassNames('control-label');
10322
+ return (jsxRuntime.jsxs("label", { className: checkboxClass, children: [jsxRuntime.jsx("input", { ref: setRefs, type: "checkbox", disabled: disabled, name: effectiveName, value: value, checked: effectiveChecked, defaultChecked: effectiveDefaultChecked, onChange: handleChange, ...rest }), jsxRuntime.jsx("span", { className: checkClass }), children && jsxRuntime.jsx("span", { className: controlLabelClass, children: children })] }));
9960
10323
  });
9961
- Link.displayName = 'Link';
10324
+ Checkbox.displayName = 'Checkbox';
9962
10325
 
10326
+ const validTreeViewSizes = ['small', 'medium', 'large'];
10327
+ // Not exported, so they stay out of the public API that `index.ts` re-exports.
10328
+ const TreeViewContext = React.createContext(null);
10329
+ const TOP = { path: [], hidden: false };
10330
+ const BranchContext = React.createContext(TOP);
10331
+ /** The box's input lets the row's click toggle it, so a change on its own does nothing. */
10332
+ const ignoreChange = () => { };
10333
+ /** Whether an `icon` was given something to render. */
10334
+ const isGiven = (node) => node !== undefined &&
10335
+ node !== null &&
10336
+ typeof node !== 'boolean' &&
10337
+ node !== '';
9963
10338
  /**
9964
- * The `ListItem` component renders a styled list item (`<li>`) element with Bulma helper class integration.
10339
+ * The `TreeView` component renders a tree of items you browse and pick from, the way a file explorer works: one tab stop, the arrow keys to move, and Right and Left to open and close.
9965
10340
  *
9966
- * @function
9967
- * @param {ListItemProps} props - Props for the ListItem component.
9968
- * @returns {JSX.Element} The rendered li element.
9969
- */
9970
- const ListItem = ({ className, textColor, bgColor, children, ...props }) => {
9971
- /**
9972
- * Generates Bulma helper classes and separates out remaining props.
9973
- */
9974
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9975
- color: textColor,
9976
- backgroundColor: bgColor,
9977
- ...props,
9978
- });
9979
- const bulmaClasses = usePrefixedClassNames();
9980
- const itemClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9981
- return (jsxRuntime.jsx("li", { className: itemClasses || undefined, ...rest, children: children }));
9982
- };
9983
-
9984
- /**
9985
- * The `Loader` component renders Bulma's `.loader`, a small spinning ring for inline loading states.
10341
+ * It's the APG Tree View pattern, drawn to look like Bulma's menu. It isn't
10342
+ * for site navigation: items aren't links, and a sidebar whose sections open
10343
+ * and close is `Menu`'s job. The root owns which items are open, through
10344
+ * `expanded`, `defaultExpanded` and `onExpandedChange`.
9986
10345
  *
9987
- * The ring is `1em` square, so `textSize` scales it. It is exposed as an
9988
- * indeterminate `progressbar` named by `ariaLabel`, and it stops spinning
9989
- * under `prefers-reduced-motion: reduce` while staying drawn. In forced
9990
- * colors its two open sides take the page's color, so it stays open.
10346
+ * `selectionMode` picks one of three trees. With `none`, nothing is picked.
10347
+ * With `single`, one item is, and `selected` says which. With `checkbox`,
10348
+ * each item has a box, a check reaches every enabled item under it, closed
10349
+ * branches included, and a parent with some of its items checked is mixed.
10350
+ * `selected` is read in a `single` tree only, and `checked` in a `checkbox`
10351
+ * tree only. Selection never follows focus.
9991
10352
  *
9992
- * @function
9993
- * @param {LoaderProps} props - Props for the Loader component.
9994
- * @param {React.Ref<HTMLSpanElement>} ref - Forwarded ref to the loader element.
9995
- * @returns {JSX.Element} The rendered loader element.
10353
+ * Disabled items stay reachable from the keys, so a screen reader finds
10354
+ * them. The tree needs a name, from `aria-label` or `aria-labelledby`, and a
10355
+ * development build warns when it has neither.
9996
10356
  *
9997
- * @example
9998
- * <Loader />
10357
+ * @function
10358
+ * @param {TreeViewProps} props - Props for the TreeView component.
10359
+ * @param {React.Ref<HTMLUListElement>} ref - Forwarded ref to the `<ul>`.
10360
+ * @returns {JSX.Element} The rendered tree.
10361
+ * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ | APG Tree View Pattern}
9999
10362
  *
10000
10363
  * @example
10001
- * <Loader textSize="3" ariaLabel="Saving" />
10002
- */
10003
- const Loader = React.forwardRef(({ className, ariaLabel = 'Loading', ...props }, ref) => {
10004
- const { bulmaHelperClasses, rest } = useBulmaClasses(props);
10005
- const loaderClass = usePrefixedClassNames('loader');
10006
- // role="progressbar", not role="status": a status region mounted together
10007
- // with its text is not reliably announced, so the loader claims only what
10008
- // it can deliver, a named indicator found by reading the page. No
10009
- // aria-valuenow, because the progress is indeterminate.
10010
- return (jsxRuntime.jsx("span", { ref: ref, className: classNames(loaderClass, bulmaHelperClasses, className), role: "progressbar", "aria-label": ariaLabel, ...rest }));
10364
+ * <TreeView aria-label="Files" selectionMode="single" defaultExpanded={['src']}>
10365
+ * <TreeView.Item value="src" label="src" icon="folder">
10366
+ * <TreeView.Item value="src/index.ts" label="index.ts" icon="file" />
10367
+ * </TreeView.Item>
10368
+ * <TreeView.Item value="package.json" label="package.json" icon="file" />
10369
+ * </TreeView>
10370
+ */
10371
+ const TreeViewRoot = React.forwardRef(function TreeView({ children, selectionMode = 'none', selected, defaultSelected = null, onSelectedChange, checked, defaultChecked = [], onCheckedChange, expanded, defaultExpanded = [], onExpandedChange, size, className, ...props }, ref) {
10372
+ const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
10373
+ const engine = useTreeView({
10374
+ mode: selectionMode,
10375
+ selected,
10376
+ defaultSelected,
10377
+ onSelectedChange,
10378
+ checked,
10379
+ defaultChecked,
10380
+ onCheckedChange,
10381
+ expanded,
10382
+ defaultExpanded,
10383
+ onExpandedChange,
10384
+ });
10385
+ const { treeRef } = engine;
10386
+ const setRefs = React.useCallback((node) => {
10387
+ treeRef.current = node;
10388
+ if (typeof ref === 'function')
10389
+ ref(node);
10390
+ else if (ref)
10391
+ ref.current = node;
10392
+ }, [ref, treeRef]);
10393
+ const named = Boolean(rest['aria-label'] || rest['aria-labelledby']);
10394
+ React.useEffect(() => {
10395
+ if (!named) {
10396
+ warnOnce('TreeView:unnamed', '[bestax-bulma] <TreeView> has no accessible name. Pass ' +
10397
+ 'aria-label or aria-labelledby, so a screen reader can say what ' +
10398
+ 'the tree holds.');
10399
+ }
10400
+ }, [named]);
10401
+ const rootClass = usePrefixedClassNames('tree-view', {
10402
+ [`is-${size}`]: !!size && validTreeViewSizes.includes(size),
10403
+ });
10404
+ // The props are spread first, so the role and the attributes the tree
10405
+ // owns win over anything a caller passes along.
10406
+ return (jsxRuntime.jsx(TreeViewContext.Provider, { value: engine, children: jsxRuntime.jsx(BranchContext.Provider, { value: TOP, children: jsxRuntime.jsx("ul", { ...rest, ref: setRefs, className: classNames(rootClass, bulmaHelperClasses, className), role: "tree", "aria-multiselectable": engine.rendersClosed || undefined, children: children }) }) }));
10011
10407
  });
10012
- Loader.displayName = 'Loader';
10013
-
10014
10408
  /**
10015
- * The `Notification` component is a Bulma-styled alert/message area for providing feedback, warnings, or information to users.
10409
+ * One item in the tree: a row with its chevron, its box, its icon and its label, and, while it's open, a group of the items under it.
10016
10410
  *
10017
10411
  * @function
10018
- * @param {NotificationProps} props - Props for the Notification component.
10019
- * @returns {JSX.Element} The rendered notification element.
10020
- * @see {@link https://bulma.io/documentation/elements/notification/ | Bulma Notification documentation}
10021
- */
10022
- const Notification = ({ className, color, textColor, isLight, hasDelete, onDelete, children, ...props }) => {
10023
- warnUnstyledColor('Notification', color);
10024
- /**
10025
- * Generates Bulma helper classes and separates out remaining props.
10026
- */
10027
- const { bulmaHelperClasses, rest } = useBulmaClasses({
10028
- color: textColor,
10029
- ...props,
10030
- });
10031
- const bulmaClasses = usePrefixedClassNames('notification', {
10032
- [`is-${color}`]: color && validColors.includes(color),
10033
- 'is-light': isLight,
10034
- 'has-delete': hasDelete,
10412
+ * @param {TreeViewItemProps} props - Props for TreeView.Item.
10413
+ * @param {React.Ref<HTMLLIElement>} ref - Forwarded ref to the `<li>`.
10414
+ * @returns {JSX.Element | null} The rendered item, or nothing under a closed one.
10415
+ */
10416
+ const TreeViewItem = React.forwardRef(function TreeViewItem({ value, label, icon, children, hasChildren = false, disabled = false, className, onClick, onKeyDown, onFocus, onBlur, ...props }, ref) {
10417
+ const tree = React.useContext(TreeViewContext);
10418
+ const branch = React.useContext(BranchContext);
10419
+ const labelId = React.useId();
10420
+ const hasItems = React.Children.toArray(children).length > 0;
10421
+ const isParent = hasItems || hasChildren;
10422
+ const open = isParent && !!tree?.isOpen(value);
10423
+ const parent = branch.path[branch.path.length - 1] ?? null;
10424
+ const itemClass = usePrefixedClassNames('tree-view-item');
10425
+ const groupClass = usePrefixedClassNames('tree-view-group');
10426
+ const toggleClass = usePrefixedClassNames('tree-view-toggle');
10427
+ const checkClass = usePrefixedClassNames('tree-view-check');
10428
+ const iconClass = usePrefixedClassNames('tree-view-icon');
10429
+ const labelClass = usePrefixedClassNames('tree-view-label');
10430
+ const register = tree?.register;
10431
+ useClientLayoutEffect(() => {
10432
+ if (!register) {
10433
+ warnOnce('TreeView.Item:outside', '[bestax-bulma] <TreeView.Item> is outside a <TreeView>, so it ' +
10434
+ 'renders nothing. Put it inside one.');
10435
+ return undefined;
10436
+ }
10437
+ return register(value, { parent, disabled, isParent });
10438
+ }, [register, value, parent, disabled, isParent]);
10439
+ const setElement = tree?.setElement;
10440
+ const setRefs = React.useCallback((node) => {
10441
+ // Only an item inside a tree renders an element to hand over.
10442
+ setElement(value, node);
10443
+ if (typeof ref === 'function')
10444
+ ref(node);
10445
+ else if (ref)
10446
+ ref.current = node;
10447
+ }, [setElement, value, ref]);
10448
+ const rowRef = React.useRef(null);
10449
+ const toggleRef = React.useRef(null);
10450
+ const childBranch = React.useMemo(() => ({
10451
+ path: [...branch.path, value],
10452
+ hidden: branch.hidden || !open,
10453
+ }), [branch, value, open]);
10454
+ const { selected, checked } = tree?.stateOf(value, branch.path, disabled) ?? {};
10455
+ const rowClass = usePrefixedClassNames('tree-view-row', {
10456
+ 'is-selected': selected,
10457
+ 'is-disabled': disabled,
10035
10458
  });
10036
- const deleteClasses = usePrefixedClassNames('delete');
10037
- const notificationClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10038
- return (jsxRuntime.jsxs("div", { className: notificationClasses, ...rest, children: [hasDelete && (jsxRuntime.jsx("button", { type: "button", className: deleteClasses, onClick: onDelete, "aria-label": "Close notification" })), children] }));
10039
- };
10040
- let notificationId = 0;
10041
- const notificationListeners = new Set();
10042
- let notifications = [];
10043
- // Queue support
10044
- let queuedNotifications = [];
10045
- let currentQueuedNotification = null;
10046
- // The server has nowhere to portal a notification to, and its copy of this
10047
- // module is shared by every request, so server rendering reads an empty list.
10048
- // Hydration reads it too, which keeps the first client render matching the
10049
- // server's.
10050
- const noNotifications = [];
10051
- const getServerNotifications = () => noNotifications;
10052
- // What a container shows right now: the stacked notifications, then the queued
10053
- // one on screen. NotificationContainer renders from this rather than from
10054
- // updates alone, so notifications raised before it mounted still appear. It is
10055
- // replaced rather than mutated, and only when listeners are notified, because
10056
- // useSyncExternalStore needs the same array back between changes. An empty
10057
- // list is `noNotifications` itself, so an empty store reads the same snapshot
10058
- // on the client as on the server, and a notify that leaves nothing showing
10059
- // when nothing was showing doesn't render the container again.
10060
- let visibleNotifications = noNotifications;
10061
- const getVisibleNotifications = () => visibleNotifications;
10062
- const notifyNotificationListeners = () => {
10063
- const allVisible = [...notifications];
10064
- if (currentQueuedNotification) {
10065
- allVisible.push(currentQueuedNotification);
10066
- }
10067
- visibleNotifications = allVisible.length > 0 ? allVisible : noNotifications;
10068
- notificationListeners.forEach(listener => listener([...allVisible]));
10069
- };
10070
- const processQueuedNotification = () => {
10071
- if (currentQueuedNotification || queuedNotifications.length === 0)
10072
- return;
10073
- currentQueuedNotification = queuedNotifications.shift();
10074
- notifyNotificationListeners();
10075
- };
10459
+ if (!tree)
10460
+ return null;
10461
+ // While it's open the children render in its group. In a checkbox tree
10462
+ // they render while it's closed too, drawing nothing, so a check knows
10463
+ // what's under it.
10464
+ const items = (open || tree.rendersClosed) && (jsxRuntime.jsx(BranchContext.Provider, { value: childBranch, children: children }));
10465
+ if (branch.hidden)
10466
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: items });
10467
+ const info = { value, parent, disabled, isParent, open };
10468
+ return (jsxRuntime.jsxs("li", { ...props, ref: setRefs, className: classNames(itemClass, className), role: "treeitem", "aria-labelledby": labelId, "aria-level": branch.path.length + 1, "aria-expanded": isParent ? open : undefined, "aria-disabled": disabled || undefined, "aria-selected": selected, "aria-checked": checked, tabIndex: tree.tabStop === value ? 0 : -1, onKeyDown: event => {
10469
+ onKeyDown?.(event);
10470
+ tree.onKeyDown(event, info);
10471
+ }, onFocus: event => {
10472
+ onFocus?.(event);
10473
+ if (event.target === event.currentTarget) {
10474
+ tree.onFocus(value, event.currentTarget);
10475
+ }
10476
+ }, onBlur: event => {
10477
+ onBlur?.(event);
10478
+ if (event.target === event.currentTarget)
10479
+ tree.onBlur();
10480
+ }, onClick: event => {
10481
+ onClick?.(event);
10482
+ // A click in its row is its own, and so is one on the item itself,
10483
+ // as a test's click on the treeitem is. One in its group belongs
10484
+ // to the item inside it, or to nothing. The chevron's box is on
10485
+ // every row, but it's a chevron only on a parent: on a leaf it's
10486
+ // more of the row.
10487
+ const target = event.target;
10488
+ if (target === event.currentTarget ||
10489
+ rowRef.current.contains(target)) {
10490
+ tree.onClick(event.currentTarget, info, isParent && toggleRef.current.contains(target));
10491
+ }
10492
+ }, children: [jsxRuntime.jsxs("div", { className: rowClass, ref: rowRef, children: [jsxRuntime.jsx("span", { className: toggleClass, "aria-hidden": "true", ref: toggleRef }), checked !== undefined && (jsxRuntime.jsx("span", { className: checkClass, "aria-hidden": "true", onClick: event => {
10493
+ // A click on the box's label is followed by one on its
10494
+ // input, and both reach the item. The label's is cancelled,
10495
+ // so the item toggles once.
10496
+ if (!(event.target instanceof HTMLInputElement)) {
10497
+ event.preventDefault();
10498
+ }
10499
+ }, children: jsxRuntime.jsx(Checkbox, { tabIndex: -1, checked: checked === true, indeterminate: checked === 'mixed', disabled: disabled, onChange: ignoreChange }) })), isGiven(icon) && (jsxRuntime.jsx("span", { className: iconClass, "aria-hidden": "true", children: typeof icon === 'string' ? jsxRuntime.jsx(Icon, { name: icon }) : icon })), jsxRuntime.jsx("span", { className: labelClass, id: labelId, children: label })] }), open && hasItems ? (jsxRuntime.jsx("ul", { className: groupClass, role: "group", children: items })) : (items)] }));
10500
+ });
10501
+ TreeViewItem.displayName = 'TreeView.Item';
10502
+ const TreeView = withSubComponents(TreeViewRoot, { Item: TreeViewItem }, 'TreeView');
10503
+
10076
10504
  /**
10077
- * Programmatic notification API for showing, closing, and managing notifications.
10505
+ * The `Block` component renders a simple container with Bulma's `.block` class, adding vertical margin between sections of content.
10078
10506
  *
10079
- * @example
10080
- * notification.success('File saved successfully');
10081
- * notification.danger('Something went wrong', { duration: 5000 });
10082
- */
10083
- const notification = {
10084
- /**
10085
- * Show a notification with the given options.
10086
- * @param {NotificationOptions} options - Notification configuration.
10087
- * @returns {string} The unique ID of the created notification.
10088
- */
10089
- show: (options) => {
10090
- const id = `notification-${++notificationId}`;
10091
- const instance = { id, options };
10092
- if (options.queue) {
10093
- queuedNotifications.push(instance);
10094
- processQueuedNotification();
10095
- }
10096
- else {
10097
- notifications.push(instance);
10098
- notifyNotificationListeners();
10099
- }
10100
- return id;
10101
- },
10102
- /**
10103
- * Show a success notification.
10104
- * @param {string|React.ReactNode} message - The message to display.
10105
- * @param {Partial<NotificationOptions>} [options] - Additional options.
10106
- * @returns {string} The notification ID.
10107
- */
10108
- success: (message, options) => {
10109
- return notification.show({ message, color: 'success', ...options });
10110
- },
10111
- /**
10112
- * Show a danger notification.
10113
- * @param {string|React.ReactNode} message - The message to display.
10114
- * @param {Partial<NotificationOptions>} [options] - Additional options.
10115
- * @returns {string} The notification ID.
10116
- */
10117
- danger: (message, options) => {
10118
- return notification.show({ message, color: 'danger', ...options });
10119
- },
10120
- /**
10121
- * Show a warning notification.
10122
- * @param {string|React.ReactNode} message - The message to display.
10123
- * @param {Partial<NotificationOptions>} [options] - Additional options.
10124
- * @returns {string} The notification ID.
10125
- */
10126
- warning: (message, options) => {
10127
- return notification.show({ message, color: 'warning', ...options });
10128
- },
10129
- /**
10130
- * Show an info notification.
10131
- * @param {string|React.ReactNode} message - The message to display.
10132
- * @param {Partial<NotificationOptions>} [options] - Additional options.
10133
- * @returns {string} The notification ID.
10134
- */
10135
- info: (message, options) => {
10136
- return notification.show({ message, color: 'info', ...options });
10137
- },
10507
+ * @function
10508
+ * @param {BlockProps} props - Props for the Block component.
10509
+ * @returns {JSX.Element} The rendered block element.
10510
+ * @see {@link https://bulma.io/documentation/elements/block/ | Bulma Block documentation}
10511
+ */
10512
+ const Block = ({ className, textColor, color, bgColor, children, ...props }) => {
10138
10513
  /**
10139
- * Close a specific notification by ID.
10140
- * @param {string} id - The notification ID to close.
10514
+ * Generates Bulma helper classes and separates out remaining props.
10141
10515
  */
10142
- close: (id) => {
10143
- if (currentQueuedNotification && currentQueuedNotification.id === id) {
10144
- currentQueuedNotification = null;
10145
- processQueuedNotification();
10146
- }
10147
- else {
10148
- queuedNotifications = queuedNotifications.filter(n => n.id !== id);
10149
- notifications = notifications.filter(n => n.id !== id);
10150
- }
10151
- notifyNotificationListeners();
10152
- },
10153
- /** Close all notifications and clear the queue. */
10154
- closeAll: () => {
10155
- notifications = [];
10156
- queuedNotifications = [];
10157
- currentQueuedNotification = null;
10158
- notifyNotificationListeners();
10159
- },
10516
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
10517
+ color: textColor ?? color,
10518
+ backgroundColor: bgColor,
10519
+ ...props,
10520
+ });
10521
+ const bulmaClasses = usePrefixedClassNames('block');
10522
+ const blockClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10523
+ return (jsxRuntime.jsx("div", { className: blockClasses, ...rest, children: children }));
10524
+ };
10525
+
10526
+ /**
10527
+ * The `Box` component renders a bordered, padded container with an optional shadow using Bulma's `.box` class.
10528
+ *
10529
+ * @function
10530
+ * @param {BoxProps} props - Props for the Box component.
10531
+ * @returns {JSX.Element} The rendered box element.
10532
+ * @see {@link https://bulma.io/documentation/elements/box/ | Bulma Box documentation}
10533
+ */
10534
+ const Box = ({ className, textColor, color, bgColor, hasShadow = true, children, style, ...props }) => {
10160
10535
  /**
10161
- * Subscribe to notification state changes.
10162
- * @param {(items: NotificationInstance[]) => void} listener - Callback invoked on changes.
10163
- * @returns {() => void} Unsubscribe function.
10536
+ * Generates Bulma helper classes and separates out remaining props.
10164
10537
  */
10165
- subscribe: (listener) => {
10166
- notificationListeners.add(listener);
10167
- return () => notificationListeners.delete(listener);
10168
- },
10538
+ const { bulmaHelperClasses, bulmaHelperStyles, rest } = useBulmaClasses({
10539
+ color: textColor ?? color,
10540
+ backgroundColor: bgColor,
10541
+ ...props,
10542
+ });
10543
+ const bulmaClasses = usePrefixedClassNames('box', {
10544
+ 'is-shadowless': !hasShadow,
10545
+ });
10546
+ const boxClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10547
+ return (jsxRuntime.jsx("div", { className: boxClasses, style: mergeBulmaStyles(bulmaHelperStyles, style), ...rest, children: children }));
10169
10548
  };
10549
+
10170
10550
  /**
10171
- * Whether a notification of this color announces itself as an alert, rather
10172
- * than politely through NotificationContainer's status region.
10551
+ * The `Code` component renders a styled `<code>` element with Bulma helper class integration.
10173
10552
  *
10174
- * @function isUrgentColor
10175
- * @param {NotificationOptions['color']} color - The notification's color.
10176
- * @returns {boolean} True for `danger` and `warning`.
10553
+ * @function
10554
+ * @param {CodeProps} props - Props for the Code component.
10555
+ * @returns {JSX.Element} The rendered code element.
10177
10556
  */
10178
- const isUrgentColor = (color) => color === 'danger' || color === 'warning';
10557
+ const Code = ({ className, textColor, bgColor, children, ...props }) => {
10558
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
10559
+ color: textColor,
10560
+ backgroundColor: bgColor,
10561
+ ...props,
10562
+ });
10563
+ const bulmaClasses = usePrefixedClassNames();
10564
+ const codeClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10565
+ return (jsxRuntime.jsx("code", { className: codeClasses || undefined, ...rest, children: children }));
10566
+ };
10567
+
10568
+ // Valid size modifiers for the content class
10569
+ const validSizes = ['small', 'medium', 'large'];
10179
10570
  /**
10180
- * Single auto-dismissing notification item used by NotificationContainer.
10571
+ * The `Content` component applies Bulma’s typographic styles to its children, enhancing the appearance of HTML elements like paragraphs, headings, lists, and tables.
10181
10572
  *
10182
10573
  * @function
10183
- * @param {{ instance: NotificationInstance; onClose: (id: string) => void; registerMessage: (id: string, node: HTMLElement | null) => void }} props - Component props.
10184
- * @returns {JSX.Element} The rendered notification item.
10574
+ * @param {ContentProps} props - Props for the Content component.
10575
+ * @returns {JSX.Element} The rendered content block.
10576
+ * @see {@link https://bulma.io/documentation/elements/content/ | Bulma Content documentation}
10185
10577
  */
10186
- const NotificationItem = ({ instance, onClose, registerMessage }) => {
10187
- const { message, color, isLight, duration = 3000, hasDelete = true, indefinite = false, pauseOnHover = true, } = instance.options;
10188
- const [isPaused, setIsPaused] = React.useState(false);
10189
- const urgent = isUrgentColor(color);
10190
- const handleClose = React.useCallback(() => {
10191
- onClose(instance.id);
10192
- }, [onClose, instance.id]);
10193
- const messageRef = React.useCallback((node) => {
10194
- registerMessage(instance.id, node);
10195
- }, [registerMessage, instance.id]);
10196
- // Auto-close timer
10197
- React.useEffect(() => {
10198
- if (indefinite || duration === 0 || isPaused)
10199
- return undefined;
10200
- const timer = setTimeout(handleClose, duration);
10201
- return () => clearTimeout(timer);
10202
- }, [duration, indefinite, isPaused, handleClose]);
10203
- const handleMouseEnter = React.useCallback(() => {
10204
- if (pauseOnHover)
10205
- setIsPaused(true);
10206
- }, [pauseOnHover]);
10207
- const handleMouseLeave = React.useCallback(() => {
10208
- if (pauseOnHover)
10209
- setIsPaused(false);
10210
- }, [pauseOnHover]);
10211
- return (jsxRuntime.jsx(Notification, { color: color, isLight: isLight, hasDelete: hasDelete, onDelete: handleClose, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, style: { pointerEvents: 'auto' }, children: urgent ? (jsxRuntime.jsx("span", { role: "alert", "aria-live": "assertive", children: message })) : (jsxRuntime.jsx("span", { ref: messageRef, children: message })) }));
10578
+ const Content = ({ className, textColor, color, bgColor, size, children, ...props }) => {
10579
+ /**
10580
+ * Generates Bulma helper classes and separates out remaining props.
10581
+ */
10582
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
10583
+ color: textColor ?? color,
10584
+ backgroundColor: bgColor,
10585
+ ...props,
10586
+ });
10587
+ const bulmaClasses = usePrefixedClassNames('content', {
10588
+ [`is-${size}`]: size && size !== 'normal' && validSizes.includes(size),
10589
+ });
10590
+ const contentClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10591
+ return (jsxRuntime.jsx("div", { className: contentClasses, ...rest, children: children }));
10212
10592
  };
10213
- // The order NotificationContainer renders its stacks in: across the top of the
10214
- // screen, then across the bottom.
10215
- const notificationStackOrder = [
10216
- 'top-left',
10217
- 'top',
10218
- 'top-right',
10219
- 'bottom-left',
10220
- 'bottom',
10221
- 'bottom-right',
10222
- ];
10593
+
10223
10594
  /**
10224
- * Inline style that fixes a stack of notifications to its place on the screen.
10595
+ * The `Delete` component provides a Bulma-styled close/delete button for dismissing modals, notifications, tags, messages, and more.
10596
+ *
10597
+ * It renders `type="button"`, so a delete button inside a form does not submit it.
10225
10598
  *
10226
10599
  * @function
10227
- * @param {NotificationPosition} position - Where the stack sits.
10228
- * @returns {React.CSSProperties} The stack's style.
10600
+ * @param {DeleteProps} props - Props for the Delete component.
10601
+ * @returns {JSX.Element} The rendered delete button.
10602
+ * @see {@link https://bulma.io/documentation/elements/delete/ | Bulma Delete documentation}
10229
10603
  */
10230
- const notificationStackStyle = (position) => {
10231
- const isBottom = position.startsWith('bottom');
10232
- const isCenter = position === 'top' || position === 'bottom';
10233
- const isRight = position.endsWith('right');
10234
- return {
10235
- position: 'fixed',
10236
- zIndex: 100,
10237
- display: 'flex',
10238
- flexDirection: isBottom ? 'column-reverse' : 'column',
10239
- gap: '0.75rem',
10240
- padding: '1rem',
10241
- pointerEvents: 'none',
10242
- maxWidth: '100%',
10243
- ...(isBottom ? { bottom: 0 } : { top: 0 }),
10244
- ...(isCenter
10245
- ? { left: '50%', transform: 'translateX(-50%)', alignItems: 'center' }
10246
- : isRight
10247
- ? { right: 0, alignItems: 'flex-end' }
10248
- : { left: 0, alignItems: 'flex-start' }),
10249
- };
10604
+ const Delete = ({ className, textColor, color, bgColor, onClick, size, ariaLabel, disabled = false, ...props }) => {
10605
+ /**
10606
+ * Generates Bulma helper classes and separates out remaining props.
10607
+ */
10608
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
10609
+ color: textColor ?? color,
10610
+ backgroundColor: bgColor,
10611
+ ...props,
10612
+ });
10613
+ const bulmaClasses = usePrefixedClassNames('delete', {
10614
+ [`is-${size}`]: size,
10615
+ 'is-disabled': disabled,
10616
+ });
10617
+ const classes = classNames(bulmaClasses, bulmaHelperClasses, className);
10618
+ return (jsxRuntime.jsx("button", { className: classes, onClick: onClick, disabled: disabled, ...rest, "aria-label": rest['aria-label'] || ariaLabel || 'Close', type: buttonType(rest.type) }));
10250
10619
  };
10620
+
10251
10621
  /**
10252
- * Container component for rendering programmatic notifications.
10253
- * Place once at your app root to enable the notification API. A notification
10254
- * shown with a `position` appears there, and one shown without goes to the
10255
- * container's `position`, so the container renders a stack for each position
10256
- * in use.
10257
- *
10258
- * It keeps a visually hidden `role="status"` live region in the page from the
10259
- * moment it mounts, even while nothing is showing, and announces every
10260
- * notification other than `danger` and `warning` through it, a moment after
10261
- * the notification appears. Those notifications carry no `role="status"` of
10262
- * their own, so `getByRole('status')` finds the region, not the notification.
10263
- * The text stays in the region briefly, even if the notification closes in
10264
- * the meantime, and while both are up the page holds two copies of it. A
10265
- * polite notification that closes before its announcement is written, a
10266
- * moment after it appears, isn't announced at all. `danger` and `warning`
10267
- * notifications announce themselves as assertive alerts. The region is hidden
10268
- * with inline styles, so it needs no stylesheet.
10622
+ * The `Divider` component renders a styled horizontal rule (`<hr>`) element with Bulma helper class integration.
10269
10623
  *
10270
10624
  * @function
10271
- * @param {{ position?: NotificationPosition }} props - Container props.
10272
- * @returns {JSX.Element | null} The rendered notification container, or null on the server and while hydrating.
10625
+ * @param {DividerProps} props - Props for the Divider component.
10626
+ * @returns {JSX.Element} The rendered hr element.
10273
10627
  */
10274
- const NotificationContainer = ({ position = 'top-right' }) => {
10275
- // Starts from the notifications already showing instead of an empty list,
10276
- // then follows changes.
10277
- const items = React.useSyncExternalStore(notification.subscribe, getVisibleNotifications, getServerNotifications);
10278
- // A portal needs a document, and it has no server-rendered counterpart, so
10279
- // the server render and the hydrating one render nothing, and the container
10280
- // portals in from the render after hydration.
10281
- const isHydrated = useIsHydrated();
10282
- const resolveContainer = usePortalContainer();
10283
- // The notifications the status region announces. Danger and warning
10284
- // announce themselves.
10285
- const politeItems = React.useMemo(() => items.filter(item => !isUrgentColor(item.options.color)), [items]);
10286
- // Each polite notification's message element, so the text the region
10287
- // announces is the text on screen, whatever the message renders.
10288
- const messageNodesRef = React.useRef(new Map());
10289
- const registerMessage = React.useCallback((id, node) => {
10290
- if (node) {
10291
- messageNodesRef.current.set(id, node);
10292
- }
10293
- else {
10294
- messageNodesRef.current.delete(id);
10295
- }
10296
- }, []);
10297
- // StatusRegion only describes polite notifications that are on screen, and
10298
- // each of those registered its message element when it mounted. The
10299
- // fallback is for a notification announced without being rendered, should
10300
- // the container ever render fewer than it announces: a plain-text message
10301
- // still says itself, and anything else is skipped.
10302
- const describe = React.useCallback((item) => {
10303
- const node = messageNodesRef.current.get(item.id);
10304
- /* istanbul ignore if: every polite notification on screen registers its message element when it mounts */
10305
- if (!node) {
10306
- const { message } = item.options;
10307
- return typeof message === 'string' ? message : null;
10308
- }
10309
- return spokenText(node);
10310
- }, []);
10311
- if (typeof document === 'undefined' || !isHydrated) {
10312
- return null;
10313
- }
10314
- const stacks = groupIntoPositionStacks(items, item => item.options.position, position, notificationStackOrder);
10315
- return reactDom.createPortal(jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [stacks.map(stack => (jsxRuntime.jsx("div", { style: notificationStackStyle(stack.position), children: stack.items.map(item => (jsxRuntime.jsx(NotificationItem, { instance: item, onClose: notification.close, registerMessage: registerMessage }, item.id))) }, stack.key))), jsxRuntime.jsx(StatusRegion, { items: politeItems, describe: describe })] }), resolveContainer());
10628
+ const Divider = ({ className, bgColor, ...props }) => {
10629
+ /**
10630
+ * Generates Bulma helper classes and separates out remaining props.
10631
+ */
10632
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
10633
+ backgroundColor: bgColor,
10634
+ ...props,
10635
+ });
10636
+ const bulmaClasses = usePrefixedClassNames();
10637
+ const dividerClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10638
+ return jsxRuntime.jsx("hr", { className: dividerClasses || undefined, ...rest });
10316
10639
  };
10317
10640
 
10318
10641
  /**
10319
- * The `OrderedList` component renders a styled ordered list (`<ol>`) element with Bulma helper class integration.
10642
+ * The `Emphasis` component renders a styled `<em>` element with Bulma helper class integration.
10320
10643
  *
10321
10644
  * @function
10322
- * @param {OrderedListProps} props - Props for the OrderedList component.
10323
- * @returns {JSX.Element} The rendered ol element.
10645
+ * @param {EmphasisProps} props - Props for the Emphasis component.
10646
+ * @returns {JSX.Element} The rendered em element.
10324
10647
  */
10325
- const OrderedListComponent = ({ className, textColor, bgColor, children, ...props }) => {
10326
- /**
10327
- * Generates Bulma helper classes and separates out remaining props.
10328
- */
10648
+ const Emphasis = ({ className, textColor, bgColor, children, ...props }) => {
10329
10649
  const { bulmaHelperClasses, rest } = useBulmaClasses({
10330
10650
  color: textColor,
10331
10651
  backgroundColor: bgColor,
10332
10652
  ...props,
10333
10653
  });
10334
10654
  const bulmaClasses = usePrefixedClassNames();
10335
- const listClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10336
- return (jsxRuntime.jsx("ol", { className: listClasses || undefined, ...rest, children: children }));
10655
+ const emphasisClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10656
+ return (jsxRuntime.jsx("em", { className: emphasisClasses || undefined, ...rest, children: children }));
10337
10657
  };
10338
- const OrderedList = withSubComponents(OrderedListComponent, { Item: ListItem }, 'OrderedList');
10339
10658
 
10340
10659
  /**
10341
- * The `Pre` component renders a styled `<pre>` element with Bulma helper class integration.
10660
+ * FigureCaption component for rendering a styled figcaption element.
10661
+ *
10662
+ * A FigureCaption wraps the HTML `<figcaption>` element with Bulma helper class integration.
10663
+ * Use it inside Figure components to provide captions for images or other media.
10342
10664
  *
10343
10665
  * @function
10344
- * @param {PreProps} props - Props for the Pre component.
10345
- * @returns {JSX.Element} The rendered pre element.
10666
+ * @param {FigureCaptionProps} props - Props for the FigureCaption component.
10667
+ * @returns {JSX.Element} The rendered figcaption element.
10346
10668
  */
10347
- const Pre = ({ className, textColor, bgColor, children, ...props }) => {
10669
+ const FigureCaption = ({ className, textColor, bgColor, children, ...props }) => {
10348
10670
  const { bulmaHelperClasses, rest } = useBulmaClasses({
10349
10671
  color: textColor,
10350
10672
  backgroundColor: bgColor,
10351
10673
  ...props,
10352
10674
  });
10353
10675
  const bulmaClasses = usePrefixedClassNames();
10354
- const preClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10355
- return (jsxRuntime.jsx("pre", { className: preClasses || undefined, ...rest, children: children }));
10676
+ const captionClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10677
+ return (jsxRuntime.jsx("figcaption", { className: captionClasses || undefined, ...rest, children: children }));
10678
+ };
10679
+ /**
10680
+ * The `Figure` component renders a styled `<figure>` element with Bulma helper class integration.
10681
+ *
10682
+ * @function
10683
+ * @param {FigureProps} props - Props for the Figure component.
10684
+ * @returns {JSX.Element} The rendered figure element.
10685
+ */
10686
+ const FigureComponent = ({ className, textColor, bgColor, children, ...props }) => {
10687
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
10688
+ color: textColor,
10689
+ backgroundColor: bgColor,
10690
+ ...props,
10691
+ });
10692
+ const bulmaClasses = usePrefixedClassNames();
10693
+ const figureClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10694
+ return (jsxRuntime.jsx("figure", { className: figureClasses || undefined, ...rest, children: children }));
10356
10695
  };
10696
+ /**
10697
+ * Figure component with Caption subcomponent.
10698
+ *
10699
+ * @example
10700
+ * <Figure>
10701
+ * <img src="image.jpg" alt="Description" />
10702
+ * <Figure.Caption>Image caption text</Figure.Caption>
10703
+ * </Figure>
10704
+ */
10705
+ const Figure = withSubComponents(FigureComponent, {
10706
+ Caption: FigureCaption,
10707
+ }, 'Figure');
10357
10708
 
10358
10709
  /**
10359
- * The `Progress` component displays a Bulma-styled progress bar.
10710
+ * Distinguishes an `IconProps` object from a plain custom node (an inline SVG, a
10711
+ * `react-icons` component, …) in a slot that accepts either — `Control`'s
10712
+ * `iconLeft`/`iconRight`, `IconText`'s `iconProps`.
10713
+ *
10714
+ * One copy, imported by both consumers: two hand-maintained copies, one per
10715
+ * consumer, is how the deprecated `icon` path came to crash both (#663).
10716
+ *
10717
+ * Its own module rather than `Icon.tsx` because `src/index.ts` wildcard-exports
10718
+ * that file, and everything published there is public forever. `polymorphic.ts`
10719
+ * makes the same call for `isCustomElement`: an internal discriminator is not
10720
+ * something to support for the life of the package.
10721
+ *
10722
+ * It tests each member's VALUE, not merely its key, and only after everything
10723
+ * React owns is out of the way. `'icon' in value` was enough
10724
+ * to claim a Font Awesome `IconDefinition` — `{ prefix, iconName, icon: [w, h, …,
10725
+ * path] }` — whose `icon` is a path array rather than a class string. That object
10726
+ * is not a renderable node either, so the honest outcome is the one this slot has
10727
+ * always had for it: the node branch, and React's own "Objects are not valid as a
10728
+ * React child". Claiming it here instead rendered a blank container that still
10729
+ * reserved layout.
10730
+ *
10731
+ * A member added to `IconProps` needs a line here, and a missed one is a crash
10732
+ * rather than a fallthrough. That is the cost of a slot taking either a props
10733
+ * object or a node; keeping the guard in one place is what keeps the cost to a
10734
+ * single line.
10735
+ */
10736
+ function isIconProps(value) {
10737
+ if (typeof value !== 'object' || value === null)
10738
+ return false;
10739
+ // Anything React owns. `isValidElement` covers elements only; a PORTAL is a
10740
+ // valid `ReactNode` that fails it and carries a `children` key, so the member
10741
+ // test below claimed it and `Control` spread it into `<Icon>` as props —
10742
+ // rendering the portal's child locally instead of in its container. Every
10743
+ // React internal carries `$$typeof`, so testing for it covers lazy, context
10744
+ // and whatever React adds next, rather than portals alone.
10745
+ if (React.isValidElement(value) || '$$typeof' in value)
10746
+ return false;
10747
+ const candidate = value;
10748
+ return (typeof candidate.name === 'string' ||
10749
+ typeof candidate.icon === 'string' ||
10750
+ 'children' in candidate);
10751
+ }
10752
+
10753
+ /**
10754
+ * The `IconText` component provides a Bulma-styled horizontal arrangement of one or more `Icon` components and optional text.
10360
10755
  *
10361
10756
  * @function
10362
- * @param {ProgressProps} props - Props for the Progress component.
10363
- * @returns {JSX.Element} The rendered progress bar element.
10364
- * @see {@link https://bulma.io/documentation/elements/progress/ | Bulma Progress documentation}
10757
+ * @param {IconTextProps} props - Props for the IconText component.
10758
+ * @returns {JSX.Element} The rendered icon text element.
10759
+ * @see {@link https://bulma.io/documentation/elements/icon/#icon-text | Bulma IconText documentation}
10365
10760
  */
10366
- const Progress = ({ className, color, size, value, max, children, ...props }) => {
10367
- warnUnstyledColor('Progress', color);
10761
+ const IconTextComponent = ({ className, textColor, color, bgColor, iconProps, children, items, ...props }) => {
10368
10762
  /**
10369
10763
  * Generates Bulma helper classes and separates out remaining props.
10370
10764
  */
10371
10765
  const { bulmaHelperClasses, rest } = useBulmaClasses({
10766
+ color: textColor ?? color,
10767
+ backgroundColor: bgColor,
10372
10768
  ...props,
10373
10769
  });
10374
- const bulmaClasses = usePrefixedClassNames('progress', {
10375
- [`is-${color}`]: color && validColors.includes(color),
10770
+ const bulmaClasses = usePrefixedClassNames('icon-text');
10771
+ const iconTextClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10772
+ return (jsxRuntime.jsx("span", { className: iconTextClasses, ...rest, children: items ? (items.map((item, index) => (jsxRuntime.jsxs(React.Fragment, { children: [item.iconProps &&
10773
+ (isIconProps(item.iconProps) ? (jsxRuntime.jsx(Icon, { ...item.iconProps })) : (jsxRuntime.jsx(Icon, { children: item.iconProps }))), item.text && jsxRuntime.jsx("span", { children: item.text })] }, index)))) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [iconProps &&
10774
+ (isIconProps(iconProps) ? (jsxRuntime.jsx(Icon, { ...iconProps })) : (jsxRuntime.jsx(Icon, { children: iconProps }))), children && jsxRuntime.jsx("span", { children: children })] })) }));
10775
+ };
10776
+ const IconText = withSubComponents(IconTextComponent, { Icon }, 'IconText');
10777
+
10778
+ /**
10779
+ * The `Image` component wraps images, iframes, or custom content in a Bulma-styled container, supporting fixed sizes, aspect ratios, rounded corners, retina images, and all Bulma helper props for color and spacing.
10780
+ *
10781
+ * @function
10782
+ * @param {ImageProps} props - Props for the Image component.
10783
+ * @returns {JSX.Element} The rendered image element.
10784
+ * @see {@link https://bulma.io/documentation/elements/image/ | Bulma Image documentation}
10785
+ */
10786
+ const Image = ({ as, className, textColor, color, bgColor, size, isRounded, isRetina, src, alt, children, ...props }) => {
10787
+ /**
10788
+ * Generates Bulma helper classes and separates out remaining props.
10789
+ */
10790
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
10791
+ color: textColor ?? color,
10792
+ backgroundColor: bgColor,
10793
+ ...props,
10794
+ });
10795
+ const bulmaClasses = usePrefixedClassNames('image', {
10376
10796
  [`is-${size}`]: size,
10797
+ 'has-ratio': size && typeof size === 'string' && size.includes('by'),
10377
10798
  });
10378
- const progressClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10379
- return (jsxRuntime.jsx("progress", { className: progressClasses, value: value, max: max, ...rest, children: children }));
10799
+ const imageClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10800
+ // Default tag logic: if "as" is provided, use it.
10801
+ // If not, use <figure> for aspect ratios or children, <div> otherwise.
10802
+ let Tag;
10803
+ if (as) {
10804
+ Tag = as;
10805
+ }
10806
+ else if (size && typeof size === 'string' && size.includes('by')) {
10807
+ Tag = 'figure';
10808
+ }
10809
+ else {
10810
+ Tag = 'div';
10811
+ }
10812
+ const roundedClass = usePrefixedClassNames('is-rounded');
10813
+ const content = children ? (children) : (jsxRuntime.jsx("img", { className: classNames({ [roundedClass]: isRounded }) || undefined, src: src, alt: alt, ...(isRetina && src ? { srcSet: `${src} 2x` } : {}) }));
10814
+ return (jsxRuntime.jsx(Tag, { className: imageClasses, ...rest, children: content }));
10380
10815
  };
10381
10816
 
10382
10817
  /**
10383
- * The `Skeleton` component provides a Bulma-styled skeleton loader for React applications, useful for indicating that content is loading.
10818
+ * Keys pressed together or one after another, in the outer `<kbd>` HTML gives a combination, with the separator between each pair as text.
10819
+ *
10820
+ * It doesn't wrap in the middle and isn't drawn as a key itself. It sets no
10821
+ * font size, so the keys in it are the size of a key on its own.
10384
10822
  *
10385
10823
  * @function
10386
- * @param {SkeletonProps} props - Props for the Skeleton component.
10387
- * @returns {JSX.Element} The rendered skeleton element.
10824
+ * @param {KbdGroupProps} props - Props for Kbd.Group.
10825
+ * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the outer `<kbd>`.
10826
+ * @returns {JSX.Element} The rendered combination.
10388
10827
  *
10389
10828
  * @example
10390
- * // Block skeleton
10391
- * <Skeleton />
10829
+ * <Kbd.Group>
10830
+ * <Kbd>Ctrl</Kbd>
10831
+ * <Kbd>Shift</Kbd>
10832
+ * <Kbd>P</Kbd>
10833
+ * </Kbd.Group>
10392
10834
  *
10393
10835
  * @example
10394
- * // Multi-line skeleton
10395
- * <Skeleton variant="lines" lines={5} />
10836
+ * <Kbd.Group separator="then">
10837
+ * <Kbd>G</Kbd>
10838
+ * <Kbd>I</Kbd>
10839
+ * </Kbd.Group>
10396
10840
  */
10397
- const Skeleton = ({ className, variant = 'block', lines = 3, children, ...props }) => {
10398
- const linesClass = usePrefixedClassNames('skeleton-lines');
10399
- const blockClass = usePrefixedClassNames('skeleton-block');
10400
- if (variant === 'lines') {
10401
- return (jsxRuntime.jsx("div", { className: classNames(linesClass, className), ...props, children: Array.from({ length: lines }).map((_, i) => (jsxRuntime.jsx("div", {}, i))) }));
10402
- }
10403
- return (jsxRuntime.jsx("div", { className: classNames(blockClass, className), ...props, children: children }));
10404
- };
10405
-
10841
+ const KbdGroup = React.forwardRef(({ className, textColor, separator = '+', children, ...props }, ref) => {
10842
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
10843
+ color: textColor,
10844
+ ...props,
10845
+ });
10846
+ const groupClass = usePrefixedClassNames('kbd-group');
10847
+ const separatorClass = usePrefixedClassNames('kbd-separator');
10848
+ // Null, undefined and booleans drop out, and Fragments are opened up, so
10849
+ // an empty Fragment leaves no separator behind and the keys in one are
10850
+ // separated like the rest. The spaces are part of the text, so a screen
10851
+ // reader reads "Ctrl plus S" rather than one run-together word, whatever
10852
+ // the CSS does.
10853
+ const keys = flattenChildren(children);
10854
+ return (jsxRuntime.jsx("kbd", { ref: ref, className: classNames(groupClass, bulmaHelperClasses, className), ...rest, children: keys.map((key, index) => (jsxRuntime.jsxs(React.Fragment, { children: [index > 0 && (jsxRuntime.jsx("span", { className: separatorClass, children: ` ${separator} ` })), key] }, React.isValidElement(key) && key.key != null ? key.key : index))) }));
10855
+ });
10856
+ KbdGroup.displayName = 'Kbd.Group';
10406
10857
  /**
10407
- * The `Span` component renders a styled inline `<span>` element with Bulma helper class integration.
10858
+ * The `Kbd` component renders a `<kbd>` drawn as a keycap, for a key the reader presses.
10859
+ *
10860
+ * It only shows the key: it doesn't listen for it. The key takes its colors
10861
+ * from the text it sits in, and its size in `em`, so it fits a paragraph, a
10862
+ * title or a filled button as it is, and `textSize` sizes it outright. For a
10863
+ * combination or a sequence, put the keys in a `Kbd.Group`. What tells
10864
+ * assistive tech a control has a shortcut is `aria-keyshortcuts` on that
10865
+ * control, with `Kbd` as the hint people see next to it.
10408
10866
  *
10409
10867
  * @function
10410
- * @param {SpanProps} props - Props for the Span component.
10411
- * @returns {JSX.Element} The rendered span element.
10868
+ * @param {KbdProps} props - Props for the Kbd component.
10869
+ * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<kbd>`.
10870
+ * @returns {JSX.Element} The rendered key.
10871
+ *
10872
+ * @example
10873
+ * <Kbd>Esc</Kbd>
10874
+ *
10875
+ * @example
10876
+ * <Kbd label="Command">⌘</Kbd>
10412
10877
  */
10413
- const Span = ({ className, textColor, bgColor, children, ...props }) => {
10414
- /**
10415
- * Generates Bulma helper classes and separates out remaining props.
10416
- */
10878
+ const KbdRoot = React.forwardRef(({ className, textColor, bgColor, label, children, ...props }, ref) => {
10417
10879
  const { bulmaHelperClasses, rest } = useBulmaClasses({
10418
10880
  color: textColor,
10419
10881
  backgroundColor: bgColor,
10420
10882
  ...props,
10421
10883
  });
10422
- const bulmaClasses = usePrefixedClassNames();
10423
- const spanClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10424
- return (jsxRuntime.jsx("span", { className: spanClasses || undefined, ...rest, children: children }));
10425
- };
10884
+ const kbdClass = usePrefixedClassNames('kbd');
10885
+ // The name is text rather than an aria-label, which ARIA in HTML
10886
+ // prohibits on kbd. Its styles are inline, so it stays hidden in a
10887
+ // modular build that leaves out Bulma's helper classes.
10888
+ return (jsxRuntime.jsx("kbd", { ref: ref, className: classNames(kbdClass, bulmaHelperClasses, className), ...rest, children: label ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { "aria-hidden": "true", children: children }), jsxRuntime.jsx("span", { style: visuallyHidden, children: label })] })) : (children) }));
10889
+ });
10890
+ const Kbd = withSubComponents(KbdRoot, { Group: KbdGroup }, 'Kbd');
10426
10891
 
10427
10892
  /**
10428
- * The `Strong` component renders a styled `<strong>` element with Bulma helper class integration.
10893
+ * The `Link` component renders a styled anchor (`<a>`) element with Bulma helper class integration.
10429
10894
  *
10430
10895
  * @function
10431
- * @param {StrongProps} props - Props for the Strong component.
10432
- * @returns {JSX.Element} The rendered strong element.
10896
+ * @param {LinkProps} props - Props for the Link component.
10897
+ * @param {React.Ref} ref - Forwarded ref to the element `as` renders.
10898
+ * @returns {JSX.Element} The rendered anchor, or whatever `as` names.
10899
+ * @see {@link https://bulma.io/documentation/elements/content/ | Bulma Content documentation}
10433
10900
  */
10434
- const Strong = ({ className, textColor, bgColor, children, ...props }) => {
10901
+ const Link = React.forwardRef(function Link(linkProps, ref) {
10902
+ const { className, textColor, bgColor, isActive, as: Component = 'a', children, ...props } = linkProps;
10903
+ /**
10904
+ * Generates Bulma helper classes and separates out remaining props.
10905
+ */
10435
10906
  const { bulmaHelperClasses, rest } = useBulmaClasses({
10436
10907
  color: textColor,
10437
10908
  backgroundColor: bgColor,
10438
10909
  ...props,
10439
10910
  });
10440
- const bulmaClasses = usePrefixedClassNames();
10441
- const strongClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10442
- return (jsxRuntime.jsx("strong", { className: strongClasses || undefined, ...rest, children: children }));
10443
- };
10911
+ const bulmaClasses = usePrefixedClassNames({
10912
+ 'is-active': isActive,
10913
+ });
10914
+ const linkClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10915
+ return (jsxRuntime.jsx(Component, { ref: ref, className: linkClasses || undefined, ...rest, ...(Component === 'button' ? { type: buttonType(rest.type) } : {}), children: children }));
10916
+ });
10917
+ Link.displayName = 'Link';
10444
10918
 
10445
- const validSubTitleSizes = ['1', '2', '3', '4', '5', '6'];
10446
- const validSubTitleElements = [
10447
- 'h1',
10448
- 'h2',
10449
- 'h3',
10450
- 'h4',
10451
- 'h5',
10452
- 'h6',
10453
- 'p',
10454
- ];
10455
10919
  /**
10456
- * The `SubTitle` component renders a Bulma-styled subtitle (secondary heading), supporting sizes `1-6` and rendering as any heading or paragraph element (`h1-h6`, `p`).
10920
+ * The `ListItem` component renders a styled list item (`<li>`) element with Bulma helper class integration.
10457
10921
  *
10458
10922
  * @function
10459
- * @param {SubTitleProps} props - Props for the SubTitle component.
10460
- * @returns {JSX.Element} The rendered subtitle element.
10461
- * @see {@link https://bulma.io/documentation/elements/title/#subtitle | Bulma Subtitle documentation}
10923
+ * @param {ListItemProps} props - Props for the ListItem component.
10924
+ * @returns {JSX.Element} The rendered li element.
10462
10925
  */
10463
- const SubTitle = ({ className, size, as = 'h1', hasSkeleton, textColor, bgColor, children, ...props }) => {
10926
+ const ListItem = ({ className, textColor, bgColor, children, ...props }) => {
10464
10927
  /**
10465
10928
  * Generates Bulma helper classes and separates out remaining props.
10466
10929
  */
@@ -10469,681 +10932,800 @@ const SubTitle = ({ className, size, as = 'h1', hasSkeleton, textColor, bgColor,
10469
10932
  backgroundColor: bgColor,
10470
10933
  ...props,
10471
10934
  });
10472
- // Validate 'as' prop at runtime
10473
- const element = validSubTitleElements.includes(as) ? as : 'h1';
10474
- // Validate 'size' prop at runtime (numeric sizes normalize to strings)
10475
- const normalized = size == null ? undefined : String(size);
10476
- const validSize = normalized && validSubTitleSizes.includes(normalized)
10477
- ? normalized
10478
- : undefined;
10479
- const bulmaClasses = usePrefixedClassNames('subtitle', {
10480
- [`is-${validSize}`]: validSize,
10481
- 'has-skeleton': hasSkeleton,
10482
- });
10483
- const subTitleClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10484
- // Determine the tag based on 'element' and 'validSize'
10485
- const Tag = element === 'p' ? 'p' : validSize ? `h${validSize}` : element;
10486
- return (jsxRuntime.jsx(Tag, { className: subTitleClasses, ...rest, children: children }));
10935
+ const bulmaClasses = usePrefixedClassNames();
10936
+ const itemClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10937
+ return (jsxRuntime.jsx("li", { className: itemClasses || undefined, ...rest, children: children }));
10487
10938
  };
10488
10939
 
10489
10940
  /**
10490
- * Thead component for rendering a styled Bulma table header.
10941
+ * The `Loader` component renders Bulma's `.loader`, a small spinning ring for inline loading states.
10491
10942
  *
10492
- * Supports Bulma helper classes for additional styling.
10943
+ * The ring is `1em` square, so `textSize` scales it. It is exposed as an
10944
+ * indeterminate `progressbar` named by `ariaLabel`, and it stops spinning
10945
+ * under `prefers-reduced-motion: reduce` while staying drawn. In forced
10946
+ * colors its two open sides take the page's color, so it stays open.
10493
10947
  *
10494
10948
  * @function
10495
- * @param {TheadProps} props - Props for the Thead component.
10496
- * @returns {JSX.Element} The rendered table header element.
10497
- * @see {@link https://bulma.io/documentation/elements/table/#table-head | Bulma Table documentation}
10949
+ * @param {LoaderProps} props - Props for the Loader component.
10950
+ * @param {React.Ref<HTMLSpanElement>} ref - Forwarded ref to the loader element.
10951
+ * @returns {JSX.Element} The rendered loader element.
10952
+ *
10953
+ * @example
10954
+ * <Loader />
10955
+ *
10956
+ * @example
10957
+ * <Loader textSize="3" ariaLabel="Saving" />
10498
10958
  */
10499
- const Thead = ({ className, children, ...props }) => {
10500
- /**
10501
- * Generates Bulma helper classes and separates out remaining props.
10502
- */
10503
- const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
10504
- const theadClasses = classNames(className, bulmaHelperClasses);
10505
- return (jsxRuntime.jsx("thead", { className: theadClasses || undefined, ...rest, children: children }));
10506
- };
10959
+ const Loader = React.forwardRef(({ className, ariaLabel = 'Loading', ...props }, ref) => {
10960
+ const { bulmaHelperClasses, rest } = useBulmaClasses(props);
10961
+ const loaderClass = usePrefixedClassNames('loader');
10962
+ // role="progressbar", not role="status": a status region mounted together
10963
+ // with its text is not reliably announced, so the loader claims only what
10964
+ // it can deliver, a named indicator found by reading the page. No
10965
+ // aria-valuenow, because the progress is indeterminate.
10966
+ return (jsxRuntime.jsx("span", { ref: ref, className: classNames(loaderClass, bulmaHelperClasses, className), role: "progressbar", "aria-label": ariaLabel, ...rest }));
10967
+ });
10968
+ Loader.displayName = 'Loader';
10507
10969
 
10508
10970
  /**
10509
- * Tbody component for rendering a styled Bulma table body.
10510
- *
10511
- * Supports Bulma helper classes for additional styling.
10971
+ * The `Notification` component is a Bulma-styled alert/message area for providing feedback, warnings, or information to users.
10512
10972
  *
10513
10973
  * @function
10514
- * @param {TbodyProps} props - Props for the Tbody component.
10515
- * @returns {JSX.Element} The rendered table body element.
10516
- * @see {@link https://bulma.io/documentation/elements/table/#table-body | Bulma Table documentation}
10974
+ * @param {NotificationProps} props - Props for the Notification component.
10975
+ * @returns {JSX.Element} The rendered notification element.
10976
+ * @see {@link https://bulma.io/documentation/elements/notification/ | Bulma Notification documentation}
10517
10977
  */
10518
- const Tbody = ({ className, children, ...props }) => {
10978
+ const Notification = ({ className, color, textColor, isLight, hasDelete, onDelete, children, ...props }) => {
10979
+ warnUnstyledColor('Notification', color);
10519
10980
  /**
10520
10981
  * Generates Bulma helper classes and separates out remaining props.
10521
10982
  */
10522
- const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
10523
- const tbodyClasses = classNames(className, bulmaHelperClasses);
10524
- return (jsxRuntime.jsx("tbody", { className: tbodyClasses || undefined, ...rest, children: children }));
10983
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
10984
+ color: textColor,
10985
+ ...props,
10986
+ });
10987
+ const bulmaClasses = usePrefixedClassNames('notification', {
10988
+ [`is-${color}`]: color && validColors.includes(color),
10989
+ 'is-light': isLight,
10990
+ 'has-delete': hasDelete,
10991
+ });
10992
+ const deleteClasses = usePrefixedClassNames('delete');
10993
+ const notificationClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10994
+ return (jsxRuntime.jsxs("div", { className: notificationClasses, ...rest, children: [hasDelete && (jsxRuntime.jsx("button", { type: "button", className: deleteClasses, onClick: onDelete, "aria-label": "Close notification" })), children] }));
10995
+ };
10996
+ let notificationId = 0;
10997
+ const notificationListeners = new Set();
10998
+ let notifications = [];
10999
+ // Queue support
11000
+ let queuedNotifications = [];
11001
+ let currentQueuedNotification = null;
11002
+ // The server has nowhere to portal a notification to, and its copy of this
11003
+ // module is shared by every request, so server rendering reads an empty list.
11004
+ // Hydration reads it too, which keeps the first client render matching the
11005
+ // server's.
11006
+ const noNotifications = [];
11007
+ const getServerNotifications = () => noNotifications;
11008
+ // What a container shows right now: the stacked notifications, then the queued
11009
+ // one on screen. NotificationContainer renders from this rather than from
11010
+ // updates alone, so notifications raised before it mounted still appear. It is
11011
+ // replaced rather than mutated, and only when listeners are notified, because
11012
+ // useSyncExternalStore needs the same array back between changes. An empty
11013
+ // list is `noNotifications` itself, so an empty store reads the same snapshot
11014
+ // on the client as on the server, and a notify that leaves nothing showing
11015
+ // when nothing was showing doesn't render the container again.
11016
+ let visibleNotifications = noNotifications;
11017
+ const getVisibleNotifications = () => visibleNotifications;
11018
+ const notifyNotificationListeners = () => {
11019
+ const allVisible = [...notifications];
11020
+ if (currentQueuedNotification) {
11021
+ allVisible.push(currentQueuedNotification);
11022
+ }
11023
+ visibleNotifications = allVisible.length > 0 ? allVisible : noNotifications;
11024
+ notificationListeners.forEach(listener => listener([...allVisible]));
11025
+ };
11026
+ const processQueuedNotification = () => {
11027
+ if (currentQueuedNotification || queuedNotifications.length === 0)
11028
+ return;
11029
+ currentQueuedNotification = queuedNotifications.shift();
11030
+ notifyNotificationListeners();
10525
11031
  };
10526
-
10527
11032
  /**
10528
- * Tfoot component for rendering a styled Bulma table footer.
10529
- *
10530
- * Supports Bulma helper classes for additional styling.
11033
+ * Programmatic notification API for showing, closing, and managing notifications.
10531
11034
  *
10532
- * @function
10533
- * @param {TfootProps} props - Props for the Tfoot component.
10534
- * @returns {JSX.Element} The rendered table footer element.
10535
- * @see {@link https://bulma.io/documentation/elements/table/#table-footer | Bulma Table documentation}
11035
+ * @example
11036
+ * notification.success('File saved successfully');
11037
+ * notification.danger('Something went wrong', { duration: 5000 });
10536
11038
  */
10537
- const Tfoot = ({ className, children, ...props }) => {
11039
+ const notification = {
10538
11040
  /**
10539
- * Generates Bulma helper classes and separates out remaining props.
11041
+ * Show a notification with the given options.
11042
+ * @param {NotificationOptions} options - Notification configuration.
11043
+ * @returns {string} The unique ID of the created notification.
11044
+ */
11045
+ show: (options) => {
11046
+ const id = `notification-${++notificationId}`;
11047
+ const instance = { id, options };
11048
+ if (options.queue) {
11049
+ queuedNotifications.push(instance);
11050
+ processQueuedNotification();
11051
+ }
11052
+ else {
11053
+ notifications.push(instance);
11054
+ notifyNotificationListeners();
11055
+ }
11056
+ return id;
11057
+ },
11058
+ /**
11059
+ * Show a success notification.
11060
+ * @param {string|React.ReactNode} message - The message to display.
11061
+ * @param {Partial<NotificationOptions>} [options] - Additional options.
11062
+ * @returns {string} The notification ID.
11063
+ */
11064
+ success: (message, options) => {
11065
+ return notification.show({ message, color: 'success', ...options });
11066
+ },
11067
+ /**
11068
+ * Show a danger notification.
11069
+ * @param {string|React.ReactNode} message - The message to display.
11070
+ * @param {Partial<NotificationOptions>} [options] - Additional options.
11071
+ * @returns {string} The notification ID.
11072
+ */
11073
+ danger: (message, options) => {
11074
+ return notification.show({ message, color: 'danger', ...options });
11075
+ },
11076
+ /**
11077
+ * Show a warning notification.
11078
+ * @param {string|React.ReactNode} message - The message to display.
11079
+ * @param {Partial<NotificationOptions>} [options] - Additional options.
11080
+ * @returns {string} The notification ID.
11081
+ */
11082
+ warning: (message, options) => {
11083
+ return notification.show({ message, color: 'warning', ...options });
11084
+ },
11085
+ /**
11086
+ * Show an info notification.
11087
+ * @param {string|React.ReactNode} message - The message to display.
11088
+ * @param {Partial<NotificationOptions>} [options] - Additional options.
11089
+ * @returns {string} The notification ID.
11090
+ */
11091
+ info: (message, options) => {
11092
+ return notification.show({ message, color: 'info', ...options });
11093
+ },
11094
+ /**
11095
+ * Close a specific notification by ID.
11096
+ * @param {string} id - The notification ID to close.
11097
+ */
11098
+ close: (id) => {
11099
+ if (currentQueuedNotification && currentQueuedNotification.id === id) {
11100
+ currentQueuedNotification = null;
11101
+ processQueuedNotification();
11102
+ }
11103
+ else {
11104
+ queuedNotifications = queuedNotifications.filter(n => n.id !== id);
11105
+ notifications = notifications.filter(n => n.id !== id);
11106
+ }
11107
+ notifyNotificationListeners();
11108
+ },
11109
+ /** Close all notifications and clear the queue. */
11110
+ closeAll: () => {
11111
+ notifications = [];
11112
+ queuedNotifications = [];
11113
+ currentQueuedNotification = null;
11114
+ notifyNotificationListeners();
11115
+ },
11116
+ /**
11117
+ * Subscribe to notification state changes.
11118
+ * @param {(items: NotificationInstance[]) => void} listener - Callback invoked on changes.
11119
+ * @returns {() => void} Unsubscribe function.
10540
11120
  */
10541
- const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
10542
- const tfootClasses = classNames(className, bulmaHelperClasses);
10543
- return (jsxRuntime.jsx("tfoot", { className: tfootClasses || undefined, ...rest, children: children }));
11121
+ subscribe: (listener) => {
11122
+ notificationListeners.add(listener);
11123
+ return () => notificationListeners.delete(listener);
11124
+ },
10544
11125
  };
10545
-
10546
11126
  /**
10547
- * The values the table `color` prop accepts, as a readonly tuple.
10548
- *
10549
- * `TableColor` is typed from it, and `Tr`, `Th` and `Td` all take a
10550
- * `TableColor`, so the tuple and those props list the same values. Map over
10551
- * it to build a color picker, or check a value that arrives at runtime before
10552
- * passing it in: the components add no color class for a value outside the
10553
- * tuple.
10554
- *
10555
- * @example
10556
- * import { Tr, Td, validTableColors } from '@allxsmith/bestax-bulma';
11127
+ * Whether a notification of this color announces itself as an alert, rather
11128
+ * than politely through NotificationContainer's status region.
10557
11129
  *
10558
- * <Tr>
10559
- * {validTableColors.map(color => (
10560
- * <Td key={color} color={color}>
10561
- * {color}
10562
- * </Td>
10563
- * ))}
10564
- * </Tr>;
11130
+ * @function isUrgentColor
11131
+ * @param {NotificationOptions['color']} color - The notification's color.
11132
+ * @returns {boolean} True for `danger` and `warning`.
10565
11133
  */
10566
- const validTableColors = [
10567
- 'primary',
10568
- 'link',
10569
- 'info',
10570
- 'success',
10571
- 'warning',
10572
- 'danger',
10573
- 'black',
10574
- 'dark',
10575
- 'light',
10576
- 'white',
10577
- ];
11134
+ const isUrgentColor = (color) => color === 'danger' || color === 'warning';
10578
11135
  /**
10579
- * Td component for rendering a styled Bulma table cell.
10580
- *
10581
- * Supports Bulma color modifiers and helper classes for additional styling.
11136
+ * Single auto-dismissing notification item used by NotificationContainer.
10582
11137
  *
10583
11138
  * @function
10584
- * @param {TdProps} props - Props for the Td component.
10585
- * @returns {JSX.Element} The rendered table cell element.
10586
- * @see {@link https://bulma.io/documentation/elements/table/#table-body | Bulma Table documentation}
11139
+ * @param {{ instance: NotificationInstance; onClose: (id: string) => void; registerMessage: (id: string, node: HTMLElement | null) => void }} props - Component props.
11140
+ * @returns {JSX.Element} The rendered notification item.
10587
11141
  */
10588
- const Td = ({ className, color, children, ...props }) => {
10589
- const colorClass = usePrefixedClassNames('', {
10590
- [`is-${color}`]: color && validTableColors.includes(color),
10591
- });
10592
- /**
10593
- * Generates Bulma helper classes and separates out remaining props.
10594
- */
10595
- const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
10596
- const tdClasses = classNames(colorClass, className, bulmaHelperClasses);
10597
- return (jsxRuntime.jsx("td", { className: tdClasses || undefined, ...rest, children: children }));
11142
+ const NotificationItem = ({ instance, onClose, registerMessage }) => {
11143
+ const { message, color, isLight, duration = 3000, hasDelete = true, indefinite = false, pauseOnHover = true, } = instance.options;
11144
+ const [isPaused, setIsPaused] = React.useState(false);
11145
+ const urgent = isUrgentColor(color);
11146
+ const handleClose = React.useCallback(() => {
11147
+ onClose(instance.id);
11148
+ }, [onClose, instance.id]);
11149
+ const messageRef = React.useCallback((node) => {
11150
+ registerMessage(instance.id, node);
11151
+ }, [registerMessage, instance.id]);
11152
+ // Auto-close timer
11153
+ React.useEffect(() => {
11154
+ if (indefinite || duration === 0 || isPaused)
11155
+ return undefined;
11156
+ const timer = setTimeout(handleClose, duration);
11157
+ return () => clearTimeout(timer);
11158
+ }, [duration, indefinite, isPaused, handleClose]);
11159
+ const handleMouseEnter = React.useCallback(() => {
11160
+ if (pauseOnHover)
11161
+ setIsPaused(true);
11162
+ }, [pauseOnHover]);
11163
+ const handleMouseLeave = React.useCallback(() => {
11164
+ if (pauseOnHover)
11165
+ setIsPaused(false);
11166
+ }, [pauseOnHover]);
11167
+ return (jsxRuntime.jsx(Notification, { color: color, isLight: isLight, hasDelete: hasDelete, onDelete: handleClose, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, style: { pointerEvents: 'auto' }, children: urgent ? (jsxRuntime.jsx("span", { role: "alert", "aria-live": "assertive", children: message })) : (jsxRuntime.jsx("span", { ref: messageRef, children: message })) }));
10598
11168
  };
10599
-
11169
+ // The order NotificationContainer renders its stacks in: across the top of the
11170
+ // screen, then across the bottom.
11171
+ const notificationStackOrder = [
11172
+ 'top-left',
11173
+ 'top',
11174
+ 'top-right',
11175
+ 'bottom-left',
11176
+ 'bottom',
11177
+ 'bottom-right',
11178
+ ];
10600
11179
  /**
10601
- * Tr component for rendering a styled Bulma table row.
10602
- *
10603
- * Supports the is-selected modifier and color modifiers.
11180
+ * Inline style that fixes a stack of notifications to its place on the screen.
10604
11181
  *
10605
11182
  * @function
10606
- * @param {TrProps} props - Props for the Tr component.
10607
- * @returns {JSX.Element} The rendered table row element.
10608
- * @see {@link https://bulma.io/documentation/elements/table/#table-row | Bulma Table documentation}
11183
+ * @param {NotificationPosition} position - Where the stack sits.
11184
+ * @returns {React.CSSProperties} The stack's style.
10609
11185
  */
10610
- const Tr = ({ className, isSelected, color, children, ...props }) => {
10611
- const bulmaClasses = usePrefixedClassNames('', {
10612
- 'is-selected': isSelected,
10613
- [`is-${color}`]: color && validTableColors.includes(color),
10614
- });
10615
- /**
10616
- * Generates Bulma helper classes and separates out remaining props.
10617
- */
10618
- const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
10619
- const trClasses = classNames(bulmaClasses, className, bulmaHelperClasses);
10620
- return (jsxRuntime.jsx("tr", { className: trClasses || undefined, ...rest, children: children }));
11186
+ const notificationStackStyle = (position) => {
11187
+ const isBottom = position.startsWith('bottom');
11188
+ const isCenter = position === 'top' || position === 'bottom';
11189
+ const isRight = position.endsWith('right');
11190
+ return {
11191
+ position: 'fixed',
11192
+ zIndex: 100,
11193
+ display: 'flex',
11194
+ flexDirection: isBottom ? 'column-reverse' : 'column',
11195
+ gap: '0.75rem',
11196
+ padding: '1rem',
11197
+ pointerEvents: 'none',
11198
+ maxWidth: '100%',
11199
+ ...(isBottom ? { bottom: 0 } : { top: 0 }),
11200
+ ...(isCenter
11201
+ ? { left: '50%', transform: 'translateX(-50%)', alignItems: 'center' }
11202
+ : isRight
11203
+ ? { right: 0, alignItems: 'flex-end' }
11204
+ : { left: 0, alignItems: 'flex-start' }),
11205
+ };
10621
11206
  };
10622
-
10623
- const validAlignments = ['left', 'right', 'centered'];
10624
11207
  /**
10625
- * Th component for rendering a styled Bulma table header cell.
10626
- *
10627
- * Supports alignment, width, and color modifiers.
11208
+ * Container component for rendering programmatic notifications.
11209
+ * Place once at your app root to enable the notification API. A notification
11210
+ * shown with a `position` appears there, and one shown without goes to the
11211
+ * container's `position`, so the container renders a stack for each position
11212
+ * in use.
10628
11213
  *
10629
- * @function
10630
- * @param {ThProps} props - Props for the Th component.
10631
- * @returns {JSX.Element} The rendered table header cell element.
10632
- * @see {@link https://bulma.io/documentation/elements/table/#table-head | Bulma Table documentation}
10633
- */
10634
- const Th = ({ className, isAligned, width, color, children, ...props }) => {
10635
- const bulmaClasses = usePrefixedClassNames('', {
10636
- [`has-text-${isAligned}`]: isAligned && validAlignments.includes(isAligned),
10637
- [`is-${color}`]: color && validTableColors.includes(color),
10638
- });
10639
- /**
10640
- * Generates Bulma helper classes and separates out remaining props.
10641
- */
10642
- const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
10643
- const thClasses = classNames(bulmaClasses, className, bulmaHelperClasses);
10644
- return (jsxRuntime.jsx("th", { className: thClasses || undefined, style: width
10645
- ? { width: typeof width === 'number' ? `${width}px` : width }
10646
- : undefined, ...rest, children: children }));
10647
- };
10648
-
10649
- /**
10650
- * The `Table` component and its subcomponents provide a highly composable, Bulma-styled table system for React.
11214
+ * It keeps a visually hidden `role="status"` live region in the page from the
11215
+ * moment it mounts, even while nothing is showing, and announces every
11216
+ * notification other than `danger` and `warning` through it, a moment after
11217
+ * the notification appears. Those notifications carry no `role="status"` of
11218
+ * their own, so `getByRole('status')` finds the region, not the notification.
11219
+ * The text stays in the region briefly, even if the notification closes in
11220
+ * the meantime, and while both are up the page holds two copies of it. A
11221
+ * polite notification that closes before its announcement is written, a
11222
+ * moment after it appears, isn't announced at all. `danger` and `warning`
11223
+ * notifications announce themselves as assertive alerts. The region is hidden
11224
+ * with inline styles, so it needs no stylesheet.
10651
11225
  *
10652
11226
  * @function
10653
- * @param {TableProps} props - Props for the Table component.
10654
- * @returns {JSX.Element} The rendered table element.
10655
- * @see {@link https://bulma.io/documentation/elements/table/ | Bulma Table documentation}
11227
+ * @param {{ position?: NotificationPosition }} props - Container props.
11228
+ * @returns {JSX.Element | null} The rendered notification container, or null on the server and while hydrating.
10656
11229
  */
10657
- const TableComponent = ({ className, isBordered, isStriped, isNarrow, isHoverable, isFullwidth, isFullWidth, isResponsive, children, ...props }) => {
10658
- /**
10659
- * Generates Bulma helper classes and separates out remaining props.
10660
- */
10661
- const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
10662
- const bulmaClasses = usePrefixedClassNames('table', {
10663
- 'is-bordered': isBordered,
10664
- 'is-striped': isStriped,
10665
- 'is-narrow': isNarrow,
10666
- 'is-hoverable': isHoverable,
10667
- 'is-fullwidth': isFullwidth ?? isFullWidth,
10668
- });
10669
- const containerClass = usePrefixedClassNames('table-container');
10670
- const tableClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10671
- const tableElement = (jsxRuntime.jsx("table", { className: tableClasses, ...rest, children: children }));
10672
- if (isResponsive) {
10673
- return jsxRuntime.jsx("div", { className: containerClass, children: tableElement });
10674
- }
10675
- return tableElement;
10676
- };
10677
- const Table = withSubComponents(TableComponent, {
10678
- Thead,
10679
- Tbody,
10680
- Tfoot,
10681
- Tr,
10682
- Th,
10683
- Td,
10684
- }, 'Table');
11230
+ const NotificationContainer = ({ position = 'top-right' }) => {
11231
+ // Starts from the notifications already showing instead of an empty list,
11232
+ // then follows changes.
11233
+ const items = React.useSyncExternalStore(notification.subscribe, getVisibleNotifications, getServerNotifications);
11234
+ // A portal needs a document, and it has no server-rendered counterpart, so
11235
+ // the server render and the hydrating one render nothing, and the container
11236
+ // portals in from the render after hydration.
11237
+ const isHydrated = useIsHydrated();
11238
+ const resolveContainer = usePortalContainer();
11239
+ // The notifications the status region announces. Danger and warning
11240
+ // announce themselves.
11241
+ const politeItems = React.useMemo(() => items.filter(item => !isUrgentColor(item.options.color)), [items]);
11242
+ // Each polite notification's message element, so the text the region
11243
+ // announces is the text on screen, whatever the message renders.
11244
+ const messageNodesRef = React.useRef(new Map());
11245
+ const registerMessage = React.useCallback((id, node) => {
11246
+ if (node) {
11247
+ messageNodesRef.current.set(id, node);
11248
+ }
11249
+ else {
11250
+ messageNodesRef.current.delete(id);
11251
+ }
11252
+ }, []);
11253
+ // StatusRegion only describes polite notifications that are on screen, and
11254
+ // each of those registered its message element when it mounted. The
11255
+ // fallback is for a notification announced without being rendered, should
11256
+ // the container ever render fewer than it announces: a plain-text message
11257
+ // still says itself, and anything else is skipped.
11258
+ const describe = React.useCallback((item) => {
11259
+ const node = messageNodesRef.current.get(item.id);
11260
+ /* istanbul ignore if: every polite notification on screen registers its message element when it mounts */
11261
+ if (!node) {
11262
+ const { message } = item.options;
11263
+ return typeof message === 'string' ? message : null;
11264
+ }
11265
+ return spokenText(node);
11266
+ }, []);
11267
+ if (typeof document === 'undefined' || !isHydrated) {
11268
+ return null;
11269
+ }
11270
+ const stacks = groupIntoPositionStacks(items, item => item.options.position, position, notificationStackOrder);
11271
+ return reactDom.createPortal(jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [stacks.map(stack => (jsxRuntime.jsx("div", { style: notificationStackStyle(stack.position), children: stack.items.map(item => (jsxRuntime.jsx(NotificationItem, { instance: item, onClose: notification.close, registerMessage: registerMessage }, item.id))) }, stack.key))), jsxRuntime.jsx(StatusRegion, { items: politeItems, describe: describe })] }), resolveContainer());
11272
+ };
10685
11273
 
10686
- const validTagColors = [
10687
- 'primary',
10688
- 'link',
10689
- 'info',
10690
- 'success',
10691
- 'warning',
10692
- 'danger',
10693
- 'black',
10694
- 'dark',
10695
- 'light',
10696
- 'white',
10697
- ];
10698
- const validTagSizes = ['normal', 'medium', 'large'];
10699
11274
  /**
10700
- * The `Tag` component renders a Bulma-styled label or badge.
11275
+ * The `OrderedList` component renders a styled ordered list (`<ol>`) element with Bulma helper class integration.
10701
11276
  *
10702
11277
  * @function
10703
- * @param {TagProps} props - Props for the Tag component.
10704
- * @returns {JSX.Element} The rendered tag element.
10705
- * @see {@link https://bulma.io/documentation/elements/tag/ | Bulma Tag documentation}
11278
+ * @param {OrderedListProps} props - Props for the OrderedList component.
11279
+ * @returns {JSX.Element} The rendered ol element.
10706
11280
  */
10707
- const Tag = ({ className, color, size, isLight, isRounded, isDelete, isHoverable, onDelete, children, ...props }) => {
11281
+ const OrderedListComponent = ({ className, textColor, bgColor, children, ...props }) => {
10708
11282
  /**
10709
11283
  * Generates Bulma helper classes and separates out remaining props.
10710
11284
  */
10711
- const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
10712
- const bulmaClasses = usePrefixedClassNames('tag', {
10713
- [`is-${color}`]: color && validTagColors.includes(color),
10714
- [`is-${size}`]: size && size !== 'normal' && validTagSizes.includes(size),
10715
- 'is-light': isLight,
10716
- 'is-rounded': isRounded,
10717
- 'is-delete': isDelete,
10718
- 'is-hoverable': isHoverable,
11285
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
11286
+ color: textColor,
11287
+ backgroundColor: bgColor,
11288
+ ...props,
10719
11289
  });
10720
- const tagClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10721
- if (isDelete) {
10722
- return (jsxRuntime.jsx("button", { className: tagClasses, onClick: onDelete, ...rest, "aria-label": rest['aria-label'] || 'Delete tag', type: buttonType(rest.type) }));
10723
- }
10724
- return (jsxRuntime.jsx("span", { className: tagClasses, ...rest, children: children }));
11290
+ const bulmaClasses = usePrefixedClassNames();
11291
+ const listClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
11292
+ return (jsxRuntime.jsx("ol", { className: listClasses || undefined, ...rest, children: children }));
10725
11293
  };
11294
+ const OrderedList = withSubComponents(OrderedListComponent, { Item: ListItem }, 'OrderedList');
10726
11295
 
10727
- const validTagsSizes = ['medium', 'large'];
10728
11296
  /**
10729
- * The `Tags` component groups multiple `Tag` components together in a Bulma-styled container that wraps onto multiple lines by default.
11297
+ * The `Pre` component renders a styled `<pre>` element with Bulma helper class integration.
10730
11298
  *
10731
11299
  * @function
10732
- * @param {TagsProps} props - Props for the Tags component.
10733
- * @returns {JSX.Element} The rendered tags container.
10734
- * @see {@link https://bulma.io/documentation/elements/tag/#list-of-tags | Bulma Tags documentation}
11300
+ * @param {PreProps} props - Props for the Pre component.
11301
+ * @returns {JSX.Element} The rendered pre element.
10735
11302
  */
10736
- const TagsComponent = ({ className, hasAddons,
10737
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- accepted for backwards compatibility, deprecated no-op (no `are-multiline` CSS ships)
10738
- isMultiline, size, children, ...props }) => {
10739
- /**
10740
- * Generates Bulma helper classes and separates out remaining props.
10741
- */
10742
- const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
10743
- const bulmaClasses = usePrefixedClassNames('tags', {
10744
- 'has-addons': hasAddons,
10745
- [`are-${size}`]: size && validTagsSizes.includes(size),
11303
+ const Pre = ({ className, textColor, bgColor, children, ...props }) => {
11304
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
11305
+ color: textColor,
11306
+ backgroundColor: bgColor,
11307
+ ...props,
10746
11308
  });
10747
- const tagsClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10748
- return (jsxRuntime.jsx("div", { className: tagsClasses, ...rest, children: children }));
11309
+ const bulmaClasses = usePrefixedClassNames();
11310
+ const preClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
11311
+ return (jsxRuntime.jsx("pre", { className: preClasses || undefined, ...rest, children: children }));
10749
11312
  };
10750
- const Tags = withSubComponents(TagsComponent, { Tag }, 'Tags');
10751
11313
 
10752
11314
  /**
10753
- * The `UnorderedList` component renders a styled unordered list (`<ul>`) element with Bulma helper class integration.
11315
+ * The `Progress` component displays a Bulma-styled progress bar.
10754
11316
  *
10755
11317
  * @function
10756
- * @param {UnorderedListProps} props - Props for the UnorderedList component.
10757
- * @returns {JSX.Element} The rendered ul element.
11318
+ * @param {ProgressProps} props - Props for the Progress component.
11319
+ * @returns {JSX.Element} The rendered progress bar element.
11320
+ * @see {@link https://bulma.io/documentation/elements/progress/ | Bulma Progress documentation}
10758
11321
  */
10759
- const UnorderedListComponent = ({ className, textColor, bgColor, children, ...props }) => {
11322
+ const Progress = ({ className, color, size, value, max, children, ...props }) => {
11323
+ warnUnstyledColor('Progress', color);
10760
11324
  /**
10761
11325
  * Generates Bulma helper classes and separates out remaining props.
10762
11326
  */
10763
11327
  const { bulmaHelperClasses, rest } = useBulmaClasses({
10764
- color: textColor,
10765
- backgroundColor: bgColor,
10766
11328
  ...props,
10767
11329
  });
10768
- const bulmaClasses = usePrefixedClassNames();
10769
- const listClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10770
- return (jsxRuntime.jsx("ul", { className: listClasses || undefined, ...rest, children: children }));
11330
+ const bulmaClasses = usePrefixedClassNames('progress', {
11331
+ [`is-${color}`]: color && validColors.includes(color),
11332
+ [`is-${size}`]: size,
11333
+ });
11334
+ const progressClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
11335
+ return (jsxRuntime.jsx("progress", { className: progressClasses, value: value, max: max, ...rest, children: children }));
10771
11336
  };
10772
- const UnorderedList = withSubComponents(UnorderedListComponent, { Item: ListItem }, 'UnorderedList');
10773
11337
 
10774
- const FieldContext = React.createContext(false);
10775
- const ControlContext = React.createContext(false);
10776
- /**
10777
- * Hook to detect if the component is inside a Field wrapper.
10778
- * Form components use this to skip rendering their own Field.
10779
- */
10780
- const useInsideField = () => React.useContext(FieldContext);
10781
- /**
10782
- * Hook to detect if the component is inside a Control wrapper.
10783
- * Form components use this to skip rendering their own Control.
10784
- */
10785
- const useInsideControl = () => React.useContext(ControlContext);
10786
- /** Provider for Field context — used internally by Field component. */
10787
- const FieldProvider = FieldContext.Provider;
10788
- /** Provider for Control context — used internally by Control component. */
10789
- const ControlProvider = ControlContext.Provider;
10790
- // Each Field-shaping prop, with what takes its place on a `Field` the caller
10791
- // wraps around the `Control`. `message` needs nothing there, because the
10792
- // wrapper still renders it inside `Field > Control`. The other Field-level
10793
- // props (`labelSize`, `labelProps`, `messageColor`) change nothing unless a
10794
- // `label` or `message` renders, so on their own they keep no `Field`.
10795
- // `bare-control.test.tsx` holds this list to what each wrapper hands its
10796
- // `Field` and to which props change its markup, so a new Field-level prop
10797
- // fails there until it is sorted into one group or the other.
10798
- const FIELD_SHAPING = [
10799
- ['label', 'label'],
10800
- ['message', undefined],
10801
- ['horizontal', 'horizontal'],
10802
- ['fieldClassName', 'className (for fieldClassName)'],
10803
- ];
10804
- /** `a`, `a and b`, `a, b and c`. */
10805
- const listOf = (items) => items.length > 1
10806
- ? `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`
10807
- : items[0];
10808
11338
  /**
10809
- * Whether a convenience form wrapper renders a `Field` of its own (#905).
10810
- * Never inside an outer `Field`. Inside a `Control` with no `Field` around
10811
- * it, a `Field` of its own would put a `.field` inside the `.control`, where
10812
- * Bulma's control styles break (its icon rules are sibling selectors on the
10813
- * input). So it renders one there only when a prop that shapes the `Field`'s
10814
- * markup is set (`label`, `message`, `horizontal`, `fieldClassName`), keeping
10815
- * the markup it always had, and warns in development that the fix is a
10816
- * `Field` around that `Control`. A prop counts when it is truthy, the same
10817
- * test that decides whether it changes the `Field`'s output.
10818
- * Internal; not part of the public API.
11339
+ * The `Skeleton` component provides a Bulma-styled skeleton loader for React applications, useful for indicating that content is loading.
11340
+ *
11341
+ * @function
11342
+ * @param {SkeletonProps} props - Props for the Skeleton component.
11343
+ * @returns {JSX.Element} The rendered skeleton element.
11344
+ *
11345
+ * @example
11346
+ * // Block skeleton
11347
+ * <Skeleton />
11348
+ *
11349
+ * @example
11350
+ * // Multi-line skeleton
11351
+ * <Skeleton variant="lines" lines={5} />
10819
11352
  */
10820
- const rendersOwnField = (component, options) => {
10821
- if (options.insideField)
10822
- return false;
10823
- if (!options.insideControl)
10824
- return true;
10825
- const held = FIELD_SHAPING.filter(([prop]) => options[prop]);
10826
- if (held.length === 0)
10827
- return false;
10828
- const names = held.map(([prop]) => prop);
10829
- const moved = held
10830
- .map(([, onField]) => onField)
10831
- .filter((onField) => onField !== undefined);
10832
- warnOnce(`${component}:Field-in-bare-Control:${names.join('+')}`, `[bestax-bulma] <${component} ${names.join(' ')}> inside a <Control> ` +
10833
- `with no <Field> around it renders its own <Field> for ` +
10834
- `${names.length > 1 ? 'those props' : 'that prop'}, which puts a ` +
10835
- `.field inside the .control, a nesting Bulma's styles are not ` +
10836
- `written for. Wrap the <Control> in a <Field> instead` +
10837
- (moved.length > 0 ? `, and set ${listOf(moved)} on that <Field>.` : '.'));
10838
- return true;
10839
- };
10840
- // Each Control-level prop, with what takes its place on the `Control` the
10841
- // caller wrapped around the wrapper. `bare-control.test.tsx` holds this to
10842
- // what each wrapper hands its own `Control`, so a new Control-level prop
10843
- // fails there until it is added here and passed to `rendersOwnControl`.
10844
- const ON_CONTROL = {
10845
- isLoading: 'isLoading',
10846
- iconLeft: 'iconLeft',
10847
- iconLeftName: 'iconLeftName',
10848
- iconLeftSize: 'iconLeftSize',
10849
- iconRight: 'iconRight',
10850
- iconRightName: 'iconRightName',
10851
- iconRightSize: 'iconRightSize',
10852
- hasIconsLeft: 'hasIconsLeft',
10853
- hasIconsRight: 'hasIconsRight',
10854
- isExpanded: 'isExpanded',
10855
- controlSize: 'size (for controlSize)',
10856
- controlClassName: 'className (for controlClassName)',
11353
+ const Skeleton = ({ className, variant = 'block', lines = 3, children, ...props }) => {
11354
+ const linesClass = usePrefixedClassNames('skeleton-lines');
11355
+ const blockClass = usePrefixedClassNames('skeleton-block');
11356
+ if (variant === 'lines') {
11357
+ return (jsxRuntime.jsx("div", { className: classNames(linesClass, className), ...props, children: Array.from({ length: lines }).map((_, i) => (jsxRuntime.jsx("div", {}, i))) }));
11358
+ }
11359
+ return (jsxRuntime.jsx("div", { className: classNames(blockClass, className), ...props, children: children }));
10857
11360
  };
11361
+
10858
11362
  /**
10859
- * Whether a convenience form wrapper renders a `Control` of its own (#921):
10860
- * only outside one. Inside a `Control` it renders none, so the props it
10861
- * would have handed its own do nothing there, and it warns in development,
10862
- * naming them and saying to set them on that `Control`. An `inline` picker
10863
- * renders none in either place, so it drops them inside a `Control` or not,
10864
- * and its warning says so without pointing at a `Control`. A wrapper passes
10865
- * only the props it hands its own `Control`, as the caller gave them, so a
10866
- * default the wrapper fills in itself never warns. A prop counts when it is
10867
- * truthy, since a falsy one would change nothing on a `Control` either.
10868
- * `iconLeftSize` and `hasIconsLeft` show nothing on a `Control` without a
10869
- * glyph, so when they move and the caller chose no left icon, the advice
10870
- * names the wrapper's default glyph too, and following it draws what the
10871
- * wrapper's own `Control` did.
10872
- * Internal; not part of the public API.
11363
+ * The `Span` component renders a styled inline `<span>` element with Bulma helper class integration.
11364
+ *
11365
+ * @function
11366
+ * @param {SpanProps} props - Props for the Span component.
11367
+ * @returns {JSX.Element} The rendered span element.
10873
11368
  */
10874
- const rendersOwnControl = (component, options) => {
10875
- if (!options.inline && !options.insideControl)
10876
- return true;
10877
- const held = Object.keys(ON_CONTROL).filter(prop => options[prop]);
10878
- if (held.length === 0)
10879
- return false;
10880
- const names = held.join(' ');
10881
- const doNothing = held.length > 1 ? 'those props do' : 'that prop does';
10882
- let message;
10883
- if (options.inline) {
10884
- message =
10885
- `[bestax-bulma] <${component} inline ${names}> renders no <Control> ` +
10886
- `in inline mode, inside a <Control> or not, so ${doNothing} ` +
10887
- `nothing. Leave ${held.length > 1 ? 'them' : 'it'} out of an ` +
10888
- `inline picker.`;
10889
- }
10890
- else {
10891
- const toSet = held.map(prop => ON_CONTROL[prop]);
10892
- const needsGlyph = (options.iconLeftSize || options.hasIconsLeft) &&
10893
- !options.iconLeft &&
10894
- options.iconLeftName === undefined &&
10895
- options.defaultIconLeftName;
10896
- if (needsGlyph) {
10897
- toSet.push(`iconLeftName="${options.defaultIconLeftName}" (its default icon)`);
10898
- }
10899
- message =
10900
- `[bestax-bulma] <${component} ${names}> inside a <Control> renders ` +
10901
- `no <Control> of its own, so ${doNothing} nothing there. Set ` +
10902
- `${listOf(toSet)} on that <Control> instead.`;
10903
- }
10904
- // Keyed by the message itself: the props named do not settle the advice
10905
- // (an empty `iconLeftName` and an unset one name the same props), so two
10906
- // calls that would advise differently must not share a key.
10907
- warnOnce(message, message);
10908
- return false;
11369
+ const Span = ({ className, textColor, bgColor, children, ...props }) => {
11370
+ /**
11371
+ * Generates Bulma helper classes and separates out remaining props.
11372
+ */
11373
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
11374
+ color: textColor,
11375
+ backgroundColor: bgColor,
11376
+ ...props,
11377
+ });
11378
+ const bulmaClasses = usePrefixedClassNames();
11379
+ const spanClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
11380
+ return (jsxRuntime.jsx("span", { className: spanClasses || undefined, ...rest, children: children }));
10909
11381
  };
10910
- const FieldLabelIdContext = React.createContext(undefined);
11382
+
10911
11383
  /**
10912
- * The id a labeled Field wants its single composed control to adopt (#495).
10913
- * `undefined` outside a Field, in unlabeled/grouped/addons Fields, or when the
10914
- * user took over the association with an explicit `labelProps.htmlFor`.
10915
- * Consumed through `useFieldLabelTarget` by the single-control bases
10916
- * (InputBase, SelectBase, TextAreaBase, and the date and time picker bases
10917
- * unless inline, #968) and, through `useAutoLabelId`, by the convenience
10918
- * inputs that render an input of their own (#939). Read it only through
10919
- * those, which report what they take: a control that read it directly would
10920
- * report nothing and lose its label (#1004), and
10921
- * `__tests__/field-label-readers.test.ts` fails on one. Internal; not part of
10922
- * the public API.
11384
+ * The `Strong` component renders a styled `<strong>` element with Bulma helper class integration.
11385
+ *
11386
+ * @function
11387
+ * @param {StrongProps} props - Props for the Strong component.
11388
+ * @returns {JSX.Element} The rendered strong element.
10923
11389
  */
10924
- const useFieldLabelId = () => React.useContext(FieldLabelIdContext);
10925
- /** Provider for the Field label-target id — used internally by Field. */
10926
- const FieldLabelIdProvider = FieldLabelIdContext.Provider;
10927
- const FieldLabelElementIdContext = React.createContext(undefined);
11390
+ const Strong = ({ className, textColor, bgColor, children, ...props }) => {
11391
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
11392
+ color: textColor,
11393
+ backgroundColor: bgColor,
11394
+ ...props,
11395
+ });
11396
+ const bulmaClasses = usePrefixedClassNames();
11397
+ const strongClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
11398
+ return (jsxRuntime.jsx("strong", { className: strongClasses || undefined, ...rest, children: children }));
11399
+ };
11400
+
11401
+ const validSubTitleSizes = ['1', '2', '3', '4', '5', '6'];
11402
+ const validSubTitleElements = [
11403
+ 'h1',
11404
+ 'h2',
11405
+ 'h3',
11406
+ 'h4',
11407
+ 'h5',
11408
+ 'h6',
11409
+ 'p',
11410
+ ];
10928
11411
  /**
10929
- * The id of a labeled Field's own `<label>`, for a group control (Radios,
10930
- * Checkboxes, Rate, DateRangeInput and DateRangeInputBase) to point
10931
- * `aria-labelledby` at, since a group cannot take the label's `htmlFor`
10932
- * (#939, #1005). Set under the same conditions as
10933
- * {@link useFieldLabelId}. Consumed through `useAutoLabelledBy`.
10934
- * Internal; not part of the public API.
11412
+ * The `SubTitle` component renders a Bulma-styled subtitle (secondary heading), supporting sizes `1-6` and rendering as any heading or paragraph element (`h1-h6`, `p`).
11413
+ *
11414
+ * @function
11415
+ * @param {SubTitleProps} props - Props for the SubTitle component.
11416
+ * @returns {JSX.Element} The rendered subtitle element.
11417
+ * @see {@link https://bulma.io/documentation/elements/title/#subtitle | Bulma Subtitle documentation}
10935
11418
  */
10936
- const useFieldLabelElementId = () => React.useContext(FieldLabelElementIdContext);
10937
- /** Provider for the Field label's own id, used internally by Field. */
10938
- const FieldLabelElementIdProvider = FieldLabelElementIdContext.Provider;
10939
- const FieldLabelForContext = React.createContext({});
11419
+ const SubTitle = ({ className, size, as = 'h1', hasSkeleton, textColor, bgColor, children, ...props }) => {
11420
+ /**
11421
+ * Generates Bulma helper classes and separates out remaining props.
11422
+ */
11423
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
11424
+ color: textColor,
11425
+ backgroundColor: bgColor,
11426
+ ...props,
11427
+ });
11428
+ // Validate 'as' prop at runtime
11429
+ const element = validSubTitleElements.includes(as) ? as : 'h1';
11430
+ // Validate 'size' prop at runtime (numeric sizes normalize to strings)
11431
+ const normalized = size == null ? undefined : String(size);
11432
+ const validSize = normalized && validSubTitleSizes.includes(normalized)
11433
+ ? normalized
11434
+ : undefined;
11435
+ const bulmaClasses = usePrefixedClassNames('subtitle', {
11436
+ [`is-${validSize}`]: validSize,
11437
+ 'has-skeleton': hasSkeleton,
11438
+ });
11439
+ const subTitleClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
11440
+ // Determine the tag based on 'element' and 'validSize'
11441
+ const Tag = element === 'p' ? 'p' : validSize ? `h${validSize}` : element;
11442
+ return (jsxRuntime.jsx(Tag, { className: subTitleClasses, ...rest, children: children }));
11443
+ };
11444
+
10940
11445
  /**
10941
- * The nearest labeled Field's `<label>`: what it points `htmlFor` at, the id
10942
- * it generated or the caller's own `labelProps.htmlFor`, and the id the label
10943
- * renders with, if it has one. An unlabeled Field passes both through
10944
- * together, so a control in the inner Field of a horizontal layout can tell
10945
- * the outer label names it, and point at that label by its id, as a range
10946
- * Slider's thumbs (#981) and Autocomplete's and Taginput's suggestion lists
10947
- * (#998, #1082) do. The
10948
- * `htmlFor` is only ever compared with a control's own id, never adopted, so
10949
- * it hands out no id for a control. It keeps the generated id after the
10950
- * label drops a `for` nothing took (#1004), which no control's id matches
10951
- * then, so the value does not change under the content that reported it.
10952
- * The `id` is set only while that label renders with it. Unlike
10953
- * {@link useFieldLabelId}, it is set in a `grouped` or `hasAddons` Field
10954
- * too, so a label wired by hand names the control its `for` picks in those
10955
- * rows as well. Consumed through `useAutoLabelId`.
10956
- * Internal; not part of the public API.
11446
+ * Thead component for rendering a styled Bulma table header.
11447
+ *
11448
+ * Supports Bulma helper classes for additional styling.
11449
+ *
11450
+ * @function
11451
+ * @param {TheadProps} props - Props for the Thead component.
11452
+ * @returns {JSX.Element} The rendered table header element.
11453
+ * @see {@link https://bulma.io/documentation/elements/table/#table-head | Bulma Table documentation}
10957
11454
  */
10958
- const useFieldLabelFor = () => React.useContext(FieldLabelForContext);
10959
- /** Provider for the Field label's `htmlFor` target and id, used internally by Field. */
10960
- const FieldLabelForProvider = FieldLabelForContext.Provider;
10961
- const FieldLabelForReportContext = React.createContext(undefined);
11455
+ const Thead = ({ className, children, ...props }) => {
11456
+ /**
11457
+ * Generates Bulma helper classes and separates out remaining props.
11458
+ */
11459
+ const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
11460
+ const theadClasses = classNames(className, bulmaHelperClasses);
11461
+ return (jsxRuntime.jsx("thead", { className: theadClasses || undefined, ...rest, children: children }));
11462
+ };
11463
+
10962
11464
  /**
10963
- * Tells the nearest labeled Field that this content took its generated id
10964
- * while `takesFor` is true (#1004). Once the Field's content has mounted, its
10965
- * label keeps the generated `for` only while something has said so, so the
10966
- * label renders none over a group, a `Checkbox`, `Radio` or `Switch`, an
10967
- * inner `Field`, an input with an `id` of its own, markup of your own, or
10968
- * nothing at all. Anything that puts the id from {@link useFieldLabelId} on
10969
- * an element has to say so, or the label drops the `for` that names it, and
10970
- * content that takes no id has nothing to do. Set under the same conditions
10971
- * as {@link useFieldLabelId}, so a label wired by hand and a `grouped` or
10972
- * `hasAddons` Field hear nothing. It reports from a layout effect, which a
10973
- * server never runs and a hydrating client runs only after its first render,
10974
- * so that render writes the `for` the way the server did.
10975
- * Internal; not part of the public API.
11465
+ * Tbody component for rendering a styled Bulma table body.
11466
+ *
11467
+ * Supports Bulma helper classes for additional styling.
11468
+ *
11469
+ * @function
11470
+ * @param {TbodyProps} props - Props for the Tbody component.
11471
+ * @returns {JSX.Element} The rendered table body element.
11472
+ * @see {@link https://bulma.io/documentation/elements/table/#table-body | Bulma Table documentation}
10976
11473
  */
10977
- const useReportFieldLabelFor = (takesFor) => {
10978
- const report = React.useContext(FieldLabelForReportContext);
10979
- useClientLayoutEffect(() => (takesFor ? report?.() : undefined), [report, takesFor]);
11474
+ const Tbody = ({ className, children, ...props }) => {
11475
+ /**
11476
+ * Generates Bulma helper classes and separates out remaining props.
11477
+ */
11478
+ const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
11479
+ const tbodyClasses = classNames(className, bulmaHelperClasses);
11480
+ return (jsxRuntime.jsx("tbody", { className: tbodyClasses || undefined, ...rest, children: children }));
10980
11481
  };
10981
- /** Provider for the labeled Field's report function, used internally by Field. */
10982
- const FieldLabelForReportProvider = FieldLabelForReportContext.Provider;
11482
+
10983
11483
  /**
10984
- * The id a control with one input of its own renders with: the caller's
10985
- * `id` when one arrived, otherwise the one a labeled Field offers (#495),
10986
- * unless it has no input to put it on (`hasInput: false`, as in an `inline`
10987
- * picker). It also tells that Field when it took the Field's id, through
10988
- * {@link useReportFieldLabelFor}, so the label keeps its `for` (#1004). Used
10989
- * by the single-control bases. Internal; not part of the public API.
11484
+ * Tfoot component for rendering a styled Bulma table footer.
11485
+ *
11486
+ * Supports Bulma helper classes for additional styling.
11487
+ *
11488
+ * @function
11489
+ * @param {TfootProps} props - Props for the Tfoot component.
11490
+ * @returns {JSX.Element} The rendered table footer element.
11491
+ * @see {@link https://bulma.io/documentation/elements/table/#table-footer | Bulma Table documentation}
10990
11492
  */
10991
- const useFieldLabelTarget = (id, hasInput = true) => {
10992
- const fieldLabelId = useFieldLabelId();
10993
- const target = id ?? (hasInput ? fieldLabelId : undefined);
10994
- useReportFieldLabelFor(fieldLabelId !== undefined && target === fieldLabelId);
10995
- return target;
11493
+ const Tfoot = ({ className, children, ...props }) => {
11494
+ /**
11495
+ * Generates Bulma helper classes and separates out remaining props.
11496
+ */
11497
+ const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
11498
+ const tfootClasses = classNames(className, bulmaHelperClasses);
11499
+ return (jsxRuntime.jsx("tfoot", { className: tfootClasses || undefined, ...rest, children: children }));
10996
11500
  };
10997
- const RadiosContext = React.createContext(undefined);
10998
- const CheckboxesContext = React.createContext(undefined);
11501
+
10999
11502
  /**
11000
- * Hook to read the full surrounding `<Radios>` group context (name + selection).
11001
- * Returns `undefined` when not inside a `<Radios>` group.
11503
+ * The values the table `color` prop accepts, as a readonly tuple.
11504
+ *
11505
+ * `TableColor` is typed from it, and `Tr`, `Th` and `Td` all take a
11506
+ * `TableColor`, so the tuple and those props list the same values. Map over
11507
+ * it to build a color picker, or check a value that arrives at runtime before
11508
+ * passing it in: the components add no color class for a value outside the
11509
+ * tuple.
11510
+ *
11511
+ * @example
11512
+ * import { Tr, Td, validTableColors } from '@allxsmith/bestax-bulma';
11513
+ *
11514
+ * <Tr>
11515
+ * {validTableColors.map(color => (
11516
+ * <Td key={color} color={color}>
11517
+ * {color}
11518
+ * </Td>
11519
+ * ))}
11520
+ * </Tr>;
11002
11521
  */
11003
- const useRadiosGroup = () => React.useContext(RadiosContext);
11522
+ const validTableColors = [
11523
+ 'primary',
11524
+ 'link',
11525
+ 'info',
11526
+ 'success',
11527
+ 'warning',
11528
+ 'danger',
11529
+ 'black',
11530
+ 'dark',
11531
+ 'light',
11532
+ 'white',
11533
+ ];
11004
11534
  /**
11005
- * Hook to read the full surrounding `<Checkboxes>` group context.
11006
- * Returns `undefined` when not inside a `<Checkboxes>` group.
11535
+ * Td component for rendering a styled Bulma table cell.
11536
+ *
11537
+ * Supports Bulma color modifiers and helper classes for additional styling.
11538
+ *
11539
+ * @function
11540
+ * @param {TdProps} props - Props for the Td component.
11541
+ * @returns {JSX.Element} The rendered table cell element.
11542
+ * @see {@link https://bulma.io/documentation/elements/table/#table-body | Bulma Table documentation}
11543
+ */
11544
+ const Td = ({ className, color, children, ...props }) => {
11545
+ const colorClass = usePrefixedClassNames('', {
11546
+ [`is-${color}`]: color && validTableColors.includes(color),
11547
+ });
11548
+ /**
11549
+ * Generates Bulma helper classes and separates out remaining props.
11550
+ */
11551
+ const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
11552
+ const tdClasses = classNames(colorClass, className, bulmaHelperClasses);
11553
+ return (jsxRuntime.jsx("td", { className: tdClasses || undefined, ...rest, children: children }));
11554
+ };
11555
+
11556
+ /**
11557
+ * Tr component for rendering a styled Bulma table row.
11558
+ *
11559
+ * Supports the is-selected modifier and color modifiers.
11560
+ *
11561
+ * @function
11562
+ * @param {TrProps} props - Props for the Tr component.
11563
+ * @returns {JSX.Element} The rendered table row element.
11564
+ * @see {@link https://bulma.io/documentation/elements/table/#table-row | Bulma Table documentation}
11007
11565
  */
11008
- const useCheckboxesGroup = () => React.useContext(CheckboxesContext);
11009
- /** Provider for the Radios group context — used internally by Radios. */
11010
- const RadiosProvider = RadiosContext.Provider;
11011
- /** Provider for the Checkboxes group context — used internally by Checkboxes. */
11012
- const CheckboxesProvider = CheckboxesContext.Provider;
11566
+ const Tr = ({ className, isSelected, color, children, ...props }) => {
11567
+ const bulmaClasses = usePrefixedClassNames('', {
11568
+ 'is-selected': isSelected,
11569
+ [`is-${color}`]: color && validTableColors.includes(color),
11570
+ });
11571
+ /**
11572
+ * Generates Bulma helper classes and separates out remaining props.
11573
+ */
11574
+ const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
11575
+ const trClasses = classNames(bulmaClasses, className, bulmaHelperClasses);
11576
+ return (jsxRuntime.jsx("tr", { className: trClasses || undefined, ...rest, children: children }));
11577
+ };
11013
11578
 
11579
+ const validAlignments = ['left', 'right', 'centered'];
11014
11580
  /**
11015
- * The values the Checkbox `color` prop accepts, as a readonly tuple.
11581
+ * Th component for rendering a styled Bulma table header cell.
11016
11582
  *
11017
- * `CheckboxProps['color']` is typed from it, so the two list the same values.
11018
- * Map over it to build a color picker, or check a value that arrives at
11019
- * runtime before passing it in: the component adds no color class for a value
11020
- * outside the tuple.
11583
+ * Supports alignment, width, and color modifiers.
11021
11584
  *
11022
- * @example
11023
- * import { Checkbox, checkboxColors } from '@allxsmith/bestax-bulma';
11585
+ * @function
11586
+ * @param {ThProps} props - Props for the Th component.
11587
+ * @returns {JSX.Element} The rendered table header cell element.
11588
+ * @see {@link https://bulma.io/documentation/elements/table/#table-head | Bulma Table documentation}
11589
+ */
11590
+ const Th = ({ className, isAligned, width, color, children, ...props }) => {
11591
+ const bulmaClasses = usePrefixedClassNames('', {
11592
+ [`has-text-${isAligned}`]: isAligned && validAlignments.includes(isAligned),
11593
+ [`is-${color}`]: color && validTableColors.includes(color),
11594
+ });
11595
+ /**
11596
+ * Generates Bulma helper classes and separates out remaining props.
11597
+ */
11598
+ const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
11599
+ const thClasses = classNames(bulmaClasses, className, bulmaHelperClasses);
11600
+ return (jsxRuntime.jsx("th", { className: thClasses || undefined, style: width
11601
+ ? { width: typeof width === 'number' ? `${width}px` : width }
11602
+ : undefined, ...rest, children: children }));
11603
+ };
11604
+
11605
+ /**
11606
+ * The `Table` component and its subcomponents provide a highly composable, Bulma-styled table system for React.
11024
11607
  *
11025
- * checkboxColors.map(color => (
11026
- * <Checkbox key={color} color={color}>
11027
- * {color}
11028
- * </Checkbox>
11029
- * ));
11608
+ * @function
11609
+ * @param {TableProps} props - Props for the Table component.
11610
+ * @returns {JSX.Element} The rendered table element.
11611
+ * @see {@link https://bulma.io/documentation/elements/table/ | Bulma Table documentation}
11030
11612
  */
11031
- const checkboxColors = [
11613
+ const TableComponent = ({ className, isBordered, isStriped, isNarrow, isHoverable, isFullwidth, isFullWidth, isResponsive, children, ...props }) => {
11614
+ /**
11615
+ * Generates Bulma helper classes and separates out remaining props.
11616
+ */
11617
+ const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
11618
+ const bulmaClasses = usePrefixedClassNames('table', {
11619
+ 'is-bordered': isBordered,
11620
+ 'is-striped': isStriped,
11621
+ 'is-narrow': isNarrow,
11622
+ 'is-hoverable': isHoverable,
11623
+ 'is-fullwidth': isFullwidth ?? isFullWidth,
11624
+ });
11625
+ const containerClass = usePrefixedClassNames('table-container');
11626
+ const tableClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
11627
+ const tableElement = (jsxRuntime.jsx("table", { className: tableClasses, ...rest, children: children }));
11628
+ if (isResponsive) {
11629
+ return jsxRuntime.jsx("div", { className: containerClass, children: tableElement });
11630
+ }
11631
+ return tableElement;
11632
+ };
11633
+ const Table = withSubComponents(TableComponent, {
11634
+ Thead,
11635
+ Tbody,
11636
+ Tfoot,
11637
+ Tr,
11638
+ Th,
11639
+ Td,
11640
+ }, 'Table');
11641
+
11642
+ const validTagColors = [
11032
11643
  'primary',
11033
11644
  'link',
11034
11645
  'info',
11035
11646
  'success',
11036
11647
  'warning',
11037
11648
  'danger',
11649
+ 'black',
11650
+ 'dark',
11651
+ 'light',
11652
+ 'white',
11038
11653
  ];
11654
+ const validTagSizes = ['normal', 'medium', 'large'];
11039
11655
  /**
11040
- * The values the Checkbox `size` prop accepts, as a readonly tuple.
11041
- *
11042
- * `CheckboxProps['size']` is typed from it, so the two list the same values.
11043
- * Use it to offer a size choice or to check a value that arrives at runtime:
11044
- * the component adds no size class for a value outside the tuple. These are
11045
- * element sizes, not the spacing scale in `validSizes`.
11046
- *
11047
- * @example
11048
- * import { checkboxSizes } from '@allxsmith/bestax-bulma';
11656
+ * The `Tag` component renders a Bulma-styled label or badge.
11049
11657
  *
11050
- * type CheckboxSize = (typeof checkboxSizes)[number];
11658
+ * @function
11659
+ * @param {TagProps} props - Props for the Tag component.
11660
+ * @returns {JSX.Element} The rendered tag element.
11661
+ * @see {@link https://bulma.io/documentation/elements/tag/ | Bulma Tag documentation}
11051
11662
  */
11052
- const checkboxSizes = ['small', 'normal', 'medium', 'large'];
11663
+ const Tag = ({ className, color, size, isLight, isRounded, isDelete, isHoverable, onDelete, children, ...props }) => {
11664
+ /**
11665
+ * Generates Bulma helper classes and separates out remaining props.
11666
+ */
11667
+ const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
11668
+ const bulmaClasses = usePrefixedClassNames('tag', {
11669
+ [`is-${color}`]: color && validTagColors.includes(color),
11670
+ [`is-${size}`]: size && size !== 'normal' && validTagSizes.includes(size),
11671
+ 'is-light': isLight,
11672
+ 'is-rounded': isRounded,
11673
+ 'is-delete': isDelete,
11674
+ 'is-hoverable': isHoverable,
11675
+ });
11676
+ const tagClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
11677
+ if (isDelete) {
11678
+ return (jsxRuntime.jsx("button", { className: tagClasses, onClick: onDelete, ...rest, "aria-label": rest['aria-label'] || 'Delete tag', type: buttonType(rest.type) }));
11679
+ }
11680
+ return (jsxRuntime.jsx("span", { className: tagClasses, ...rest, children: children }));
11681
+ };
11682
+
11683
+ const validTagsSizes = ['medium', 'large'];
11053
11684
  /**
11054
- * The `Checkbox` component provides a Bulma-styled checkbox input.
11685
+ * The `Tags` component groups multiple `Tag` components together in a Bulma-styled container that wraps onto multiple lines by default.
11055
11686
  *
11056
11687
  * @function
11057
- * @param {CheckboxProps} props - Props for the Checkbox component.
11058
- * @returns {JSX.Element} The rendered checkbox element.
11059
- *
11060
- * @example
11061
- * // Basic checkbox
11062
- * <Checkbox>Accept terms</Checkbox>
11688
+ * @param {TagsProps} props - Props for the Tags component.
11689
+ * @returns {JSX.Element} The rendered tags container.
11690
+ * @see {@link https://bulma.io/documentation/elements/tag/#list-of-tags | Bulma Tags documentation}
11691
+ */
11692
+ const TagsComponent = ({ className, hasAddons,
11693
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- accepted for backwards compatibility, deprecated no-op (no `are-multiline` CSS ships)
11694
+ isMultiline, size, children, ...props }) => {
11695
+ /**
11696
+ * Generates Bulma helper classes and separates out remaining props.
11697
+ */
11698
+ const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
11699
+ const bulmaClasses = usePrefixedClassNames('tags', {
11700
+ 'has-addons': hasAddons,
11701
+ [`are-${size}`]: size && validTagsSizes.includes(size),
11702
+ });
11703
+ const tagsClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
11704
+ return (jsxRuntime.jsx("div", { className: tagsClasses, ...rest, children: children }));
11705
+ };
11706
+ const Tags = withSubComponents(TagsComponent, { Tag }, 'Tags');
11707
+
11708
+ /**
11709
+ * The `UnorderedList` component renders a styled unordered list (`<ul>`) element with Bulma helper class integration.
11063
11710
  *
11064
- * @example
11065
- * // Inside a group
11066
- * <Checkboxes name="tags" defaultValue={['react']}>
11067
- * <Checkbox value="react">React</Checkbox>
11068
- * <Checkbox value="vue">Vue</Checkbox>
11069
- * </Checkboxes>
11711
+ * @function
11712
+ * @param {UnorderedListProps} props - Props for the UnorderedList component.
11713
+ * @returns {JSX.Element} The rendered ul element.
11070
11714
  */
11071
- const Checkbox = React.forwardRef(({ color, size, className, children, textColor, disabled, name, value, checked, defaultChecked, indeterminate, onChange, ...props }, ref) => {
11715
+ const UnorderedListComponent = ({ className, textColor, bgColor, children, ...props }) => {
11716
+ /**
11717
+ * Generates Bulma helper classes and separates out remaining props.
11718
+ */
11072
11719
  const { bulmaHelperClasses, rest } = useBulmaClasses({
11073
11720
  color: textColor,
11721
+ backgroundColor: bgColor,
11074
11722
  ...props,
11075
11723
  });
11076
- // The input's own ref, merged with the forwarded one. A callback ref's
11077
- // return value is handed back, so React 19 still runs a cleanup it
11078
- // returns, as it did when the ref went straight to the input.
11079
- const inputRef = React.useRef(null);
11080
- const setRefs = React.useCallback((node) => {
11081
- inputRef.current = node;
11082
- if (typeof ref === 'function')
11083
- return ref(node);
11084
- if (ref)
11085
- ref.current = node;
11086
- }, [ref]);
11087
- // `indeterminate` is a DOM property with no attribute, so it's set on the
11088
- // input rather than rendered. A click clears it, so this runs after every
11089
- // render, not only when the prop changes. Unset, it's left alone, so a
11090
- // ref that sets the property itself still works. `ownsIndeterminateRef`
11091
- // remembers that this component set it, so a prop that goes back to unset
11092
- // clears the dash once instead of leaving it on.
11093
- const ownsIndeterminateRef = React.useRef(false);
11094
- useClientLayoutEffect(() => {
11095
- if (indeterminate !== undefined) {
11096
- ownsIndeterminateRef.current = true;
11097
- inputRef.current.indeterminate = indeterminate;
11098
- }
11099
- else if (ownsIndeterminateRef.current) {
11100
- ownsIndeterminateRef.current = false;
11101
- inputRef.current.indeterminate = false;
11102
- }
11103
- });
11104
- // Inherit name + selection state from a surrounding <Checkboxes> group.
11105
- // Local props always win over the group (explicit > implicit).
11106
- const group = useCheckboxesGroup();
11107
- const effectiveName = name ?? group?.name;
11108
- // Group-managed checked state — only when the group is in
11109
- // controlled/uncontrolled mode (group.value is defined) AND this Checkbox
11110
- // has a value to compare against. Local `checked` always wins.
11111
- const groupManaged = group?.value !== undefined && value !== undefined;
11112
- const groupHas = groupManaged && group.value.includes(String(value));
11113
- const effectiveChecked = checked !== undefined ? checked : groupManaged ? groupHas : undefined;
11114
- // When the group manages checked, suppress local defaultChecked to avoid
11115
- // the controlled/uncontrolled React warning.
11116
- const effectiveDefaultChecked = groupManaged ? undefined : defaultChecked;
11117
- const handleChange = React.useCallback((e) => {
11118
- // The click that got here cleared the mixed state. Put it back now,
11119
- // since a click that changes no state renders nothing to do it.
11120
- if (indeterminate !== undefined)
11121
- e.target.indeterminate = indeterminate;
11122
- // Local onChange always fires.
11123
- onChange?.(e);
11124
- // Dispatch to the group additionally — toggle this value's
11125
- // membership in the group's array.
11126
- if (group?.onChange && value !== undefined) {
11127
- const valStr = String(value);
11128
- const currentArr = group.value ?? [];
11129
- const next = e.target.checked
11130
- ? currentArr.includes(valStr)
11131
- ? currentArr
11132
- : [...currentArr, valStr]
11133
- : currentArr.filter(v => v !== valStr);
11134
- group.onChange(next);
11135
- }
11136
- }, [onChange, group, value, indeterminate]);
11137
- const mainClass = usePrefixedClassNames('styled-checkbox', 'checkbox', {
11138
- [`is-${color}`]: color && checkboxColors.includes(color),
11139
- [`is-${size}`]: size && checkboxSizes.includes(size),
11140
- });
11141
- const checkboxClass = classNames(mainClass, bulmaHelperClasses, className);
11142
- const checkClass = usePrefixedClassNames('check');
11143
- const controlLabelClass = usePrefixedClassNames('control-label');
11144
- return (jsxRuntime.jsxs("label", { className: checkboxClass, children: [jsxRuntime.jsx("input", { ref: setRefs, type: "checkbox", disabled: disabled, name: effectiveName, value: value, checked: effectiveChecked, defaultChecked: effectiveDefaultChecked, onChange: handleChange, ...rest }), jsxRuntime.jsx("span", { className: checkClass }), children && jsxRuntime.jsx("span", { className: controlLabelClass, children: children })] }));
11145
- });
11146
- Checkbox.displayName = 'Checkbox';
11724
+ const bulmaClasses = usePrefixedClassNames();
11725
+ const listClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
11726
+ return (jsxRuntime.jsx("ul", { className: listClasses || undefined, ...rest, children: children }));
11727
+ };
11728
+ const UnorderedList = withSubComponents(UnorderedListComponent, { Item: ListItem }, 'UnorderedList');
11147
11729
 
11148
11730
  // Whether the nearest enclosing `Control` shows its `isLoading` spinner. The
11149
11731
  // picker bases read it so their right-side launcher gives way to that
@@ -20119,6 +20701,8 @@ exports.ToastContainer = ToastContainer;
20119
20701
  exports.ToggleButton = ToggleButton;
20120
20702
  exports.Tooltip = Tooltip;
20121
20703
  exports.Tr = Tr;
20704
+ exports.TreeView = TreeView;
20705
+ exports.TreeViewItem = TreeViewItem;
20122
20706
  exports.UnorderedList = UnorderedList;
20123
20707
  exports.checkboxColors = checkboxColors;
20124
20708
  exports.checkboxSizes = checkboxSizes;