@allxsmith/bestax-bulma 5.67.1 → 5.67.3
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/Card.js +3 -14
- package/dist/esm/components/Card.js.map +1 -1
- package/dist/esm/components/Command.js +14 -3
- package/dist/esm/components/Command.js.map +1 -1
- package/dist/esm/components/ContextMenu.js +10 -10
- package/dist/esm/components/ContextMenu.js.map +1 -1
- package/dist/esm/components/Dialog.js +7 -3
- package/dist/esm/components/Dialog.js.map +1 -1
- package/dist/esm/components/HoverCard.js +2 -2
- package/dist/esm/components/HoverCard.js.map +1 -1
- package/dist/esm/components/Menu.js +2 -1
- package/dist/esm/components/Menu.js.map +1 -1
- package/dist/esm/components/Modal.js +11 -9
- 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/Tabs.js +4 -3
- package/dist/esm/components/Tabs.js.map +1 -1
- package/dist/esm/elements/CopyButton.js +6 -3
- package/dist/esm/elements/CopyButton.js.map +1 -1
- package/dist/esm/elements/IconButton.js +3 -2
- package/dist/esm/elements/IconButton.js.map +1 -1
- package/dist/esm/elements/Stat.js +3 -2
- package/dist/esm/elements/Stat.js.map +1 -1
- package/dist/esm/form/ColorInputBase.js.map +1 -1
- package/dist/esm/form/Combobox.js +2 -2
- package/dist/esm/form/Combobox.js.map +1 -1
- package/dist/esm/form/DateInputBase.js +1 -1
- package/dist/esm/form/DateInputBase.js.map +1 -1
- package/dist/esm/form/DateRangeInputBase.js +1 -1
- package/dist/esm/form/DateRangeInputBase.js.map +1 -1
- package/dist/esm/form/DateTimeInputBase.js +3 -3
- package/dist/esm/form/DateTimeInputBase.js.map +1 -1
- package/dist/esm/form/Field.js +6 -6
- package/dist/esm/form/Field.js.map +1 -1
- package/dist/esm/form/TimeInputBase.js.map +1 -1
- package/dist/esm/form/TreeSelect.js.map +1 -1
- package/dist/esm/form/_pickerInternals/Calendar.js +19 -10
- package/dist/esm/form/_pickerInternals/Calendar.js.map +1 -1
- package/dist/esm/form/_pickerInternals/PickerPopover.js +3 -3
- package/dist/esm/form/_pickerInternals/PickerPopover.js.map +1 -1
- package/dist/esm/helpers/Config.js.map +1 -1
- package/dist/esm/helpers/elementType.js +42 -0
- package/dist/esm/helpers/elementType.js.map +1 -0
- package/dist/esm/helpers/portal.js +57 -1
- package/dist/esm/helpers/portal.js.map +1 -1
- package/dist/esm/helpers/useAnchoredPosition.js +23 -12
- package/dist/esm/helpers/useAnchoredPosition.js.map +1 -1
- package/dist/esm/helpers/withSubComponents.js +1 -1
- package/dist/esm/helpers/withSubComponents.js.map +1 -1
- package/dist/esm/layout/AppFrame.js +4 -3
- package/dist/esm/layout/AppFrame.js.map +1 -1
- package/dist/esm/layout/Splitter.js +3 -2
- package/dist/esm/layout/Splitter.js.map +1 -1
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +201 -84
- package/dist/index.cjs.map +1 -1
- package/dist/types/components/HoverCard.d.ts +9 -8
- package/dist/types/components/Popover.d.ts +6 -5
- package/dist/types/form/ColorInputBase.d.ts +2 -1
- package/dist/types/form/Combobox.d.ts +6 -7
- package/dist/types/form/DateInputBase.d.ts +12 -3
- package/dist/types/form/DateRangeInputBase.d.ts +12 -3
- package/dist/types/form/DateTimeInputBase.d.ts +12 -3
- package/dist/types/form/TimeInputBase.d.ts +2 -1
- package/dist/types/form/TreeSelect.d.ts +1 -1
- package/dist/types/form/_pickerInternals/Calendar.d.ts +12 -0
- package/dist/types/helpers/Config.d.ts +6 -3
- package/dist/types/helpers/elementType.d.ts +20 -0
- package/dist/types/helpers/portal.d.ts +44 -0
- package/dist/types/helpers/useAnchoredPosition.d.ts +3 -1
- package/dist/types/helpers/withSubComponents.d.ts +1 -1
- package/dist/types-cjs/components/HoverCard.d.ts +9 -8
- package/dist/types-cjs/components/Popover.d.ts +6 -5
- package/dist/types-cjs/form/ColorInputBase.d.ts +2 -1
- package/dist/types-cjs/form/Combobox.d.ts +6 -7
- package/dist/types-cjs/form/DateInputBase.d.ts +12 -3
- package/dist/types-cjs/form/DateRangeInputBase.d.ts +12 -3
- package/dist/types-cjs/form/DateTimeInputBase.d.ts +12 -3
- package/dist/types-cjs/form/TimeInputBase.d.ts +2 -1
- package/dist/types-cjs/form/TreeSelect.d.ts +1 -1
- package/dist/types-cjs/form/_pickerInternals/Calendar.d.ts +12 -0
- package/dist/types-cjs/helpers/Config.d.ts +6 -3
- package/dist/types-cjs/helpers/elementType.d.ts +20 -0
- package/dist/types-cjs/helpers/portal.d.ts +44 -0
- package/dist/types-cjs/helpers/useAnchoredPosition.d.ts +3 -1
- package/dist/types-cjs/helpers/withSubComponents.d.ts +1 -1
- 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 +16 -2
- package/src/scss/form/_dateinput.scss +4 -0
- package/src/scss/form/_datetimeinput.scss +9 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
2
|
'use client';
|
|
3
3
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
import { createContext, useContext } from 'react';
|
|
4
5
|
import { createPortal } from 'react-dom';
|
|
5
6
|
import { useIsHydrated } from './useIsHydrated.js';
|
|
6
7
|
import { useConfig } from './Config.js';
|
|
@@ -48,6 +49,47 @@ function usePortalContainer() {
|
|
|
48
49
|
const { portalContainer } = useConfig();
|
|
49
50
|
return container => resolvePortalContainer(container || portalContainer);
|
|
50
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* The element the overlay around a popup hands it to portal into, `null`
|
|
54
|
+
* outside any, and inside one whose element hasn't mounted yet.
|
|
55
|
+
*/
|
|
56
|
+
const PortalScopeContext = createContext(null);
|
|
57
|
+
/**
|
|
58
|
+
* INTERNAL, deliberately not exported from `src/index.ts`. Makes `node` where
|
|
59
|
+
* the popups inside it portal, unless a `ConfigProvider` sets a
|
|
60
|
+
* `portalContainer`. Modal, Dialog and Command.Dialog wrap their content in
|
|
61
|
+
* it, each with the element that carries their `aria-modal`, since a screen
|
|
62
|
+
* reader that honors it hides everything outside that element. The nearest
|
|
63
|
+
* one wins, so a Dialog's own box wins over the Modal it renders.
|
|
64
|
+
*
|
|
65
|
+
* @function PortalScope
|
|
66
|
+
* @param props - `node`, the element popups portal into, and `children`.
|
|
67
|
+
* @returns The children, inside the scope.
|
|
68
|
+
*/
|
|
69
|
+
function PortalScope({ node, children, }) {
|
|
70
|
+
return (jsx(PortalScopeContext.Provider, { value: node, children: children }));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* INTERNAL. Where a popup portals, a panel a component opens against
|
|
74
|
+
* something on the page, such as Popover's: the `portalContainer` a
|
|
75
|
+
* `ConfigProvider` sets, else the overlay it's in (see `PortalScope`), else
|
|
76
|
+
* `undefined`, which `resolvePortalContainer` reads as `document.body`.
|
|
77
|
+
*
|
|
78
|
+
* The overlay's element only exists once it has mounted, so a popup open as
|
|
79
|
+
* the overlay first renders mounts in `document.body`, and moves into the
|
|
80
|
+
* overlay in the update that follows, before the browser paints. Moving
|
|
81
|
+
* remounts it, as any change of portal target does. It isn't held back
|
|
82
|
+
* instead, because the popups measure their panel against the trigger as
|
|
83
|
+
* they open, and a panel that isn't there yet is never placed.
|
|
84
|
+
*
|
|
85
|
+
* @function usePopupContainer
|
|
86
|
+
* @returns The popup's container, `undefined` for `document.body`.
|
|
87
|
+
*/
|
|
88
|
+
function usePopupContainer() {
|
|
89
|
+
const { portalContainer } = useConfig();
|
|
90
|
+
const scope = useContext(PortalScopeContext);
|
|
91
|
+
return portalContainer || scope || undefined;
|
|
92
|
+
}
|
|
51
93
|
/**
|
|
52
94
|
* Reads Modal's `portal` prop (`boolean | string | HTMLElement`) for a
|
|
53
95
|
* component that takes it: whether the content is in its portal yet, and
|
|
@@ -137,6 +179,20 @@ function Portal({ children, container, disabled = false }) {
|
|
|
137
179
|
const target = resolveContainer(container);
|
|
138
180
|
return createPortal(jsx(LocalePortalScope, { target: target, children: children }), target);
|
|
139
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* INTERNAL. `Portal` for a popup, into the container `usePopupContainer`
|
|
184
|
+
* names, so a popup opened inside a Modal, Dialog or Command.Dialog stays
|
|
185
|
+
* inside it. `disabled` renders the children in place, as `Portal`'s does.
|
|
186
|
+
*
|
|
187
|
+
* @function PopupPortal
|
|
188
|
+
* @param props - The children, and `disabled`.
|
|
189
|
+
* @returns The portal, the children in place when `disabled`, or nothing
|
|
190
|
+
* before hydration.
|
|
191
|
+
*/
|
|
192
|
+
function PopupPortal({ children, disabled = false, }) {
|
|
193
|
+
const container = usePopupContainer();
|
|
194
|
+
return (jsx(Portal, { disabled: disabled, container: container, children: children }));
|
|
195
|
+
}
|
|
140
196
|
|
|
141
|
-
export { Portal, resolvePortalContainer, usePortalContainer, usePortalProp };
|
|
197
|
+
export { PopupPortal, Portal, PortalScope, resolvePortalContainer, usePopupContainer, usePortalContainer, usePortalProp };
|
|
142
198
|
//# sourceMappingURL=portal.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"portal.js","sources":["../../../src/helpers/portal.tsx"],"sourcesContent":["import React from 'react';\nimport { createPortal } from 'react-dom';\nimport { useIsHydrated } from './useIsHydrated';\nimport { useConfig } from './Config';\nimport { LocalePortalScope } from './LocaleProvider';\n\n/**\n * Resolves a portal target: an `HTMLElement` is used directly, a non-empty\n * `string` is treated as a `document.querySelector` selector (falling back to\n * `document.body` when it matches nothing), and any falsy value — `undefined`\n * or the empty string — resolves to `document.body`.\n *\n * The empty string is deliberately treated as \"no target\" rather than passed\n * through: `document.querySelector('')` throws a `SyntaxError`, and callers\n * building a selector from state can easily hand us `''`.\n *\n * @function resolvePortalContainer\n * @param container - The requested portal target, if any.\n * @returns The resolved DOM node to portal into.\n */\nexport function resolvePortalContainer(\n container?: string | HTMLElement\n): HTMLElement {\n if (!container) {\n return document.body;\n }\n if (typeof container === 'string') {\n return (\n (document.querySelector(container) as HTMLElement | null) ?? document.body\n );\n }\n return container;\n}\n\n/**\n * Where a component's portal goes, as a function to call when the portal is\n * created: the target the component was given, else the `portalContainer` a\n * `ConfigProvider` sets, else `document.body`, each read as\n * `resolvePortalContainer` reads it. A component that portals resolves its\n * target through this, so the provider's default reaches it.\n *\n * The hook only reads context, so it is safe to call on the server, ahead of\n * any early return. The function it returns reads the DOM, so call it only\n * where the portal is created, which the server never reaches.\n *\n * @function usePortalContainer\n * @returns A function that takes the component's own target, if any, and\n * returns the DOM node to portal into.\n */\nexport function usePortalContainer(): (\n container?: string | HTMLElement\n) => HTMLElement {\n const { portalContainer } = useConfig();\n return container => resolvePortalContainer(container || portalContainer);\n}\n\n/**\n * What a component that takes Modal's `portal` prop does with it now. See\n * `usePortalProp`.\n */\nexport interface PortalPropState {\n /**\n * Whether the content renders in its portal: `portal` is set and\n * hydration is over. Before that it renders in place.\n */\n isPortaled: boolean;\n /**\n * The target to pass `Portal` as its `container`: the selector or element\n * `portal` names, or `undefined` for `true`, which leaves the choice to the\n * `portalContainer` a `ConfigProvider` sets and then `document.body`.\n */\n container?: string | HTMLElement;\n /**\n * The `portalContainer` a `ConfigProvider` sets, while it's what places the\n * content (portaled, with `portal` set to `true`), else `undefined`. A\n * change to it moves the content, so effects that hold its nodes depend\n * on it.\n */\n defaultTarget?: string | HTMLElement;\n}\n\n/**\n * Reads Modal's `portal` prop (`boolean | string | HTMLElement`) for a\n * component that takes it: whether the content is in its portal yet, and\n * where it goes.\n *\n * A portal has no server-rendered counterpart, so such a component renders in\n * place on the server and while hydrating, and moves into its portal once the\n * client takes over. Render the content in `Portal` with\n * `disabled={!isPortaled}` and this `container`. Moving remounts the content,\n * so an effect that holds one of its nodes depends on `isPortaled`, `portal`\n * and `defaultTarget`.\n *\n * @function usePortalProp\n * @param portal - The component's `portal` prop.\n * @returns Whether it's portaled now, its `container`, and the provider's\n * target while that places it.\n */\nexport function usePortalProp(\n portal?: boolean | string | HTMLElement\n): PortalPropState {\n const hydrated = useIsHydrated();\n const { portalContainer } = useConfig();\n const isPortaled = Boolean(portal) && hydrated;\n return {\n isPortaled,\n container: typeof portal === 'boolean' ? undefined : portal,\n defaultTarget: isPortaled && portal === true ? portalContainer : undefined,\n };\n}\n\n/**\n * Props for the Portal component.\n */\nexport interface PortalProps {\n /** Content to render into the container. */\n children?: React.ReactNode;\n /**\n * Where the content goes: an element, or a `document.querySelector`\n * selector. Omitted or empty, it goes to the `portalContainer` a\n * `ConfigProvider` sets, and to `document.body` when none is set. A\n * selector that matches nothing means `document.body` too. A selector is\n * looked up again on each render, so the\n * content follows the target when it changes. Moving to another target\n * remounts the children and resets their state, which includes a selector\n * that only starts matching after the content has shown in `document.body`.\n * Render the target first, or pass the element once you have it.\n */\n container?: string | HTMLElement;\n /**\n * Render the children in place instead, on the server as well as the\n * client. Lets a component that portals only sometimes keep a single code\n * path.\n * @defaultValue false\n */\n disabled?: boolean;\n}\n\n/**\n * Renders its children into another part of the page, `document.body` unless `container` or a `ConfigProvider` says otherwise, so floating content escapes an ancestor's `overflow`, `transform` or stacking context.\n *\n * React's server renderer can't render a portal, so `Portal` renders nothing\n * on the server and during hydration, and mounts its children in the commit\n * that follows. The first client render then matches the server markup. In an app\n * without server rendering it portals on the first render.\n *\n * React context, such as `ConfigProvider`'s class prefix, reaches the\n * portaled children, and their React events bubble through the React tree.\n * CSS inheritance follows the DOM instead: a scoped (non-`isRoot`) `Theme`\n * sets its variables on a wrapper element the portaled content has left, so\n * that content sees the page-level theme, and styles scoped to a shadow root\n * don't reach `document.body` at all. To keep them, portal into an element\n * inside the themed area or the shadow root: pass it as `container`, or as\n * the `portalContainer` of a `ConfigProvider` around that area. The second\n * also reaches what the library's components portal, such as Popover's\n * `appendToBody` panel or a `Modal` with `portal`.\n *\n * Inside a `LocaleProvider`, the children keep its `dir` and `lang`. A\n * container inside the provider's own wrapper takes the children as they\n * are, and they follow the provider through the page. Any other,\n * `document.body` included, gets them inside one `<div>` carrying the\n * provider's values, with `display: contents` so it adds no box. It is\n * still an element, so content that must be the container's direct child,\n * a `<tr>` in a `<tbody>`, or that a direct-child selector styles, belongs\n * in a container inside the provider. Which of the two is settled when the\n * children mount, so a change of locale updates them in place rather than\n * remounting them.\n *\n * Tab order and focus follow the DOM too. Content portaled from inside a\n * focus trap is no longer inside the trapped element, so the trap neither\n * reaches it nor holds focus there: render nested overlays inside the\n * trapped element, or give them a trap of their own. To trap portaled\n * content, put `useFocusTrap`'s ref on the element inside the `Portal`.\n *\n * @function\n * @param {PortalProps} props - Props for the Portal component.\n * @returns The portal, the children in place when `disabled`, or nothing\n * before hydration.\n *\n * @example\n * <Portal>\n * <div className=\"box\">Rendered at the end of document.body</div>\n * </Portal>\n *\n * @example\n * // Into a dedicated overlay root\n * <Portal container=\"#overlays\">\n * <CommandPalette />\n * </Portal>\n */\nexport function Portal({ children, container, disabled = false }: PortalProps) {\n const hydrated = useIsHydrated();\n const resolveContainer = usePortalContainer();\n if (disabled) return <>{children}</>;\n if (!hydrated) return null;\n const target = resolveContainer(container);\n return createPortal(\n <LocalePortalScope target={target}>{children}</LocalePortalScope>,\n target\n );\n}\n"],"names":["_jsx","_Fragment"],"mappings":";;;;;;;;AAMA;;;;;;;;;;;;;AAaG;AACG,SAAU,sBAAsB,CACpC,SAAgC,EAAA;IAEhC,IAAI,CAAC,SAAS,EAAE;QACd,OAAO,QAAQ,CAAC,IAAI;IACtB;AACA,IAAA,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE;AACjC,QAAA,QACG,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAwB,IAAI,QAAQ,CAAC,IAAI;IAE9E;AACA,IAAA,OAAO,SAAS;AAClB;AAEA;;;;;;;;;;;;;;AAcG;SACa,kBAAkB,GAAA;AAGhC,IAAA,MAAM,EAAE,eAAe,EAAE,GAAG,SAAS,EAAE;IACvC,OAAO,SAAS,IAAI,sBAAsB,CAAC,SAAS,IAAI,eAAe,CAAC;AAC1E;AA2BA;;;;;;;;;;;;;;;;AAgBG;AACG,SAAU,aAAa,CAC3B,MAAuC,EAAA;AAEvC,IAAA,MAAM,QAAQ,GAAG,aAAa,EAAE;AAChC,IAAA,MAAM,EAAE,eAAe,EAAE,GAAG,SAAS,EAAE;IACvC,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,QAAQ;IAC9C,OAAO;QACL,UAAU;AACV,QAAA,SAAS,EAAE,OAAO,MAAM,KAAK,SAAS,GAAG,SAAS,GAAG,MAAM;AAC3D,QAAA,aAAa,EAAE,UAAU,IAAI,MAAM,KAAK,IAAI,GAAG,eAAe,GAAG,SAAS;KAC3E;AACH;AA6BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDG;AACG,SAAU,MAAM,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,GAAG,KAAK,EAAe,EAAA;AAC3E,IAAA,MAAM,QAAQ,GAAG,aAAa,EAAE;AAChC,IAAA,MAAM,gBAAgB,GAAG,kBAAkB,EAAE;AAC7C,IAAA,IAAI,QAAQ;QAAE,OAAOA,GAAA,CAAAC,QAAA,EAAA,EAAA,QAAA,EAAG,QAAQ,EAAA,CAAI;AACpC,IAAA,IAAI,CAAC,QAAQ;AAAE,QAAA,OAAO,IAAI;AAC1B,IAAA,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,CAAC;AAC1C,IAAA,OAAO,YAAY,CACjBD,GAAA,CAAC,iBAAiB,IAAC,MAAM,EAAE,MAAM,EAAA,QAAA,EAAG,QAAQ,EAAA,CAAqB,EACjE,MAAM,CACP;AACH;;"}
|
|
1
|
+
{"version":3,"file":"portal.js","sources":["../../../src/helpers/portal.tsx"],"sourcesContent":["import React, { createContext, useContext } from 'react';\nimport { createPortal } from 'react-dom';\nimport { useIsHydrated } from './useIsHydrated';\nimport { useConfig } from './Config';\nimport { LocalePortalScope } from './LocaleProvider';\n\n/**\n * Resolves a portal target: an `HTMLElement` is used directly, a non-empty\n * `string` is treated as a `document.querySelector` selector (falling back to\n * `document.body` when it matches nothing), and any falsy value — `undefined`\n * or the empty string — resolves to `document.body`.\n *\n * The empty string is deliberately treated as \"no target\" rather than passed\n * through: `document.querySelector('')` throws a `SyntaxError`, and callers\n * building a selector from state can easily hand us `''`.\n *\n * @function resolvePortalContainer\n * @param container - The requested portal target, if any.\n * @returns The resolved DOM node to portal into.\n */\nexport function resolvePortalContainer(\n container?: string | HTMLElement\n): HTMLElement {\n if (!container) {\n return document.body;\n }\n if (typeof container === 'string') {\n return (\n (document.querySelector(container) as HTMLElement | null) ?? document.body\n );\n }\n return container;\n}\n\n/**\n * Where a component's portal goes, as a function to call when the portal is\n * created: the target the component was given, else the `portalContainer` a\n * `ConfigProvider` sets, else `document.body`, each read as\n * `resolvePortalContainer` reads it. A component that portals resolves its\n * target through this, so the provider's default reaches it.\n *\n * The hook only reads context, so it is safe to call on the server, ahead of\n * any early return. The function it returns reads the DOM, so call it only\n * where the portal is created, which the server never reaches.\n *\n * @function usePortalContainer\n * @returns A function that takes the component's own target, if any, and\n * returns the DOM node to portal into.\n */\nexport function usePortalContainer(): (\n container?: string | HTMLElement\n) => HTMLElement {\n const { portalContainer } = useConfig();\n return container => resolvePortalContainer(container || portalContainer);\n}\n\n/**\n * The element the overlay around a popup hands it to portal into, `null`\n * outside any, and inside one whose element hasn't mounted yet.\n */\nconst PortalScopeContext = createContext<HTMLElement | null>(null);\n\n/**\n * INTERNAL, deliberately not exported from `src/index.ts`. Makes `node` where\n * the popups inside it portal, unless a `ConfigProvider` sets a\n * `portalContainer`. Modal, Dialog and Command.Dialog wrap their content in\n * it, each with the element that carries their `aria-modal`, since a screen\n * reader that honors it hides everything outside that element. The nearest\n * one wins, so a Dialog's own box wins over the Modal it renders.\n *\n * @function PortalScope\n * @param props - `node`, the element popups portal into, and `children`.\n * @returns The children, inside the scope.\n */\nexport function PortalScope({\n node,\n children,\n}: {\n node: HTMLElement | null;\n children?: React.ReactNode;\n}) {\n return (\n <PortalScopeContext.Provider value={node}>\n {children}\n </PortalScopeContext.Provider>\n );\n}\n\n/**\n * INTERNAL. Where a popup portals, a panel a component opens against\n * something on the page, such as Popover's: the `portalContainer` a\n * `ConfigProvider` sets, else the overlay it's in (see `PortalScope`), else\n * `undefined`, which `resolvePortalContainer` reads as `document.body`.\n *\n * The overlay's element only exists once it has mounted, so a popup open as\n * the overlay first renders mounts in `document.body`, and moves into the\n * overlay in the update that follows, before the browser paints. Moving\n * remounts it, as any change of portal target does. It isn't held back\n * instead, because the popups measure their panel against the trigger as\n * they open, and a panel that isn't there yet is never placed.\n *\n * @function usePopupContainer\n * @returns The popup's container, `undefined` for `document.body`.\n */\nexport function usePopupContainer(): string | HTMLElement | undefined {\n const { portalContainer } = useConfig();\n const scope = useContext(PortalScopeContext);\n return portalContainer || scope || undefined;\n}\n\n/**\n * What a component that takes Modal's `portal` prop does with it now. See\n * `usePortalProp`.\n */\nexport interface PortalPropState {\n /**\n * Whether the content renders in its portal: `portal` is set and\n * hydration is over. Before that it renders in place.\n */\n isPortaled: boolean;\n /**\n * The target to pass `Portal` as its `container`: the selector or element\n * `portal` names, or `undefined` for `true`, which leaves the choice to the\n * `portalContainer` a `ConfigProvider` sets and then `document.body`.\n */\n container?: string | HTMLElement;\n /**\n * The `portalContainer` a `ConfigProvider` sets, while it's what places the\n * content (portaled, with `portal` set to `true`), else `undefined`. A\n * change to it moves the content, so effects that hold its nodes depend\n * on it.\n */\n defaultTarget?: string | HTMLElement;\n}\n\n/**\n * Reads Modal's `portal` prop (`boolean | string | HTMLElement`) for a\n * component that takes it: whether the content is in its portal yet, and\n * where it goes.\n *\n * A portal has no server-rendered counterpart, so such a component renders in\n * place on the server and while hydrating, and moves into its portal once the\n * client takes over. Render the content in `Portal` with\n * `disabled={!isPortaled}` and this `container`. Moving remounts the content,\n * so an effect that holds one of its nodes depends on `isPortaled`, `portal`\n * and `defaultTarget`.\n *\n * @function usePortalProp\n * @param portal - The component's `portal` prop.\n * @returns Whether it's portaled now, its `container`, and the provider's\n * target while that places it.\n */\nexport function usePortalProp(\n portal?: boolean | string | HTMLElement\n): PortalPropState {\n const hydrated = useIsHydrated();\n const { portalContainer } = useConfig();\n const isPortaled = Boolean(portal) && hydrated;\n return {\n isPortaled,\n container: typeof portal === 'boolean' ? undefined : portal,\n defaultTarget: isPortaled && portal === true ? portalContainer : undefined,\n };\n}\n\n/**\n * Props for the Portal component.\n */\nexport interface PortalProps {\n /** Content to render into the container. */\n children?: React.ReactNode;\n /**\n * Where the content goes: an element, or a `document.querySelector`\n * selector. Omitted or empty, it goes to the `portalContainer` a\n * `ConfigProvider` sets, and to `document.body` when none is set. A\n * selector that matches nothing means `document.body` too. A selector is\n * looked up again on each render, so the\n * content follows the target when it changes. Moving to another target\n * remounts the children and resets their state, which includes a selector\n * that only starts matching after the content has shown in `document.body`.\n * Render the target first, or pass the element once you have it.\n */\n container?: string | HTMLElement;\n /**\n * Render the children in place instead, on the server as well as the\n * client. Lets a component that portals only sometimes keep a single code\n * path.\n * @defaultValue false\n */\n disabled?: boolean;\n}\n\n/**\n * Renders its children into another part of the page, `document.body` unless `container` or a `ConfigProvider` says otherwise, so floating content escapes an ancestor's `overflow`, `transform` or stacking context.\n *\n * React's server renderer can't render a portal, so `Portal` renders nothing\n * on the server and during hydration, and mounts its children in the commit\n * that follows. The first client render then matches the server markup. In an app\n * without server rendering it portals on the first render.\n *\n * React context, such as `ConfigProvider`'s class prefix, reaches the\n * portaled children, and their React events bubble through the React tree.\n * CSS inheritance follows the DOM instead: a scoped (non-`isRoot`) `Theme`\n * sets its variables on a wrapper element the portaled content has left, so\n * that content sees the page-level theme, and styles scoped to a shadow root\n * don't reach `document.body` at all. To keep them, portal into an element\n * inside the themed area or the shadow root: pass it as `container`, or as\n * the `portalContainer` of a `ConfigProvider` around that area. The second\n * also reaches what the library's components portal, such as Popover's\n * `appendToBody` panel or a `Modal` with `portal`.\n *\n * Inside a `LocaleProvider`, the children keep its `dir` and `lang`. A\n * container inside the provider's own wrapper takes the children as they\n * are, and they follow the provider through the page. Any other,\n * `document.body` included, gets them inside one `<div>` carrying the\n * provider's values, with `display: contents` so it adds no box. It is\n * still an element, so content that must be the container's direct child,\n * a `<tr>` in a `<tbody>`, or that a direct-child selector styles, belongs\n * in a container inside the provider. Which of the two is settled when the\n * children mount, so a change of locale updates them in place rather than\n * remounting them.\n *\n * Tab order and focus follow the DOM too. Content portaled from inside a\n * focus trap is no longer inside the trapped element, so the trap neither\n * reaches it nor holds focus there: render nested overlays inside the\n * trapped element, or give them a trap of their own. To trap portaled\n * content, put `useFocusTrap`'s ref on the element inside the `Portal`.\n *\n * @function\n * @param {PortalProps} props - Props for the Portal component.\n * @returns The portal, the children in place when `disabled`, or nothing\n * before hydration.\n *\n * @example\n * <Portal>\n * <div className=\"box\">Rendered at the end of document.body</div>\n * </Portal>\n *\n * @example\n * // Into a dedicated overlay root\n * <Portal container=\"#overlays\">\n * <CommandPalette />\n * </Portal>\n */\nexport function Portal({ children, container, disabled = false }: PortalProps) {\n const hydrated = useIsHydrated();\n const resolveContainer = usePortalContainer();\n if (disabled) return <>{children}</>;\n if (!hydrated) return null;\n const target = resolveContainer(container);\n return createPortal(\n <LocalePortalScope target={target}>{children}</LocalePortalScope>,\n target\n );\n}\n\n/**\n * INTERNAL. `Portal` for a popup, into the container `usePopupContainer`\n * names, so a popup opened inside a Modal, Dialog or Command.Dialog stays\n * inside it. `disabled` renders the children in place, as `Portal`'s does.\n *\n * @function PopupPortal\n * @param props - The children, and `disabled`.\n * @returns The portal, the children in place when `disabled`, or nothing\n * before hydration.\n */\nexport function PopupPortal({\n children,\n disabled = false,\n}: Omit<PortalProps, 'container'>) {\n const container = usePopupContainer();\n return (\n <Portal disabled={disabled} container={container}>\n {children}\n </Portal>\n );\n}\n"],"names":["_jsx","_Fragment"],"mappings":";;;;;;;;;AAMA;;;;;;;;;;;;;AAaG;AACG,SAAU,sBAAsB,CACpC,SAAgC,EAAA;IAEhC,IAAI,CAAC,SAAS,EAAE;QACd,OAAO,QAAQ,CAAC,IAAI;IACtB;AACA,IAAA,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE;AACjC,QAAA,QACG,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAwB,IAAI,QAAQ,CAAC,IAAI;IAE9E;AACA,IAAA,OAAO,SAAS;AAClB;AAEA;;;;;;;;;;;;;;AAcG;SACa,kBAAkB,GAAA;AAGhC,IAAA,MAAM,EAAE,eAAe,EAAE,GAAG,SAAS,EAAE;IACvC,OAAO,SAAS,IAAI,sBAAsB,CAAC,SAAS,IAAI,eAAe,CAAC;AAC1E;AAEA;;;AAGG;AACH,MAAM,kBAAkB,GAAG,aAAa,CAAqB,IAAI,CAAC;AAElE;;;;;;;;;;;AAWG;SACa,WAAW,CAAC,EAC1B,IAAI,EACJ,QAAQ,GAIT,EAAA;AACC,IAAA,QACEA,GAAA,CAAC,kBAAkB,CAAC,QAAQ,EAAA,EAAC,KAAK,EAAE,IAAI,EAAA,QAAA,EACrC,QAAQ,EAAA,CACmB;AAElC;AAEA;;;;;;;;;;;;;;;AAeG;SACa,iBAAiB,GAAA;AAC/B,IAAA,MAAM,EAAE,eAAe,EAAE,GAAG,SAAS,EAAE;AACvC,IAAA,MAAM,KAAK,GAAG,UAAU,CAAC,kBAAkB,CAAC;AAC5C,IAAA,OAAO,eAAe,IAAI,KAAK,IAAI,SAAS;AAC9C;AA2BA;;;;;;;;;;;;;;;;AAgBG;AACG,SAAU,aAAa,CAC3B,MAAuC,EAAA;AAEvC,IAAA,MAAM,QAAQ,GAAG,aAAa,EAAE;AAChC,IAAA,MAAM,EAAE,eAAe,EAAE,GAAG,SAAS,EAAE;IACvC,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,QAAQ;IAC9C,OAAO;QACL,UAAU;AACV,QAAA,SAAS,EAAE,OAAO,MAAM,KAAK,SAAS,GAAG,SAAS,GAAG,MAAM;AAC3D,QAAA,aAAa,EAAE,UAAU,IAAI,MAAM,KAAK,IAAI,GAAG,eAAe,GAAG,SAAS;KAC3E;AACH;AA6BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDG;AACG,SAAU,MAAM,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,GAAG,KAAK,EAAe,EAAA;AAC3E,IAAA,MAAM,QAAQ,GAAG,aAAa,EAAE;AAChC,IAAA,MAAM,gBAAgB,GAAG,kBAAkB,EAAE;AAC7C,IAAA,IAAI,QAAQ;QAAE,OAAOA,GAAA,CAAAC,QAAA,EAAA,EAAA,QAAA,EAAG,QAAQ,EAAA,CAAI;AACpC,IAAA,IAAI,CAAC,QAAQ;AAAE,QAAA,OAAO,IAAI;AAC1B,IAAA,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,CAAC;AAC1C,IAAA,OAAO,YAAY,CACjBD,GAAA,CAAC,iBAAiB,IAAC,MAAM,EAAE,MAAM,EAAA,QAAA,EAAG,QAAQ,EAAA,CAAqB,EACjE,MAAM,CACP;AACH;AAEA;;;;;;;;;AASG;AACG,SAAU,WAAW,CAAC,EAC1B,QAAQ,EACR,QAAQ,GAAG,KAAK,GACe,EAAA;AAC/B,IAAA,MAAM,SAAS,GAAG,iBAAiB,EAAE;AACrC,IAAA,QACEA,GAAA,CAAC,MAAM,EAAA,EAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAA,QAAA,EAC7C,QAAQ,EAAA,CACF;AAEb;;"}
|
|
@@ -36,7 +36,9 @@ const samePosition = (a, b) => a.position === b.position &&
|
|
|
36
36
|
* anchor changes size, so a fixed panel follows its anchor and its own
|
|
37
37
|
* content. Those measure once in the next frame, however many arrive before
|
|
38
38
|
* it. With `followContent` it also measures after each render. A
|
|
39
|
-
* measurement that changes nothing doesn't render again.
|
|
39
|
+
* measurement that changes nothing doesn't render again. A panel that's
|
|
40
|
+
* replaced while it's open, as a portal moving to another target replaces
|
|
41
|
+
* it, is watched from the next measurement on.
|
|
40
42
|
*
|
|
41
43
|
* The first measurement happens in a layout effect, so the panel is placed
|
|
42
44
|
* before it paints. It needs both elements on the page while `active` is
|
|
@@ -60,11 +62,25 @@ function useAnchoredPosition(anchorRef, panelRef, { active, position, fixed, off
|
|
|
60
62
|
return undefined;
|
|
61
63
|
const view = (anchorRef.current ?? panelRef.current)?.ownerDocument.defaultView ??
|
|
62
64
|
window;
|
|
65
|
+
// The panel's content can change size without the component holding the
|
|
66
|
+
// hook rendering, as an image loading or a child's own state does, and
|
|
67
|
+
// so can the anchor. Set up below, once `update` exists.
|
|
68
|
+
let resizes = null;
|
|
69
|
+
// The panel being watched. A portal that moves to another target
|
|
70
|
+
// remounts the panel, as a popup that opens with the dialog it's in
|
|
71
|
+
// does, so each measurement watches the one that's there now.
|
|
72
|
+
let watched = null;
|
|
63
73
|
const update = () => {
|
|
64
74
|
const anchor = anchorRef.current;
|
|
65
75
|
const panel = panelRef.current;
|
|
66
76
|
if (!anchor || !panel)
|
|
67
77
|
return;
|
|
78
|
+
if (panel !== watched) {
|
|
79
|
+
if (watched)
|
|
80
|
+
resizes?.unobserve(watched);
|
|
81
|
+
resizes?.observe(panel);
|
|
82
|
+
watched = panel;
|
|
83
|
+
}
|
|
68
84
|
const rect = anchor.getBoundingClientRect();
|
|
69
85
|
const measured = panel.getBoundingClientRect();
|
|
70
86
|
// The width the panel has once it's at least as wide as the anchor.
|
|
@@ -95,8 +111,6 @@ function useAnchoredPosition(anchorRef, panelRef, { active, position, fixed, off
|
|
|
95
111
|
: { position: corner };
|
|
96
112
|
setResolved(prev => (samePosition(prev, next) ? prev : next));
|
|
97
113
|
};
|
|
98
|
-
updateRef.current = update;
|
|
99
|
-
update();
|
|
100
114
|
// A scroll, a resize or a change in size measures in the next frame,
|
|
101
115
|
// once however many of them ask.
|
|
102
116
|
let frame = 0;
|
|
@@ -108,17 +122,14 @@ function useAnchoredPosition(anchorRef, panelRef, { active, position, fixed, off
|
|
|
108
122
|
update();
|
|
109
123
|
});
|
|
110
124
|
};
|
|
125
|
+
const Observer = view.ResizeObserver;
|
|
126
|
+
resizes = typeof Observer === 'undefined' ? null : new Observer(schedule);
|
|
127
|
+
if (anchorRef.current)
|
|
128
|
+
resizes?.observe(anchorRef.current);
|
|
129
|
+
updateRef.current = update;
|
|
130
|
+
update();
|
|
111
131
|
view.addEventListener('resize', schedule, { passive: true });
|
|
112
132
|
view.addEventListener('scroll', schedule, { passive: true, capture: true });
|
|
113
|
-
// The panel's content can change size without the component holding the
|
|
114
|
-
// hook rendering, as an image loading or a child's own state does, and
|
|
115
|
-
// so can the anchor.
|
|
116
|
-
const Observer = view.ResizeObserver;
|
|
117
|
-
const resizes = typeof Observer === 'undefined' ? null : new Observer(schedule);
|
|
118
|
-
for (const element of [anchorRef.current, panelRef.current]) {
|
|
119
|
-
if (element)
|
|
120
|
-
resizes?.observe(element);
|
|
121
|
-
}
|
|
122
133
|
return () => {
|
|
123
134
|
updateRef.current = null;
|
|
124
135
|
view.cancelAnimationFrame(frame);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAnchoredPosition.js","sources":["../../../src/helpers/useAnchoredPosition.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useRef, useState } from 'react';\nimport type { RefObject } from 'react';\nimport type { PickerPosition } from '../form/_pickerInternals/pickerTypes';\nimport { useClientLayoutEffect } from './useClientLayoutEffect';\n\n/** A corner of the anchor the panel opens from, once `auto` is resolved. */\nexport type AnchoredCorner = Exclude<PickerPosition, 'auto'>;\n\n/**\n * Where the panel goes: the corner it opens from and, when it is fixed to\n * the viewport, its coordinates there.\n */\nexport interface AnchoredPosition {\n position: AnchoredCorner;\n top?: number;\n left?: number;\n /**\n * With `matchWidth`, the anchor's width, which a fixed panel takes as its\n * `min-width`.\n */\n minWidth?: number;\n}\n\n/**\n * Options for `useAnchoredPosition`.\n */\nexport interface UseAnchoredPositionOptions {\n /** Whether the panel is on the page. Nothing is measured while it is not. */\n active: boolean;\n /**\n * The corner asked for, or `auto` to pick one that keeps the panel in the\n * viewport. A named corner is physical, whatever the page's direction.\n * Left out, the corner is `bottom-left` lined up with the anchor's inline\n * start, which is its right edge right to left. A panel in place gets that\n * from its stylesheet, as Popover's and the pickers' do; a fixed panel's\n * coordinates read the anchor's direction.\n */\n position?: PickerPosition;\n /**\n * Whether the panel is fixed to the viewport (it was portaled away from the\n * anchor), which needs coordinates. Otherwise the panel's CSS places it\n * against the anchor from the corner alone.\n */\n fixed: boolean;\n /**\n * The gap between the anchor and the panel, in pixels, that the hook puts\n * there itself: added to the fixed coordinates and to the room `auto` asks\n * for below the anchor. Popover and the pickers' popover pass `0` and take\n * their gap from a margin in their stylesheet, read through `styledGap`, so\n * a theme's offset moves the panel and `auto` alike. A new panel should do\n * the same rather than rely on this default, which no stylesheet sees.\n * @defaultValue 4\n */\n offset?: number;\n /**\n * Reads the gap the panel's own stylesheet puts between it and the anchor,\n * in pixels, each time the panel is measured. `auto` leaves room for it as\n * well; the coordinates don't include it, because the stylesheet applies it.\n */\n styledGap?: (panel: HTMLElement) => number;\n /**\n * Whether the panel is at least as wide as the anchor, as a list under its\n * field is. A panel in place gets that from its stylesheet. A fixed one\n * can't, so its placement carries the anchor's width as `minWidth` for the\n * panel to set, and its coordinates and `auto` already allow for it.\n * @defaultValue false\n */\n matchWidth?: boolean;\n /**\n * Measures again after each render of the component holding the hook,\n * before the panel paints, for a panel whose content that component\n * changes while it's open, as a filtered list's does. A change in size is\n * followed without it too, but a frame later, so a panel above its anchor\n * would show for a frame where its old height put it.\n * @defaultValue false\n */\n followContent?: boolean;\n}\n\n/**\n * A `styledGap` for a panel whose stylesheet puts the gap in a margin on the\n * side facing the anchor, negative on a portaled panel above it, so the\n * larger magnitude is the gap. In pixels.\n */\nexport function marginGap(panel: HTMLElement): number {\n const { marginTop, marginBottom } = getComputedStyle(panel);\n return Math.max(\n Math.abs(parseFloat(marginTop)) || 0,\n Math.abs(parseFloat(marginBottom)) || 0\n );\n}\n\n/**\n * The corner that keeps a panel of this size in the viewport, preferring\n * below the anchor and lined up with its left edge.\n */\nfunction resolveAuto(\n view: Window,\n rect: DOMRect,\n panelWidth: number,\n panelHeight: number,\n gap: number\n): AnchoredCorner {\n const fitsBelow = rect.bottom + panelHeight + gap <= view.innerHeight;\n const fitsRight = rect.left + panelWidth <= view.innerWidth;\n if (fitsBelow) return fitsRight ? 'bottom-left' : 'bottom-right';\n return fitsRight ? 'top-left' : 'top-right';\n}\n\nconst samePosition = (a: AnchoredPosition, b: AnchoredPosition): boolean =>\n a.position === b.position &&\n a.top === b.top &&\n a.left === b.left &&\n a.minWidth === b.minWidth;\n\n/**\n * Places a floating panel against its anchor: resolves `auto` to a corner\n * and, for a panel fixed to the viewport, works out its coordinates. It\n * measures again on resize, on any scroll of the anchor's window (an\n * iframe's, when the anchor renders into one), and when the panel or the\n * anchor changes size, so a fixed panel follows its anchor and its own\n * content. Those measure once in the next frame, however many arrive before\n * it. With `followContent` it also measures after each render. A\n * measurement that changes nothing doesn't render again.\n *\n * The first measurement happens in a layout effect, so the panel is placed\n * before it paints. It needs both elements on the page while `active` is\n * on, and keeps the last placement when either is missing.\n *\n * @function useAnchoredPosition\n * @param anchorRef - The element the panel opens from.\n * @param panelRef - The floating panel.\n * @param options - Whether to measure, the corner asked for, and how the panel is placed.\n * @returns The corner the panel opens from, with coordinates when it is fixed.\n */\nexport function useAnchoredPosition(\n anchorRef: RefObject<HTMLElement | null>,\n panelRef: RefObject<HTMLElement | null>,\n {\n active,\n position,\n fixed,\n offset = 4,\n styledGap,\n matchWidth = false,\n followContent = false,\n }: UseAnchoredPositionOptions\n): AnchoredPosition {\n const [resolved, setResolved] = useState<AnchoredPosition>({\n position:\n position === undefined || position === 'auto' ? 'bottom-left' : position,\n });\n // The measurement while the panel is active, for `followContent` to run\n // again after a render.\n const updateRef = useRef<(() => void) | null>(null);\n\n useClientLayoutEffect(() => {\n if (!active) return undefined;\n const view =\n (anchorRef.current ?? panelRef.current)?.ownerDocument.defaultView ??\n window;\n const update = () => {\n const anchor = anchorRef.current;\n const panel = panelRef.current;\n if (!anchor || !panel) return;\n const rect = anchor.getBoundingClientRect();\n const measured = panel.getBoundingClientRect();\n // The width the panel has once it's at least as wide as the anchor.\n const width = matchWidth\n ? Math.max(measured.width, rect.width)\n : measured.width;\n const { height } = measured;\n const corner =\n position === 'auto'\n ? resolveAuto(\n view,\n rect,\n width,\n height,\n offset + (styledGap?.(panel) ?? 0)\n )\n : (position ?? 'bottom-left');\n // The default lines up with the anchor's inline start, so right to\n // left a fixed panel lines up the right edges. A named corner doesn't,\n // and a panel in place takes the default's side from its stylesheet.\n const startsRight =\n position === undefined &&\n fixed &&\n getComputedStyle(anchor).direction === 'rtl';\n const next: AnchoredPosition = fixed\n ? {\n position: corner,\n top: corner.startsWith('bottom')\n ? rect.bottom + offset\n : rect.top - height - offset,\n left:\n corner.endsWith('left') && !startsRight\n ? rect.left\n : rect.right - width,\n ...(matchWidth ? { minWidth: rect.width } : {}),\n }\n : { position: corner };\n setResolved(prev => (samePosition(prev, next) ? prev : next));\n };\n updateRef.current = update;\n update();\n\n // A scroll, a resize or a change in size measures in the next frame,\n // once however many of them ask.\n let frame = 0;\n const schedule = () => {\n if (frame) return;\n frame = view.requestAnimationFrame(() => {\n frame = 0;\n update();\n });\n };\n view.addEventListener('resize', schedule, { passive: true });\n view.addEventListener('scroll', schedule, { passive: true, capture: true });\n // The panel's content can change size without the component holding the\n // hook rendering, as an image loading or a child's own state does, and\n // so can the anchor.\n const Observer = view.ResizeObserver;\n const resizes =\n typeof Observer === 'undefined' ? null : new Observer(schedule);\n for (const element of [anchorRef.current, panelRef.current]) {\n if (element) resizes?.observe(element);\n }\n return () => {\n updateRef.current = null;\n view.cancelAnimationFrame(frame);\n resizes?.disconnect();\n view.removeEventListener('resize', schedule);\n view.removeEventListener('scroll', schedule, { capture: true });\n };\n }, [\n active,\n anchorRef,\n panelRef,\n position,\n fixed,\n offset,\n styledGap,\n matchWidth,\n ]);\n\n // After every render, on purpose: the panel's content is what changed.\n useClientLayoutEffect(() => {\n if (followContent) updateRef.current?.();\n });\n\n return resolved;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useAnchoredPosition.js","sources":["../../../src/helpers/useAnchoredPosition.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useRef, useState } from 'react';\nimport type { RefObject } from 'react';\nimport type { PickerPosition } from '../form/_pickerInternals/pickerTypes';\nimport { useClientLayoutEffect } from './useClientLayoutEffect';\n\n/** A corner of the anchor the panel opens from, once `auto` is resolved. */\nexport type AnchoredCorner = Exclude<PickerPosition, 'auto'>;\n\n/**\n * Where the panel goes: the corner it opens from and, when it is fixed to\n * the viewport, its coordinates there.\n */\nexport interface AnchoredPosition {\n position: AnchoredCorner;\n top?: number;\n left?: number;\n /**\n * With `matchWidth`, the anchor's width, which a fixed panel takes as its\n * `min-width`.\n */\n minWidth?: number;\n}\n\n/**\n * Options for `useAnchoredPosition`.\n */\nexport interface UseAnchoredPositionOptions {\n /** Whether the panel is on the page. Nothing is measured while it is not. */\n active: boolean;\n /**\n * The corner asked for, or `auto` to pick one that keeps the panel in the\n * viewport. A named corner is physical, whatever the page's direction.\n * Left out, the corner is `bottom-left` lined up with the anchor's inline\n * start, which is its right edge right to left. A panel in place gets that\n * from its stylesheet, as Popover's and the pickers' do; a fixed panel's\n * coordinates read the anchor's direction.\n */\n position?: PickerPosition;\n /**\n * Whether the panel is fixed to the viewport (it was portaled away from the\n * anchor), which needs coordinates. Otherwise the panel's CSS places it\n * against the anchor from the corner alone.\n */\n fixed: boolean;\n /**\n * The gap between the anchor and the panel, in pixels, that the hook puts\n * there itself: added to the fixed coordinates and to the room `auto` asks\n * for below the anchor. Popover and the pickers' popover pass `0` and take\n * their gap from a margin in their stylesheet, read through `styledGap`, so\n * a theme's offset moves the panel and `auto` alike. A new panel should do\n * the same rather than rely on this default, which no stylesheet sees.\n * @defaultValue 4\n */\n offset?: number;\n /**\n * Reads the gap the panel's own stylesheet puts between it and the anchor,\n * in pixels, each time the panel is measured. `auto` leaves room for it as\n * well; the coordinates don't include it, because the stylesheet applies it.\n */\n styledGap?: (panel: HTMLElement) => number;\n /**\n * Whether the panel is at least as wide as the anchor, as a list under its\n * field is. A panel in place gets that from its stylesheet. A fixed one\n * can't, so its placement carries the anchor's width as `minWidth` for the\n * panel to set, and its coordinates and `auto` already allow for it.\n * @defaultValue false\n */\n matchWidth?: boolean;\n /**\n * Measures again after each render of the component holding the hook,\n * before the panel paints, for a panel whose content that component\n * changes while it's open, as a filtered list's does. A change in size is\n * followed without it too, but a frame later, so a panel above its anchor\n * would show for a frame where its old height put it.\n * @defaultValue false\n */\n followContent?: boolean;\n}\n\n/**\n * A `styledGap` for a panel whose stylesheet puts the gap in a margin on the\n * side facing the anchor, negative on a portaled panel above it, so the\n * larger magnitude is the gap. In pixels.\n */\nexport function marginGap(panel: HTMLElement): number {\n const { marginTop, marginBottom } = getComputedStyle(panel);\n return Math.max(\n Math.abs(parseFloat(marginTop)) || 0,\n Math.abs(parseFloat(marginBottom)) || 0\n );\n}\n\n/**\n * The corner that keeps a panel of this size in the viewport, preferring\n * below the anchor and lined up with its left edge.\n */\nfunction resolveAuto(\n view: Window,\n rect: DOMRect,\n panelWidth: number,\n panelHeight: number,\n gap: number\n): AnchoredCorner {\n const fitsBelow = rect.bottom + panelHeight + gap <= view.innerHeight;\n const fitsRight = rect.left + panelWidth <= view.innerWidth;\n if (fitsBelow) return fitsRight ? 'bottom-left' : 'bottom-right';\n return fitsRight ? 'top-left' : 'top-right';\n}\n\nconst samePosition = (a: AnchoredPosition, b: AnchoredPosition): boolean =>\n a.position === b.position &&\n a.top === b.top &&\n a.left === b.left &&\n a.minWidth === b.minWidth;\n\n/**\n * Places a floating panel against its anchor: resolves `auto` to a corner\n * and, for a panel fixed to the viewport, works out its coordinates. It\n * measures again on resize, on any scroll of the anchor's window (an\n * iframe's, when the anchor renders into one), and when the panel or the\n * anchor changes size, so a fixed panel follows its anchor and its own\n * content. Those measure once in the next frame, however many arrive before\n * it. With `followContent` it also measures after each render. A\n * measurement that changes nothing doesn't render again. A panel that's\n * replaced while it's open, as a portal moving to another target replaces\n * it, is watched from the next measurement on.\n *\n * The first measurement happens in a layout effect, so the panel is placed\n * before it paints. It needs both elements on the page while `active` is\n * on, and keeps the last placement when either is missing.\n *\n * @function useAnchoredPosition\n * @param anchorRef - The element the panel opens from.\n * @param panelRef - The floating panel.\n * @param options - Whether to measure, the corner asked for, and how the panel is placed.\n * @returns The corner the panel opens from, with coordinates when it is fixed.\n */\nexport function useAnchoredPosition(\n anchorRef: RefObject<HTMLElement | null>,\n panelRef: RefObject<HTMLElement | null>,\n {\n active,\n position,\n fixed,\n offset = 4,\n styledGap,\n matchWidth = false,\n followContent = false,\n }: UseAnchoredPositionOptions\n): AnchoredPosition {\n const [resolved, setResolved] = useState<AnchoredPosition>({\n position:\n position === undefined || position === 'auto' ? 'bottom-left' : position,\n });\n // The measurement while the panel is active, for `followContent` to run\n // again after a render.\n const updateRef = useRef<(() => void) | null>(null);\n\n useClientLayoutEffect(() => {\n if (!active) return undefined;\n const view =\n (anchorRef.current ?? panelRef.current)?.ownerDocument.defaultView ??\n window;\n // The panel's content can change size without the component holding the\n // hook rendering, as an image loading or a child's own state does, and\n // so can the anchor. Set up below, once `update` exists.\n let resizes: ResizeObserver | null = null;\n // The panel being watched. A portal that moves to another target\n // remounts the panel, as a popup that opens with the dialog it's in\n // does, so each measurement watches the one that's there now.\n let watched: HTMLElement | null = null;\n const update = () => {\n const anchor = anchorRef.current;\n const panel = panelRef.current;\n if (!anchor || !panel) return;\n if (panel !== watched) {\n if (watched) resizes?.unobserve(watched);\n resizes?.observe(panel);\n watched = panel;\n }\n const rect = anchor.getBoundingClientRect();\n const measured = panel.getBoundingClientRect();\n // The width the panel has once it's at least as wide as the anchor.\n const width = matchWidth\n ? Math.max(measured.width, rect.width)\n : measured.width;\n const { height } = measured;\n const corner =\n position === 'auto'\n ? resolveAuto(\n view,\n rect,\n width,\n height,\n offset + (styledGap?.(panel) ?? 0)\n )\n : (position ?? 'bottom-left');\n // The default lines up with the anchor's inline start, so right to\n // left a fixed panel lines up the right edges. A named corner doesn't,\n // and a panel in place takes the default's side from its stylesheet.\n const startsRight =\n position === undefined &&\n fixed &&\n getComputedStyle(anchor).direction === 'rtl';\n const next: AnchoredPosition = fixed\n ? {\n position: corner,\n top: corner.startsWith('bottom')\n ? rect.bottom + offset\n : rect.top - height - offset,\n left:\n corner.endsWith('left') && !startsRight\n ? rect.left\n : rect.right - width,\n ...(matchWidth ? { minWidth: rect.width } : {}),\n }\n : { position: corner };\n setResolved(prev => (samePosition(prev, next) ? prev : next));\n };\n\n // A scroll, a resize or a change in size measures in the next frame,\n // once however many of them ask.\n let frame = 0;\n const schedule = () => {\n if (frame) return;\n frame = view.requestAnimationFrame(() => {\n frame = 0;\n update();\n });\n };\n const Observer = view.ResizeObserver;\n resizes = typeof Observer === 'undefined' ? null : new Observer(schedule);\n if (anchorRef.current) resizes?.observe(anchorRef.current);\n\n updateRef.current = update;\n update();\n view.addEventListener('resize', schedule, { passive: true });\n view.addEventListener('scroll', schedule, { passive: true, capture: true });\n return () => {\n updateRef.current = null;\n view.cancelAnimationFrame(frame);\n resizes?.disconnect();\n view.removeEventListener('resize', schedule);\n view.removeEventListener('scroll', schedule, { capture: true });\n };\n }, [\n active,\n anchorRef,\n panelRef,\n position,\n fixed,\n offset,\n styledGap,\n matchWidth,\n ]);\n\n // After every render, on purpose: the panel's content is what changed.\n useClientLayoutEffect(() => {\n if (followContent) updateRef.current?.();\n });\n\n return resolved;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Internal helper — it MUST mutate and return the same function object
|
|
7
7
|
* (`Object.assign`), never wrap the base in a new function: identity checks
|
|
8
|
-
* like `child
|
|
8
|
+
* like `isElementOf(child, ModalCard)` (Modal) and Field's displayName-based
|
|
9
9
|
* child detection rely on the attached component being the same object as
|
|
10
10
|
* the separately exported one.
|
|
11
11
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withSubComponents.js","sources":["../../../src/helpers/withSubComponents.ts"],"sourcesContent":["/**\n * Attaches compound sub-components as statics on a base component and\n * optionally sets the parent's displayName.\n *\n * Internal helper — it MUST mutate and return the same function object\n * (`Object.assign`), never wrap the base in a new function: identity checks\n * like `child
|
|
1
|
+
{"version":3,"file":"withSubComponents.js","sources":["../../../src/helpers/withSubComponents.ts"],"sourcesContent":["/**\n * Attaches compound sub-components as statics on a base component and\n * optionally sets the parent's displayName.\n *\n * Internal helper — it MUST mutate and return the same function object\n * (`Object.assign`), never wrap the base in a new function: identity checks\n * like `isElementOf(child, ModalCard)` (Modal) and Field's displayName-based\n * child detection rely on the attached component being the same object as\n * the separately exported one.\n *\n * displayName is only ever set on the parent (and only when provided) —\n * sub-components keep whatever displayName they declare themselves.\n */\nexport function withSubComponents<\n Base extends { displayName?: string },\n Subs extends Record<string, unknown>,\n>(base: Base, subs: Subs, displayName?: string): Base & Subs {\n const compound = Object.assign(base, subs) as Base & Subs;\n if (displayName !== undefined) {\n compound.displayName = displayName;\n }\n return compound;\n}\n"],"names":[],"mappings":";AAAA;;;;;;;;;;;;AAYG;SACa,iBAAiB,CAG/B,IAAU,EAAE,IAAU,EAAE,WAAoB,EAAA;IAC5C,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAgB;AACzD,IAAA,IAAI,WAAW,KAAK,SAAS,EAAE;AAC7B,QAAA,QAAQ,CAAC,WAAW,GAAG,WAAW;IACpC;AACA,IAAA,OAAO,QAAQ;AACjB;;"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
2
|
'use client';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
|
-
import
|
|
4
|
+
import { createContext, forwardRef, useContext, useRef, useCallback, useEffect, useState, useId, useMemo } from 'react';
|
|
5
5
|
import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
|
|
6
6
|
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
7
7
|
import { withSubComponents } from '../helpers/withSubComponents.js';
|
|
8
|
+
import { isElementOf } from '../helpers/elementType.js';
|
|
8
9
|
import { flattenChildren } from '../helpers/flattenChildren.js';
|
|
9
10
|
import { useLatestRef } from '../helpers/useLatestRef.js';
|
|
10
11
|
import { useOverlayStack, useOverlayEscape } from '../helpers/overlayStack.js';
|
|
@@ -279,8 +280,8 @@ const AppFrameBurger = forwardRef(({ className, onClick, ...props }, ref) => {
|
|
|
279
280
|
} }));
|
|
280
281
|
});
|
|
281
282
|
AppFrameBurger.displayName = 'AppFrame.Burger';
|
|
282
|
-
const isMain = (child) =>
|
|
283
|
-
const isNav = (child) =>
|
|
283
|
+
const isMain = (child) => isElementOf(child, AppFrameMain);
|
|
284
|
+
const isNav = (child) => isElementOf(child, AppFrameNav);
|
|
284
285
|
/**
|
|
285
286
|
* The `AppFrame` component lays out an app's pages: a header across the top, a navigation column down the side, the page, and an aside and a footer, with the nav a drawer behind a burger on narrow screens.
|
|
286
287
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppFrame.js","sources":["../../../src/layout/AppFrame.tsx"],"sourcesContent":["import React, {\n createContext,\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { flattenChildren } from '../helpers/flattenChildren';\nimport { useLatestRef } from '../helpers/useLatestRef';\nimport { useOverlayEscape, useOverlayStack } from '../helpers/overlayStack';\nimport { useScrollLock } from '../helpers/scrollLock';\nimport { useInertSiblings } from '../helpers/useInertSiblings';\nimport { trapTab } from '../helpers/useFocusTrap';\nimport { useOverlayFocus } from '../helpers/useOverlayFocus';\nimport { useOutsidePress } from '../helpers/useOutsidePress';\nimport { getActiveElementInTree } from '../helpers/shadowDom';\nimport { useBulmaBreakpoint } from '../helpers/useBulmaViewport';\nimport { warnOnce } from '../helpers/devWarnings';\nimport { NavbarBurger } from '../components/Navbar';\nimport type { NavbarBurgerProps } from '../components/Navbar';\nimport { Delete } from '../elements/Delete';\nimport type { ContainerBreakpoint } from './Container';\n\n/**\n * The strings AppFrame renders, each with an English default. A key left\n * out, or set to an empty string, keeps its default.\n */\nexport interface AppFrameLabels {\n /**\n * The skip link's text.\n * @defaultValue 'Skip to main content'\n */\n skipLink?: string;\n /**\n * The nav's accessible name, when `AppFrame.Nav` has no `aria-label` or\n * `aria-labelledby`. Leave the word \"navigation\" out: the role says it.\n * @defaultValue 'Sections'\n */\n nav?: string;\n /**\n * The name of the close button at the top of the drawer.\n * @defaultValue 'Close navigation'\n */\n closeNav?: string;\n /**\n * The burger's name, when `AppFrame.Burger` has no `aria-label`.\n * @defaultValue 'menu', Navbar.Burger's\n */\n burger?: string;\n}\n\n/**\n * Props for the AppFrame component. The helper props, the other attributes\n * and the `ref` land on the root `<div>`.\n *\n * @extraProp {React.Ref<HTMLDivElement>} [ref] - Ref forwarded to the root `<div>`.\n */\nexport interface AppFrameProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /**\n * The parts, each a direct child: `Header`, `Nav`, `Main`, `Aside` and\n * `Footer`, in that order, since it's the Tab order. The frame reads the\n * `id` of `Main` and `Nav` off its children for the skip link and the\n * burger, so a part wrapped in a component of your own takes no `id` of\n * its own. Put the frame at the top of the page, outside any `main` or\n * sectioning element.\n */\n children?: React.ReactNode;\n /**\n * The width from which the nav is a column beside the page. Below it, the\n * nav is a drawer the burger opens. The stylesheet switches the layout at\n * Bulma's breakpoint, and the script reads the width at Bulma's default\n * breakpoints, so a theme that moves Bulma's breakpoints moves the layout\n * but not which state the burger toggles.\n * @defaultValue 'desktop'\n */\n breakpoint?: ContainerBreakpoint;\n /**\n * Whether the drawer is open, below the breakpoint. With it, the state is\n * yours: update it from `onNavOpenChange`. It has no effect from the\n * breakpoint up, where the frame asks to close it.\n */\n navOpen?: boolean;\n /** Whether the drawer starts open. */\n defaultNavOpen?: boolean;\n /**\n * Called with the state asked for: by the burger, `useAppFrame()`, Escape,\n * a press outside, the close button, a followed link (see\n * `AppFrame.Nav`'s `closeOnNavigate`), or the window being at or past the\n * breakpoint while it's open, which closes it.\n */\n onNavOpenChange?: (open: boolean) => void;\n /**\n * Whether the column is collapsed out of view, from the breakpoint up.\n * With it, the state is yours: update it from `onNavCollapsedChange`. It\n * has no effect below the breakpoint, where the drawer always shows the\n * whole nav.\n */\n navCollapsed?: boolean;\n /** Whether the column starts collapsed. */\n defaultNavCollapsed?: boolean;\n /** Called with the state asked for, by the burger or `useAppFrame()`. */\n onNavCollapsedChange?: (collapsed: boolean) => void;\n /**\n * Renders a skip link first, visible when it has focus, that moves focus\n * to `AppFrame.Main`. Turn it off only for a page that has its own.\n */\n skipLink?: boolean;\n /** The strings the frame renders. Each one left out keeps its English default. */\n labels?: AppFrameLabels;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * Props for AppFrame.Header, on its `<header>`, which sticks to the top.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<header>`.\n */\nexport interface AppFrameHeaderProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /**\n * Usually a `Navbar`, not `fixed`, with `AppFrame.Burger` in its\n * `Navbar.Brand`. A header taller than Bulma's navbar sets\n * `--bulma-app-frame-header-height` on `:root`, so the nav column, an\n * in-page link and focus clear it.\n */\n children?: React.ReactNode;\n}\n\n/**\n * Props for AppFrame.Nav, on its `<nav>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<nav>`.\n * @extraProp {string} [id] - The nav's id, which the burger's `aria-controls` points at. The frame makes one when there's none.\n * @extraProp {string} [aria-label] - The nav's name. Without it or `aria-labelledby`, it's the frame's `labels.nav`, \"Sections\".\n */\nexport interface AppFrameNavProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /**\n * Usually a `Menu`. A nav taller than the window scrolls on its own. With\n * `overlay`, the rest of the frame is inert while the drawer is open, so an\n * overlay opened from the drawer, such as a `Modal`, goes inside the nav or\n * renders outside the frame (Modal's `portal`).\n */\n children?: React.ReactNode;\n /**\n * The column's width, and the drawer's up to the window's width less a\n * strip of backdrop. A number is pixels, and a string any CSS length. It\n * writes `--bulma-app-frame-nav-width`, 16rem by default.\n */\n width?: number | string;\n /**\n * Shows a backdrop behind the drawer and makes it modal: Tab stays inside,\n * the page scroll locks, and the rest of the frame is inert, while what\n * renders outside it, such as a Modal opened from the drawer or a toast,\n * stays live. Without it the drawer covers the page without holding Tab,\n * and focus can move to the page beside it. Either way focus moves in as\n * it opens. Sidebar's `overlay`.\n */\n overlay?: boolean;\n /**\n * Closes the drawer on Escape, while it's the topmost overlay, so Escape\n * closes one layer at a time.\n */\n closeOnEscape?: boolean;\n /**\n * Closes the drawer on a press outside it while it's the topmost overlay:\n * a click on the backdrop, or without `overlay` a press anywhere outside\n * it. Focus goes back to the burger as it closes. Without `overlay`, the\n * press's own mousedown then moves it, as on any page: to what was pressed\n * if that takes focus, and otherwise off the burger to the page. A touch\n * that scrolls leaves it on the burger. A press on a control whose\n * `aria-controls` names the nav, such as the burger, is left to that\n * control. Popover's `closeOnClickOutside`.\n */\n closeOnClickOutside?: boolean;\n /**\n * Closes the drawer when a link inside it is followed with a plain click:\n * not with a modifier key held, and not on a link with a `target` other\n * than `_self` or a `download`. Only a link the nav holds in the page\n * counts, not one in a Modal or a popup it portals elsewhere. Focus goes\n * back to the burger, and the navigation takes it from there: a router\n * that moves focus on navigation moves it, and an in-page `#` link leaves\n * it at its target, as it does anywhere on the page. Turn it off for a\n * drawer of filters rather than links.\n */\n closeOnNavigate?: boolean;\n}\n\n/**\n * Props for AppFrame.Main, on the page's one `<main>`. Don't put another\n * `<main>` inside it. It takes your `id`, or a generated one, for the skip\n * link, and `tabIndex={-1}` so the link can move focus to it.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<main>`.\n * @extraProp {string} [id] - Its id, which the skip link points at. The frame makes one when there's none.\n */\nexport interface AppFrameMainProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** The page. A `Container` or a `Section` inside it gives Bulma's widths and padding. */\n children?: React.ReactNode;\n}\n\n/**\n * Props for AppFrame.Aside, on its `<aside>`, a complementary landmark.\n * Give it an `aria-label` when the page has more than one.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<aside>`.\n */\nexport interface AppFrameAsideProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** What sits beside the page: an outline, details, filters. */\n children?: React.ReactNode;\n /**\n * Its width from the breakpoint up; below it, the aside moves under the\n * page. A number is pixels, and a string any CSS length. It writes\n * `--bulma-app-frame-aside-width`, 20rem by default.\n */\n width?: number | string;\n}\n\n/**\n * Props for AppFrame.Footer, on its `<footer>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<footer>`.\n */\nexport interface AppFrameFooterProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Plain content, or bestax's `Footer` with `as=\"div\"` for Bulma's footer look. */\n children?: React.ReactNode;\n}\n\n/**\n * Props for AppFrame.Burger: Navbar.Burger's, less the state the frame\n * fills in. Its `<button>` opens and closes the drawer below the breakpoint\n * and collapses and expands the column from it up, with `aria-expanded` and\n * `aria-controls` following the nav. It shows at every width. Hide it from\n * the breakpoint up with the visibility helpers when the column never\n * collapses. Outside an AppFrame it controls nothing.\n */\nexport type AppFrameBurgerProps = Omit<\n NavbarBurgerProps,\n 'active' | 'aria-expanded' | 'aria-controls'\n>;\n\n/** What `useAppFrame()` returns. */\nexport interface AppFrameState {\n /** Whether the drawer is open. */\n navOpen: boolean;\n /** Asks to open or close the drawer, through `onNavOpenChange`. */\n setNavOpen: (open: boolean) => void;\n /** Whether the column is collapsed. */\n navCollapsed: boolean;\n /** Asks to collapse or expand the column, through `onNavCollapsedChange`. */\n setNavCollapsed: (collapsed: boolean) => void;\n /**\n * Whether the nav is a drawer at the current width. `false` on the server\n * and until the first layout effect has read the width.\n */\n isDrawer: boolean;\n /** Toggles whichever state applies at the current width, as the burger does. */\n toggleNav: () => void;\n /** The nav's `id`, for a custom toggle's `aria-controls`. */\n navId: string;\n}\n\n/** What the frame tells its parts. */\ninterface FrameContextValue {\n state: AppFrameState;\n labels: Required<Omit<AppFrameLabels, 'burger'>> &\n Pick<AppFrameLabels, 'burger'>;\n mainId: string;\n /**\n * Whether the frame has read the width, so it knows whether the nav is a\n * drawer. Not on the server, nor in the render that hydrates.\n */\n modeKnown: boolean;\n}\n\nconst FrameContext = createContext<FrameContextValue | null>(null);\n\n/** A width prop as CSS: a number is pixels. */\nconst cssLength = (value: number | string) =>\n typeof value === 'number' ? `${value}px` : value;\n\n/**\n * Hands `node` to a part's own ref and to the one forwarded to it. A\n * forwarded callback that returns a cleanup, as React 19 allows, has it held\n * in `cleanup` and run on detach in place of a call with `null`, on React 18\n * as well.\n */\nfunction setRefs<T>(\n node: T | null,\n own: React.MutableRefObject<T | null>,\n forwarded: React.ForwardedRef<T>,\n cleanup: React.MutableRefObject<(() => void) | null>\n) {\n own.current = node;\n if (typeof forwarded === 'function') {\n if (node === null) {\n const detach = cleanup.current;\n cleanup.current = null;\n if (detach) detach();\n else forwarded(null);\n return;\n }\n const returned: unknown = forwarded(node);\n cleanup.current =\n typeof returned === 'function' ? (returned as () => void) : null;\n } else if (forwarded) {\n forwarded.current = node;\n }\n}\n\n/**\n * Warns when a part has an `id` the frame couldn't read, because it isn't a\n * direct child, so the skip link or the burger points at the wrong one.\n */\nfunction warnOnUnreadId(part: string, id: unknown, expected: string) {\n if (id && id !== expected) {\n warnOnce(\n `AppFrame:${part}-id`,\n `[bestax-bulma] <AppFrame.${part}> has an id <AppFrame> can't read, ` +\n `so ${part === 'Main' ? 'the skip link' : \"the burger's aria-controls\"} ` +\n `misses it. Put AppFrame.${part} directly inside AppFrame.`\n );\n }\n}\n\n/**\n * The top bar of an AppFrame, which sticks while the page scrolls.\n *\n * @function\n * @param {AppFrameHeaderProps} props - Props for AppFrame.Header.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<header>`.\n * @returns {JSX.Element} The rendered header.\n */\nexport const AppFrameHeader = forwardRef<HTMLElement, AppFrameHeaderProps>(\n ({ className, children, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const headerClass = usePrefixedClassNames('app-frame-header');\n return (\n <header\n ref={ref}\n className={classNames(headerClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </header>\n );\n }\n);\n\nAppFrameHeader.displayName = 'AppFrame.Header';\n\n/**\n * The navigation of an AppFrame: a column beside the page from the breakpoint up, and a drawer below it.\n *\n * @function\n * @param {AppFrameNavProps} props - Props for AppFrame.Nav.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<nav>`.\n * @returns {JSX.Element} The rendered nav, and the drawer's backdrop.\n */\nexport const AppFrameNav = forwardRef<HTMLElement, AppFrameNavProps>(\n (\n {\n className,\n style,\n width,\n overlay = true,\n closeOnEscape = true,\n closeOnClickOutside = true,\n closeOnNavigate = true,\n onClick,\n onPointerDownCapture,\n children,\n ...props\n },\n ref\n ) => {\n const frame = useContext(FrameContext);\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const navClass = usePrefixedClassNames('app-frame-nav');\n const closeClass = usePrefixedClassNames('app-frame-close');\n const backdropClass = usePrefixedClassNames('app-frame-backdrop');\n const activeClass = usePrefixedClassNames('is-active');\n const navRef = useRef<HTMLElement | null>(null);\n const backdropRef = useRef<HTMLDivElement | null>(null);\n const navCleanupRef = useRef<(() => void) | null>(null);\n const navRefs = useCallback(\n (node: HTMLElement | null) => setRefs(node, navRef, ref, navCleanupRef),\n [ref]\n );\n\n const navId = rest.id || frame?.state.navId;\n if (frame) warnOnUnreadId('Nav', rest.id, frame.state.navId);\n // The drawer is open only below the breakpoint. Above it the nav is a\n // column, and none of what follows applies.\n const isDrawer = !!frame?.state.isDrawer;\n const open = isDrawer && !!frame?.state.navOpen;\n // The open drawer is Bulma's active state, on the nav as on a navbar's\n // menu, and on its backdrop.\n const shown = { [activeClass]: !!frame?.state.navOpen };\n const modal = open && overlay;\n const close = () => frame?.state.setNavOpen(false);\n\n // The rest of the frame goes inert: the header, the page, the aside and\n // the footer, but not the backdrop, which takes the presses outside the\n // drawer rather than let them through to what's under it. Not the whole\n // page, since a Modal or a toast renders beside the frame, often into an\n // element that's there before the drawer opens, and a Modal opened from\n // the drawer has to stay usable. First, so that as the drawer closes it\n // lets go before focus goes back to the burger: an inert element can't\n // take focus.\n useInertSiblings(navRef, modal, false, backdropRef);\n const isTopmost = useOverlayStack(open, overlay);\n useScrollLock(modal);\n\n // Tab stays inside while the drawer is the topmost modal overlay, as in\n // Sidebar, so a Modal opened from the drawer holds Tab until it closes.\n useEffect(() => {\n if (!modal) return undefined;\n return trapTab(\n () => navRef.current,\n window,\n () => isTopmost('modal')\n );\n }, [modal, isTopmost]);\n\n useOverlayEscape(open && closeOnEscape, isTopmost, close);\n\n // Without an overlay there's no backdrop: a press outside the drawer\n // closes it, as Popover's does, while it's the topmost overlay, so a\n // press in a Modal opened over it is the Modal's. A press on a control\n // that names the nav in its `aria-controls`, such as the burger, is left\n // to that control, which toggles it.\n const markInsidePress = useOutsidePress(\n navRef,\n open && !overlay && closeOnClickOutside,\n e => {\n const id = navRef.current?.id;\n const controlsNav = (node: EventTarget) =>\n !!(node as Element)\n .getAttribute?.('aria-controls')\n ?.split(/\\s+/)\n .includes(id as string);\n if (!isTopmost() || e.composedPath().some(controlsNav)) return;\n close();\n }\n );\n\n // Focus moves to the drawer's first tab stop, the close button, as it\n // opens, and back to whatever had it as it closes, unless it has left\n // the drawer. After a press outside, the press's own mousedown then takes\n // it wherever the press goes. When the window widened, the drawer is the\n // column again, and focus on its links stays; the close button hides\n // there, so focus on it, or dropped from it to <body>, goes back too.\n const isDrawerRef = useLatestRef(isDrawer);\n useOverlayFocus(navRef, open, {\n modal: overlay,\n restore: () => {\n if (isDrawerRef.current) return true;\n const nav = navRef.current as HTMLElement;\n const active = getActiveElementInTree(nav);\n return (\n !nav.contains(active) || !!active?.classList.contains(closeClass)\n );\n },\n });\n\n const handleClick = (e: React.MouseEvent<HTMLElement>) => {\n onClick?.(e);\n if (\n !open ||\n !closeOnNavigate ||\n e.button !== 0 ||\n e.metaKey ||\n e.ctrlKey ||\n e.shiftKey ||\n e.altKey\n ) {\n return;\n }\n // Not skipped for a click whose default was prevented: a router's\n // link prevents it to navigate without a page load. Only a link the nav\n // holds: one in a Modal portaled from it is the Modal's.\n const link = (e.target as Element).closest('a[href]');\n if (!link || !navRef.current?.contains(link)) return;\n if (link.hasAttribute('download')) return;\n const target = link.getAttribute('target');\n if (target && target !== '_self') return;\n close();\n };\n\n const navStyle =\n width === undefined\n ? style\n : ({\n ...style,\n '--bulma-app-frame-nav-width': cssLength(width),\n } as React.CSSProperties);\n\n return (\n <>\n <nav\n ref={navRefs}\n className={classNames(navClass, shown, bulmaHelperClasses, className)}\n style={navStyle}\n onClick={handleClick}\n onPointerDownCapture={e => {\n onPointerDownCapture?.(e);\n markInsidePress(e);\n }}\n {...rest}\n id={navId}\n aria-label={\n rest['aria-label'] ||\n (rest['aria-labelledby'] ? undefined : frame?.labels.nav)\n }\n >\n {frame && (\n <Delete\n className={closeClass}\n size=\"medium\"\n ariaLabel={frame.labels.closeNav}\n onClick={close}\n />\n )}\n {children}\n </nav>\n {frame && overlay && (\n <div\n ref={backdropRef}\n className={classNames(backdropClass, shown)}\n aria-hidden=\"true\"\n onClick={\n closeOnClickOutside\n ? () => {\n // Not under an overlay opened over the drawer.\n if (isTopmost()) close();\n }\n : undefined\n }\n />\n )}\n </>\n );\n }\n);\n\nAppFrameNav.displayName = 'AppFrame.Nav';\n\n/**\n * The page's main content in an AppFrame, which the skip link moves focus to.\n *\n * @function\n * @param {AppFrameMainProps} props - Props for AppFrame.Main.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<main>`.\n * @returns {JSX.Element} The rendered main.\n */\nexport const AppFrameMain = forwardRef<HTMLElement, AppFrameMainProps>(\n ({ className, children, ...props }, ref) => {\n const frame = useContext(FrameContext);\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const mainClass = usePrefixedClassNames('app-frame-main');\n if (frame) warnOnUnreadId('Main', rest.id, frame.mainId);\n return (\n <main\n ref={ref}\n tabIndex={-1}\n className={classNames(mainClass, bulmaHelperClasses, className)}\n {...rest}\n id={rest.id || frame?.mainId}\n >\n {children}\n </main>\n );\n }\n);\n\nAppFrameMain.displayName = 'AppFrame.Main';\n\n/**\n * Content beside the page in an AppFrame, which moves under it below the breakpoint.\n *\n * @function\n * @param {AppFrameAsideProps} props - Props for AppFrame.Aside.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<aside>`.\n * @returns {JSX.Element} The rendered aside.\n */\nexport const AppFrameAside = forwardRef<HTMLElement, AppFrameAsideProps>(\n ({ className, style, width, children, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const asideClass = usePrefixedClassNames('app-frame-aside');\n const asideStyle =\n width === undefined\n ? style\n : ({\n ...style,\n '--bulma-app-frame-aside-width': cssLength(width),\n } as React.CSSProperties);\n return (\n <aside\n ref={ref}\n className={classNames(asideClass, bulmaHelperClasses, className)}\n style={asideStyle}\n {...rest}\n >\n {children}\n </aside>\n );\n }\n);\n\nAppFrameAside.displayName = 'AppFrame.Aside';\n\n/**\n * The bottom of an AppFrame, under the page.\n *\n * @function\n * @param {AppFrameFooterProps} props - Props for AppFrame.Footer.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<footer>`.\n * @returns {JSX.Element} The rendered footer.\n */\nexport const AppFrameFooter = forwardRef<HTMLElement, AppFrameFooterProps>(\n ({ className, children, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const footerClass = usePrefixedClassNames('app-frame-footer');\n return (\n <footer\n ref={ref}\n className={classNames(footerClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </footer>\n );\n }\n);\n\nAppFrameFooter.displayName = 'AppFrame.Footer';\n\n/**\n * The button that opens the drawer, or collapses the column from the breakpoint up: Navbar.Burger, wired to the frame.\n *\n * @function\n * @param {AppFrameBurgerProps} props - Props for AppFrame.Burger.\n * @param {React.Ref<HTMLButtonElement>} ref - Forwarded ref to the `<button>`.\n * @returns {JSX.Element} The rendered burger.\n */\nexport const AppFrameBurger = forwardRef<\n HTMLButtonElement,\n AppFrameBurgerProps\n>(({ className, onClick, ...props }, ref) => {\n // Outside a frame it's a Navbar.Burger that controls nothing.\n const frame = useContext(FrameContext);\n const burgerClass = usePrefixedClassNames('app-frame-burger');\n const state = frame?.state;\n // Until the frame knows whether the nav is a drawer, the burger says\n // nothing it can't stand behind: no `aria-expanded`, and its bars. It can't\n // act before then anyway, since that's before hydration. Outside a frame\n // it's a plain Navbar.Burger.\n const known = !!frame?.modeKnown;\n const expanded =\n known && !!state && (state.isDrawer ? state.navOpen : !state.navCollapsed);\n return (\n <NavbarBurger\n ref={ref}\n {...props}\n className={classNames(burgerClass, className)}\n aria-label={props['aria-label'] || frame?.labels.burger}\n active={expanded}\n aria-expanded={frame && !known ? null : expanded}\n aria-controls={state?.navId}\n onClick={e => {\n onClick?.(e);\n if (e.defaultPrevented || !frame) return;\n // Safari, and Firefox on a Mac, don't focus a button that's pressed.\n // Focused, it's the element the drawer hands focus back to.\n e.currentTarget.focus();\n frame.state.toggleNav();\n }}\n />\n );\n});\n\nAppFrameBurger.displayName = 'AppFrame.Burger';\n\nconst isMain = (\n child: React.ReactNode\n): child is React.ReactElement<AppFrameMainProps> =>\n React.isValidElement(child) && child.type === AppFrameMain;\n\nconst isNav = (\n child: React.ReactNode\n): child is React.ReactElement<AppFrameNavProps> =>\n React.isValidElement(child) && child.type === AppFrameNav;\n\n/**\n * The `AppFrame` component lays out an app's pages: a header across the top, a navigation column down the side, the page, and an aside and a footer, with the nav a drawer behind a burger on narrow screens.\n *\n * The header and the column stick while the document scrolls, and a skip\n * link comes first. The layout is CSS, so the server renders it as the\n * browser does. It holds two states, each yours to control: whether the\n * drawer is open, and whether the column is collapsed. `AppFrame.Burger`\n * and `useAppFrame()` toggle whichever applies at the current width.\n *\n * @function\n * @param {AppFrameProps} props - Props for the AppFrame component.\n * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the root `<div>`.\n * @returns {JSX.Element} The rendered frame.\n *\n * @example\n * <AppFrame>\n * <AppFrame.Header>\n * <Navbar aria-label=\"Site\">\n * <Navbar.Brand>\n * <Navbar.Item href=\"/\">Acme</Navbar.Item>\n * <AppFrame.Burger />\n * </Navbar.Brand>\n * </Navbar>\n * </AppFrame.Header>\n * <AppFrame.Nav>\n * <Menu>…</Menu>\n * </AppFrame.Nav>\n * <AppFrame.Main>…</AppFrame.Main>\n * </AppFrame>\n */\nconst AppFrameRoot = forwardRef<HTMLDivElement, AppFrameProps>(\n (\n {\n className,\n textColor,\n bgColor,\n breakpoint = 'desktop',\n navOpen: navOpenProp,\n defaultNavOpen = false,\n onNavOpenChange,\n navCollapsed: navCollapsedProp,\n defaultNavCollapsed = false,\n onNavCollapsedChange,\n skipLink = true,\n labels,\n children,\n ...props\n },\n ref\n ) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const rootRef = useRef<HTMLDivElement | null>(null);\n const rootCleanupRef = useRef<(() => void) | null>(null);\n const rootRefs = useCallback(\n (node: HTMLDivElement | null) =>\n setRefs(node, rootRef, ref, rootCleanupRef),\n [ref]\n );\n\n const [openState, setOpenState] = useState(defaultNavOpen);\n const [collapsedState, setCollapsedState] = useState(defaultNavCollapsed);\n const navOpen = navOpenProp ?? openState;\n const navCollapsed = navCollapsedProp ?? collapsedState;\n const openControlled = navOpenProp !== undefined;\n const collapsedControlled = navCollapsedProp !== undefined;\n const onNavOpenChangeRef = useLatestRef(onNavOpenChange);\n const onNavCollapsedChangeRef = useLatestRef(onNavCollapsedChange);\n\n const setNavOpen = useCallback(\n (open: boolean) => {\n onNavOpenChangeRef.current?.(open);\n if (!openControlled) setOpenState(open);\n },\n [openControlled, onNavOpenChangeRef]\n );\n const setNavCollapsed = useCallback(\n (collapsed: boolean) => {\n onNavCollapsedChangeRef.current?.(collapsed);\n if (!collapsedControlled) setCollapsedState(collapsed);\n },\n [collapsedControlled, onNavCollapsedChangeRef]\n );\n\n // The layout switches in the stylesheet. The script needs the width\n // only to know which state the burger toggles, and to close the drawer\n // once the window is at or past the breakpoint.\n const wide = useBulmaBreakpoint(breakpoint);\n const isDrawer = wide === false;\n const isWide = wide === true;\n\n useEffect(() => {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- the width is known only in the browser, and the drawer closes in answer to it\n if (isWide && navOpen) setNavOpen(false);\n }, [isWide, navOpen, setNavOpen]);\n\n const toggleNav = useCallback(() => {\n if (isDrawer) setNavOpen(!navOpen);\n else setNavCollapsed(!navCollapsed);\n }, [isDrawer, navOpen, navCollapsed, setNavOpen, setNavCollapsed]);\n\n // The ids of the parts the skip link and the burger point at, read off\n // the children so the server renders them too.\n const baseId = useId();\n const parts = flattenChildren(children);\n const mainId = parts.find(isMain)?.props.id || `${baseId}-main`;\n const navId = parts.find(isNav)?.props.id || `${baseId}-nav`;\n\n const { skipLink: skipText, nav, closeNav, burger } = labels ?? {};\n const value = useMemo<FrameContextValue>(\n () => ({\n state: {\n navOpen,\n setNavOpen,\n navCollapsed,\n setNavCollapsed,\n isDrawer,\n toggleNav,\n navId,\n },\n labels: {\n skipLink: skipText || 'Skip to main content',\n nav: nav || 'Sections',\n closeNav: closeNav || 'Close navigation',\n burger: burger || undefined,\n },\n mainId,\n modeKnown: wide !== null,\n }),\n [\n navOpen,\n setNavOpen,\n navCollapsed,\n setNavCollapsed,\n isDrawer,\n toggleNav,\n navId,\n skipText,\n nav,\n closeNav,\n burger,\n mainId,\n wide,\n ]\n );\n\n // Its header and footer are the page's banner and contentinfo only\n // outside `main` and sectioning content, as HTML-AAM maps them.\n useEffect(() => {\n const host = rootRef.current?.parentElement?.closest(\n 'main, article, aside, nav, section'\n );\n if (host) {\n warnOnce(\n 'AppFrame:nested',\n `[bestax-bulma] <AppFrame> sits inside a <${host.localName}>, where ` +\n \"its header and footer are no longer the page's banner and \" +\n 'contentinfo landmarks. Put AppFrame at the top of the page.'\n );\n }\n }, []);\n\n const rootClass = usePrefixedClassNames('app-frame', {\n [`is-${breakpoint}`]: true,\n 'is-nav-collapsed': navCollapsed,\n });\n const skipClass = usePrefixedClassNames('app-frame-skip-link');\n\n return (\n <div\n ref={rootRefs}\n className={classNames(rootClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {skipLink && (\n <a className={skipClass} href={`#${mainId}`}>\n {value.labels.skipLink}\n </a>\n )}\n <FrameContext.Provider value={value}>{children}</FrameContext.Provider>\n </div>\n );\n }\n);\n\n/**\n * The frame's state, for a control of your own inside it: whether the\n * drawer is open and the column collapsed, setters that go through the\n * frame's callbacks, and `toggleNav()`, which does what the burger does.\n * Outside an AppFrame it's `null`, as the library's other context hooks\n * are, so a control that can sit outside one renders a fallback or nothing.\n *\n * @function useAppFrame\n * @returns The frame's state, or `null` outside an AppFrame.\n */\nexport function useAppFrame(): AppFrameState | null {\n return useContext(FrameContext)?.state ?? null;\n}\n\nexport const AppFrame = withSubComponents(\n AppFrameRoot,\n {\n Header: AppFrameHeader,\n Nav: AppFrameNav,\n Main: AppFrameMain,\n Aside: AppFrameAside,\n Footer: AppFrameFooter,\n Burger: AppFrameBurger,\n },\n 'AppFrame'\n);\n\nexport default AppFrame;\n"],"names":["_jsx","_jsxs","_Fragment"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AppFrame.js","sources":["../../../src/layout/AppFrame.tsx"],"sourcesContent":["import React, {\n createContext,\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { isElementOf } from '../helpers/elementType';\nimport { flattenChildren } from '../helpers/flattenChildren';\nimport { useLatestRef } from '../helpers/useLatestRef';\nimport { useOverlayEscape, useOverlayStack } from '../helpers/overlayStack';\nimport { useScrollLock } from '../helpers/scrollLock';\nimport { useInertSiblings } from '../helpers/useInertSiblings';\nimport { trapTab } from '../helpers/useFocusTrap';\nimport { useOverlayFocus } from '../helpers/useOverlayFocus';\nimport { useOutsidePress } from '../helpers/useOutsidePress';\nimport { getActiveElementInTree } from '../helpers/shadowDom';\nimport { useBulmaBreakpoint } from '../helpers/useBulmaViewport';\nimport { warnOnce } from '../helpers/devWarnings';\nimport { NavbarBurger } from '../components/Navbar';\nimport type { NavbarBurgerProps } from '../components/Navbar';\nimport { Delete } from '../elements/Delete';\nimport type { ContainerBreakpoint } from './Container';\n\n/**\n * The strings AppFrame renders, each with an English default. A key left\n * out, or set to an empty string, keeps its default.\n */\nexport interface AppFrameLabels {\n /**\n * The skip link's text.\n * @defaultValue 'Skip to main content'\n */\n skipLink?: string;\n /**\n * The nav's accessible name, when `AppFrame.Nav` has no `aria-label` or\n * `aria-labelledby`. Leave the word \"navigation\" out: the role says it.\n * @defaultValue 'Sections'\n */\n nav?: string;\n /**\n * The name of the close button at the top of the drawer.\n * @defaultValue 'Close navigation'\n */\n closeNav?: string;\n /**\n * The burger's name, when `AppFrame.Burger` has no `aria-label`.\n * @defaultValue 'menu', Navbar.Burger's\n */\n burger?: string;\n}\n\n/**\n * Props for the AppFrame component. The helper props, the other attributes\n * and the `ref` land on the root `<div>`.\n *\n * @extraProp {React.Ref<HTMLDivElement>} [ref] - Ref forwarded to the root `<div>`.\n */\nexport interface AppFrameProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /**\n * The parts, each a direct child: `Header`, `Nav`, `Main`, `Aside` and\n * `Footer`, in that order, since it's the Tab order. The frame reads the\n * `id` of `Main` and `Nav` off its children for the skip link and the\n * burger, so a part wrapped in a component of your own takes no `id` of\n * its own. Put the frame at the top of the page, outside any `main` or\n * sectioning element.\n */\n children?: React.ReactNode;\n /**\n * The width from which the nav is a column beside the page. Below it, the\n * nav is a drawer the burger opens. The stylesheet switches the layout at\n * Bulma's breakpoint, and the script reads the width at Bulma's default\n * breakpoints, so a theme that moves Bulma's breakpoints moves the layout\n * but not which state the burger toggles.\n * @defaultValue 'desktop'\n */\n breakpoint?: ContainerBreakpoint;\n /**\n * Whether the drawer is open, below the breakpoint. With it, the state is\n * yours: update it from `onNavOpenChange`. It has no effect from the\n * breakpoint up, where the frame asks to close it.\n */\n navOpen?: boolean;\n /** Whether the drawer starts open. */\n defaultNavOpen?: boolean;\n /**\n * Called with the state asked for: by the burger, `useAppFrame()`, Escape,\n * a press outside, the close button, a followed link (see\n * `AppFrame.Nav`'s `closeOnNavigate`), or the window being at or past the\n * breakpoint while it's open, which closes it.\n */\n onNavOpenChange?: (open: boolean) => void;\n /**\n * Whether the column is collapsed out of view, from the breakpoint up.\n * With it, the state is yours: update it from `onNavCollapsedChange`. It\n * has no effect below the breakpoint, where the drawer always shows the\n * whole nav.\n */\n navCollapsed?: boolean;\n /** Whether the column starts collapsed. */\n defaultNavCollapsed?: boolean;\n /** Called with the state asked for, by the burger or `useAppFrame()`. */\n onNavCollapsedChange?: (collapsed: boolean) => void;\n /**\n * Renders a skip link first, visible when it has focus, that moves focus\n * to `AppFrame.Main`. Turn it off only for a page that has its own.\n */\n skipLink?: boolean;\n /** The strings the frame renders. Each one left out keeps its English default. */\n labels?: AppFrameLabels;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * Props for AppFrame.Header, on its `<header>`, which sticks to the top.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<header>`.\n */\nexport interface AppFrameHeaderProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /**\n * Usually a `Navbar`, not `fixed`, with `AppFrame.Burger` in its\n * `Navbar.Brand`. A header taller than Bulma's navbar sets\n * `--bulma-app-frame-header-height` on `:root`, so the nav column, an\n * in-page link and focus clear it.\n */\n children?: React.ReactNode;\n}\n\n/**\n * Props for AppFrame.Nav, on its `<nav>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<nav>`.\n * @extraProp {string} [id] - The nav's id, which the burger's `aria-controls` points at. The frame makes one when there's none.\n * @extraProp {string} [aria-label] - The nav's name. Without it or `aria-labelledby`, it's the frame's `labels.nav`, \"Sections\".\n */\nexport interface AppFrameNavProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /**\n * Usually a `Menu`. A nav taller than the window scrolls on its own. With\n * `overlay`, the rest of the frame is inert while the drawer is open, so an\n * overlay opened from the drawer, such as a `Modal`, goes inside the nav or\n * renders outside the frame (Modal's `portal`).\n */\n children?: React.ReactNode;\n /**\n * The column's width, and the drawer's up to the window's width less a\n * strip of backdrop. A number is pixels, and a string any CSS length. It\n * writes `--bulma-app-frame-nav-width`, 16rem by default.\n */\n width?: number | string;\n /**\n * Shows a backdrop behind the drawer and makes it modal: Tab stays inside,\n * the page scroll locks, and the rest of the frame is inert, while what\n * renders outside it, such as a Modal opened from the drawer or a toast,\n * stays live. Without it the drawer covers the page without holding Tab,\n * and focus can move to the page beside it. Either way focus moves in as\n * it opens. Sidebar's `overlay`.\n */\n overlay?: boolean;\n /**\n * Closes the drawer on Escape, while it's the topmost overlay, so Escape\n * closes one layer at a time.\n */\n closeOnEscape?: boolean;\n /**\n * Closes the drawer on a press outside it while it's the topmost overlay:\n * a click on the backdrop, or without `overlay` a press anywhere outside\n * it. Focus goes back to the burger as it closes. Without `overlay`, the\n * press's own mousedown then moves it, as on any page: to what was pressed\n * if that takes focus, and otherwise off the burger to the page. A touch\n * that scrolls leaves it on the burger. A press on a control whose\n * `aria-controls` names the nav, such as the burger, is left to that\n * control. Popover's `closeOnClickOutside`.\n */\n closeOnClickOutside?: boolean;\n /**\n * Closes the drawer when a link inside it is followed with a plain click:\n * not with a modifier key held, and not on a link with a `target` other\n * than `_self` or a `download`. Only a link the nav holds in the page\n * counts, not one in a Modal or a popup it portals elsewhere. Focus goes\n * back to the burger, and the navigation takes it from there: a router\n * that moves focus on navigation moves it, and an in-page `#` link leaves\n * it at its target, as it does anywhere on the page. Turn it off for a\n * drawer of filters rather than links.\n */\n closeOnNavigate?: boolean;\n}\n\n/**\n * Props for AppFrame.Main, on the page's one `<main>`. Don't put another\n * `<main>` inside it. It takes your `id`, or a generated one, for the skip\n * link, and `tabIndex={-1}` so the link can move focus to it.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<main>`.\n * @extraProp {string} [id] - Its id, which the skip link points at. The frame makes one when there's none.\n */\nexport interface AppFrameMainProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** The page. A `Container` or a `Section` inside it gives Bulma's widths and padding. */\n children?: React.ReactNode;\n}\n\n/**\n * Props for AppFrame.Aside, on its `<aside>`, a complementary landmark.\n * Give it an `aria-label` when the page has more than one.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<aside>`.\n */\nexport interface AppFrameAsideProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** What sits beside the page: an outline, details, filters. */\n children?: React.ReactNode;\n /**\n * Its width from the breakpoint up; below it, the aside moves under the\n * page. A number is pixels, and a string any CSS length. It writes\n * `--bulma-app-frame-aside-width`, 20rem by default.\n */\n width?: number | string;\n}\n\n/**\n * Props for AppFrame.Footer, on its `<footer>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<footer>`.\n */\nexport interface AppFrameFooterProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Plain content, or bestax's `Footer` with `as=\"div\"` for Bulma's footer look. */\n children?: React.ReactNode;\n}\n\n/**\n * Props for AppFrame.Burger: Navbar.Burger's, less the state the frame\n * fills in. Its `<button>` opens and closes the drawer below the breakpoint\n * and collapses and expands the column from it up, with `aria-expanded` and\n * `aria-controls` following the nav. It shows at every width. Hide it from\n * the breakpoint up with the visibility helpers when the column never\n * collapses. Outside an AppFrame it controls nothing.\n */\nexport type AppFrameBurgerProps = Omit<\n NavbarBurgerProps,\n 'active' | 'aria-expanded' | 'aria-controls'\n>;\n\n/** What `useAppFrame()` returns. */\nexport interface AppFrameState {\n /** Whether the drawer is open. */\n navOpen: boolean;\n /** Asks to open or close the drawer, through `onNavOpenChange`. */\n setNavOpen: (open: boolean) => void;\n /** Whether the column is collapsed. */\n navCollapsed: boolean;\n /** Asks to collapse or expand the column, through `onNavCollapsedChange`. */\n setNavCollapsed: (collapsed: boolean) => void;\n /**\n * Whether the nav is a drawer at the current width. `false` on the server\n * and until the first layout effect has read the width.\n */\n isDrawer: boolean;\n /** Toggles whichever state applies at the current width, as the burger does. */\n toggleNav: () => void;\n /** The nav's `id`, for a custom toggle's `aria-controls`. */\n navId: string;\n}\n\n/** What the frame tells its parts. */\ninterface FrameContextValue {\n state: AppFrameState;\n labels: Required<Omit<AppFrameLabels, 'burger'>> &\n Pick<AppFrameLabels, 'burger'>;\n mainId: string;\n /**\n * Whether the frame has read the width, so it knows whether the nav is a\n * drawer. Not on the server, nor in the render that hydrates.\n */\n modeKnown: boolean;\n}\n\nconst FrameContext = createContext<FrameContextValue | null>(null);\n\n/** A width prop as CSS: a number is pixels. */\nconst cssLength = (value: number | string) =>\n typeof value === 'number' ? `${value}px` : value;\n\n/**\n * Hands `node` to a part's own ref and to the one forwarded to it. A\n * forwarded callback that returns a cleanup, as React 19 allows, has it held\n * in `cleanup` and run on detach in place of a call with `null`, on React 18\n * as well.\n */\nfunction setRefs<T>(\n node: T | null,\n own: React.MutableRefObject<T | null>,\n forwarded: React.ForwardedRef<T>,\n cleanup: React.MutableRefObject<(() => void) | null>\n) {\n own.current = node;\n if (typeof forwarded === 'function') {\n if (node === null) {\n const detach = cleanup.current;\n cleanup.current = null;\n if (detach) detach();\n else forwarded(null);\n return;\n }\n const returned: unknown = forwarded(node);\n cleanup.current =\n typeof returned === 'function' ? (returned as () => void) : null;\n } else if (forwarded) {\n forwarded.current = node;\n }\n}\n\n/**\n * Warns when a part has an `id` the frame couldn't read, because it isn't a\n * direct child, so the skip link or the burger points at the wrong one.\n */\nfunction warnOnUnreadId(part: string, id: unknown, expected: string) {\n if (id && id !== expected) {\n warnOnce(\n `AppFrame:${part}-id`,\n `[bestax-bulma] <AppFrame.${part}> has an id <AppFrame> can't read, ` +\n `so ${part === 'Main' ? 'the skip link' : \"the burger's aria-controls\"} ` +\n `misses it. Put AppFrame.${part} directly inside AppFrame.`\n );\n }\n}\n\n/**\n * The top bar of an AppFrame, which sticks while the page scrolls.\n *\n * @function\n * @param {AppFrameHeaderProps} props - Props for AppFrame.Header.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<header>`.\n * @returns {JSX.Element} The rendered header.\n */\nexport const AppFrameHeader = forwardRef<HTMLElement, AppFrameHeaderProps>(\n ({ className, children, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const headerClass = usePrefixedClassNames('app-frame-header');\n return (\n <header\n ref={ref}\n className={classNames(headerClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </header>\n );\n }\n);\n\nAppFrameHeader.displayName = 'AppFrame.Header';\n\n/**\n * The navigation of an AppFrame: a column beside the page from the breakpoint up, and a drawer below it.\n *\n * @function\n * @param {AppFrameNavProps} props - Props for AppFrame.Nav.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<nav>`.\n * @returns {JSX.Element} The rendered nav, and the drawer's backdrop.\n */\nexport const AppFrameNav = forwardRef<HTMLElement, AppFrameNavProps>(\n (\n {\n className,\n style,\n width,\n overlay = true,\n closeOnEscape = true,\n closeOnClickOutside = true,\n closeOnNavigate = true,\n onClick,\n onPointerDownCapture,\n children,\n ...props\n },\n ref\n ) => {\n const frame = useContext(FrameContext);\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const navClass = usePrefixedClassNames('app-frame-nav');\n const closeClass = usePrefixedClassNames('app-frame-close');\n const backdropClass = usePrefixedClassNames('app-frame-backdrop');\n const activeClass = usePrefixedClassNames('is-active');\n const navRef = useRef<HTMLElement | null>(null);\n const backdropRef = useRef<HTMLDivElement | null>(null);\n const navCleanupRef = useRef<(() => void) | null>(null);\n const navRefs = useCallback(\n (node: HTMLElement | null) => setRefs(node, navRef, ref, navCleanupRef),\n [ref]\n );\n\n const navId = rest.id || frame?.state.navId;\n if (frame) warnOnUnreadId('Nav', rest.id, frame.state.navId);\n // The drawer is open only below the breakpoint. Above it the nav is a\n // column, and none of what follows applies.\n const isDrawer = !!frame?.state.isDrawer;\n const open = isDrawer && !!frame?.state.navOpen;\n // The open drawer is Bulma's active state, on the nav as on a navbar's\n // menu, and on its backdrop.\n const shown = { [activeClass]: !!frame?.state.navOpen };\n const modal = open && overlay;\n const close = () => frame?.state.setNavOpen(false);\n\n // The rest of the frame goes inert: the header, the page, the aside and\n // the footer, but not the backdrop, which takes the presses outside the\n // drawer rather than let them through to what's under it. Not the whole\n // page, since a Modal or a toast renders beside the frame, often into an\n // element that's there before the drawer opens, and a Modal opened from\n // the drawer has to stay usable. First, so that as the drawer closes it\n // lets go before focus goes back to the burger: an inert element can't\n // take focus.\n useInertSiblings(navRef, modal, false, backdropRef);\n const isTopmost = useOverlayStack(open, overlay);\n useScrollLock(modal);\n\n // Tab stays inside while the drawer is the topmost modal overlay, as in\n // Sidebar, so a Modal opened from the drawer holds Tab until it closes.\n useEffect(() => {\n if (!modal) return undefined;\n return trapTab(\n () => navRef.current,\n window,\n () => isTopmost('modal')\n );\n }, [modal, isTopmost]);\n\n useOverlayEscape(open && closeOnEscape, isTopmost, close);\n\n // Without an overlay there's no backdrop: a press outside the drawer\n // closes it, as Popover's does, while it's the topmost overlay, so a\n // press in a Modal opened over it is the Modal's. A press on a control\n // that names the nav in its `aria-controls`, such as the burger, is left\n // to that control, which toggles it.\n const markInsidePress = useOutsidePress(\n navRef,\n open && !overlay && closeOnClickOutside,\n e => {\n const id = navRef.current?.id;\n const controlsNav = (node: EventTarget) =>\n !!(node as Element)\n .getAttribute?.('aria-controls')\n ?.split(/\\s+/)\n .includes(id as string);\n if (!isTopmost() || e.composedPath().some(controlsNav)) return;\n close();\n }\n );\n\n // Focus moves to the drawer's first tab stop, the close button, as it\n // opens, and back to whatever had it as it closes, unless it has left\n // the drawer. After a press outside, the press's own mousedown then takes\n // it wherever the press goes. When the window widened, the drawer is the\n // column again, and focus on its links stays; the close button hides\n // there, so focus on it, or dropped from it to <body>, goes back too.\n const isDrawerRef = useLatestRef(isDrawer);\n useOverlayFocus(navRef, open, {\n modal: overlay,\n restore: () => {\n if (isDrawerRef.current) return true;\n const nav = navRef.current as HTMLElement;\n const active = getActiveElementInTree(nav);\n return (\n !nav.contains(active) || !!active?.classList.contains(closeClass)\n );\n },\n });\n\n const handleClick = (e: React.MouseEvent<HTMLElement>) => {\n onClick?.(e);\n if (\n !open ||\n !closeOnNavigate ||\n e.button !== 0 ||\n e.metaKey ||\n e.ctrlKey ||\n e.shiftKey ||\n e.altKey\n ) {\n return;\n }\n // Not skipped for a click whose default was prevented: a router's\n // link prevents it to navigate without a page load. Only a link the nav\n // holds: one in a Modal portaled from it is the Modal's.\n const link = (e.target as Element).closest('a[href]');\n if (!link || !navRef.current?.contains(link)) return;\n if (link.hasAttribute('download')) return;\n const target = link.getAttribute('target');\n if (target && target !== '_self') return;\n close();\n };\n\n const navStyle =\n width === undefined\n ? style\n : ({\n ...style,\n '--bulma-app-frame-nav-width': cssLength(width),\n } as React.CSSProperties);\n\n return (\n <>\n <nav\n ref={navRefs}\n className={classNames(navClass, shown, bulmaHelperClasses, className)}\n style={navStyle}\n onClick={handleClick}\n onPointerDownCapture={e => {\n onPointerDownCapture?.(e);\n markInsidePress(e);\n }}\n {...rest}\n id={navId}\n aria-label={\n rest['aria-label'] ||\n (rest['aria-labelledby'] ? undefined : frame?.labels.nav)\n }\n >\n {frame && (\n <Delete\n className={closeClass}\n size=\"medium\"\n ariaLabel={frame.labels.closeNav}\n onClick={close}\n />\n )}\n {children}\n </nav>\n {frame && overlay && (\n <div\n ref={backdropRef}\n className={classNames(backdropClass, shown)}\n aria-hidden=\"true\"\n onClick={\n closeOnClickOutside\n ? () => {\n // Not under an overlay opened over the drawer.\n if (isTopmost()) close();\n }\n : undefined\n }\n />\n )}\n </>\n );\n }\n);\n\nAppFrameNav.displayName = 'AppFrame.Nav';\n\n/**\n * The page's main content in an AppFrame, which the skip link moves focus to.\n *\n * @function\n * @param {AppFrameMainProps} props - Props for AppFrame.Main.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<main>`.\n * @returns {JSX.Element} The rendered main.\n */\nexport const AppFrameMain = forwardRef<HTMLElement, AppFrameMainProps>(\n ({ className, children, ...props }, ref) => {\n const frame = useContext(FrameContext);\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const mainClass = usePrefixedClassNames('app-frame-main');\n if (frame) warnOnUnreadId('Main', rest.id, frame.mainId);\n return (\n <main\n ref={ref}\n tabIndex={-1}\n className={classNames(mainClass, bulmaHelperClasses, className)}\n {...rest}\n id={rest.id || frame?.mainId}\n >\n {children}\n </main>\n );\n }\n);\n\nAppFrameMain.displayName = 'AppFrame.Main';\n\n/**\n * Content beside the page in an AppFrame, which moves under it below the breakpoint.\n *\n * @function\n * @param {AppFrameAsideProps} props - Props for AppFrame.Aside.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<aside>`.\n * @returns {JSX.Element} The rendered aside.\n */\nexport const AppFrameAside = forwardRef<HTMLElement, AppFrameAsideProps>(\n ({ className, style, width, children, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const asideClass = usePrefixedClassNames('app-frame-aside');\n const asideStyle =\n width === undefined\n ? style\n : ({\n ...style,\n '--bulma-app-frame-aside-width': cssLength(width),\n } as React.CSSProperties);\n return (\n <aside\n ref={ref}\n className={classNames(asideClass, bulmaHelperClasses, className)}\n style={asideStyle}\n {...rest}\n >\n {children}\n </aside>\n );\n }\n);\n\nAppFrameAside.displayName = 'AppFrame.Aside';\n\n/**\n * The bottom of an AppFrame, under the page.\n *\n * @function\n * @param {AppFrameFooterProps} props - Props for AppFrame.Footer.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<footer>`.\n * @returns {JSX.Element} The rendered footer.\n */\nexport const AppFrameFooter = forwardRef<HTMLElement, AppFrameFooterProps>(\n ({ className, children, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const footerClass = usePrefixedClassNames('app-frame-footer');\n return (\n <footer\n ref={ref}\n className={classNames(footerClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </footer>\n );\n }\n);\n\nAppFrameFooter.displayName = 'AppFrame.Footer';\n\n/**\n * The button that opens the drawer, or collapses the column from the breakpoint up: Navbar.Burger, wired to the frame.\n *\n * @function\n * @param {AppFrameBurgerProps} props - Props for AppFrame.Burger.\n * @param {React.Ref<HTMLButtonElement>} ref - Forwarded ref to the `<button>`.\n * @returns {JSX.Element} The rendered burger.\n */\nexport const AppFrameBurger = forwardRef<\n HTMLButtonElement,\n AppFrameBurgerProps\n>(({ className, onClick, ...props }, ref) => {\n // Outside a frame it's a Navbar.Burger that controls nothing.\n const frame = useContext(FrameContext);\n const burgerClass = usePrefixedClassNames('app-frame-burger');\n const state = frame?.state;\n // Until the frame knows whether the nav is a drawer, the burger says\n // nothing it can't stand behind: no `aria-expanded`, and its bars. It can't\n // act before then anyway, since that's before hydration. Outside a frame\n // it's a plain Navbar.Burger.\n const known = !!frame?.modeKnown;\n const expanded =\n known && !!state && (state.isDrawer ? state.navOpen : !state.navCollapsed);\n return (\n <NavbarBurger\n ref={ref}\n {...props}\n className={classNames(burgerClass, className)}\n aria-label={props['aria-label'] || frame?.labels.burger}\n active={expanded}\n aria-expanded={frame && !known ? null : expanded}\n aria-controls={state?.navId}\n onClick={e => {\n onClick?.(e);\n if (e.defaultPrevented || !frame) return;\n // Safari, and Firefox on a Mac, don't focus a button that's pressed.\n // Focused, it's the element the drawer hands focus back to.\n e.currentTarget.focus();\n frame.state.toggleNav();\n }}\n />\n );\n});\n\nAppFrameBurger.displayName = 'AppFrame.Burger';\n\nconst isMain = (\n child: React.ReactNode\n): child is React.ReactElement<AppFrameMainProps> =>\n isElementOf(child, AppFrameMain);\n\nconst isNav = (\n child: React.ReactNode\n): child is React.ReactElement<AppFrameNavProps> =>\n isElementOf(child, AppFrameNav);\n\n/**\n * The `AppFrame` component lays out an app's pages: a header across the top, a navigation column down the side, the page, and an aside and a footer, with the nav a drawer behind a burger on narrow screens.\n *\n * The header and the column stick while the document scrolls, and a skip\n * link comes first. The layout is CSS, so the server renders it as the\n * browser does. It holds two states, each yours to control: whether the\n * drawer is open, and whether the column is collapsed. `AppFrame.Burger`\n * and `useAppFrame()` toggle whichever applies at the current width.\n *\n * @function\n * @param {AppFrameProps} props - Props for the AppFrame component.\n * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the root `<div>`.\n * @returns {JSX.Element} The rendered frame.\n *\n * @example\n * <AppFrame>\n * <AppFrame.Header>\n * <Navbar aria-label=\"Site\">\n * <Navbar.Brand>\n * <Navbar.Item href=\"/\">Acme</Navbar.Item>\n * <AppFrame.Burger />\n * </Navbar.Brand>\n * </Navbar>\n * </AppFrame.Header>\n * <AppFrame.Nav>\n * <Menu>…</Menu>\n * </AppFrame.Nav>\n * <AppFrame.Main>…</AppFrame.Main>\n * </AppFrame>\n */\nconst AppFrameRoot = forwardRef<HTMLDivElement, AppFrameProps>(\n (\n {\n className,\n textColor,\n bgColor,\n breakpoint = 'desktop',\n navOpen: navOpenProp,\n defaultNavOpen = false,\n onNavOpenChange,\n navCollapsed: navCollapsedProp,\n defaultNavCollapsed = false,\n onNavCollapsedChange,\n skipLink = true,\n labels,\n children,\n ...props\n },\n ref\n ) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const rootRef = useRef<HTMLDivElement | null>(null);\n const rootCleanupRef = useRef<(() => void) | null>(null);\n const rootRefs = useCallback(\n (node: HTMLDivElement | null) =>\n setRefs(node, rootRef, ref, rootCleanupRef),\n [ref]\n );\n\n const [openState, setOpenState] = useState(defaultNavOpen);\n const [collapsedState, setCollapsedState] = useState(defaultNavCollapsed);\n const navOpen = navOpenProp ?? openState;\n const navCollapsed = navCollapsedProp ?? collapsedState;\n const openControlled = navOpenProp !== undefined;\n const collapsedControlled = navCollapsedProp !== undefined;\n const onNavOpenChangeRef = useLatestRef(onNavOpenChange);\n const onNavCollapsedChangeRef = useLatestRef(onNavCollapsedChange);\n\n const setNavOpen = useCallback(\n (open: boolean) => {\n onNavOpenChangeRef.current?.(open);\n if (!openControlled) setOpenState(open);\n },\n [openControlled, onNavOpenChangeRef]\n );\n const setNavCollapsed = useCallback(\n (collapsed: boolean) => {\n onNavCollapsedChangeRef.current?.(collapsed);\n if (!collapsedControlled) setCollapsedState(collapsed);\n },\n [collapsedControlled, onNavCollapsedChangeRef]\n );\n\n // The layout switches in the stylesheet. The script needs the width\n // only to know which state the burger toggles, and to close the drawer\n // once the window is at or past the breakpoint.\n const wide = useBulmaBreakpoint(breakpoint);\n const isDrawer = wide === false;\n const isWide = wide === true;\n\n useEffect(() => {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- the width is known only in the browser, and the drawer closes in answer to it\n if (isWide && navOpen) setNavOpen(false);\n }, [isWide, navOpen, setNavOpen]);\n\n const toggleNav = useCallback(() => {\n if (isDrawer) setNavOpen(!navOpen);\n else setNavCollapsed(!navCollapsed);\n }, [isDrawer, navOpen, navCollapsed, setNavOpen, setNavCollapsed]);\n\n // The ids of the parts the skip link and the burger point at, read off\n // the children so the server renders them too.\n const baseId = useId();\n const parts = flattenChildren(children);\n const mainId = parts.find(isMain)?.props.id || `${baseId}-main`;\n const navId = parts.find(isNav)?.props.id || `${baseId}-nav`;\n\n const { skipLink: skipText, nav, closeNav, burger } = labels ?? {};\n const value = useMemo<FrameContextValue>(\n () => ({\n state: {\n navOpen,\n setNavOpen,\n navCollapsed,\n setNavCollapsed,\n isDrawer,\n toggleNav,\n navId,\n },\n labels: {\n skipLink: skipText || 'Skip to main content',\n nav: nav || 'Sections',\n closeNav: closeNav || 'Close navigation',\n burger: burger || undefined,\n },\n mainId,\n modeKnown: wide !== null,\n }),\n [\n navOpen,\n setNavOpen,\n navCollapsed,\n setNavCollapsed,\n isDrawer,\n toggleNav,\n navId,\n skipText,\n nav,\n closeNav,\n burger,\n mainId,\n wide,\n ]\n );\n\n // Its header and footer are the page's banner and contentinfo only\n // outside `main` and sectioning content, as HTML-AAM maps them.\n useEffect(() => {\n const host = rootRef.current?.parentElement?.closest(\n 'main, article, aside, nav, section'\n );\n if (host) {\n warnOnce(\n 'AppFrame:nested',\n `[bestax-bulma] <AppFrame> sits inside a <${host.localName}>, where ` +\n \"its header and footer are no longer the page's banner and \" +\n 'contentinfo landmarks. Put AppFrame at the top of the page.'\n );\n }\n }, []);\n\n const rootClass = usePrefixedClassNames('app-frame', {\n [`is-${breakpoint}`]: true,\n 'is-nav-collapsed': navCollapsed,\n });\n const skipClass = usePrefixedClassNames('app-frame-skip-link');\n\n return (\n <div\n ref={rootRefs}\n className={classNames(rootClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {skipLink && (\n <a className={skipClass} href={`#${mainId}`}>\n {value.labels.skipLink}\n </a>\n )}\n <FrameContext.Provider value={value}>{children}</FrameContext.Provider>\n </div>\n );\n }\n);\n\n/**\n * The frame's state, for a control of your own inside it: whether the\n * drawer is open and the column collapsed, setters that go through the\n * frame's callbacks, and `toggleNav()`, which does what the burger does.\n * Outside an AppFrame it's `null`, as the library's other context hooks\n * are, so a control that can sit outside one renders a fallback or nothing.\n *\n * @function useAppFrame\n * @returns The frame's state, or `null` outside an AppFrame.\n */\nexport function useAppFrame(): AppFrameState | null {\n return useContext(FrameContext)?.state ?? null;\n}\n\nexport const AppFrame = withSubComponents(\n AppFrameRoot,\n {\n Header: AppFrameHeader,\n Nav: AppFrameNav,\n Main: AppFrameMain,\n Aside: AppFrameAside,\n Footer: AppFrameFooter,\n Burger: AppFrameBurger,\n },\n 'AppFrame'\n);\n\nexport default AppFrame;\n"],"names":["_jsx","_jsxs","_Fragment"],"mappings":"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"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
2
|
'use client';
|
|
3
3
|
import { jsx } from 'react/jsx-runtime';
|
|
4
|
-
import
|
|
4
|
+
import { createContext, forwardRef, useContext, useId, useState, useRef, useCallback, useEffect } from 'react';
|
|
5
5
|
import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
|
|
6
6
|
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
7
7
|
import { flattenChildren } from '../helpers/flattenChildren.js';
|
|
8
8
|
import { withSubComponents } from '../helpers/withSubComponents.js';
|
|
9
|
+
import { isElementOf } from '../helpers/elementType.js';
|
|
9
10
|
import { useClientLayoutEffect } from '../helpers/useClientLayoutEffect.js';
|
|
10
11
|
import { warnOnce } from '../helpers/devWarnings.js';
|
|
11
12
|
import { limitToPercent, defaultSizes, fitSizes, primaryOf, primaryRange, resizePair, sameSizes } from '../helpers/splitterLayout.js';
|
|
@@ -35,7 +36,7 @@ const SplitterPane = forwardRef(({ className, textColor, bgColor, label: _label,
|
|
|
35
36
|
style: state ? { ...style, flex: `${state.size} 1 0px` } : style, hidden: state?.collapsed || rest.hidden, children: children }));
|
|
36
37
|
});
|
|
37
38
|
SplitterPane.displayName = 'Splitter.Pane';
|
|
38
|
-
const isPane = (child) =>
|
|
39
|
+
const isPane = (child) => isElementOf(child, SplitterPane);
|
|
39
40
|
/** The bounds with pane `i`'s changed, for a move that mustn't snap it shut. */
|
|
40
41
|
const withBound = (bounds, i, change) => bounds.map((bound, j) => (j === i ? { ...bound, ...change } : bound));
|
|
41
42
|
/** The bounds with neither pane beside handle `h` able to shut. */
|