@allxsmith/bestax-bulma 5.40.0 → 5.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bestax.css +1 -1
- package/dist/bestax.css.map +1 -1
- package/dist/esm/components/Menu.js +77 -2
- package/dist/esm/components/Menu.js.map +1 -1
- package/dist/esm/components/Modal.js +3 -3
- package/dist/esm/components/Modal.js.map +1 -1
- package/dist/esm/components/Popover.js +2 -2
- package/dist/esm/components/Popover.js.map +1 -1
- package/dist/esm/components/Sidebar.js +2 -2
- package/dist/esm/components/Sidebar.js.map +1 -1
- package/dist/esm/elements/Image.js +4 -12
- package/dist/esm/elements/Image.js.map +1 -1
- package/dist/esm/helpers/useFocusTrap.js +60 -41
- package/dist/esm/helpers/useFocusTrap.js.map +1 -1
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +162 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/types/components/Menu.d.ts +49 -0
- package/dist/types/elements/Image.d.ts +6 -1
- package/dist/types/helpers/useFocusTrap.d.ts +16 -8
- package/dist/types-cjs/components/Menu.d.ts +49 -0
- package/dist/types-cjs/elements/Image.d.ts +6 -1
- package/dist/types-cjs/helpers/useFocusTrap.d.ts +16 -8
- package/dist/versions/bestax-no-dark-mode.css +1 -1
- package/dist/versions/bestax-no-dark-mode.css.map +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
- package/dist/versions/bestax-no-helpers.css +1 -1
- package/dist/versions/bestax-no-helpers.css.map +1 -1
- package/dist/versions/bestax-prefixed.css +1 -1
- package/dist/versions/bestax-prefixed.css.map +1 -1
- package/package.json +1 -1
- package/src/scss/components/_menu.scss +130 -3
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
-
import React, { createContext, forwardRef, useContext } from 'react';
|
|
3
|
+
import React, { createContext, forwardRef, useContext, useId, useRef, useState } from 'react';
|
|
4
4
|
import { classNames, usePrefixedClassNames } from '../helpers/classNames.js';
|
|
5
5
|
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
6
6
|
import { withSubComponents } from '../helpers/withSubComponents.js';
|
|
7
7
|
import { omitAttrs } from '../helpers/anchorAttrs.js';
|
|
8
8
|
import { buttonType } from '../helpers/buttonType.js';
|
|
9
|
+
import { inertProps } from '../helpers/inertProps.js';
|
|
10
|
+
import { getActiveElementInTree } from '../helpers/shadowDom.js';
|
|
11
|
+
import { useClientLayoutEffect } from '../helpers/useClientLayoutEffect.js';
|
|
9
12
|
import { isCustomElement } from '../helpers/polymorphic.js';
|
|
10
13
|
|
|
11
14
|
// Context to track MenuList nesting level
|
|
@@ -117,12 +120,84 @@ const MenuItem = forwardRef(function MenuItem(itemProps, ref) {
|
|
|
117
120
|
: {}), children: labelChildren }), nestedMenuLists] }));
|
|
118
121
|
});
|
|
119
122
|
MenuItem.displayName = 'MenuItem';
|
|
123
|
+
/**
|
|
124
|
+
* A section of the menu that opens and closes: its heading is a button drawn like `Menu.Label`, and its `Menu.List` of links shows while it's open.
|
|
125
|
+
*
|
|
126
|
+
* It's APG's disclosure navigation, for a sidebar of link groups: the links
|
|
127
|
+
* stay ordinary links, each its own tab stop, and a closed section's list is
|
|
128
|
+
* `hidden` and `inert`, so Tab and assistive tech skip it. It goes directly in
|
|
129
|
+
* `Menu`, holding a `Menu.List`. Closing it while focus is on one of its links
|
|
130
|
+
* moves focus to its heading.
|
|
131
|
+
*
|
|
132
|
+
* @function
|
|
133
|
+
* @param {MenuSectionProps} props - Props for the MenuSection component.
|
|
134
|
+
* @returns {JSX.Element} The rendered menu section.
|
|
135
|
+
* @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation/ | APG Disclosure Navigation Menu example}
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* <Menu>
|
|
139
|
+
* <Menu.Section label="Components" defaultOpen>
|
|
140
|
+
* <Menu.List>
|
|
141
|
+
* <Menu.Item href="/button" active>Button</Menu.Item>
|
|
142
|
+
* <Menu.Item href="/card">Card</Menu.Item>
|
|
143
|
+
* </Menu.List>
|
|
144
|
+
* </Menu.Section>
|
|
145
|
+
* </Menu>
|
|
146
|
+
*/
|
|
147
|
+
const MenuSection = ({ className, label, children, open: controlledOpen, defaultOpen = false, onOpenChange, ...props }) => {
|
|
148
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
149
|
+
const sectionClass = usePrefixedClassNames('menu-section');
|
|
150
|
+
const toggleClass = usePrefixedClassNames('menu-section-toggle');
|
|
151
|
+
const labelClass = usePrefixedClassNames('menu-section-label');
|
|
152
|
+
const chevronClass = usePrefixedClassNames('menu-section-chevron');
|
|
153
|
+
const listId = useId();
|
|
154
|
+
const toggleRef = useRef(null);
|
|
155
|
+
const listRef = useRef(null);
|
|
156
|
+
// The caller owns the state when `open` is set, and the section does
|
|
157
|
+
// otherwise, as with Collapse. The button reports in both modes.
|
|
158
|
+
const isControlled = controlledOpen !== undefined;
|
|
159
|
+
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
160
|
+
const isOpen = controlledOpen ?? internalOpen;
|
|
161
|
+
const handleClick = () => {
|
|
162
|
+
const next = !isOpen;
|
|
163
|
+
onOpenChange?.(next);
|
|
164
|
+
if (!isControlled)
|
|
165
|
+
setInternalOpen(next);
|
|
166
|
+
};
|
|
167
|
+
// An app holding `open` can close a section while focus is on one of its
|
|
168
|
+
// links, as a sidebar that keeps only the current route's section open
|
|
169
|
+
// does after a link is followed. The browser then drops focus to <body>
|
|
170
|
+
// a frame later, and the next Tab starts from the top of the page. The
|
|
171
|
+
// heading is where the reader was, so focus goes there. A layout effect,
|
|
172
|
+
// because it runs in the same task as the commit that hid the list, while
|
|
173
|
+
// the link still has focus. A close from the button itself finds focus on
|
|
174
|
+
// the button and leaves it.
|
|
175
|
+
useClientLayoutEffect(() => {
|
|
176
|
+
if (isOpen)
|
|
177
|
+
return;
|
|
178
|
+
const list = listRef.current;
|
|
179
|
+
if (list.contains(getActiveElementInTree(list))) {
|
|
180
|
+
toggleRef.current.focus();
|
|
181
|
+
}
|
|
182
|
+
}, [isOpen]);
|
|
183
|
+
// A real button, so Enter and Space work with no key handler, and
|
|
184
|
+
// `type="button"`, so a menu inside a form isn't submitted by it.
|
|
185
|
+
//
|
|
186
|
+
// A closed list is `hidden`, so it isn't drawn and find-in-page doesn't
|
|
187
|
+
// reach it, and `inert`, so its links stay out of Tab and assistive tech
|
|
188
|
+
// even if a stylesheet sets `display` on it and shows it anyway. The markup
|
|
189
|
+
// is the same from the server and on both React majors, which `inertProps`
|
|
190
|
+
// sees to. It stays mounted, so `aria-controls` always names an element
|
|
191
|
+
// that exists.
|
|
192
|
+
return (jsxs("div", { className: classNames(sectionClass, bulmaHelperClasses, className), ...rest, children: [jsxs("button", { ref: toggleRef, type: "button", className: toggleClass, "aria-expanded": isOpen, "aria-controls": listId, onClick: handleClick, children: [jsx("span", { className: labelClass, children: label }), jsx("span", { className: chevronClass, "aria-hidden": "true" })] }), jsx("div", { ref: listRef, id: listId, hidden: !isOpen, ...inertProps(!isOpen), children: children })] }));
|
|
193
|
+
};
|
|
120
194
|
// Attach static subcomponents
|
|
121
195
|
const Menu = withSubComponents(MenuComponent, {
|
|
122
196
|
Label: MenuLabel,
|
|
123
197
|
List: MenuList,
|
|
124
198
|
Item: MenuItem,
|
|
199
|
+
Section: MenuSection,
|
|
125
200
|
}, 'Menu');
|
|
126
201
|
|
|
127
|
-
export { Menu, MenuItem, MenuLabel, MenuList };
|
|
202
|
+
export { Menu, MenuItem, MenuLabel, MenuList, MenuSection };
|
|
128
203
|
//# sourceMappingURL=Menu.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Menu.js","sources":["../../../src/components/Menu.tsx"],"sourcesContent":["import React, { createContext, forwardRef, useContext } from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { omitAttrs } from '../helpers/anchorAttrs';\nimport { buttonType } from '../helpers/buttonType';\nimport {\n isCustomElement,\n type PolymorphicComponent,\n} from '../helpers/polymorphic';\n\n// Context to track MenuList nesting level\nconst MenuListLevelContext = createContext(0);\n\n/**\n * Props for the Menu component.\n */\nexport interface MenuProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** Menu content (labels, lists, items, etc). */\n children: React.ReactNode;\n}\n\n/**\n * The `Menu` component provides Bulma's vertical navigation menu: a simple, accessible sidebar or section menu for your Bulma React UI.\n *\n * @function\n * @param {MenuProps} props - Props for the Menu component.\n * @returns {JSX.Element} The rendered menu.\n * @see {@link https://bulma.io/documentation/components/menu/ | Bulma Menu documentation}\n */\nconst MenuComponent: React.FC<MenuProps> = ({\n className,\n children,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n // Generate Bulma classes with prefix\n const bulmaClasses = usePrefixedClassNames('menu');\n\n return (\n <aside\n className={classNames(bulmaClasses, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </aside>\n );\n};\n\n/**\n * Props for the MenuLabel component.\n */\nexport interface MenuLabelProps\n extends\n Omit<React.HTMLAttributes<HTMLParagraphElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** Label content. */\n children: React.ReactNode;\n}\n\n/**\n * Bulma Menu label component.\n *\n * @function\n * @param {MenuLabelProps} props - Props for the MenuLabel component.\n * @returns {JSX.Element} The rendered menu label.\n */\nexport const MenuLabel: React.FC<MenuLabelProps> = ({\n className,\n children,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n return (\n <p\n className={classNames(\n usePrefixedClassNames('menu-label'),\n className,\n bulmaHelperClasses\n )}\n {...rest}\n >\n {children}\n </p>\n );\n};\n\n/**\n * Props for the MenuList component.\n * @extraProp {React.AriaRole} [role] - Ignored: every level renders `role=\"list\"` in its place, so a menu or tree role can't go here.\n */\nexport interface MenuListProps\n extends\n Omit<React.HTMLAttributes<HTMLUListElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** List items. */\n children: React.ReactNode;\n}\n\n/**\n * MenuList applies `menu-list` class only at the top level (not for nested lists).\n *\n * Every level renders `role=\"list\"`, which Safari needs: WebKit stops\n * exposing a `<ul>` as a list once its markers are hidden, and Bulma's reset\n * hides them.\n *\n * @function\n * @param {MenuListProps} props - Props for the MenuList component.\n * @returns {JSX.Element} The rendered menu list.\n */\nexport const MenuList: React.FC<MenuListProps> = ({\n className,\n children,\n ...props\n}) => {\n const level = useContext(MenuListLevelContext);\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n const ulClass = classNames(className, bulmaHelperClasses, {\n [usePrefixedClassNames('menu-list')]: level === 0,\n });\n\n // Increment level for nested MenuLists. role=\"list\" restates the <ul>'s\n // own role, for Safari: WebKit stops exposing a list as one once its\n // markers are hidden, as Bulma's reset hides them, unless the role is set\n // explicitly. After the spread, so a caller's role can't take it away.\n return (\n <MenuListLevelContext.Provider value={level + 1}>\n <ul className={ulClass || undefined} {...rest} role=\"list\">\n {children}\n </ul>\n </MenuListLevelContext.Provider>\n );\n};\n\n/**\n * The MenuItem component's own props.\n *\n * A menu item is two elements: a wrapping `<li>` and, inside it, the element\n * `as` names. The props here are the ones the `<li>` consumes; everything else\n * follows `as` onto the inner element.\n */\nexport interface MenuItemOwnProps extends BulmaClassesProps {\n /** Additional CSS classes for the wrapping `<li>`. */\n className?: string;\n /** Item content and optional nested MenuList. */\n children: React.ReactNode;\n /**\n * Marks the item as the current one. It adds Bulma's `is-active` class, and\n * `aria-current=\"page\"` on the element `as` renders, so assistive tech can\n * tell which item is current too. An `aria-current` you pass wins, even\n * `undefined`: use it to say something else (`\"location\"` for a section of\n * one page, say), or `aria-current={false}` to turn it off. A menu should\n * mark one page, so when the section above the current page is `active` too,\n * give it `aria-current=\"location\"`, and give an `active` item that isn't\n * navigation, such as an `as=\"button\"` filter, `aria-current={false}`.\n */\n active?: boolean;\n /** Inline styles for the wrapping `<li>`. */\n style?: React.CSSProperties;\n /** `id` for the wrapping `<li>`. */\n id?: string;\n /** `title` for the wrapping `<li>`. */\n title?: string;\n /** ARIA role for the wrapping `<li>`. */\n role?: React.AriaRole;\n /** Tab index for the wrapping `<li>`. */\n tabIndex?: number;\n /** Test id for the wrapping `<li>`. */\n 'data-testid'?: string;\n}\n\n/**\n * Props for the MenuItem component. Everything the `<li>` does not consume\n * follows `as` onto the inner element: with the default `'a'` that means `href`\n * and the other anchor attributes, and with `as={Link}` it means that\n * component's own props.\n *\n * @extraProp {PolymorphicRef<React.ElementType>} [ref] - Ref forwarded to the inner element `as` renders, not the wrapping `<li>`, typed from `as`: the DOM node for an intrinsic tag, or whatever handle a custom component exposes.\n */\nexport type MenuItemProps<T extends React.ElementType = 'a'> =\n MenuItemOwnProps &\n Omit<React.ComponentPropsWithoutRef<T>, keyof MenuItemOwnProps | 'as'> & {\n /**\n * Custom link component (e.g. `Link` from router).\n *\n * `'button'` renders `type=\"button\"` unless you pass `type=\"submit\"` or `type=\"reset\"`, so\n * an item inside a form does not submit it.\n */\n as?: T;\n };\n\n/**\n * `href` alone — annotated so a typo is a compile error rather than a silent\n * no-op, the way both siblings' sets are.\n */\nconst STRIP_FROM_NON_LINK: Readonly<Record<'href', true>> = { href: true };\n\n/**\n * The shape the implementation destructures. The public contract is the generic\n * `MenuItemProps<T>` above — the body cannot see through `T`.\n */\ntype MenuItemImplProps = MenuItemOwnProps & {\n as?: React.ElementType;\n type?: React.ButtonHTMLAttributes<HTMLButtonElement>['type'];\n};\n\n/**\n * MenuItem supports `as` prop for custom link components, e.g., react-router-dom Link.\n *\n * Give each item an `href`, a router link through `as`, or `as=\"button\"`, so\n * the keyboard can reach it: an `<a>` with no `href` isn't a link, and Tab\n * skips it.\n *\n * @function\n * @param {MenuItemProps} props - Props for the MenuItem component.\n * @param {React.Ref} ref - Forwarded ref to the inner element `as` renders.\n * @returns {JSX.Element} The rendered menu item.\n */\nexport const MenuItem = forwardRef(function MenuItem(\n itemProps: MenuItemProps,\n ref: React.Ref<HTMLElement>\n) {\n const {\n className,\n children,\n active,\n as: Component = 'a',\n 'data-testid': testId,\n style,\n id,\n title,\n role,\n tabIndex,\n ...rest\n } = itemProps as MenuItemImplProps;\n const { bulmaHelperClasses, rest: forwarded } = useBulmaClasses(rest);\n // `href` reaches an anchor or a custom component (which owns its own prop\n // contract), but not another intrinsic tag — `<span href>` is invalid HTML.\n // The same rule Avatar applies through `isLinkLike` and Button through its\n // intrinsic-only strip. Before #641 `href` was an own prop re-applied only\n // for `as=\"a\"`; deriving it from `as` must not quietly widen that.\n const isLinkLike =\n Component === 'a' ||\n typeof Component !== 'string' ||\n isCustomElement(Component);\n // `href` ALONE. `Dropdown.Item` strips every anchor-only attribute except\n // `type`; `Level.Item` strips all of them, plus `rel`.\n //\n // Not because nothing else can arrive: `as` here is an open `React.ElementType`,\n // and a non-fresh spread skips excess-property checking, so a typed caller\n // CAN deliver `target` or `download` and they are forwarded. The set stays at\n // `href` because widening it would be wrong for an open `as` — `referrerPolicy`\n // on `as=\"img\"`, `target` on `as=\"form\"` and `media` on `as=\"source\"` are all\n // legal, and a flat strip would delete them. Getting that right needs a\n // per-attribute-per-element rule, which `bestax-migrate` already models in\n // `LINK_ATTR_ELEMENTS`.\n const linkProps = isLinkLike\n ? forwarded\n : omitAttrs(forwarded, STRIP_FROM_NON_LINK);\n const itemClass = classNames(\n { [usePrefixedClassNames('is-active')]: active },\n bulmaHelperClasses\n );\n\n // Split children into label and nested MenuList(s)\n const labelChildren: React.ReactNode[] = [];\n const nestedMenuLists: React.ReactNode[] = [];\n React.Children.forEach(children, child => {\n if (React.isValidElement(child) && child.type === MenuList) {\n nestedMenuLists.push(child);\n } else {\n labelChildren.push(child);\n }\n });\n\n return (\n <li\n className={className}\n data-testid={testId}\n style={style}\n id={id}\n title={title}\n role={role}\n tabIndex={tabIndex}\n >\n <Component\n ref={ref}\n className={itemClass || undefined}\n // The active item is the current page, the way Pagination marks its\n // page. Before the spread, so an `aria-current` the caller passes\n // decides, `false` and `undefined` included. bestax-migrate converts\n // `is-active` to `active` only beside an `aria-current` of the\n // element's own, and relies on that to render the same markup.\n aria-current={active ? 'page' : undefined}\n {...linkProps}\n // A menu item inside a form must not submit it, and `<button>` defaults\n // to type=\"submit\". A sidebar menu sitting in a form is ordinary. A\n // caller's `submit` or `reset` still wins. Anything else becomes\n // `button`, since HTML reads a value it does not define for a button as\n // submit too; `buttonType` says more.\n //\n // After `linkProps`, reading through it rather than before it: React\n // treats `type={undefined}` as \"remove the attribute\", and a spread\n // carrying the key with no value is how that arrives, so a default\n // spread first would be erased by it. `Dropdown.Item` and `Avatar`\n // apply their button type after the spread too.\n {...(Component === 'button'\n ? { type: buttonType(linkProps.type) }\n : {})}\n >\n {labelChildren}\n </Component>\n {nestedMenuLists}\n </li>\n );\n}) as PolymorphicComponent<MenuItemOwnProps, 'a'>;\n\nMenuItem.displayName = 'MenuItem';\n\n// Attach static subcomponents\nexport const Menu = withSubComponents(\n MenuComponent,\n {\n Label: MenuLabel,\n List: MenuList,\n Item: MenuItem,\n },\n 'Menu'\n);\n\nexport default Menu;\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Menu.js","sources":["../../../src/components/Menu.tsx"],"sourcesContent":["import React, {\n createContext,\n forwardRef,\n useContext,\n useId,\n useRef,\n useState,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { omitAttrs } from '../helpers/anchorAttrs';\nimport { buttonType } from '../helpers/buttonType';\nimport { inertProps } from '../helpers/inertProps';\nimport { getActiveElementInTree } from '../helpers/shadowDom';\nimport { useClientLayoutEffect } from '../helpers/useClientLayoutEffect';\nimport {\n isCustomElement,\n type PolymorphicComponent,\n} from '../helpers/polymorphic';\n\n// Context to track MenuList nesting level\nconst MenuListLevelContext = createContext(0);\n\n/**\n * Props for the Menu component.\n */\nexport interface MenuProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** Menu content (labels, lists, items, etc). */\n children: React.ReactNode;\n}\n\n/**\n * The `Menu` component provides Bulma's vertical navigation menu: a simple, accessible sidebar or section menu for your Bulma React UI.\n *\n * @function\n * @param {MenuProps} props - Props for the Menu component.\n * @returns {JSX.Element} The rendered menu.\n * @see {@link https://bulma.io/documentation/components/menu/ | Bulma Menu documentation}\n */\nconst MenuComponent: React.FC<MenuProps> = ({\n className,\n children,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n // Generate Bulma classes with prefix\n const bulmaClasses = usePrefixedClassNames('menu');\n\n return (\n <aside\n className={classNames(bulmaClasses, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </aside>\n );\n};\n\n/**\n * Props for the MenuLabel component.\n */\nexport interface MenuLabelProps\n extends\n Omit<React.HTMLAttributes<HTMLParagraphElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** Label content. */\n children: React.ReactNode;\n}\n\n/**\n * Bulma Menu label component.\n *\n * @function\n * @param {MenuLabelProps} props - Props for the MenuLabel component.\n * @returns {JSX.Element} The rendered menu label.\n */\nexport const MenuLabel: React.FC<MenuLabelProps> = ({\n className,\n children,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n return (\n <p\n className={classNames(\n usePrefixedClassNames('menu-label'),\n className,\n bulmaHelperClasses\n )}\n {...rest}\n >\n {children}\n </p>\n );\n};\n\n/**\n * Props for the MenuList component.\n * @extraProp {React.AriaRole} [role] - Ignored: every level renders `role=\"list\"` in its place, so a menu or tree role can't go here.\n */\nexport interface MenuListProps\n extends\n Omit<React.HTMLAttributes<HTMLUListElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** List items. */\n children: React.ReactNode;\n}\n\n/**\n * MenuList applies `menu-list` class only at the top level (not for nested lists).\n *\n * Every level renders `role=\"list\"`, which Safari needs: WebKit stops\n * exposing a `<ul>` as a list once its markers are hidden, and Bulma's reset\n * hides them.\n *\n * @function\n * @param {MenuListProps} props - Props for the MenuList component.\n * @returns {JSX.Element} The rendered menu list.\n */\nexport const MenuList: React.FC<MenuListProps> = ({\n className,\n children,\n ...props\n}) => {\n const level = useContext(MenuListLevelContext);\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n const ulClass = classNames(className, bulmaHelperClasses, {\n [usePrefixedClassNames('menu-list')]: level === 0,\n });\n\n // Increment level for nested MenuLists. role=\"list\" restates the <ul>'s\n // own role, for Safari: WebKit stops exposing a list as one once its\n // markers are hidden, as Bulma's reset hides them, unless the role is set\n // explicitly. After the spread, so a caller's role can't take it away.\n return (\n <MenuListLevelContext.Provider value={level + 1}>\n <ul className={ulClass || undefined} {...rest} role=\"list\">\n {children}\n </ul>\n </MenuListLevelContext.Provider>\n );\n};\n\n/**\n * The MenuItem component's own props.\n *\n * A menu item is two elements: a wrapping `<li>` and, inside it, the element\n * `as` names. The props here are the ones the `<li>` consumes; everything else\n * follows `as` onto the inner element.\n */\nexport interface MenuItemOwnProps extends BulmaClassesProps {\n /** Additional CSS classes for the wrapping `<li>`. */\n className?: string;\n /** Item content and optional nested MenuList. */\n children: React.ReactNode;\n /**\n * Marks the item as the current one. It adds Bulma's `is-active` class, and\n * `aria-current=\"page\"` on the element `as` renders, so assistive tech can\n * tell which item is current too. An `aria-current` you pass wins, even\n * `undefined`: use it to say something else (`\"location\"` for a section of\n * one page, say), or `aria-current={false}` to turn it off. A menu should\n * mark one page, so when the section above the current page is `active` too,\n * give it `aria-current=\"location\"`, and give an `active` item that isn't\n * navigation, such as an `as=\"button\"` filter, `aria-current={false}`.\n */\n active?: boolean;\n /** Inline styles for the wrapping `<li>`. */\n style?: React.CSSProperties;\n /** `id` for the wrapping `<li>`. */\n id?: string;\n /** `title` for the wrapping `<li>`. */\n title?: string;\n /** ARIA role for the wrapping `<li>`. */\n role?: React.AriaRole;\n /** Tab index for the wrapping `<li>`. */\n tabIndex?: number;\n /** Test id for the wrapping `<li>`. */\n 'data-testid'?: string;\n}\n\n/**\n * Props for the MenuItem component. Everything the `<li>` does not consume\n * follows `as` onto the inner element: with the default `'a'` that means `href`\n * and the other anchor attributes, and with `as={Link}` it means that\n * component's own props.\n *\n * @extraProp {PolymorphicRef<React.ElementType>} [ref] - Ref forwarded to the inner element `as` renders, not the wrapping `<li>`, typed from `as`: the DOM node for an intrinsic tag, or whatever handle a custom component exposes.\n */\nexport type MenuItemProps<T extends React.ElementType = 'a'> =\n MenuItemOwnProps &\n Omit<React.ComponentPropsWithoutRef<T>, keyof MenuItemOwnProps | 'as'> & {\n /**\n * Custom link component (e.g. `Link` from router).\n *\n * `'button'` renders `type=\"button\"` unless you pass `type=\"submit\"` or `type=\"reset\"`, so\n * an item inside a form does not submit it.\n */\n as?: T;\n };\n\n/**\n * `href` alone — annotated so a typo is a compile error rather than a silent\n * no-op, the way both siblings' sets are.\n */\nconst STRIP_FROM_NON_LINK: Readonly<Record<'href', true>> = { href: true };\n\n/**\n * The shape the implementation destructures. The public contract is the generic\n * `MenuItemProps<T>` above — the body cannot see through `T`.\n */\ntype MenuItemImplProps = MenuItemOwnProps & {\n as?: React.ElementType;\n type?: React.ButtonHTMLAttributes<HTMLButtonElement>['type'];\n};\n\n/**\n * MenuItem supports `as` prop for custom link components, e.g., react-router-dom Link.\n *\n * Give each item an `href`, a router link through `as`, or `as=\"button\"`, so\n * the keyboard can reach it: an `<a>` with no `href` isn't a link, and Tab\n * skips it.\n *\n * @function\n * @param {MenuItemProps} props - Props for the MenuItem component.\n * @param {React.Ref} ref - Forwarded ref to the inner element `as` renders.\n * @returns {JSX.Element} The rendered menu item.\n */\nexport const MenuItem = forwardRef(function MenuItem(\n itemProps: MenuItemProps,\n ref: React.Ref<HTMLElement>\n) {\n const {\n className,\n children,\n active,\n as: Component = 'a',\n 'data-testid': testId,\n style,\n id,\n title,\n role,\n tabIndex,\n ...rest\n } = itemProps as MenuItemImplProps;\n const { bulmaHelperClasses, rest: forwarded } = useBulmaClasses(rest);\n // `href` reaches an anchor or a custom component (which owns its own prop\n // contract), but not another intrinsic tag — `<span href>` is invalid HTML.\n // The same rule Avatar applies through `isLinkLike` and Button through its\n // intrinsic-only strip. Before #641 `href` was an own prop re-applied only\n // for `as=\"a\"`; deriving it from `as` must not quietly widen that.\n const isLinkLike =\n Component === 'a' ||\n typeof Component !== 'string' ||\n isCustomElement(Component);\n // `href` ALONE. `Dropdown.Item` strips every anchor-only attribute except\n // `type`; `Level.Item` strips all of them, plus `rel`.\n //\n // Not because nothing else can arrive: `as` here is an open `React.ElementType`,\n // and a non-fresh spread skips excess-property checking, so a typed caller\n // CAN deliver `target` or `download` and they are forwarded. The set stays at\n // `href` because widening it would be wrong for an open `as` — `referrerPolicy`\n // on `as=\"img\"`, `target` on `as=\"form\"` and `media` on `as=\"source\"` are all\n // legal, and a flat strip would delete them. Getting that right needs a\n // per-attribute-per-element rule, which `bestax-migrate` already models in\n // `LINK_ATTR_ELEMENTS`.\n const linkProps = isLinkLike\n ? forwarded\n : omitAttrs(forwarded, STRIP_FROM_NON_LINK);\n const itemClass = classNames(\n { [usePrefixedClassNames('is-active')]: active },\n bulmaHelperClasses\n );\n\n // Split children into label and nested MenuList(s)\n const labelChildren: React.ReactNode[] = [];\n const nestedMenuLists: React.ReactNode[] = [];\n React.Children.forEach(children, child => {\n if (React.isValidElement(child) && child.type === MenuList) {\n nestedMenuLists.push(child);\n } else {\n labelChildren.push(child);\n }\n });\n\n return (\n <li\n className={className}\n data-testid={testId}\n style={style}\n id={id}\n title={title}\n role={role}\n tabIndex={tabIndex}\n >\n <Component\n ref={ref}\n className={itemClass || undefined}\n // The active item is the current page, the way Pagination marks its\n // page. Before the spread, so an `aria-current` the caller passes\n // decides, `false` and `undefined` included. bestax-migrate converts\n // `is-active` to `active` only beside an `aria-current` of the\n // element's own, and relies on that to render the same markup.\n aria-current={active ? 'page' : undefined}\n {...linkProps}\n // A menu item inside a form must not submit it, and `<button>` defaults\n // to type=\"submit\". A sidebar menu sitting in a form is ordinary. A\n // caller's `submit` or `reset` still wins. Anything else becomes\n // `button`, since HTML reads a value it does not define for a button as\n // submit too; `buttonType` says more.\n //\n // After `linkProps`, reading through it rather than before it: React\n // treats `type={undefined}` as \"remove the attribute\", and a spread\n // carrying the key with no value is how that arrives, so a default\n // spread first would be erased by it. `Dropdown.Item` and `Avatar`\n // apply their button type after the spread too.\n {...(Component === 'button'\n ? { type: buttonType(linkProps.type) }\n : {})}\n >\n {labelChildren}\n </Component>\n {nestedMenuLists}\n </li>\n );\n}) as PolymorphicComponent<MenuItemOwnProps, 'a'>;\n\nMenuItem.displayName = 'MenuItem';\n\n/**\n * Props for the MenuSection component. The helper props and the other\n * attributes land on the wrapping `<div>`.\n */\nexport interface MenuSectionProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes for the wrapping `<div>`. */\n className?: string;\n /** **Required.** The section's heading, drawn like `Menu.Label` inside the button that opens and closes it. Keep links and buttons out of it, since it's inside one. */\n label: React.ReactNode;\n /**\n * The `Menu.List` of links the section shows while it's open. The section\n * itself goes directly in `Menu`, beside its labels and lists: inside a\n * `Menu.Item` it would render inside the item's link, and inside a\n * `Menu.List` it wouldn't be a list item.\n */\n children: React.ReactNode;\n /** Controlled open state. If provided, the section is controlled, and its button only reports through `onOpenChange`. */\n open?: boolean;\n /** Initial open state for uncontrolled usage. Open the section that holds the current page, so the reader sees where they are. */\n defaultOpen?: boolean;\n /** Called with the state the button asks for (`true` to open) each time it is clicked or activated with Enter or Space, in every mode. Pair it with `open` to let the button drive a controlled section. */\n onOpenChange?: (open: boolean) => void;\n}\n\n/**\n * A section of the menu that opens and closes: its heading is a button drawn like `Menu.Label`, and its `Menu.List` of links shows while it's open.\n *\n * It's APG's disclosure navigation, for a sidebar of link groups: the links\n * stay ordinary links, each its own tab stop, and a closed section's list is\n * `hidden` and `inert`, so Tab and assistive tech skip it. It goes directly in\n * `Menu`, holding a `Menu.List`. Closing it while focus is on one of its links\n * moves focus to its heading.\n *\n * @function\n * @param {MenuSectionProps} props - Props for the MenuSection component.\n * @returns {JSX.Element} The rendered menu section.\n * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation/ | APG Disclosure Navigation Menu example}\n *\n * @example\n * <Menu>\n * <Menu.Section label=\"Components\" defaultOpen>\n * <Menu.List>\n * <Menu.Item href=\"/button\" active>Button</Menu.Item>\n * <Menu.Item href=\"/card\">Card</Menu.Item>\n * </Menu.List>\n * </Menu.Section>\n * </Menu>\n */\nexport const MenuSection: React.FC<MenuSectionProps> = ({\n className,\n label,\n children,\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const sectionClass = usePrefixedClassNames('menu-section');\n const toggleClass = usePrefixedClassNames('menu-section-toggle');\n const labelClass = usePrefixedClassNames('menu-section-label');\n const chevronClass = usePrefixedClassNames('menu-section-chevron');\n const listId = useId();\n const toggleRef = useRef<HTMLButtonElement>(null);\n const listRef = useRef<HTMLDivElement>(null);\n\n // The caller owns the state when `open` is set, and the section does\n // otherwise, as with Collapse. The button reports in both modes.\n const isControlled = controlledOpen !== undefined;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const isOpen = controlledOpen ?? internalOpen;\n\n const handleClick = () => {\n const next = !isOpen;\n onOpenChange?.(next);\n if (!isControlled) setInternalOpen(next);\n };\n\n // An app holding `open` can close a section while focus is on one of its\n // links, as a sidebar that keeps only the current route's section open\n // does after a link is followed. The browser then drops focus to <body>\n // a frame later, and the next Tab starts from the top of the page. The\n // heading is where the reader was, so focus goes there. A layout effect,\n // because it runs in the same task as the commit that hid the list, while\n // the link still has focus. A close from the button itself finds focus on\n // the button and leaves it.\n useClientLayoutEffect(() => {\n if (isOpen) return;\n const list = listRef.current!;\n if (list.contains(getActiveElementInTree(list))) {\n toggleRef.current!.focus();\n }\n }, [isOpen]);\n\n // A real button, so Enter and Space work with no key handler, and\n // `type=\"button\"`, so a menu inside a form isn't submitted by it.\n //\n // A closed list is `hidden`, so it isn't drawn and find-in-page doesn't\n // reach it, and `inert`, so its links stay out of Tab and assistive tech\n // even if a stylesheet sets `display` on it and shows it anyway. The markup\n // is the same from the server and on both React majors, which `inertProps`\n // sees to. It stays mounted, so `aria-controls` always names an element\n // that exists.\n return (\n <div\n className={classNames(sectionClass, bulmaHelperClasses, className)}\n {...rest}\n >\n <button\n ref={toggleRef}\n type=\"button\"\n className={toggleClass}\n aria-expanded={isOpen}\n aria-controls={listId}\n onClick={handleClick}\n >\n <span className={labelClass}>{label}</span>\n <span className={chevronClass} aria-hidden=\"true\" />\n </button>\n <div ref={listRef} id={listId} hidden={!isOpen} {...inertProps(!isOpen)}>\n {children}\n </div>\n </div>\n );\n};\n\n// Attach static subcomponents\nexport const Menu = withSubComponents(\n MenuComponent,\n {\n Label: MenuLabel,\n List: MenuList,\n Item: MenuItem,\n Section: MenuSection,\n },\n 'Menu'\n);\n\nexport default Menu;\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|
|
@@ -13,7 +13,7 @@ import { usePortalContainer } from '../helpers/portal.js';
|
|
|
13
13
|
import { useIsHydrated } from '../helpers/useIsHydrated.js';
|
|
14
14
|
import { useLatestRef } from '../helpers/useLatestRef.js';
|
|
15
15
|
import { useOverlayStack } from '../helpers/overlayStack.js';
|
|
16
|
-
import { trapTab,
|
|
16
|
+
import { trapTab, focusTabStop } from '../helpers/useFocusTrap.js';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Modal.Background - Renders the modal background overlay.
|
|
@@ -260,8 +260,8 @@ const ModalRoot = forwardRef(function ModalRoot({ active, isActive, onClose, cla
|
|
|
260
260
|
previouslyFocusedRef.current =
|
|
261
261
|
getDeepestActiveElement();
|
|
262
262
|
const node = modalRootRef.current;
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
if (node)
|
|
264
|
+
focusTabStop(node);
|
|
265
265
|
return () => {
|
|
266
266
|
// Only hand focus back if this modal still owns it: closing a background
|
|
267
267
|
// modal must not pull focus out of one that is still open on top. A
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Modal.js","sources":["../../../src/components/Modal.tsx"],"sourcesContent":["import React, {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport {\n classNames,\n usePrefixedClassNames,\n prefixedClassNames,\n} from '../helpers/classNames';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { buttonType } from '../helpers/buttonType';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\nimport { useConfig } from '../helpers/Config';\nimport { useScrollLock } from '../helpers/scrollLock';\nimport {\n getActiveElementInTree,\n getDeepestActiveElement,\n} from '../helpers/shadowDom';\nimport { usePortalContainer } from '../helpers/portal';\nimport { useIsHydrated } from '../helpers/useIsHydrated';\nimport { useLatestRef } from '../helpers/useLatestRef';\nimport { useOverlayStack } from '../helpers/overlayStack';\nimport { firstTabStop, trapTab } from '../helpers/useFocusTrap';\n\n/**\n * Props for the Modal component.\n * @extraProp {React.Ref<HTMLDivElement>} [ref] - Ref forwarded to the root `.modal` element.\n */\nexport interface ModalProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color' | 'title'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Whether the modal is open/visible. */\n active?: boolean;\n /** Alias for `active`. Whether the modal is open/visible. */\n isActive?: boolean; // Alias for active\n /** Callback invoked to request modal close (background or close button). */\n onClose?: () => void;\n /** Additional CSS classes for the modal. */\n className?: string;\n /** Text color for modal content. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color for modal content. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Title/header for modal-card variant. (Legacy API only) */\n modalCardTitle?: React.ReactNode;\n /** Footer for modal-card variant. (Legacy API only) */\n modalCardFoot?: React.ReactNode;\n /**\n * Modal style: `'card'` for modal-card, `'content'` for modal-content. (Legacy API only)\n * @defaultValue auto\n */\n type?: 'card' | 'content';\n /** Modal body/content or compound components. */\n children?: React.ReactNode;\n /**\n * Close the modal when the Escape key is pressed (calls `onClose`). Only the\n * topmost open overlay responds, whichever Modal, Dialog, Sidebar or\n * full-page Loading opened last, so Escape closes one layer at a time. The\n * modal marks the Escape it closes on as used with `preventDefault()`, and\n * a Toast waits until no overlay is open. Turned off, a modal on top still\n * keeps Escape from the layers under it. A popup inside it, such as a\n * Dropdown's menu or a date picker's calendar, takes its own Escape first,\n * and so does content that calls `preventDefault()` on it.\n * @defaultValue true\n */\n closeOnEscape?: boolean;\n /**\n * Lock body scroll while the modal is active. Ref-counted and shared with\n * `Dialog`, `Sidebar` and `Loading`, so whichever overlay closes first does\n * not unlock the page underneath one that is still open.\n * @defaultValue true\n */\n lockScroll?: boolean;\n /**\n * Renders the modal into a portal target instead of inline, so it isn't\n * clipped by an ancestor with `overflow: hidden`, `filter` or `transform`.\n * `true` portals to the `portalContainer` a `ConfigProvider` sets,\n * `document.body` by default; a string is used as a\n * `document.querySelector` selector; an element is used directly. Renders\n * inline on the server and while hydrating, moving into the portal once the\n * client takes over, so hydration matches.\n * @defaultValue false\n */\n portal?: boolean | string | HTMLElement;\n}\n\n/**\n * Props for Modal.Background component.\n */\nexport interface ModalBackgroundProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * Props for Modal.Content component.\n */\nexport interface ModalContentProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * Props for Modal.Card component.\n */\nexport interface ModalCardProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * Props for Modal.Card.Head component.\n */\nexport interface ModalCardHeadProps extends React.HTMLAttributes<HTMLElement> {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * Props for Modal.Card.Title component.\n */\nexport interface ModalCardTitleProps extends React.HTMLAttributes<HTMLParagraphElement> {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * Props for Modal.Card.Body component.\n */\nexport interface ModalCardBodyProps extends React.HTMLAttributes<HTMLElement> {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * Props for Modal.Card.Foot component.\n */\nexport interface ModalCardFootProps extends React.HTMLAttributes<HTMLElement> {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * Props for Modal.Close component.\n * @extraProp {'button' | 'submit' | 'reset'} [type='button'] - Button type. Defaults to `'button'`, so a close button inside a form does not submit it. Pass `'submit'` or `'reset'` and yours is used; any other value, or a spread carrying `type: undefined`, renders `'button'`.\n * @extraProp {string} [aria-label='close'] - Accessible name. Pass your own to replace it. An empty one, or a spread carrying `'aria-label': undefined`, keeps the default rather than leaving the button unnamed.\n */\nexport interface ModalCloseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Additional CSS classes. */\n className?: string;\n /** Size of the close button (only applies to 'floating' variant). */\n size?: 'small' | 'medium' | 'large';\n /** Button variant. 'delete' (default) for modal card headers, 'floating' for overlay close button. */\n variant?: 'delete' | 'floating';\n}\n\n/**\n * Modal.Background - Renders the modal background overlay.\n *\n * @function\n * @param {ModalBackgroundProps} props - Component props.\n * @returns {JSX.Element} Modal background element.\n */\nconst ModalBackground: React.FC<ModalBackgroundProps> = ({\n className,\n ...props\n}) => {\n const classes = classNames(\n usePrefixedClassNames('modal-background'),\n className\n );\n return <div className={classes} {...props} />;\n};\n\n/**\n * Modal.Content - Renders modal content wrapper for custom content.\n *\n * @function\n * @param {ModalContentProps} props - Component props.\n * @returns {JSX.Element} Modal content element.\n */\nconst ModalContent: React.FC<ModalContentProps> = ({ className, ...props }) => {\n const classes = classNames(usePrefixedClassNames('modal-content'), className);\n return <div className={classes} {...props} />;\n};\n\n/**\n * Modal.Card.Head - Renders modal card header section.\n *\n * @function\n * @param {ModalCardHeadProps} props - Component props.\n * @returns {JSX.Element} Modal card header element.\n */\nconst ModalCardHead: React.FC<ModalCardHeadProps> = ({\n className,\n ...props\n}) => {\n const classes = classNames(\n usePrefixedClassNames('modal-card-head'),\n className\n );\n return <header className={classes} {...props} />;\n};\n\n/**\n * Modal.Card.Title - Renders modal card title.\n *\n * @function\n * @param {ModalCardTitleProps} props - Component props.\n * @returns {JSX.Element} Modal card title element.\n */\nconst ModalCardTitle: React.FC<ModalCardTitleProps> = ({\n className,\n ...props\n}) => {\n const classes = classNames(\n usePrefixedClassNames('modal-card-title'),\n className\n );\n return <p className={classes} {...props} />;\n};\n\n/**\n * Modal.Card.Body - Renders modal card body section.\n *\n * @function\n * @param {ModalCardBodyProps} props - Component props.\n * @returns {JSX.Element} Modal card body element.\n */\nconst ModalCardBody: React.FC<ModalCardBodyProps> = ({\n className,\n ...props\n}) => {\n const classes = classNames(\n usePrefixedClassNames('modal-card-body'),\n className\n );\n return <section className={classes} {...props} />;\n};\n\n/**\n * Modal.Card.Foot - Renders modal card footer section.\n *\n * @function\n * @param {ModalCardFootProps} props - Component props.\n * @returns {JSX.Element} Modal card footer element.\n */\nconst ModalCardFoot: React.FC<ModalCardFootProps> = ({\n className,\n ...props\n}) => {\n const classes = classNames(\n usePrefixedClassNames('modal-card-foot'),\n className\n );\n return <footer className={classes} {...props} />;\n};\n\n/**\n * Modal.Card - Renders modal card wrapper with compound components.\n * Use with Modal.Card.Head, Modal.Card.Title, Modal.Card.Body, and Modal.Card.Foot.\n *\n * @function\n * @param {ModalCardProps} props - Component props.\n * @returns {JSX.Element} Modal card element.\n */\nconst ModalCardComponent: React.FC<ModalCardProps> = ({\n className,\n ...props\n}) => {\n const classes = classNames(usePrefixedClassNames('modal-card'), className);\n return <div className={classes} {...props} />;\n};\n\nconst ModalCard = withSubComponents(ModalCardComponent, {\n Head: ModalCardHead,\n Title: ModalCardTitle,\n Body: ModalCardBody,\n Foot: ModalCardFoot,\n});\n\n/**\n * Modal.Close - Renders modal close button with two variant styles.\n *\n * @function\n * @param {ModalCloseProps} props - Component props.\n * @returns {JSX.Element} Close button element.\n *\n * @remarks\n * Supports two variants:\n * - 'delete' (default): For use in modal card headers, renders with 'delete' class\n * - 'floating': For floating overlay close button, renders with 'modal-close' class\n *\n * The size prop only applies to the 'floating' variant.\n */\nconst ModalClose: React.FC<ModalCloseProps> = ({\n className,\n size = 'large',\n variant = 'delete',\n ...props\n}) => {\n const classes = classNames(\n usePrefixedClassNames(\n variant === 'delete' ? 'delete' : 'modal-close',\n variant === 'floating' && size && { [`is-${size}`]: true }\n ),\n className\n );\n return (\n <button\n className={classes}\n {...props}\n // After the spread, reading through it: a key the spread carries with no\n // value would otherwise erase the default, leaving a nameless button\n // that submits the form around it. An empty label falls back too: the\n // button is drawn as an icon, so `''` would leave it unnamed. A caller's\n // own value still wins; `buttonType` says more.\n aria-label={props['aria-label'] || 'close'}\n type={buttonType(props.type)}\n />\n );\n};\n\n/**\n * The `Modal` component provides an empty, accessible overlay for arbitrary content — for a ready-made confirm or alert, reach for `Dialog` instead.\n *\n * @function\n * @param {ModalProps} props - Props for the Modal component.\n * @returns {JSX.Element} The rendered modal.\n *\n * @example\n * // Legacy API\n * <Modal active={isOpen} onClose={handleClose} modalCardTitle=\"Title\">\n * Content\n * </Modal>\n *\n * @example\n * // Compound Component API\n * <Modal isActive={isOpen}>\n * <Modal.Background onClick={handleClose} />\n * <Modal.Card>\n * <Modal.Card.Head>\n * <Modal.Card.Title>Title</Modal.Card.Title>\n * <Modal.Close onClick={handleClose} />\n * </Modal.Card.Head>\n * <Modal.Card.Body>Content</Modal.Card.Body>\n * </Modal.Card>\n * </Modal>\n *\n * @see {@link https://bulma.io/documentation/components/modal/ | Bulma Modal documentation}\n */\nconst ModalRoot = forwardRef<HTMLDivElement, ModalProps>(function ModalRoot(\n {\n active,\n isActive,\n onClose,\n className,\n textColor,\n bgColor,\n modalCardTitle,\n modalCardFoot,\n type,\n children,\n closeOnEscape = true,\n lockScroll = true,\n portal = false,\n role,\n 'aria-modal': ariaModalProp,\n ...props\n },\n ref\n) {\n const { classPrefix, portalContainer } = useConfig();\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n\n // Support both active and isActive props\n const isModalActive = active ?? isActive ?? false;\n\n const modalRootRef = useRef<HTMLDivElement>(null);\n\n // Cleanup returned by a consumer's callback ref on attach, held until detach.\n const consumerCleanupRef = useRef<(() => void) | null>(null);\n\n // The forwarded ref and the internal one must both see the node: focus\n // management and the Tab trap read `modalRootRef`, while consumers expect\n // their own ref to resolve.\n const combinedRef = useCallback(\n (node: HTMLDivElement | null) => {\n (modalRootRef as React.MutableRefObject<HTMLDivElement | null>).current =\n node;\n if (typeof ref === 'function') {\n // React 19 lets a callback ref return a cleanup function and detaches\n // by running it instead of calling the ref with `null`; React 18\n // discards the return value entirely. Returning it from here would be\n // a React-19-only contract, so instead we hold the cleanup and run it\n // ourselves on detach — a consumer's cleanup ref then behaves the same\n // on both majors of the CI matrix. Same shape as `Dropdown`.\n if (node === null) {\n const consumerCleanup = consumerCleanupRef.current;\n consumerCleanupRef.current = null;\n if (consumerCleanup) {\n consumerCleanup();\n } else {\n ref(null);\n }\n return;\n }\n const cleanup: unknown = ref(node);\n consumerCleanupRef.current =\n typeof cleanup === 'function' ? (cleanup as () => void) : null;\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;\n }\n },\n [ref]\n );\n const previouslyFocusedRef = useRef<HTMLElement | null>(null);\n const generatedTitleId = useId();\n // A portal has no server-rendered counterpart, so the server render and the\n // hydrating render have to stay inline and only move into the portal once\n // we're past hydration.\n const onClient = useIsHydrated();\n const resolveContainer = usePortalContainer();\n // Moving into the portal, or from one portal target to another, remounts\n // the modal's subtree, so any effect holding a DOM node from before the move\n // is holding a detached one. Effects that touch the modal's nodes key on\n // this, on `portal` and on `defaultTarget`, so they re-run against the new\n // tree.\n const isPortaled = Boolean(portal) && onClient;\n // The configured `portalContainer` only places a modal portaled with\n // `portal` set to `true`. In place, or with a target of its own, a change\n // to it moves nothing, so it mustn't re-run the effects either.\n const defaultTarget =\n isPortaled && portal === true ? portalContainer : undefined;\n\n useScrollLock(isModalActive && lockScroll);\n\n // Read when Escape arrives, so the key listener stays put while `onClose`\n // changes identity, as an inline one does on every render of the parent.\n const onCloseRef = useLatestRef(onClose);\n\n // Joins the stack of open overlays, so the keyboard handling below only\n // applies while this modal is on top.\n const isTopmost = useOverlayStack(isModalActive);\n\n // Escape closes, only for the topmost overlay. The listener is on\n // `window`, where a keydown arrives last, so everything inside the modal\n // has had the key first: React's handlers, and `document` listeners added\n // after this one, such as a date picker's.\n useEffect(() => {\n if (!isModalActive) return undefined;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key !== 'Escape' || !isTopmost()) return;\n // Something inside already used this Escape, such as a popup that\n // closed on it, so the modal waits for the next one. One the modal\n // closes on is marked used, so nothing under it acts on it as well.\n if (closeOnEscape && onCloseRef.current && !e.defaultPrevented) {\n e.preventDefault();\n onCloseRef.current();\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [isModalActive, closeOnEscape, onCloseRef, isTopmost]);\n\n // Keep Tab within the modal: `aria-modal` hides the rest of the page from\n // assistive technology, so the keyboard order has to agree. Only while\n // it's the topmost modal overlay, so an overlay that isn't modal, such as\n // a Sidebar without an overlay, takes Escape from it but leaves it Tab.\n // It's the trap `useFocusTrap` sets, so it wraps at the stops Tab really\n // visits, skipping hidden, inert and closed content, and sends back a Tab\n // the browser still takes out. On `window` for the same reason as Escape:\n // a trap inside, such as a portaled popup's, has had the Tab first, and\n // one it used is left where it put focus. It reads the modal's node as\n // each key arrives, so a portal move that remounts the node, including one\n // to a selector that only starts matching while the modal is open, keeps\n // it trapped.\n useEffect(() => {\n if (!isModalActive) return undefined;\n return trapTab(\n () => modalRootRef.current,\n window,\n () => isTopmost('modal')\n );\n }, [isModalActive, isTopmost]);\n\n // Move focus into the modal on open, restore it on close\n useEffect(() => {\n if (!isModalActive) return undefined;\n\n // The element that had focus, wherever it sits: a portaled modal renders\n // under `document.body` while its opener can be inside a shadow root.\n previouslyFocusedRef.current =\n getDeepestActiveElement() as HTMLElement | null;\n const node = modalRootRef.current;\n const focusable = node ? firstTabStop(node) : null;\n (focusable ?? node)?.focus();\n\n return () => {\n // Only hand focus back if this modal still owns it: closing a background\n // modal must not pull focus out of one that is still open on top. A\n // removed subtree leaves focus on <body>, which still counts as ours.\n const activeElement = getActiveElementInTree(node);\n if (\n activeElement &&\n activeElement !== document.body &&\n !node?.contains(activeElement)\n ) {\n return;\n }\n previouslyFocusedRef.current?.focus?.();\n };\n // `isPortaled` flips once when a hydrated portal modal moves out of the\n // inline tree, and `portal` or `defaultTarget` changing moves a portaled\n // one to another target; re-running rebinds focus onto the remounted\n // nodes. The cleanup above restores focus to the pre-open element first\n // (the detached subtree has left focus on <body>), so the re-run re-records\n // the same element and restore-on-close still lands in the right place.\n }, [isModalActive, isPortaled, portal, defaultTarget]);\n\n // Check if children contain compound components\n const hasCompoundComponents = React.Children.toArray(children).some(\n child =>\n React.isValidElement(child) &&\n (child.type === ModalBackground ||\n child.type === ModalContent ||\n child.type === ModalCard ||\n child.type === ModalClose)\n );\n\n // Generate Bulma classes with prefix\n const bulmaClasses = usePrefixedClassNames('modal', {\n 'is-active': isModalActive,\n });\n const deleteClass = usePrefixedClassNames('delete');\n\n const modalClasses = classNames(bulmaClasses, bulmaHelperClasses, className);\n\n const resolvedRole = role ?? (isModalActive ? 'dialog' : undefined);\n const resolvedAriaModal =\n ariaModalProp ??\n (isModalActive && resolvedRole !== 'presentation' ? 'true' : undefined);\n // A dialog needs an accessible name: the legacy card title is wired up\n // automatically, compound users pass their own aria-label/aria-labelledby\n // (both arrive in `rest`, which is spread last and so wins).\n const titleId = modalCardTitle ? generatedTitleId : undefined;\n\n let modalElement: React.ReactElement;\n\n // If using compound components, render children as-is\n if (hasCompoundComponents) {\n modalElement = (\n <div\n className={modalClasses}\n ref={combinedRef}\n role={resolvedRole}\n aria-modal={resolvedAriaModal}\n tabIndex={-1}\n {...rest}\n data-testid=\"modal\"\n >\n {children}\n </div>\n );\n } else {\n // Legacy API: EXPLICIT type wins; fallback to auto detection if not provided\n let isModalCard: boolean;\n if (type === 'card') isModalCard = true;\n else if (type === 'content') isModalCard = false;\n else isModalCard = !!modalCardTitle || !!modalCardFoot;\n\n modalElement = (\n <div\n className={modalClasses}\n ref={combinedRef}\n role={resolvedRole}\n aria-modal={resolvedAriaModal}\n aria-labelledby={titleId}\n tabIndex={-1}\n {...rest}\n data-testid=\"modal\"\n >\n <div\n className={prefixedClassNames(classPrefix, 'modal-background')}\n onClick={onClose}\n data-testid=\"modal-background\"\n />\n {isModalCard ? (\n <div className={prefixedClassNames(classPrefix, 'modal-card')}>\n {modalCardTitle && (\n <header\n className={prefixedClassNames(classPrefix, 'modal-card-head')}\n >\n <p\n id={titleId}\n className={prefixedClassNames(\n classPrefix,\n 'modal-card-title'\n )}\n >\n {modalCardTitle}\n </p>\n {onClose && (\n <button\n className={deleteClass}\n aria-label=\"close\"\n onClick={onClose}\n type=\"button\"\n data-testid=\"modal-close\"\n />\n )}\n </header>\n )}\n <section\n className={prefixedClassNames(classPrefix, 'modal-card-body')}\n data-testid=\"modal-body\"\n >\n {children}\n </section>\n {modalCardFoot && (\n <footer\n className={prefixedClassNames(classPrefix, 'modal-card-foot')}\n >\n {modalCardFoot}\n </footer>\n )}\n </div>\n ) : (\n <div\n className={prefixedClassNames(classPrefix, 'modal-content')}\n data-testid=\"modal-content\"\n >\n {children}\n </div>\n )}\n {/* Show floating close button for modal-content, or for modal-card when no header */}\n {(!isModalCard || (!modalCardTitle && onClose)) && onClose && (\n <button\n className={prefixedClassNames(\n classPrefix,\n 'modal-close',\n 'is-large'\n )}\n aria-label=\"close\"\n onClick={onClose}\n type=\"button\"\n data-testid=\"modal-close-float\"\n />\n )}\n </div>\n );\n }\n\n if (isPortaled) {\n const target = resolveContainer(\n typeof portal === 'boolean' ? undefined : portal\n );\n return createPortal(modalElement, target);\n }\n\n return modalElement;\n});\n\nexport const Modal = withSubComponents(\n ModalRoot,\n {\n Background: ModalBackground,\n Content: ModalContent,\n Card: ModalCard,\n Close: ModalClose,\n },\n 'Modal'\n);\nexport default Modal;\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Modal.js","sources":["../../../src/components/Modal.tsx"],"sourcesContent":["import React, {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport {\n classNames,\n usePrefixedClassNames,\n prefixedClassNames,\n} from '../helpers/classNames';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { buttonType } from '../helpers/buttonType';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\nimport { useConfig } from '../helpers/Config';\nimport { useScrollLock } from '../helpers/scrollLock';\nimport {\n getActiveElementInTree,\n getDeepestActiveElement,\n} from '../helpers/shadowDom';\nimport { usePortalContainer } from '../helpers/portal';\nimport { useIsHydrated } from '../helpers/useIsHydrated';\nimport { useLatestRef } from '../helpers/useLatestRef';\nimport { useOverlayStack } from '../helpers/overlayStack';\nimport { focusTabStop, trapTab } from '../helpers/useFocusTrap';\n\n/**\n * Props for the Modal component.\n * @extraProp {React.Ref<HTMLDivElement>} [ref] - Ref forwarded to the root `.modal` element.\n */\nexport interface ModalProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color' | 'title'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Whether the modal is open/visible. */\n active?: boolean;\n /** Alias for `active`. Whether the modal is open/visible. */\n isActive?: boolean; // Alias for active\n /** Callback invoked to request modal close (background or close button). */\n onClose?: () => void;\n /** Additional CSS classes for the modal. */\n className?: string;\n /** Text color for modal content. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color for modal content. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Title/header for modal-card variant. (Legacy API only) */\n modalCardTitle?: React.ReactNode;\n /** Footer for modal-card variant. (Legacy API only) */\n modalCardFoot?: React.ReactNode;\n /**\n * Modal style: `'card'` for modal-card, `'content'` for modal-content. (Legacy API only)\n * @defaultValue auto\n */\n type?: 'card' | 'content';\n /** Modal body/content or compound components. */\n children?: React.ReactNode;\n /**\n * Close the modal when the Escape key is pressed (calls `onClose`). Only the\n * topmost open overlay responds, whichever Modal, Dialog, Sidebar or\n * full-page Loading opened last, so Escape closes one layer at a time. The\n * modal marks the Escape it closes on as used with `preventDefault()`, and\n * a Toast waits until no overlay is open. Turned off, a modal on top still\n * keeps Escape from the layers under it. A popup inside it, such as a\n * Dropdown's menu or a date picker's calendar, takes its own Escape first,\n * and so does content that calls `preventDefault()` on it.\n * @defaultValue true\n */\n closeOnEscape?: boolean;\n /**\n * Lock body scroll while the modal is active. Ref-counted and shared with\n * `Dialog`, `Sidebar` and `Loading`, so whichever overlay closes first does\n * not unlock the page underneath one that is still open.\n * @defaultValue true\n */\n lockScroll?: boolean;\n /**\n * Renders the modal into a portal target instead of inline, so it isn't\n * clipped by an ancestor with `overflow: hidden`, `filter` or `transform`.\n * `true` portals to the `portalContainer` a `ConfigProvider` sets,\n * `document.body` by default; a string is used as a\n * `document.querySelector` selector; an element is used directly. Renders\n * inline on the server and while hydrating, moving into the portal once the\n * client takes over, so hydration matches.\n * @defaultValue false\n */\n portal?: boolean | string | HTMLElement;\n}\n\n/**\n * Props for Modal.Background component.\n */\nexport interface ModalBackgroundProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * Props for Modal.Content component.\n */\nexport interface ModalContentProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * Props for Modal.Card component.\n */\nexport interface ModalCardProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * Props for Modal.Card.Head component.\n */\nexport interface ModalCardHeadProps extends React.HTMLAttributes<HTMLElement> {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * Props for Modal.Card.Title component.\n */\nexport interface ModalCardTitleProps extends React.HTMLAttributes<HTMLParagraphElement> {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * Props for Modal.Card.Body component.\n */\nexport interface ModalCardBodyProps extends React.HTMLAttributes<HTMLElement> {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * Props for Modal.Card.Foot component.\n */\nexport interface ModalCardFootProps extends React.HTMLAttributes<HTMLElement> {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * Props for Modal.Close component.\n * @extraProp {'button' | 'submit' | 'reset'} [type='button'] - Button type. Defaults to `'button'`, so a close button inside a form does not submit it. Pass `'submit'` or `'reset'` and yours is used; any other value, or a spread carrying `type: undefined`, renders `'button'`.\n * @extraProp {string} [aria-label='close'] - Accessible name. Pass your own to replace it. An empty one, or a spread carrying `'aria-label': undefined`, keeps the default rather than leaving the button unnamed.\n */\nexport interface ModalCloseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Additional CSS classes. */\n className?: string;\n /** Size of the close button (only applies to 'floating' variant). */\n size?: 'small' | 'medium' | 'large';\n /** Button variant. 'delete' (default) for modal card headers, 'floating' for overlay close button. */\n variant?: 'delete' | 'floating';\n}\n\n/**\n * Modal.Background - Renders the modal background overlay.\n *\n * @function\n * @param {ModalBackgroundProps} props - Component props.\n * @returns {JSX.Element} Modal background element.\n */\nconst ModalBackground: React.FC<ModalBackgroundProps> = ({\n className,\n ...props\n}) => {\n const classes = classNames(\n usePrefixedClassNames('modal-background'),\n className\n );\n return <div className={classes} {...props} />;\n};\n\n/**\n * Modal.Content - Renders modal content wrapper for custom content.\n *\n * @function\n * @param {ModalContentProps} props - Component props.\n * @returns {JSX.Element} Modal content element.\n */\nconst ModalContent: React.FC<ModalContentProps> = ({ className, ...props }) => {\n const classes = classNames(usePrefixedClassNames('modal-content'), className);\n return <div className={classes} {...props} />;\n};\n\n/**\n * Modal.Card.Head - Renders modal card header section.\n *\n * @function\n * @param {ModalCardHeadProps} props - Component props.\n * @returns {JSX.Element} Modal card header element.\n */\nconst ModalCardHead: React.FC<ModalCardHeadProps> = ({\n className,\n ...props\n}) => {\n const classes = classNames(\n usePrefixedClassNames('modal-card-head'),\n className\n );\n return <header className={classes} {...props} />;\n};\n\n/**\n * Modal.Card.Title - Renders modal card title.\n *\n * @function\n * @param {ModalCardTitleProps} props - Component props.\n * @returns {JSX.Element} Modal card title element.\n */\nconst ModalCardTitle: React.FC<ModalCardTitleProps> = ({\n className,\n ...props\n}) => {\n const classes = classNames(\n usePrefixedClassNames('modal-card-title'),\n className\n );\n return <p className={classes} {...props} />;\n};\n\n/**\n * Modal.Card.Body - Renders modal card body section.\n *\n * @function\n * @param {ModalCardBodyProps} props - Component props.\n * @returns {JSX.Element} Modal card body element.\n */\nconst ModalCardBody: React.FC<ModalCardBodyProps> = ({\n className,\n ...props\n}) => {\n const classes = classNames(\n usePrefixedClassNames('modal-card-body'),\n className\n );\n return <section className={classes} {...props} />;\n};\n\n/**\n * Modal.Card.Foot - Renders modal card footer section.\n *\n * @function\n * @param {ModalCardFootProps} props - Component props.\n * @returns {JSX.Element} Modal card footer element.\n */\nconst ModalCardFoot: React.FC<ModalCardFootProps> = ({\n className,\n ...props\n}) => {\n const classes = classNames(\n usePrefixedClassNames('modal-card-foot'),\n className\n );\n return <footer className={classes} {...props} />;\n};\n\n/**\n * Modal.Card - Renders modal card wrapper with compound components.\n * Use with Modal.Card.Head, Modal.Card.Title, Modal.Card.Body, and Modal.Card.Foot.\n *\n * @function\n * @param {ModalCardProps} props - Component props.\n * @returns {JSX.Element} Modal card element.\n */\nconst ModalCardComponent: React.FC<ModalCardProps> = ({\n className,\n ...props\n}) => {\n const classes = classNames(usePrefixedClassNames('modal-card'), className);\n return <div className={classes} {...props} />;\n};\n\nconst ModalCard = withSubComponents(ModalCardComponent, {\n Head: ModalCardHead,\n Title: ModalCardTitle,\n Body: ModalCardBody,\n Foot: ModalCardFoot,\n});\n\n/**\n * Modal.Close - Renders modal close button with two variant styles.\n *\n * @function\n * @param {ModalCloseProps} props - Component props.\n * @returns {JSX.Element} Close button element.\n *\n * @remarks\n * Supports two variants:\n * - 'delete' (default): For use in modal card headers, renders with 'delete' class\n * - 'floating': For floating overlay close button, renders with 'modal-close' class\n *\n * The size prop only applies to the 'floating' variant.\n */\nconst ModalClose: React.FC<ModalCloseProps> = ({\n className,\n size = 'large',\n variant = 'delete',\n ...props\n}) => {\n const classes = classNames(\n usePrefixedClassNames(\n variant === 'delete' ? 'delete' : 'modal-close',\n variant === 'floating' && size && { [`is-${size}`]: true }\n ),\n className\n );\n return (\n <button\n className={classes}\n {...props}\n // After the spread, reading through it: a key the spread carries with no\n // value would otherwise erase the default, leaving a nameless button\n // that submits the form around it. An empty label falls back too: the\n // button is drawn as an icon, so `''` would leave it unnamed. A caller's\n // own value still wins; `buttonType` says more.\n aria-label={props['aria-label'] || 'close'}\n type={buttonType(props.type)}\n />\n );\n};\n\n/**\n * The `Modal` component provides an empty, accessible overlay for arbitrary content — for a ready-made confirm or alert, reach for `Dialog` instead.\n *\n * @function\n * @param {ModalProps} props - Props for the Modal component.\n * @returns {JSX.Element} The rendered modal.\n *\n * @example\n * // Legacy API\n * <Modal active={isOpen} onClose={handleClose} modalCardTitle=\"Title\">\n * Content\n * </Modal>\n *\n * @example\n * // Compound Component API\n * <Modal isActive={isOpen}>\n * <Modal.Background onClick={handleClose} />\n * <Modal.Card>\n * <Modal.Card.Head>\n * <Modal.Card.Title>Title</Modal.Card.Title>\n * <Modal.Close onClick={handleClose} />\n * </Modal.Card.Head>\n * <Modal.Card.Body>Content</Modal.Card.Body>\n * </Modal.Card>\n * </Modal>\n *\n * @see {@link https://bulma.io/documentation/components/modal/ | Bulma Modal documentation}\n */\nconst ModalRoot = forwardRef<HTMLDivElement, ModalProps>(function ModalRoot(\n {\n active,\n isActive,\n onClose,\n className,\n textColor,\n bgColor,\n modalCardTitle,\n modalCardFoot,\n type,\n children,\n closeOnEscape = true,\n lockScroll = true,\n portal = false,\n role,\n 'aria-modal': ariaModalProp,\n ...props\n },\n ref\n) {\n const { classPrefix, portalContainer } = useConfig();\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n\n // Support both active and isActive props\n const isModalActive = active ?? isActive ?? false;\n\n const modalRootRef = useRef<HTMLDivElement>(null);\n\n // Cleanup returned by a consumer's callback ref on attach, held until detach.\n const consumerCleanupRef = useRef<(() => void) | null>(null);\n\n // The forwarded ref and the internal one must both see the node: focus\n // management and the Tab trap read `modalRootRef`, while consumers expect\n // their own ref to resolve.\n const combinedRef = useCallback(\n (node: HTMLDivElement | null) => {\n (modalRootRef as React.MutableRefObject<HTMLDivElement | null>).current =\n node;\n if (typeof ref === 'function') {\n // React 19 lets a callback ref return a cleanup function and detaches\n // by running it instead of calling the ref with `null`; React 18\n // discards the return value entirely. Returning it from here would be\n // a React-19-only contract, so instead we hold the cleanup and run it\n // ourselves on detach — a consumer's cleanup ref then behaves the same\n // on both majors of the CI matrix. Same shape as `Dropdown`.\n if (node === null) {\n const consumerCleanup = consumerCleanupRef.current;\n consumerCleanupRef.current = null;\n if (consumerCleanup) {\n consumerCleanup();\n } else {\n ref(null);\n }\n return;\n }\n const cleanup: unknown = ref(node);\n consumerCleanupRef.current =\n typeof cleanup === 'function' ? (cleanup as () => void) : null;\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;\n }\n },\n [ref]\n );\n const previouslyFocusedRef = useRef<HTMLElement | null>(null);\n const generatedTitleId = useId();\n // A portal has no server-rendered counterpart, so the server render and the\n // hydrating render have to stay inline and only move into the portal once\n // we're past hydration.\n const onClient = useIsHydrated();\n const resolveContainer = usePortalContainer();\n // Moving into the portal, or from one portal target to another, remounts\n // the modal's subtree, so any effect holding a DOM node from before the move\n // is holding a detached one. Effects that touch the modal's nodes key on\n // this, on `portal` and on `defaultTarget`, so they re-run against the new\n // tree.\n const isPortaled = Boolean(portal) && onClient;\n // The configured `portalContainer` only places a modal portaled with\n // `portal` set to `true`. In place, or with a target of its own, a change\n // to it moves nothing, so it mustn't re-run the effects either.\n const defaultTarget =\n isPortaled && portal === true ? portalContainer : undefined;\n\n useScrollLock(isModalActive && lockScroll);\n\n // Read when Escape arrives, so the key listener stays put while `onClose`\n // changes identity, as an inline one does on every render of the parent.\n const onCloseRef = useLatestRef(onClose);\n\n // Joins the stack of open overlays, so the keyboard handling below only\n // applies while this modal is on top.\n const isTopmost = useOverlayStack(isModalActive);\n\n // Escape closes, only for the topmost overlay. The listener is on\n // `window`, where a keydown arrives last, so everything inside the modal\n // has had the key first: React's handlers, and `document` listeners added\n // after this one, such as a date picker's.\n useEffect(() => {\n if (!isModalActive) return undefined;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key !== 'Escape' || !isTopmost()) return;\n // Something inside already used this Escape, such as a popup that\n // closed on it, so the modal waits for the next one. One the modal\n // closes on is marked used, so nothing under it acts on it as well.\n if (closeOnEscape && onCloseRef.current && !e.defaultPrevented) {\n e.preventDefault();\n onCloseRef.current();\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [isModalActive, closeOnEscape, onCloseRef, isTopmost]);\n\n // Keep Tab within the modal: `aria-modal` hides the rest of the page from\n // assistive technology, so the keyboard order has to agree. Only while\n // it's the topmost modal overlay, so an overlay that isn't modal, such as\n // a Sidebar without an overlay, takes Escape from it but leaves it Tab.\n // It's the trap `useFocusTrap` sets, so it wraps at the stops Tab really\n // visits, skipping hidden, inert and closed content, and sends back a Tab\n // the browser still takes out. On `window` for the same reason as Escape:\n // a trap inside, such as a portaled popup's, has had the Tab first, and\n // one it used is left where it put focus. It reads the modal's node as\n // each key arrives, so a portal move that remounts the node, including one\n // to a selector that only starts matching while the modal is open, keeps\n // it trapped.\n useEffect(() => {\n if (!isModalActive) return undefined;\n return trapTab(\n () => modalRootRef.current,\n window,\n () => isTopmost('modal')\n );\n }, [isModalActive, isTopmost]);\n\n // Move focus into the modal on open, restore it on close\n useEffect(() => {\n if (!isModalActive) return undefined;\n\n // The element that had focus, wherever it sits: a portaled modal renders\n // under `document.body` while its opener can be inside a shadow root.\n previouslyFocusedRef.current =\n getDeepestActiveElement() as HTMLElement | null;\n const node = modalRootRef.current;\n if (node) focusTabStop(node);\n\n return () => {\n // Only hand focus back if this modal still owns it: closing a background\n // modal must not pull focus out of one that is still open on top. A\n // removed subtree leaves focus on <body>, which still counts as ours.\n const activeElement = getActiveElementInTree(node);\n if (\n activeElement &&\n activeElement !== document.body &&\n !node?.contains(activeElement)\n ) {\n return;\n }\n previouslyFocusedRef.current?.focus?.();\n };\n // `isPortaled` flips once when a hydrated portal modal moves out of the\n // inline tree, and `portal` or `defaultTarget` changing moves a portaled\n // one to another target; re-running rebinds focus onto the remounted\n // nodes. The cleanup above restores focus to the pre-open element first\n // (the detached subtree has left focus on <body>), so the re-run re-records\n // the same element and restore-on-close still lands in the right place.\n }, [isModalActive, isPortaled, portal, defaultTarget]);\n\n // Check if children contain compound components\n const hasCompoundComponents = React.Children.toArray(children).some(\n child =>\n React.isValidElement(child) &&\n (child.type === ModalBackground ||\n child.type === ModalContent ||\n child.type === ModalCard ||\n child.type === ModalClose)\n );\n\n // Generate Bulma classes with prefix\n const bulmaClasses = usePrefixedClassNames('modal', {\n 'is-active': isModalActive,\n });\n const deleteClass = usePrefixedClassNames('delete');\n\n const modalClasses = classNames(bulmaClasses, bulmaHelperClasses, className);\n\n const resolvedRole = role ?? (isModalActive ? 'dialog' : undefined);\n const resolvedAriaModal =\n ariaModalProp ??\n (isModalActive && resolvedRole !== 'presentation' ? 'true' : undefined);\n // A dialog needs an accessible name: the legacy card title is wired up\n // automatically, compound users pass their own aria-label/aria-labelledby\n // (both arrive in `rest`, which is spread last and so wins).\n const titleId = modalCardTitle ? generatedTitleId : undefined;\n\n let modalElement: React.ReactElement;\n\n // If using compound components, render children as-is\n if (hasCompoundComponents) {\n modalElement = (\n <div\n className={modalClasses}\n ref={combinedRef}\n role={resolvedRole}\n aria-modal={resolvedAriaModal}\n tabIndex={-1}\n {...rest}\n data-testid=\"modal\"\n >\n {children}\n </div>\n );\n } else {\n // Legacy API: EXPLICIT type wins; fallback to auto detection if not provided\n let isModalCard: boolean;\n if (type === 'card') isModalCard = true;\n else if (type === 'content') isModalCard = false;\n else isModalCard = !!modalCardTitle || !!modalCardFoot;\n\n modalElement = (\n <div\n className={modalClasses}\n ref={combinedRef}\n role={resolvedRole}\n aria-modal={resolvedAriaModal}\n aria-labelledby={titleId}\n tabIndex={-1}\n {...rest}\n data-testid=\"modal\"\n >\n <div\n className={prefixedClassNames(classPrefix, 'modal-background')}\n onClick={onClose}\n data-testid=\"modal-background\"\n />\n {isModalCard ? (\n <div className={prefixedClassNames(classPrefix, 'modal-card')}>\n {modalCardTitle && (\n <header\n className={prefixedClassNames(classPrefix, 'modal-card-head')}\n >\n <p\n id={titleId}\n className={prefixedClassNames(\n classPrefix,\n 'modal-card-title'\n )}\n >\n {modalCardTitle}\n </p>\n {onClose && (\n <button\n className={deleteClass}\n aria-label=\"close\"\n onClick={onClose}\n type=\"button\"\n data-testid=\"modal-close\"\n />\n )}\n </header>\n )}\n <section\n className={prefixedClassNames(classPrefix, 'modal-card-body')}\n data-testid=\"modal-body\"\n >\n {children}\n </section>\n {modalCardFoot && (\n <footer\n className={prefixedClassNames(classPrefix, 'modal-card-foot')}\n >\n {modalCardFoot}\n </footer>\n )}\n </div>\n ) : (\n <div\n className={prefixedClassNames(classPrefix, 'modal-content')}\n data-testid=\"modal-content\"\n >\n {children}\n </div>\n )}\n {/* Show floating close button for modal-content, or for modal-card when no header */}\n {(!isModalCard || (!modalCardTitle && onClose)) && onClose && (\n <button\n className={prefixedClassNames(\n classPrefix,\n 'modal-close',\n 'is-large'\n )}\n aria-label=\"close\"\n onClick={onClose}\n type=\"button\"\n data-testid=\"modal-close-float\"\n />\n )}\n </div>\n );\n }\n\n if (isPortaled) {\n const target = resolveContainer(\n typeof portal === 'boolean' ? undefined : portal\n );\n return createPortal(modalElement, target);\n }\n\n return modalElement;\n});\n\nexport const Modal = withSubComponents(\n ModalRoot,\n {\n Background: ModalBackground,\n Content: ModalContent,\n Card: ModalCard,\n Close: ModalClose,\n },\n 'Modal'\n);\nexport default Modal;\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
|
6
6
|
import { withSubComponents } from '../helpers/withSubComponents.js';
|
|
7
7
|
import { Portal } from '../helpers/portal.js';
|
|
8
8
|
import { useIsHydrated } from '../helpers/useIsHydrated.js';
|
|
9
|
-
import { useFocusTrap,
|
|
9
|
+
import { useFocusTrap, focusTabStop } from '../helpers/useFocusTrap.js';
|
|
10
10
|
import { useAnchoredPosition, marginGap } from '../helpers/useAnchoredPosition.js';
|
|
11
11
|
import { useClientLayoutEffect } from '../helpers/useClientLayoutEffect.js';
|
|
12
12
|
import { useLatestRef } from '../helpers/useLatestRef.js';
|
|
@@ -114,7 +114,7 @@ const PopoverComponent = ({ trigger, children, open: controlledOpen, defaultOpen
|
|
|
114
114
|
const panel = panelRef.current;
|
|
115
115
|
if (!panelMounted || trapFocus || !panel)
|
|
116
116
|
return undefined;
|
|
117
|
-
(
|
|
117
|
+
focusTabStop(panel);
|
|
118
118
|
return () => {
|
|
119
119
|
const doc = panel.ownerDocument;
|
|
120
120
|
const now = getDeepestActiveElement(doc);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Popover.js","sources":["../../../src/components/Popover.tsx"],"sourcesContent":["import React, {\n cloneElement,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { Portal } from '../helpers/portal';\nimport { useIsHydrated } from '../helpers/useIsHydrated';\nimport { firstTabStop, useFocusTrap } from '../helpers/useFocusTrap';\nimport { marginGap, useAnchoredPosition } from '../helpers/useAnchoredPosition';\nimport { useClientLayoutEffect } from '../helpers/useClientLayoutEffect';\nimport { useLatestRef } from '../helpers/useLatestRef';\nimport { getDeepestActiveElement, isEventInside } from '../helpers/shadowDom';\nimport { buttonType } from '../helpers/buttonType';\nimport { warnOnce } from '../helpers/devWarnings';\nimport { Button, ButtonOwnProps } from '../elements/Button';\nimport type { PickerPosition } from '../form/_pickerInternals/pickerTypes';\n\n/**\n * Where the panel opens against its trigger: below (`bottom-*`) or above\n * (`top-*`), lined up with the trigger's left or right edge. `auto` picks the\n * one that keeps the panel in the viewport, preferring below and lined up on\n * the left. The same union the date and time pickers take.\n */\nexport type PopoverPosition = PickerPosition;\n\n/**\n * Props for the Popover component. `className`, `id`, the other HTML\n * attributes and the helper props land on the wrapper around the trigger,\n * not on the panel; `contentClassName` reaches the panel.\n * @extraProp {string} [className] - Additional classes for the wrapper around the trigger. The panel takes `contentClassName`.\n */\nexport interface PopoverProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color' | 'children'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /**\n * The element that opens and closes the popover, such as a `Button`.\n * Popover gives it `aria-haspopup=\"dialog\"`, `aria-expanded`,\n * `aria-controls` while the panel is open, and click and key handlers that\n * run after its own (call `preventDefault()` in yours to keep the popover\n * from toggling). It has to be a single element that passes `onClick` and\n * `onKeyDown` to its DOM node. Enter and Space toggle a trigger the browser\n * doesn't click on its own, and an intrinsic element that isn't already a\n * control (a `<span>`) also gets `role=\"button\"` and `tabIndex={0}` unless\n * it sets them; a component that renders one has to take those itself.\n */\n trigger: React.ReactElement;\n /** The panel's content, usually `Popover.Header`, `Popover.Body` and `Popover.Footer`. */\n children?: React.ReactNode;\n /**\n * Controlled open state. When set, the trigger and every dismissal only\n * report through `onOpenChange`.\n */\n open?: boolean;\n /**\n * Initial open state when uncontrolled. Opening moves focus into the\n * panel, so a popover that starts open takes focus once the page loads.\n * @defaultValue false\n */\n defaultOpen?: boolean;\n /**\n * Called with the state asked for: `true` from the trigger opening it,\n * `false` from the trigger, `Popover.Close`, Escape or an outside\n * `pointerdown`. Fires in both controlled and uncontrolled use.\n */\n onOpenChange?: (open: boolean) => void;\n /**\n * Where the panel opens against the trigger.\n * @defaultValue 'bottom-left'\n */\n position?: PopoverPosition;\n /**\n * Render the panel through `Portal`, into the `portalContainer` a\n * `ConfigProvider` sets, `document.body` by default, fixed to the viewport,\n * so an ancestor's `overflow`, `transform` or stacking context can't clip\n * it. The panel then shows from the commit after hydration, never in server\n * markup. It is measured against the trigger when it opens and on resize\n * and scroll, so it doesn't follow content that changes size while it is\n * open. Theming variables set on an ancestor of the trigger reach it only\n * when its container is inside that ancestor.\n * @defaultValue false\n */\n appendToBody?: boolean;\n /**\n * Keep Tab and Shift+Tab inside the panel while it is open, as the ARIA\n * dialog pattern does for a non-modal dialog. Pointer focus leaves freely\n * and the page stays usable. With `false`, Tab moves past the panel and\n * the popover stays open. Focus moves into the panel on open and back to\n * the trigger on close either way.\n * @defaultValue true\n */\n trapFocus?: boolean;\n /**\n * Close on a `pointerdown` outside the trigger and the panel. Content the\n * panel renders through a portal (a nested `Popover`, a portaled picker)\n * still counts as inside.\n * @defaultValue true\n */\n closeOnClickOutside?: boolean;\n /**\n * Close on Escape while focus is in the panel or on the trigger. A dialog\n * nested in the panel, such as a date picker's calendar, takes its own\n * Escape first, and so does content that calls `preventDefault()` on it.\n * @defaultValue true\n */\n closeOnEscape?: boolean;\n /**\n * The panel's accessible name. Without it the panel is named by its\n * `Popover.Header`, and a development build warns when it has neither.\n */\n ariaLabel?: string;\n /** Additional classes for the panel (`.popover-content`). */\n contentClassName?: string;\n}\n\ninterface PopoverContextValue {\n /** The id `Popover.Header` takes, which names the panel. */\n titleId: string;\n /** Records a mounted `Popover.Header`; the returned function forgets it. */\n registerTitle: () => () => void;\n /** Asks the popover to close. */\n close: () => void;\n}\n\nconst PopoverContext = createContext<PopoverContextValue | null>(null);\n\n/** The trigger props Popover reads before replacing them. */\ninterface TriggerProps {\n onClick?: (event: React.MouseEvent<HTMLElement>) => void;\n onKeyDown?: (event: React.KeyboardEvent<HTMLElement>) => void;\n role?: string;\n tabIndex?: number;\n href?: unknown;\n}\n\n/** Intrinsic elements that are controls already, and keep their own role. */\nconst NATIVE_CONTROLS = new Set([\n 'button',\n 'input',\n 'select',\n 'textarea',\n 'summary',\n]);\n\n/**\n * Whether the browser answers this key on `el` itself, by clicking it or by\n * using the key as input, so the trigger's click handler covers it.\n */\nfunction handlesKeyItself(el: Element, key: string): boolean {\n if (NATIVE_CONTROLS.has(el.localName)) return true;\n return el.localName === 'a' && key === 'Enter' && el.hasAttribute('href');\n}\n\n/** The innermost dialog the event passed through, which is the one its Escape belongs to. */\nfunction nearestDialog(event: Event): EventTarget | null {\n return (\n event.composedPath().find(node => {\n const role = (node as Element).getAttribute?.('role');\n return role === 'dialog' || role === 'alertdialog';\n }) ?? null\n );\n}\n\n/**\n * The `Popover` component opens a panel of interactive content, such as a filter form or a set of share options, anchored to the element that toggles it.\n *\n * The panel is a non-modal `role=\"dialog\"`: focus moves into it on open\n * and returns to the trigger on close, while the rest of the page stays\n * reachable by pointer and assistive technology. For hover-only hints use\n * `Tooltip`, and for a list of actions use `Dropdown`.\n *\n * @function\n * @param {PopoverProps} props - Props for the Popover component.\n * @returns {JSX.Element} The trigger in its wrapper, and the panel while open.\n *\n * @example\n * <Popover trigger={<Button>Filters</Button>}>\n * <Popover.Header>Filter rows</Popover.Header>\n * <Popover.Body>…</Popover.Body>\n * <Popover.Footer>\n * <Popover.Close>Done</Popover.Close>\n * </Popover.Footer>\n * </Popover>\n */\nconst PopoverComponent: React.FC<PopoverProps> = ({\n trigger,\n children,\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n position = 'bottom-left',\n appendToBody = false,\n trapFocus = true,\n closeOnClickOutside = true,\n closeOnEscape = true,\n ariaLabel,\n className,\n contentClassName,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const rootRef = useRef<HTMLDivElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n // The last pointerdown React saw inside the panel. React's tree includes\n // content the panel portals elsewhere, which the DOM path doesn't.\n const insidePointerRef = useRef<Event | null>(null);\n const baseId = useId();\n const panelId = `${baseId}-panel`;\n const titleId = `${baseId}-title`;\n\n const isControlled = controlledOpen !== undefined;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const isOpen = controlledOpen ?? internalOpen;\n\n const requestOpen = useCallback(\n (next: boolean) => {\n onOpenChange?.(next);\n if (!isControlled) setInternalOpen(next);\n },\n [isControlled, onOpenChange]\n );\n const close = useCallback(() => requestOpen(false), [requestOpen]);\n\n // A portaled panel has no server markup, so it only exists from the commit\n // after hydration. Everything that measures or focuses it waits for that.\n const hydrated = useIsHydrated();\n const panelMounted = isOpen && (hydrated || !appendToBody);\n\n const resolved = useAnchoredPosition(rootRef, panelRef, {\n active: panelMounted,\n position,\n fixed: appendToBody,\n // The gap comes from `--bulma-popover-offset` in the stylesheet, so the\n // coordinates leave it out and `auto` reads it off the panel.\n offset: 0,\n styledGap: marginGap,\n });\n\n // The trigger renders first inside the wrapper. Read when needed rather\n // than held, so a trigger that remounted is still the one found.\n const triggerNode = useCallback((): HTMLElement | null => {\n const first = rootRef.current?.firstElementChild;\n return first && first !== panelRef.current ? (first as HTMLElement) : null;\n }, []);\n const restoreFocusRef = useMemo(\n () => ({\n get current() {\n return triggerNode();\n },\n }),\n [triggerNode]\n );\n\n useFocusTrap(panelRef, {\n active: panelMounted && trapFocus,\n restoreFocus: restoreFocusRef,\n });\n\n // Without the trap, focus still arrives in the panel and goes back to the\n // trigger, on the trap's terms: only while the panel still holds it.\n useEffect(() => {\n const panel = panelRef.current;\n if (!panelMounted || trapFocus || !panel) return undefined;\n (firstTabStop(panel) ?? panel).focus();\n return () => {\n const doc = panel.ownerDocument;\n const now = getDeepestActiveElement(doc);\n if (!now || now === doc.body || panel.contains(now)) {\n triggerNode()?.focus();\n }\n };\n }, [panelMounted, trapFocus, triggerNode]);\n\n // Read when a press lands outside, so the listener stays put while `close`\n // changes identity, which it does with `onOpenChange`, and an inline one\n // changes on every render of the parent.\n const closeRef = useLatestRef(close);\n\n useEffect(() => {\n if (!panelMounted || !closeOnClickOutside) return undefined;\n const handlePointerDown = (e: PointerEvent) => {\n if (e === insidePointerRef.current || isEventInside(e, rootRef.current)) {\n return;\n }\n closeRef.current();\n };\n // The wrapper is on the page whenever the panel is. Its document, an\n // iframe's when the tree renders into one, is where presses land.\n const doc = (rootRef.current as HTMLDivElement).ownerDocument;\n doc.addEventListener('pointerdown', handlePointerDown);\n return () => doc.removeEventListener('pointerdown', handlePointerDown);\n }, [panelMounted, closeOnClickOutside, closeRef]);\n\n const handlePanelKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key !== 'Escape' || !closeOnEscape || e.defaultPrevented) return;\n // React bubbles the key here from portaled content as well, so a dialog\n // of its own on the way (a nested popover, a picker's calendar) owns it.\n const owner = nearestDialog(e.nativeEvent);\n if (owner && owner !== panelRef.current) return;\n e.preventDefault();\n close();\n };\n\n const triggerProps = trigger.props as TriggerProps;\n\n const handleTriggerClick = (e: React.MouseEvent<HTMLElement>) => {\n triggerProps.onClick?.(e);\n if (!e.defaultPrevented) requestOpen(!isOpen);\n };\n\n const handleTriggerKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {\n triggerProps.onKeyDown?.(e);\n // A key on something inside the trigger is that element's to answer.\n if (e.defaultPrevented || e.target !== e.currentTarget) return;\n if (e.key === 'Escape') {\n if (isOpen && closeOnEscape) {\n e.preventDefault();\n close();\n }\n return;\n }\n if (e.key !== 'Enter' && e.key !== ' ') return;\n if (handlesKeyItself(e.currentTarget, e.key)) return;\n // Space would scroll the page; a held key repeats.\n e.preventDefault();\n if (!e.repeat) requestOpen(!isOpen);\n };\n\n const needsButtonRole =\n typeof trigger.type === 'string' &&\n !NATIVE_CONTROLS.has(trigger.type) &&\n !(trigger.type === 'a' && triggerProps.href != null);\n\n const clonedTrigger = cloneElement(\n trigger as React.ReactElement<Record<string, unknown>>,\n {\n ...(needsButtonRole\n ? {\n role: triggerProps.role ?? 'button',\n tabIndex: triggerProps.tabIndex ?? 0,\n }\n : {}),\n 'aria-haspopup': 'dialog',\n 'aria-expanded': isOpen,\n 'aria-controls': panelMounted ? panelId : undefined,\n onClick: handleTriggerClick,\n onKeyDown: handleTriggerKeyDown,\n }\n );\n\n const rootClasses = classNames(\n usePrefixedClassNames('popover', { 'is-active': isOpen }),\n bulmaHelperClasses,\n className\n );\n const panelClasses = classNames(\n usePrefixedClassNames('popover-content', {\n [`is-${resolved.position}`]: true,\n 'is-portal': appendToBody,\n }),\n contentClassName\n );\n\n return (\n // The ref after the spread: React 19 hands a function component a `ref`\n // prop like any other, and the wrapper has to stay ours.\n <div className={rootClasses} {...rest} ref={rootRef}>\n {clonedTrigger}\n {isOpen && (\n <Portal disabled={!appendToBody}>\n <PopoverPanel\n panelRef={panelRef}\n titleId={titleId}\n ariaLabel={ariaLabel}\n close={close}\n id={panelId}\n className={panelClasses}\n style={\n appendToBody\n ? {\n position: 'fixed',\n top: resolved.top,\n left: resolved.left,\n }\n : undefined\n }\n onKeyDown={handlePanelKeyDown}\n onPointerDownCapture={e => {\n insidePointerRef.current = e.nativeEvent;\n }}\n >\n {children}\n </PopoverPanel>\n </Portal>\n )}\n </div>\n );\n};\n\ninterface PopoverPanelProps extends React.HTMLAttributes<HTMLDivElement> {\n panelRef: React.RefObject<HTMLDivElement | null>;\n titleId: string;\n ariaLabel?: string;\n close: () => void;\n}\n\n/**\n * The open panel. It mounts with each opening, so it counts the\n * `Popover.Header`s inside it afresh every time.\n */\nfunction PopoverPanel({\n panelRef,\n titleId,\n ariaLabel,\n close,\n children,\n ...attrs\n}: PopoverPanelProps) {\n // Headers register in a layout effect, after this first render, and this\n // panel's own layout effect runs after theirs. Until then the panel assumes\n // it has one, so a panel that does is named from its first commit, before\n // focus moves in, and one that doesn't drops the reference before it paints.\n const [titlesCounted, setTitlesCounted] = useState(false);\n const [titleCount, setTitleCount] = useState(0);\n const registerTitle = useCallback(() => {\n setTitleCount(n => n + 1);\n return () => setTitleCount(n => n - 1);\n }, []);\n useClientLayoutEffect(() => {\n setTitlesCounted(true);\n }, []);\n const named = Boolean(ariaLabel) || titleCount > 0;\n\n useEffect(() => {\n if (titlesCounted && !named) {\n warnOnce(\n 'Popover:unnamed',\n '[bestax-bulma] <Popover> opened a panel with no accessible name. ' +\n 'Pass ariaLabel, or render a Popover.Header inside it.'\n );\n }\n }, [titlesCounted, named]);\n\n const context = useMemo(\n () => ({ titleId, registerTitle, close }),\n [titleId, registerTitle, close]\n );\n\n return (\n <PopoverContext.Provider value={context}>\n <div\n {...attrs}\n ref={panelRef}\n role=\"dialog\"\n aria-label={ariaLabel}\n aria-labelledby={\n !ariaLabel && (!titlesCounted || titleCount > 0) ? titleId : undefined\n }\n tabIndex={-1}\n >\n {children}\n </div>\n </PopoverContext.Provider>\n );\n}\n\n/**\n * Props for the Popover.Header component. Its `id` is the one the panel's\n * `aria-labelledby` names, so it isn't yours to set.\n */\nexport interface PopoverHeaderProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color' | 'id'>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * The popover's title, which names the panel for assistive technology.\n *\n * @function\n * @param {PopoverHeaderProps} props - Props for the Popover.Header component.\n * @returns {JSX.Element} The header element.\n */\nexport const PopoverHeader: React.FC<PopoverHeaderProps> = ({\n className,\n ...props\n}) => {\n const context = useContext(PopoverContext);\n const registerTitle = context?.registerTitle;\n // Before paint, so the panel is named by the time it shows.\n useClientLayoutEffect(() => registerTitle?.(), [registerTitle]);\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n return (\n <div\n className={classNames(\n usePrefixedClassNames('popover-header'),\n bulmaHelperClasses,\n className\n )}\n {...rest}\n id={context?.titleId}\n />\n );\n};\n\n/**\n * Props for the Popover.Body component.\n */\nexport interface PopoverBodyProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color'>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * The popover's main content area.\n *\n * @function\n * @param {PopoverBodyProps} props - Props for the Popover.Body component.\n * @returns {JSX.Element} The body element.\n */\nexport const PopoverBody: React.FC<PopoverBodyProps> = ({\n className,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n return (\n <div\n className={classNames(\n usePrefixedClassNames('popover-body'),\n bulmaHelperClasses,\n className\n )}\n {...rest}\n />\n );\n};\n\n/**\n * Props for the Popover.Footer component.\n */\nexport interface PopoverFooterProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color'>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * A row of actions along the bottom of the popover, aligned to the end.\n *\n * @function\n * @param {PopoverFooterProps} props - Props for the Popover.Footer component.\n * @returns {JSX.Element} The footer element.\n */\nexport const PopoverFooter: React.FC<PopoverFooterProps> = ({\n className,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n return (\n <div\n className={classNames(\n usePrefixedClassNames('popover-footer'),\n bulmaHelperClasses,\n className\n )}\n {...rest}\n />\n );\n};\n\n/**\n * Props for the Popover.Close component: the props of a `Button` rendered as\n * a `<button>`.\n * @extraProp {'button' | 'submit' | 'reset'} [type='button'] - Button type. Defaults to `'button'`, so a close button inside a form doesn't submit it.\n * @extraProp {React.MouseEventHandler<HTMLButtonElement>} [onClick] - Runs before the popover closes. Call `preventDefault()` to keep it open.\n */\nexport interface PopoverCloseProps\n extends\n ButtonOwnProps,\n Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof ButtonOwnProps> {}\n\n/**\n * A `Button` that closes the popover, handing focus back to the trigger.\n *\n * @function\n * @param {PopoverCloseProps} props - Props for the Popover.Close component.\n * @returns {JSX.Element} The close button.\n */\nexport const PopoverClose: React.FC<PopoverCloseProps> = ({\n onClick,\n ...props\n}) => {\n const context = useContext(PopoverContext);\n return (\n <Button\n {...props}\n type={buttonType(props.type)}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (!e.defaultPrevented) context?.close();\n }}\n />\n );\n};\n\n/** Popover with Header, Body, Footer and Close sub-components. */\nexport const Popover = withSubComponents(\n PopoverComponent,\n {\n Header: PopoverHeader,\n Body: PopoverBody,\n Footer: PopoverFooter,\n Close: PopoverClose,\n },\n 'Popover'\n);\n\nexport default Popover;\n"],"names":["_jsxs","_jsx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Popover.js","sources":["../../../src/components/Popover.tsx"],"sourcesContent":["import React, {\n cloneElement,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { Portal } from '../helpers/portal';\nimport { useIsHydrated } from '../helpers/useIsHydrated';\nimport { focusTabStop, useFocusTrap } from '../helpers/useFocusTrap';\nimport { marginGap, useAnchoredPosition } from '../helpers/useAnchoredPosition';\nimport { useClientLayoutEffect } from '../helpers/useClientLayoutEffect';\nimport { useLatestRef } from '../helpers/useLatestRef';\nimport { getDeepestActiveElement, isEventInside } from '../helpers/shadowDom';\nimport { buttonType } from '../helpers/buttonType';\nimport { warnOnce } from '../helpers/devWarnings';\nimport { Button, ButtonOwnProps } from '../elements/Button';\nimport type { PickerPosition } from '../form/_pickerInternals/pickerTypes';\n\n/**\n * Where the panel opens against its trigger: below (`bottom-*`) or above\n * (`top-*`), lined up with the trigger's left or right edge. `auto` picks the\n * one that keeps the panel in the viewport, preferring below and lined up on\n * the left. The same union the date and time pickers take.\n */\nexport type PopoverPosition = PickerPosition;\n\n/**\n * Props for the Popover component. `className`, `id`, the other HTML\n * attributes and the helper props land on the wrapper around the trigger,\n * not on the panel; `contentClassName` reaches the panel.\n * @extraProp {string} [className] - Additional classes for the wrapper around the trigger. The panel takes `contentClassName`.\n */\nexport interface PopoverProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color' | 'children'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /**\n * The element that opens and closes the popover, such as a `Button`.\n * Popover gives it `aria-haspopup=\"dialog\"`, `aria-expanded`,\n * `aria-controls` while the panel is open, and click and key handlers that\n * run after its own (call `preventDefault()` in yours to keep the popover\n * from toggling). It has to be a single element that passes `onClick` and\n * `onKeyDown` to its DOM node. Enter and Space toggle a trigger the browser\n * doesn't click on its own, and an intrinsic element that isn't already a\n * control (a `<span>`) also gets `role=\"button\"` and `tabIndex={0}` unless\n * it sets them; a component that renders one has to take those itself.\n */\n trigger: React.ReactElement;\n /** The panel's content, usually `Popover.Header`, `Popover.Body` and `Popover.Footer`. */\n children?: React.ReactNode;\n /**\n * Controlled open state. When set, the trigger and every dismissal only\n * report through `onOpenChange`.\n */\n open?: boolean;\n /**\n * Initial open state when uncontrolled. Opening moves focus into the\n * panel, so a popover that starts open takes focus once the page loads.\n * @defaultValue false\n */\n defaultOpen?: boolean;\n /**\n * Called with the state asked for: `true` from the trigger opening it,\n * `false` from the trigger, `Popover.Close`, Escape or an outside\n * `pointerdown`. Fires in both controlled and uncontrolled use.\n */\n onOpenChange?: (open: boolean) => void;\n /**\n * Where the panel opens against the trigger.\n * @defaultValue 'bottom-left'\n */\n position?: PopoverPosition;\n /**\n * Render the panel through `Portal`, into the `portalContainer` a\n * `ConfigProvider` sets, `document.body` by default, fixed to the viewport,\n * so an ancestor's `overflow`, `transform` or stacking context can't clip\n * it. The panel then shows from the commit after hydration, never in server\n * markup. It is measured against the trigger when it opens and on resize\n * and scroll, so it doesn't follow content that changes size while it is\n * open. Theming variables set on an ancestor of the trigger reach it only\n * when its container is inside that ancestor.\n * @defaultValue false\n */\n appendToBody?: boolean;\n /**\n * Keep Tab and Shift+Tab inside the panel while it is open, as the ARIA\n * dialog pattern does for a non-modal dialog. Pointer focus leaves freely\n * and the page stays usable. With `false`, Tab moves past the panel and\n * the popover stays open. Focus moves into the panel on open and back to\n * the trigger on close either way.\n * @defaultValue true\n */\n trapFocus?: boolean;\n /**\n * Close on a `pointerdown` outside the trigger and the panel. Content the\n * panel renders through a portal (a nested `Popover`, a portaled picker)\n * still counts as inside.\n * @defaultValue true\n */\n closeOnClickOutside?: boolean;\n /**\n * Close on Escape while focus is in the panel or on the trigger. A dialog\n * nested in the panel, such as a date picker's calendar, takes its own\n * Escape first, and so does content that calls `preventDefault()` on it.\n * @defaultValue true\n */\n closeOnEscape?: boolean;\n /**\n * The panel's accessible name. Without it the panel is named by its\n * `Popover.Header`, and a development build warns when it has neither.\n */\n ariaLabel?: string;\n /** Additional classes for the panel (`.popover-content`). */\n contentClassName?: string;\n}\n\ninterface PopoverContextValue {\n /** The id `Popover.Header` takes, which names the panel. */\n titleId: string;\n /** Records a mounted `Popover.Header`; the returned function forgets it. */\n registerTitle: () => () => void;\n /** Asks the popover to close. */\n close: () => void;\n}\n\nconst PopoverContext = createContext<PopoverContextValue | null>(null);\n\n/** The trigger props Popover reads before replacing them. */\ninterface TriggerProps {\n onClick?: (event: React.MouseEvent<HTMLElement>) => void;\n onKeyDown?: (event: React.KeyboardEvent<HTMLElement>) => void;\n role?: string;\n tabIndex?: number;\n href?: unknown;\n}\n\n/** Intrinsic elements that are controls already, and keep their own role. */\nconst NATIVE_CONTROLS = new Set([\n 'button',\n 'input',\n 'select',\n 'textarea',\n 'summary',\n]);\n\n/**\n * Whether the browser answers this key on `el` itself, by clicking it or by\n * using the key as input, so the trigger's click handler covers it.\n */\nfunction handlesKeyItself(el: Element, key: string): boolean {\n if (NATIVE_CONTROLS.has(el.localName)) return true;\n return el.localName === 'a' && key === 'Enter' && el.hasAttribute('href');\n}\n\n/** The innermost dialog the event passed through, which is the one its Escape belongs to. */\nfunction nearestDialog(event: Event): EventTarget | null {\n return (\n event.composedPath().find(node => {\n const role = (node as Element).getAttribute?.('role');\n return role === 'dialog' || role === 'alertdialog';\n }) ?? null\n );\n}\n\n/**\n * The `Popover` component opens a panel of interactive content, such as a filter form or a set of share options, anchored to the element that toggles it.\n *\n * The panel is a non-modal `role=\"dialog\"`: focus moves into it on open\n * and returns to the trigger on close, while the rest of the page stays\n * reachable by pointer and assistive technology. For hover-only hints use\n * `Tooltip`, and for a list of actions use `Dropdown`.\n *\n * @function\n * @param {PopoverProps} props - Props for the Popover component.\n * @returns {JSX.Element} The trigger in its wrapper, and the panel while open.\n *\n * @example\n * <Popover trigger={<Button>Filters</Button>}>\n * <Popover.Header>Filter rows</Popover.Header>\n * <Popover.Body>…</Popover.Body>\n * <Popover.Footer>\n * <Popover.Close>Done</Popover.Close>\n * </Popover.Footer>\n * </Popover>\n */\nconst PopoverComponent: React.FC<PopoverProps> = ({\n trigger,\n children,\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n position = 'bottom-left',\n appendToBody = false,\n trapFocus = true,\n closeOnClickOutside = true,\n closeOnEscape = true,\n ariaLabel,\n className,\n contentClassName,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const rootRef = useRef<HTMLDivElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n // The last pointerdown React saw inside the panel. React's tree includes\n // content the panel portals elsewhere, which the DOM path doesn't.\n const insidePointerRef = useRef<Event | null>(null);\n const baseId = useId();\n const panelId = `${baseId}-panel`;\n const titleId = `${baseId}-title`;\n\n const isControlled = controlledOpen !== undefined;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const isOpen = controlledOpen ?? internalOpen;\n\n const requestOpen = useCallback(\n (next: boolean) => {\n onOpenChange?.(next);\n if (!isControlled) setInternalOpen(next);\n },\n [isControlled, onOpenChange]\n );\n const close = useCallback(() => requestOpen(false), [requestOpen]);\n\n // A portaled panel has no server markup, so it only exists from the commit\n // after hydration. Everything that measures or focuses it waits for that.\n const hydrated = useIsHydrated();\n const panelMounted = isOpen && (hydrated || !appendToBody);\n\n const resolved = useAnchoredPosition(rootRef, panelRef, {\n active: panelMounted,\n position,\n fixed: appendToBody,\n // The gap comes from `--bulma-popover-offset` in the stylesheet, so the\n // coordinates leave it out and `auto` reads it off the panel.\n offset: 0,\n styledGap: marginGap,\n });\n\n // The trigger renders first inside the wrapper. Read when needed rather\n // than held, so a trigger that remounted is still the one found.\n const triggerNode = useCallback((): HTMLElement | null => {\n const first = rootRef.current?.firstElementChild;\n return first && first !== panelRef.current ? (first as HTMLElement) : null;\n }, []);\n const restoreFocusRef = useMemo(\n () => ({\n get current() {\n return triggerNode();\n },\n }),\n [triggerNode]\n );\n\n useFocusTrap(panelRef, {\n active: panelMounted && trapFocus,\n restoreFocus: restoreFocusRef,\n });\n\n // Without the trap, focus still arrives in the panel and goes back to the\n // trigger, on the trap's terms: only while the panel still holds it.\n useEffect(() => {\n const panel = panelRef.current;\n if (!panelMounted || trapFocus || !panel) return undefined;\n focusTabStop(panel);\n return () => {\n const doc = panel.ownerDocument;\n const now = getDeepestActiveElement(doc);\n if (!now || now === doc.body || panel.contains(now)) {\n triggerNode()?.focus();\n }\n };\n }, [panelMounted, trapFocus, triggerNode]);\n\n // Read when a press lands outside, so the listener stays put while `close`\n // changes identity, which it does with `onOpenChange`, and an inline one\n // changes on every render of the parent.\n const closeRef = useLatestRef(close);\n\n useEffect(() => {\n if (!panelMounted || !closeOnClickOutside) return undefined;\n const handlePointerDown = (e: PointerEvent) => {\n if (e === insidePointerRef.current || isEventInside(e, rootRef.current)) {\n return;\n }\n closeRef.current();\n };\n // The wrapper is on the page whenever the panel is. Its document, an\n // iframe's when the tree renders into one, is where presses land.\n const doc = (rootRef.current as HTMLDivElement).ownerDocument;\n doc.addEventListener('pointerdown', handlePointerDown);\n return () => doc.removeEventListener('pointerdown', handlePointerDown);\n }, [panelMounted, closeOnClickOutside, closeRef]);\n\n const handlePanelKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key !== 'Escape' || !closeOnEscape || e.defaultPrevented) return;\n // React bubbles the key here from portaled content as well, so a dialog\n // of its own on the way (a nested popover, a picker's calendar) owns it.\n const owner = nearestDialog(e.nativeEvent);\n if (owner && owner !== panelRef.current) return;\n e.preventDefault();\n close();\n };\n\n const triggerProps = trigger.props as TriggerProps;\n\n const handleTriggerClick = (e: React.MouseEvent<HTMLElement>) => {\n triggerProps.onClick?.(e);\n if (!e.defaultPrevented) requestOpen(!isOpen);\n };\n\n const handleTriggerKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {\n triggerProps.onKeyDown?.(e);\n // A key on something inside the trigger is that element's to answer.\n if (e.defaultPrevented || e.target !== e.currentTarget) return;\n if (e.key === 'Escape') {\n if (isOpen && closeOnEscape) {\n e.preventDefault();\n close();\n }\n return;\n }\n if (e.key !== 'Enter' && e.key !== ' ') return;\n if (handlesKeyItself(e.currentTarget, e.key)) return;\n // Space would scroll the page; a held key repeats.\n e.preventDefault();\n if (!e.repeat) requestOpen(!isOpen);\n };\n\n const needsButtonRole =\n typeof trigger.type === 'string' &&\n !NATIVE_CONTROLS.has(trigger.type) &&\n !(trigger.type === 'a' && triggerProps.href != null);\n\n const clonedTrigger = cloneElement(\n trigger as React.ReactElement<Record<string, unknown>>,\n {\n ...(needsButtonRole\n ? {\n role: triggerProps.role ?? 'button',\n tabIndex: triggerProps.tabIndex ?? 0,\n }\n : {}),\n 'aria-haspopup': 'dialog',\n 'aria-expanded': isOpen,\n 'aria-controls': panelMounted ? panelId : undefined,\n onClick: handleTriggerClick,\n onKeyDown: handleTriggerKeyDown,\n }\n );\n\n const rootClasses = classNames(\n usePrefixedClassNames('popover', { 'is-active': isOpen }),\n bulmaHelperClasses,\n className\n );\n const panelClasses = classNames(\n usePrefixedClassNames('popover-content', {\n [`is-${resolved.position}`]: true,\n 'is-portal': appendToBody,\n }),\n contentClassName\n );\n\n return (\n // The ref after the spread: React 19 hands a function component a `ref`\n // prop like any other, and the wrapper has to stay ours.\n <div className={rootClasses} {...rest} ref={rootRef}>\n {clonedTrigger}\n {isOpen && (\n <Portal disabled={!appendToBody}>\n <PopoverPanel\n panelRef={panelRef}\n titleId={titleId}\n ariaLabel={ariaLabel}\n close={close}\n id={panelId}\n className={panelClasses}\n style={\n appendToBody\n ? {\n position: 'fixed',\n top: resolved.top,\n left: resolved.left,\n }\n : undefined\n }\n onKeyDown={handlePanelKeyDown}\n onPointerDownCapture={e => {\n insidePointerRef.current = e.nativeEvent;\n }}\n >\n {children}\n </PopoverPanel>\n </Portal>\n )}\n </div>\n );\n};\n\ninterface PopoverPanelProps extends React.HTMLAttributes<HTMLDivElement> {\n panelRef: React.RefObject<HTMLDivElement | null>;\n titleId: string;\n ariaLabel?: string;\n close: () => void;\n}\n\n/**\n * The open panel. It mounts with each opening, so it counts the\n * `Popover.Header`s inside it afresh every time.\n */\nfunction PopoverPanel({\n panelRef,\n titleId,\n ariaLabel,\n close,\n children,\n ...attrs\n}: PopoverPanelProps) {\n // Headers register in a layout effect, after this first render, and this\n // panel's own layout effect runs after theirs. Until then the panel assumes\n // it has one, so a panel that does is named from its first commit, before\n // focus moves in, and one that doesn't drops the reference before it paints.\n const [titlesCounted, setTitlesCounted] = useState(false);\n const [titleCount, setTitleCount] = useState(0);\n const registerTitle = useCallback(() => {\n setTitleCount(n => n + 1);\n return () => setTitleCount(n => n - 1);\n }, []);\n useClientLayoutEffect(() => {\n setTitlesCounted(true);\n }, []);\n const named = Boolean(ariaLabel) || titleCount > 0;\n\n useEffect(() => {\n if (titlesCounted && !named) {\n warnOnce(\n 'Popover:unnamed',\n '[bestax-bulma] <Popover> opened a panel with no accessible name. ' +\n 'Pass ariaLabel, or render a Popover.Header inside it.'\n );\n }\n }, [titlesCounted, named]);\n\n const context = useMemo(\n () => ({ titleId, registerTitle, close }),\n [titleId, registerTitle, close]\n );\n\n return (\n <PopoverContext.Provider value={context}>\n <div\n {...attrs}\n ref={panelRef}\n role=\"dialog\"\n aria-label={ariaLabel}\n aria-labelledby={\n !ariaLabel && (!titlesCounted || titleCount > 0) ? titleId : undefined\n }\n tabIndex={-1}\n >\n {children}\n </div>\n </PopoverContext.Provider>\n );\n}\n\n/**\n * Props for the Popover.Header component. Its `id` is the one the panel's\n * `aria-labelledby` names, so it isn't yours to set.\n */\nexport interface PopoverHeaderProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color' | 'id'>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * The popover's title, which names the panel for assistive technology.\n *\n * @function\n * @param {PopoverHeaderProps} props - Props for the Popover.Header component.\n * @returns {JSX.Element} The header element.\n */\nexport const PopoverHeader: React.FC<PopoverHeaderProps> = ({\n className,\n ...props\n}) => {\n const context = useContext(PopoverContext);\n const registerTitle = context?.registerTitle;\n // Before paint, so the panel is named by the time it shows.\n useClientLayoutEffect(() => registerTitle?.(), [registerTitle]);\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n return (\n <div\n className={classNames(\n usePrefixedClassNames('popover-header'),\n bulmaHelperClasses,\n className\n )}\n {...rest}\n id={context?.titleId}\n />\n );\n};\n\n/**\n * Props for the Popover.Body component.\n */\nexport interface PopoverBodyProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color'>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * The popover's main content area.\n *\n * @function\n * @param {PopoverBodyProps} props - Props for the Popover.Body component.\n * @returns {JSX.Element} The body element.\n */\nexport const PopoverBody: React.FC<PopoverBodyProps> = ({\n className,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n return (\n <div\n className={classNames(\n usePrefixedClassNames('popover-body'),\n bulmaHelperClasses,\n className\n )}\n {...rest}\n />\n );\n};\n\n/**\n * Props for the Popover.Footer component.\n */\nexport interface PopoverFooterProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color'>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n}\n\n/**\n * A row of actions along the bottom of the popover, aligned to the end.\n *\n * @function\n * @param {PopoverFooterProps} props - Props for the Popover.Footer component.\n * @returns {JSX.Element} The footer element.\n */\nexport const PopoverFooter: React.FC<PopoverFooterProps> = ({\n className,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n return (\n <div\n className={classNames(\n usePrefixedClassNames('popover-footer'),\n bulmaHelperClasses,\n className\n )}\n {...rest}\n />\n );\n};\n\n/**\n * Props for the Popover.Close component: the props of a `Button` rendered as\n * a `<button>`.\n * @extraProp {'button' | 'submit' | 'reset'} [type='button'] - Button type. Defaults to `'button'`, so a close button inside a form doesn't submit it.\n * @extraProp {React.MouseEventHandler<HTMLButtonElement>} [onClick] - Runs before the popover closes. Call `preventDefault()` to keep it open.\n */\nexport interface PopoverCloseProps\n extends\n ButtonOwnProps,\n Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof ButtonOwnProps> {}\n\n/**\n * A `Button` that closes the popover, handing focus back to the trigger.\n *\n * @function\n * @param {PopoverCloseProps} props - Props for the Popover.Close component.\n * @returns {JSX.Element} The close button.\n */\nexport const PopoverClose: React.FC<PopoverCloseProps> = ({\n onClick,\n ...props\n}) => {\n const context = useContext(PopoverContext);\n return (\n <Button\n {...props}\n type={buttonType(props.type)}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (!e.defaultPrevented) context?.close();\n }}\n />\n );\n};\n\n/** Popover with Header, Body, Footer and Close sub-components. */\nexport const Popover = withSubComponents(\n PopoverComponent,\n {\n Header: PopoverHeader,\n Body: PopoverBody,\n Footer: PopoverFooter,\n Close: PopoverClose,\n },\n 'Popover'\n);\n\nexport default Popover;\n"],"names":["_jsxs","_jsx"],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@ import { useConfig } from '../helpers/Config.js';
|
|
|
11
11
|
import { buttonType } from '../helpers/buttonType.js';
|
|
12
12
|
import { useLatestRef } from '../helpers/useLatestRef.js';
|
|
13
13
|
import { useOverlayStack, isModalOverlayOpen } from '../helpers/overlayStack.js';
|
|
14
|
-
import { trapTab,
|
|
14
|
+
import { trapTab, focusTabStop } from '../helpers/useFocusTrap.js';
|
|
15
15
|
import { inertProps } from '../helpers/inertProps.js';
|
|
16
16
|
import { getDeepestActiveElement, getActiveElementInTree } from '../helpers/shadowDom.js';
|
|
17
17
|
|
|
@@ -132,7 +132,7 @@ const SidebarComponent = forwardRef(({ isOpen, onClose, position = 'left', width
|
|
|
132
132
|
// the sidebar's other controls out of reach. It leaves focus alone
|
|
133
133
|
// then, and the cleanup below finds focus outside and leaves it too.
|
|
134
134
|
if (overlay || !isModalOverlayOpen()) {
|
|
135
|
-
(
|
|
135
|
+
focusTabStop(element);
|
|
136
136
|
}
|
|
137
137
|
return () => {
|
|
138
138
|
// Only while the sidebar still holds focus: closing one under another
|