@allxsmith/bestax-bulma 5.18.1 → 5.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -1235,6 +1235,27 @@ const ColumnsComponent = ({ className, textColor, color: _fieldColor, bgColor, i
1235
1235
  };
1236
1236
  const Columns = withSubComponents(ColumnsComponent, { Column }, 'Columns');
1237
1237
 
1238
+ /**
1239
+ * The `type` to render on a `<button>` a component defaults to `button`: the
1240
+ * caller's own value when it is `button`, `submit` or `reset`, and `button`
1241
+ * otherwise.
1242
+ *
1243
+ * A missing `type` is not the only way a button ends up submitting the form
1244
+ * around it. HTML reads an invalid value, such as the anchor's MIME-type
1245
+ * `type="text/html"`, as submit too, and a props spread carrying
1246
+ * `type: undefined` removes the attribute outright. Both can arrive through a
1247
+ * spread or an untyped caller the component's own types never see, so this
1248
+ * checks the value rather than trusting where it came from.
1249
+ *
1250
+ * Call it AFTER the forwarded props are spread and pass it the forwarded
1251
+ * `type`, so its result is the attribute that renders.
1252
+ */
1253
+ function buttonType(given) {
1254
+ return given === 'submit' || given === 'reset' || given === 'button'
1255
+ ? given
1256
+ : 'button';
1257
+ }
1258
+
1238
1259
  /**
1239
1260
  * Whether an `as` target is a custom element rather than a built-in tag.
1240
1261
  *
@@ -1532,7 +1553,7 @@ const Avatar = forwardRef(function Avatar(avatarProps, ref) {
1532
1553
  // The decorative opt-out never applies to an interactive avatar — a link or
1533
1554
  // button must always expose an accessible name.
1534
1555
  const isDecorative = alt === '' && !isInteractive;
1535
- const a11yProps = showImage
1556
+ const a11yDefaults = showImage
1536
1557
  ? isInteractive && !accessibleName
1537
1558
  ? // The img alt normally names the control; with no alt/name (an API
1538
1559
  // returning only a photo URL) the link/button would be nameless.
@@ -1544,10 +1565,18 @@ const Avatar = forwardRef(function Avatar(avatarProps, ref) {
1544
1565
  ...(isInteractive ? {} : { role: 'img' }),
1545
1566
  'aria-label': accessibleName || name || 'Avatar',
1546
1567
  };
1547
- // A clickable avatar inside a form must not submit it; default the native
1548
- // button type (an explicit type passed through rest still wins).
1549
- const buttonTypeProps = Tag === 'button' ? { type: 'button' } : {};
1550
- return (jsxs(Tag, { ref: ref, className: combinedClasses, style: { ...sizeStyle, ...style }, ...buttonTypeProps, ...linkProps, ...a11yProps, ...rest, children: [showImage && (jsx("img", { ...imageProps, ref: imgRef, src: src, alt: accessibleName ?? '', onError: e => {
1568
+ // Each default yields to a value the caller passed, and is applied after
1569
+ // `rest` reading through it rather than spread before it. React treats an
1570
+ // `undefined` attribute as "remove it", and a spread carrying the key with no
1571
+ // value is how that arrives, so a default spread first was erased by it: an
1572
+ // `aria-label: undefined` left a button avatar nameless, and `role` and
1573
+ // `aria-hidden` went the same way. The button `type` below has the same
1574
+ // shape (#690).
1575
+ const a11yProps = Object.fromEntries(Object.entries(a11yDefaults).map(([key, fallback]) => [
1576
+ key,
1577
+ rest[key] ?? fallback,
1578
+ ]));
1579
+ return (jsxs(Tag, { ref: ref, className: combinedClasses, style: { ...sizeStyle, ...style }, ...linkProps, ...rest, ...a11yProps, ...(Tag === 'button' ? { type: buttonType(rest.type) } : {}), children: [showImage && (jsx("img", { ...imageProps, ref: imgRef, src: src, alt: accessibleName ?? '', onError: e => {
1551
1580
  imageProps?.onError?.(e);
1552
1581
  setErroredSrc(src);
1553
1582
  } }, src)), showInitials && (jsx("span", { className: initialsClass, children: resolvedInitials })), showIcon && icon, showDefaultIcon && jsx(DefaultAvatarIcon, {})] }));
@@ -2047,7 +2076,6 @@ const CardFooterItem = ({ as = 'span', className, children, color, bgColor, text
2047
2076
  }
2048
2077
  const forwarded = omitAttrs(rest, STRIP_FROM_NON_ANCHOR$1);
2049
2078
  if (as === 'button') {
2050
- const forwardedType = forwarded.type;
2051
2079
  return (jsx("button", { className: itemClasses, ...forwarded,
2052
2080
  // A footer item button must not submit an enclosing form by default —
2053
2081
  // `<button>` defaults to `type="submit"`, and a Save/Cancel action row
@@ -2057,13 +2085,9 @@ const CardFooterItem = ({ as = 'span', className, children, color, bgColor, text
2057
2085
  // typed here as the `<a>` MIME string (`STRIP_FROM_NON_ANCHOR` above
2058
2086
  // says why it is not withheld), so `as="button" type="text/html"`
2059
2087
  // compiles, and HTML's INVALID-value default for a button's `type` is
2060
- // submit too. So anything that is not one of the three native button
2061
- // types falls back to `button` rather than being forwarded.
2062
- type: forwardedType === 'submit' ||
2063
- forwardedType === 'reset' ||
2064
- forwardedType === 'button'
2065
- ? forwardedType
2066
- : 'button', children: children }));
2088
+ // submit too. `buttonType` keeps `button`, `submit` and `reset` and
2089
+ // turns anything else into `button`.
2090
+ type: buttonType(forwarded.type), children: children }));
2067
2091
  }
2068
2092
  return (jsx("span", { className: itemClasses, ...omitAttrs(forwarded, STRIP_FROM_NON_BUTTON), children: children }));
2069
2093
  };
@@ -2400,9 +2424,7 @@ const DropdownItem = ((itemProps) => {
2400
2424
  // carrying `role: undefined` would otherwise erase the default, and an
2401
2425
  // item with no role drops out of the menu and its arrow-key order.
2402
2426
  role: forwarded.role ?? 'menuitem', ...(Component === 'button'
2403
- ? {
2404
- type: forwarded.type ?? 'button',
2405
- }
2427
+ ? { type: buttonType(forwarded.type) }
2406
2428
  : {}), children: children }));
2407
2429
  });
2408
2430
  /**
@@ -2511,7 +2533,9 @@ const MenuItem = forwardRef(function MenuItem(itemProps, ref) {
2511
2533
  labelChildren.push(child);
2512
2534
  }
2513
2535
  });
2514
- return (jsxs("li", { className: className, "data-testid": testId, style: style, id: id, title: title, role: role, tabIndex: tabIndex, children: [jsx(Component, { ref: ref, className: itemClass || undefined, ...linkProps, children: labelChildren }), nestedMenuLists] }));
2536
+ return (jsxs("li", { className: className, "data-testid": testId, style: style, id: id, title: title, role: role, tabIndex: tabIndex, children: [jsx(Component, { ref: ref, className: itemClass || undefined, ...linkProps, ...(Component === 'button'
2537
+ ? { type: buttonType(linkProps.type) }
2538
+ : {}), children: labelChildren }), nestedMenuLists] }));
2515
2539
  });
2516
2540
  MenuItem.displayName = 'MenuItem';
2517
2541
  // Attach static subcomponents
@@ -4711,6 +4735,48 @@ const Sidebar = withSubComponents(SidebarComponent, {
4711
4735
  Footer: SidebarFooter,
4712
4736
  }, 'Sidebar');
4713
4737
 
4738
+ /**
4739
+ * Splits the items a container shows into one stack per position in use. An
4740
+ * item goes to its own position when it has one and to the container's
4741
+ * otherwise. A position with no items gets no stack.
4742
+ *
4743
+ * Stacks come back in `order`, so their order in the page follows the screen
4744
+ * rather than which position happened to be used first, and a stack keeps its
4745
+ * place while the others come and go. A position missing from `order` sorts
4746
+ * after the known ones.
4747
+ *
4748
+ * @function groupIntoPositionStacks
4749
+ * @param items - The items the container shows, in the order they were shown.
4750
+ * @param positionOf - Reads an item's own position, if it has one.
4751
+ * @param containerPosition - Where an item without a position of its own goes.
4752
+ * @param order - Every known position, in the order stacks should render.
4753
+ * @returns One stack per position in use.
4754
+ */
4755
+ function groupIntoPositionStacks(items, positionOf, containerPosition, order) {
4756
+ const byPosition = new Map();
4757
+ for (const item of items) {
4758
+ const position = positionOf(item) ?? containerPosition;
4759
+ const stack = byPosition.get(position);
4760
+ if (stack) {
4761
+ stack.push(item);
4762
+ }
4763
+ else {
4764
+ byPosition.set(position, [item]);
4765
+ }
4766
+ }
4767
+ const rank = (position) => {
4768
+ const index = order.indexOf(position);
4769
+ return index === -1 ? order.length : index;
4770
+ };
4771
+ // Array.prototype.sort is stable, so unknown positions keep the order they
4772
+ // were first used in.
4773
+ return Array.from(byPosition, ([position, stackItems]) => ({
4774
+ key: position === containerPosition ? 'container' : `at-${position}`,
4775
+ position,
4776
+ items: stackItems,
4777
+ })).sort((a, b) => rank(a.position) - rank(b.position));
4778
+ }
4779
+
4714
4780
  /**
4715
4781
  * The `Toast` component provides brief notification messages with optional action and cancel buttons.
4716
4782
  *
@@ -4987,25 +5053,38 @@ const toast = {
4987
5053
  return () => toastListeners.delete(listener);
4988
5054
  },
4989
5055
  };
5056
+ // The order ToastContainer renders its stacks in: across the top of the
5057
+ // screen, then across the bottom.
5058
+ const toastStackOrder = [
5059
+ 'top-left',
5060
+ 'top-center',
5061
+ 'top-right',
5062
+ 'bottom-left',
5063
+ 'bottom-center',
5064
+ 'bottom-right',
5065
+ ];
4990
5066
  /**
4991
5067
  * Container component for rendering programmatic toasts.
4992
- * Place once at your app root to enable the toast API.
5068
+ * Place once at your app root to enable the toast API. A toast shown with a
5069
+ * `position` appears there, and one shown without goes to the container's
5070
+ * `position`.
4993
5071
  *
4994
5072
  * @function
4995
5073
  * @param {{ position?: ToastPosition }} props - Container props.
4996
5074
  * @returns {JSX.Element | null} The rendered toast container, or null if empty.
4997
5075
  */
4998
- const ToastContainer = ({ position = 'top-right', }) => {
5076
+ const ToastContainer = ({ position = 'top-right' }) => {
4999
5077
  // Starts from the toasts already showing instead of an empty list, then
5000
5078
  // follows changes.
5001
5079
  const toastList = useSyncExternalStore(toast.subscribe, getVisibleToasts, getServerToasts);
5002
5080
  if (typeof document === 'undefined' || toastList.length === 0) {
5003
5081
  return null;
5004
5082
  }
5005
- return createPortal(jsx("div", { className: `toast-container is-${position}`, children: toastList.map(t => {
5083
+ const stacks = groupIntoPositionStacks(toastList, t => t.props.position, position, toastStackOrder);
5084
+ return createPortal(stacks.map(stack => (jsx("div", { className: `toast-container is-${stack.position}`, children: stack.items.map(t => {
5006
5085
  const { queue: _queue, ...toastProps } = t.props;
5007
- return (jsx(Toast, { ...toastProps, position: position, onClose: () => toast.close(t.id) }, t.id));
5008
- }) }), document.body);
5086
+ return (jsx(Toast, { ...toastProps, position: stack.position, onClose: () => toast.close(t.id) }, t.id));
5087
+ }) }, stack.key))), document.body);
5009
5088
  };
5010
5089
 
5011
5090
  /**
@@ -6449,25 +6528,28 @@ const NotificationItem = ({ instance, onClose }) => {
6449
6528
  }, [pauseOnHover]);
6450
6529
  return (jsx(Notification, { color: color, isLight: isLight, hasDelete: hasDelete, onDelete: handleClose, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, style: { pointerEvents: 'auto' }, children: urgent ? (jsx("span", { role: "alert", "aria-live": "assertive", children: message })) : (jsx("span", { role: "status", "aria-live": "polite", children: message })) }));
6451
6530
  };
6531
+ // The order NotificationContainer renders its stacks in: across the top of the
6532
+ // screen, then across the bottom.
6533
+ const notificationStackOrder = [
6534
+ 'top-left',
6535
+ 'top',
6536
+ 'top-right',
6537
+ 'bottom-left',
6538
+ 'bottom',
6539
+ 'bottom-right',
6540
+ ];
6452
6541
  /**
6453
- * Container component for rendering programmatic notifications.
6454
- * Place once at your app root to enable the notification API.
6542
+ * Inline style that fixes a stack of notifications to its place on the screen.
6455
6543
  *
6456
6544
  * @function
6457
- * @param {{ position?: NotificationPosition }} props - Container props.
6458
- * @returns {JSX.Element | null} The rendered notification container, or null if empty.
6545
+ * @param {NotificationPosition} position - Where the stack sits.
6546
+ * @returns {React.CSSProperties} The stack's style.
6459
6547
  */
6460
- const NotificationContainer = ({ position = 'top-right' }) => {
6461
- // Starts from the notifications already showing instead of an empty list,
6462
- // then follows changes.
6463
- const items = useSyncExternalStore(notification.subscribe, getVisibleNotifications, getServerNotifications);
6464
- if (typeof document === 'undefined' || items.length === 0) {
6465
- return null;
6466
- }
6548
+ const notificationStackStyle = (position) => {
6467
6549
  const isBottom = position.startsWith('bottom');
6468
6550
  const isCenter = position === 'top' || position === 'bottom';
6469
6551
  const isRight = position.endsWith('right');
6470
- const containerStyle = {
6552
+ return {
6471
6553
  position: 'fixed',
6472
6554
  zIndex: 100,
6473
6555
  display: 'flex',
@@ -6483,7 +6565,27 @@ const NotificationContainer = ({ position = 'top-right' }) => {
6483
6565
  ? { right: 0, alignItems: 'flex-end' }
6484
6566
  : { left: 0, alignItems: 'flex-start' }),
6485
6567
  };
6486
- return createPortal(jsx("div", { style: containerStyle, children: items.map(item => (jsx(NotificationItem, { instance: item, onClose: notification.close }, item.id))) }), document.body);
6568
+ };
6569
+ /**
6570
+ * Container component for rendering programmatic notifications.
6571
+ * Place once at your app root to enable the notification API. A notification
6572
+ * shown with a `position` appears there, and one shown without goes to the
6573
+ * container's `position`, so the container renders a stack for each position
6574
+ * in use.
6575
+ *
6576
+ * @function
6577
+ * @param {{ position?: NotificationPosition }} props - Container props.
6578
+ * @returns {JSX.Element | null} The rendered notification container, or null if empty.
6579
+ */
6580
+ const NotificationContainer = ({ position = 'top-right' }) => {
6581
+ // Starts from the notifications already showing instead of an empty list,
6582
+ // then follows changes.
6583
+ const items = useSyncExternalStore(notification.subscribe, getVisibleNotifications, getServerNotifications);
6584
+ if (typeof document === 'undefined' || items.length === 0) {
6585
+ return null;
6586
+ }
6587
+ const stacks = groupIntoPositionStacks(items, item => item.options.position, position, notificationStackOrder);
6588
+ return createPortal(stacks.map(stack => (jsx("div", { style: notificationStackStyle(stack.position), children: stack.items.map(item => (jsx(NotificationItem, { instance: item, onClose: notification.close }, item.id))) }, stack.key))), document.body);
6487
6589
  };
6488
6590
 
6489
6591
  /**