@allxsmith/bestax-bulma 5.60.0 → 5.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bestax.css +1 -1
- package/dist/bestax.css.map +1 -1
- package/dist/esm/components/Carousel.js +3 -43
- package/dist/esm/components/Carousel.js.map +1 -1
- package/dist/esm/components/Command.js +3 -3
- package/dist/esm/components/Navbar.js +3 -1
- package/dist/esm/components/Navbar.js.map +1 -1
- package/dist/esm/components/Popover.js +7 -27
- package/dist/esm/components/Popover.js.map +1 -1
- package/dist/esm/components/Sidebar.js +17 -58
- package/dist/esm/components/Sidebar.js.map +1 -1
- package/dist/esm/form/ColorPicker.js +4 -4
- package/dist/esm/form/ColorPicker.js.map +1 -1
- package/dist/esm/form/Combobox.js +3 -3
- package/dist/esm/helpers/overlayStack.js +33 -1
- package/dist/esm/helpers/overlayStack.js.map +1 -1
- package/dist/esm/helpers/useBulmaViewport.js +82 -0
- package/dist/esm/helpers/useBulmaViewport.js.map +1 -0
- package/dist/esm/helpers/useInertSiblings.js +7 -3
- package/dist/esm/helpers/useInertSiblings.js.map +1 -1
- package/dist/esm/helpers/useOutsidePress.js +53 -0
- package/dist/esm/helpers/useOutsidePress.js.map +1 -0
- package/dist/esm/helpers/useOverlayFocus.js +74 -0
- package/dist/esm/helpers/useOverlayFocus.js.map +1 -0
- package/dist/esm/layout/AppFrame.js +438 -0
- package/dist/esm/layout/AppFrame.js.map +1 -0
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +678 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +1 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/Navbar.d.ts +4 -3
- package/dist/types/helpers/overlayStack.d.ts +17 -0
- package/dist/types/helpers/useBulmaViewport.d.ts +34 -0
- package/dist/types/helpers/useInertSiblings.d.ts +4 -1
- package/dist/types/helpers/useOutsidePress.d.ts +24 -0
- package/dist/types/helpers/useOverlayFocus.d.ts +44 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/layout/AppFrame.d.ts +306 -0
- package/dist/types-cjs/components/Navbar.d.ts +4 -3
- package/dist/types-cjs/helpers/overlayStack.d.ts +17 -0
- package/dist/types-cjs/helpers/useBulmaViewport.d.ts +34 -0
- package/dist/types-cjs/helpers/useInertSiblings.d.ts +4 -1
- package/dist/types-cjs/helpers/useOutsidePress.d.ts +24 -0
- package/dist/types-cjs/helpers/useOverlayFocus.d.ts +44 -0
- package/dist/types-cjs/index.d.ts +1 -0
- package/dist/types-cjs/layout/AppFrame.d.ts +306 -0
- 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/layout/_appframe.scss +306 -0
- package/src/scss/layout/_index.scss +1 -0
package/dist/index.cjs
CHANGED
|
@@ -4755,6 +4755,37 @@ function useOverlayStack(inStack, modal = true) {
|
|
|
4755
4755
|
return !above.some(overlay => among !== 'modal' || overlay.modal);
|
|
4756
4756
|
}, []);
|
|
4757
4757
|
}
|
|
4758
|
+
/**
|
|
4759
|
+
* Calls `onEscape` for an Escape while `active` and the overlay is the
|
|
4760
|
+
* topmost one, as `isTopmost` (from `useOverlayStack`) says, and marks the key
|
|
4761
|
+
* used with `preventDefault()`, so nothing under it acts on it too.
|
|
4762
|
+
*
|
|
4763
|
+
* It listens on `window`, so everything inside the overlay has had the key
|
|
4764
|
+
* first, and an Escape something there used, such as a popup closing, is
|
|
4765
|
+
* left to it. The listener stays put while `onEscape` changes identity, as an
|
|
4766
|
+
* inline one does on every render of the parent: it reads the latest one as
|
|
4767
|
+
* the key arrives.
|
|
4768
|
+
*
|
|
4769
|
+
* @function useOverlayEscape
|
|
4770
|
+
* @param active - Whether the overlay is open and closes on Escape.
|
|
4771
|
+
* @param isTopmost - The overlay's check from `useOverlayStack`.
|
|
4772
|
+
* @param onEscape - What closing it does.
|
|
4773
|
+
*/
|
|
4774
|
+
function useOverlayEscape(active, isTopmost, onEscape) {
|
|
4775
|
+
const onEscapeRef = useLatestRef(onEscape);
|
|
4776
|
+
React.useEffect(() => {
|
|
4777
|
+
if (!active)
|
|
4778
|
+
return undefined;
|
|
4779
|
+
const handleKeyDown = (e) => {
|
|
4780
|
+
if (e.key !== 'Escape' || e.defaultPrevented || !isTopmost())
|
|
4781
|
+
return;
|
|
4782
|
+
e.preventDefault();
|
|
4783
|
+
onEscapeRef.current();
|
|
4784
|
+
};
|
|
4785
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
4786
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
4787
|
+
}, [active, isTopmost, onEscapeRef]);
|
|
4788
|
+
}
|
|
4758
4789
|
|
|
4759
4790
|
/**
|
|
4760
4791
|
* Whether `el` is editable while its parent is not, which makes it an editing
|
|
@@ -5680,7 +5711,9 @@ const NavbarBurger = React.forwardRef(function NavbarBurger({ className, active,
|
|
|
5680
5711
|
});
|
|
5681
5712
|
return (jsxRuntime.jsxs("button", { ref: ref, className: classNames(usePrefixedClassNames('navbar-burger', {
|
|
5682
5713
|
'is-active': active,
|
|
5683
|
-
}), bulmaHelperClasses, className), ...rest, "aria-label": rest['aria-label'] || 'menu', "aria-expanded": rest['aria-expanded']
|
|
5714
|
+
}), bulmaHelperClasses, className), ...rest, "aria-label": rest['aria-label'] || 'menu', "aria-expanded": rest['aria-expanded'] === null
|
|
5715
|
+
? undefined
|
|
5716
|
+
: (rest['aria-expanded'] ?? !!active), type: buttonType(rest.type), children: [jsxRuntime.jsx("span", { "aria-hidden": "true" }), jsxRuntime.jsx("span", { "aria-hidden": "true" }), jsxRuntime.jsx("span", { "aria-hidden": "true" }), jsxRuntime.jsx("span", { "aria-hidden": "true" }), children] }));
|
|
5684
5717
|
});
|
|
5685
5718
|
NavbarBurger.displayName = 'NavbarBurger';
|
|
5686
5719
|
/**
|
|
@@ -6851,16 +6884,20 @@ const above = (el) => el.parentElement ?? el.getRootNode().host;
|
|
|
6851
6884
|
* @param active - Whether what's beside it is inert.
|
|
6852
6885
|
* @param wholePage - Whether that goes for the whole page rather than only
|
|
6853
6886
|
* the element's siblings.
|
|
6887
|
+
* @param except - Ref to an element beside it to leave reachable too, such
|
|
6888
|
+
* as the overlay's own backdrop, which has to take the presses that close
|
|
6889
|
+
* it rather than let them through to what's under it.
|
|
6854
6890
|
*/
|
|
6855
|
-
function useInertSiblings(ref, active, wholePage) {
|
|
6891
|
+
function useInertSiblings(ref, active, wholePage, except) {
|
|
6856
6892
|
React.useEffect(() => {
|
|
6857
6893
|
const el = ref.current;
|
|
6858
6894
|
if (!active || !el)
|
|
6859
6895
|
return undefined;
|
|
6860
6896
|
const doc = el.ownerDocument;
|
|
6897
|
+
const keep = except?.current;
|
|
6861
6898
|
const held = [];
|
|
6862
6899
|
for (let node = el; node && node !== doc.body; node = wholePage ? above(node) : undefined) {
|
|
6863
|
-
held.push(...siblingsOf(node).filter(hold));
|
|
6900
|
+
held.push(...siblingsOf(node).filter(sibling => sibling !== keep && hold(sibling)));
|
|
6864
6901
|
}
|
|
6865
6902
|
// Focus this takes away, to hand back later. It's looked up as `el`'s
|
|
6866
6903
|
// tree sees it, where focus inside a nested shadow root reads as its
|
|
@@ -6875,7 +6912,7 @@ function useInertSiblings(ref, active, wholePage) {
|
|
|
6875
6912
|
if (doc.activeElement === doc.body)
|
|
6876
6913
|
taken?.focus();
|
|
6877
6914
|
};
|
|
6878
|
-
}, [ref, active, wholePage]);
|
|
6915
|
+
}, [ref, active, wholePage, except]);
|
|
6879
6916
|
}
|
|
6880
6917
|
|
|
6881
6918
|
/**
|
|
@@ -7971,6 +8008,51 @@ function useAnchoredPosition(anchorRef, panelRef, { active, position, fixed, off
|
|
|
7971
8008
|
return resolved;
|
|
7972
8009
|
}
|
|
7973
8010
|
|
|
8011
|
+
// INTERNAL: deliberately not exported from src/index.ts.
|
|
8012
|
+
/**
|
|
8013
|
+
* Calls `onOutsidePress` for a `pointerdown` outside `ref`'s element while
|
|
8014
|
+
* `active`, as a Popover closes on one.
|
|
8015
|
+
*
|
|
8016
|
+
* A press counts as inside in two ways: on the element's own path in the DOM,
|
|
8017
|
+
* and anywhere in a React tree the returned handler is put on, as
|
|
8018
|
+
* `onPointerDownCapture`. React's tree includes content portaled out of it,
|
|
8019
|
+
* which the DOM path doesn't, and React has seen a press there by the time
|
|
8020
|
+
* the document hears it.
|
|
8021
|
+
*
|
|
8022
|
+
* The listener is on the element's own document, an iframe's when the tree
|
|
8023
|
+
* renders into one, and reads the latest `onOutsidePress` as a press
|
|
8024
|
+
* arrives, so it stays put while the callback changes identity.
|
|
8025
|
+
*
|
|
8026
|
+
* @function useOutsidePress
|
|
8027
|
+
* @param ref - Ref to the element a press inside of doesn't count.
|
|
8028
|
+
* @param active - Whether to listen.
|
|
8029
|
+
* @param onOutsidePress - Called with each press outside.
|
|
8030
|
+
* @returns The `onPointerDownCapture` handler that marks a press in its tree
|
|
8031
|
+
* as inside.
|
|
8032
|
+
*/
|
|
8033
|
+
function useOutsidePress(ref, active, onOutsidePress) {
|
|
8034
|
+
// The last pointerdown React saw inside.
|
|
8035
|
+
const insidePressRef = React.useRef(null);
|
|
8036
|
+
const onOutsidePressRef = useLatestRef(onOutsidePress);
|
|
8037
|
+
React.useEffect(() => {
|
|
8038
|
+
const element = ref.current;
|
|
8039
|
+
if (!active || !element)
|
|
8040
|
+
return undefined;
|
|
8041
|
+
const handlePointerDown = (e) => {
|
|
8042
|
+
if (e === insidePressRef.current || isEventInside(e, ref.current)) {
|
|
8043
|
+
return;
|
|
8044
|
+
}
|
|
8045
|
+
onOutsidePressRef.current(e);
|
|
8046
|
+
};
|
|
8047
|
+
const doc = element.ownerDocument;
|
|
8048
|
+
doc.addEventListener('pointerdown', handlePointerDown);
|
|
8049
|
+
return () => doc.removeEventListener('pointerdown', handlePointerDown);
|
|
8050
|
+
}, [ref, active, onOutsidePressRef]);
|
|
8051
|
+
return React.useCallback((event) => {
|
|
8052
|
+
insidePressRef.current = event.nativeEvent;
|
|
8053
|
+
}, []);
|
|
8054
|
+
}
|
|
8055
|
+
|
|
7974
8056
|
const PopoverContext = React.createContext(null);
|
|
7975
8057
|
/** Intrinsic elements that are controls already, and keep their own role. */
|
|
7976
8058
|
const NATIVE_CONTROLS = new Set([
|
|
@@ -8021,9 +8103,6 @@ const PopoverComponent = ({ trigger, children, open: controlledOpen, defaultOpen
|
|
|
8021
8103
|
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
8022
8104
|
const rootRef = React.useRef(null);
|
|
8023
8105
|
const panelRef = React.useRef(null);
|
|
8024
|
-
// The last pointerdown React saw inside the panel. React's tree includes
|
|
8025
|
-
// content the panel portals elsewhere, which the DOM path doesn't.
|
|
8026
|
-
const insidePointerRef = React.useRef(null);
|
|
8027
8106
|
const baseId = React.useId();
|
|
8028
8107
|
const panelId = `${baseId}-panel`;
|
|
8029
8108
|
const titleId = `${baseId}-title`;
|
|
@@ -8079,25 +8158,10 @@ const PopoverComponent = ({ trigger, children, open: controlledOpen, defaultOpen
|
|
|
8079
8158
|
}
|
|
8080
8159
|
};
|
|
8081
8160
|
}, [panelMounted, trapFocus, triggerNode]);
|
|
8082
|
-
//
|
|
8083
|
-
//
|
|
8084
|
-
//
|
|
8085
|
-
const
|
|
8086
|
-
React.useEffect(() => {
|
|
8087
|
-
if (!panelMounted || !closeOnClickOutside)
|
|
8088
|
-
return undefined;
|
|
8089
|
-
const handlePointerDown = (e) => {
|
|
8090
|
-
if (e === insidePointerRef.current || isEventInside(e, rootRef.current)) {
|
|
8091
|
-
return;
|
|
8092
|
-
}
|
|
8093
|
-
closeRef.current();
|
|
8094
|
-
};
|
|
8095
|
-
// The wrapper is on the page whenever the panel is. Its document, an
|
|
8096
|
-
// iframe's when the tree renders into one, is where presses land.
|
|
8097
|
-
const doc = rootRef.current.ownerDocument;
|
|
8098
|
-
doc.addEventListener('pointerdown', handlePointerDown);
|
|
8099
|
-
return () => doc.removeEventListener('pointerdown', handlePointerDown);
|
|
8100
|
-
}, [panelMounted, closeOnClickOutside, closeRef]);
|
|
8161
|
+
// A press outside the wrapper, which holds the trigger and the panel,
|
|
8162
|
+
// closes it. One inside the panel's React tree counts as inside, so content
|
|
8163
|
+
// the panel portals elsewhere does too.
|
|
8164
|
+
const markInsidePress = useOutsidePress(rootRef, panelMounted && closeOnClickOutside, close);
|
|
8101
8165
|
const handlePanelKeyDown = (e) => {
|
|
8102
8166
|
if (e.key !== 'Escape' || !closeOnEscape || e.defaultPrevented)
|
|
8103
8167
|
return;
|
|
@@ -8163,9 +8227,7 @@ const PopoverComponent = ({ trigger, children, open: controlledOpen, defaultOpen
|
|
|
8163
8227
|
top: resolved.top,
|
|
8164
8228
|
left: resolved.left,
|
|
8165
8229
|
}
|
|
8166
|
-
: undefined, onKeyDown: handlePanelKeyDown, onPointerDownCapture:
|
|
8167
|
-
insidePointerRef.current = e.nativeEvent;
|
|
8168
|
-
}, children: children }) }))] }));
|
|
8230
|
+
: undefined, onKeyDown: handlePanelKeyDown, onPointerDownCapture: markInsidePress, children: children }) }))] }));
|
|
8169
8231
|
};
|
|
8170
8232
|
/**
|
|
8171
8233
|
* The open panel. It mounts with each opening, so it counts the
|
|
@@ -9293,6 +9355,69 @@ const TimelineRoot = React.forwardRef(({ className, children, active, pending, a
|
|
|
9293
9355
|
});
|
|
9294
9356
|
const Timeline = withSubComponents(TimelineRoot, { Item: TimelineItem }, 'Timeline');
|
|
9295
9357
|
|
|
9358
|
+
// INTERNAL: deliberately not exported from src/index.ts.
|
|
9359
|
+
/**
|
|
9360
|
+
* Moves focus into an overlay as it opens, to the first stop Tab wraps to
|
|
9361
|
+
* inside `ref`'s element or the element itself, and hands it back to
|
|
9362
|
+
* whatever had it as it closes, as `Modal` does. Focus goes back only while
|
|
9363
|
+
* the overlay still holds it, or it's been dropped to `<body>`: closing one
|
|
9364
|
+
* under another overlay mustn't pull focus out of the one on top, and focus
|
|
9365
|
+
* moved out to the page stays there. The opener is found wherever it sits,
|
|
9366
|
+
* inside a shadow root too, in the overlay's own document, an iframe's when
|
|
9367
|
+
* it renders into one. It's read in a layout effect as the overlay opens, so
|
|
9368
|
+
* before the commit's passive effects, such as one that makes the page
|
|
9369
|
+
* inert. Content inside the overlay with a layout effect of its own, or an
|
|
9370
|
+
* `autoFocus`, can still move focus first, and then there's no opener to go
|
|
9371
|
+
* back to: focus is never handed back into the overlay that's closing.
|
|
9372
|
+
*
|
|
9373
|
+
* @function useOverlayFocus
|
|
9374
|
+
* @param ref - Ref to the overlay's element.
|
|
9375
|
+
* @param open - Whether the overlay is open.
|
|
9376
|
+
* @param options - Whether it's modal, what remounts it, and whether a
|
|
9377
|
+
* close hands focus back.
|
|
9378
|
+
*/
|
|
9379
|
+
function useOverlayFocus(ref, open, { modal, remountKey, restore }) {
|
|
9380
|
+
const openerRef = React.useRef(null);
|
|
9381
|
+
const restoreRef = useLatestRef(restore);
|
|
9382
|
+
// The element that had focus, wherever it sits: a portaled overlay renders
|
|
9383
|
+
// under `document.body` while its opener can be inside a shadow root.
|
|
9384
|
+
useClientLayoutEffect(() => {
|
|
9385
|
+
if (!open)
|
|
9386
|
+
return;
|
|
9387
|
+
const element = ref.current;
|
|
9388
|
+
/* istanbul ignore if: as below, the ref is set while the overlay is open */
|
|
9389
|
+
if (!element)
|
|
9390
|
+
return;
|
|
9391
|
+
// Focus already inside, where an `autoFocus` in the overlay puts it, has
|
|
9392
|
+
// no opener to go back to.
|
|
9393
|
+
openerRef.current = element.contains(getActiveElementInTree(element))
|
|
9394
|
+
? null
|
|
9395
|
+
: getDeepestActiveElement(element.ownerDocument);
|
|
9396
|
+
}, [open, ref]);
|
|
9397
|
+
React.useEffect(() => {
|
|
9398
|
+
if (!open)
|
|
9399
|
+
return undefined;
|
|
9400
|
+
const element = ref.current;
|
|
9401
|
+
/* istanbul ignore if: the overlay renders whenever it's open, so its ref is set before this runs */
|
|
9402
|
+
if (!element)
|
|
9403
|
+
return undefined;
|
|
9404
|
+
if (modal || !isModalOverlayOpen()) {
|
|
9405
|
+
focusTabStop(element);
|
|
9406
|
+
}
|
|
9407
|
+
return () => {
|
|
9408
|
+
// A removed subtree leaves focus on <body>, which still counts as held.
|
|
9409
|
+
const active = getActiveElementInTree(element);
|
|
9410
|
+
const held = active === element.ownerDocument.body || element.contains(active);
|
|
9411
|
+
// Asked now rather than as it opened: it's about this close.
|
|
9412
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
9413
|
+
if (held && (restoreRef.current?.() ?? true)) {
|
|
9414
|
+
openerRef.current?.focus();
|
|
9415
|
+
}
|
|
9416
|
+
};
|
|
9417
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- `modal` is read as the overlay opens, on purpose
|
|
9418
|
+
}, [open, remountKey, ref, restoreRef]);
|
|
9419
|
+
}
|
|
9420
|
+
|
|
9296
9421
|
/**
|
|
9297
9422
|
* The `Sidebar` component provides a slide-out navigation panel that appears from the left or right side of the screen.
|
|
9298
9423
|
*
|
|
@@ -9342,31 +9467,15 @@ const SidebarComponent = React.forwardRef(({ isOpen, onClose, position = 'left',
|
|
|
9342
9467
|
handleClose();
|
|
9343
9468
|
}
|
|
9344
9469
|
}, [overlayClose, handleClose]);
|
|
9345
|
-
// Read when Escape arrives, so the key listener stays put while
|
|
9346
|
-
// `onClose` changes identity, as an inline one does on every render of
|
|
9347
|
-
// the parent.
|
|
9348
|
-
const handleCloseRef = useLatestRef(handleClose);
|
|
9349
9470
|
// Joins the stack of open overlays, so the sidebar answers Escape only
|
|
9350
9471
|
// while it's the topmost overlay, and keeps it from those under it. It's
|
|
9351
9472
|
// modal with an overlay: without one it leaves Tab to a modal overlay
|
|
9352
9473
|
// under it.
|
|
9353
9474
|
const isTopmost = useOverlayStack(isOpen, overlay);
|
|
9354
9475
|
const closesOnEscape = escapeClose && canCancel && Boolean(onClose);
|
|
9355
|
-
//
|
|
9356
|
-
// sidebar has had the key first, and an Escape it used is left to it.
|
|
9357
|
-
|
|
9358
|
-
React.useEffect(() => {
|
|
9359
|
-
if (!isOpen || !closesOnEscape)
|
|
9360
|
-
return;
|
|
9361
|
-
const handleKeyDown = (e) => {
|
|
9362
|
-
if (e.key !== 'Escape' || e.defaultPrevented || !isTopmost())
|
|
9363
|
-
return;
|
|
9364
|
-
e.preventDefault();
|
|
9365
|
-
handleCloseRef.current();
|
|
9366
|
-
};
|
|
9367
|
-
window.addEventListener('keydown', handleKeyDown);
|
|
9368
|
-
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
9369
|
-
}, [isOpen, closesOnEscape, handleCloseRef, isTopmost]);
|
|
9476
|
+
// Escape closes it, on `window`, as in `Modal`, so everything inside the
|
|
9477
|
+
// sidebar has had the key first, and an Escape it used is left to it.
|
|
9478
|
+
useOverlayEscape(isOpen && closesOnEscape, isTopmost, handleClose);
|
|
9370
9479
|
// Keep Tab inside while there's an overlay, as `Modal` does:
|
|
9371
9480
|
// `aria-modal` hides the rest of the page from assistive technology, so
|
|
9372
9481
|
// the keyboard order has to agree. Only while it's the topmost modal
|
|
@@ -9389,41 +9498,17 @@ const SidebarComponent = React.forwardRef(({ isOpen, onClose, position = 'left',
|
|
|
9389
9498
|
useScrollLock(isOpen && overlay);
|
|
9390
9499
|
// Move focus into the sidebar on open, to the first stop Tab wraps to or
|
|
9391
9500
|
// the sidebar itself, and hand it back on close, as `Modal` does. A
|
|
9392
|
-
// different `defaultTarget` remounts the portaled sidebar:
|
|
9393
|
-
//
|
|
9394
|
-
//
|
|
9395
|
-
//
|
|
9396
|
-
//
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
return undefined;
|
|
9404
|
-
// The element that had focus, wherever it sits: a portaled sidebar
|
|
9405
|
-
// renders under `document.body` while its opener can be inside a
|
|
9406
|
-
// shadow root.
|
|
9407
|
-
const opener = getDeepestActiveElement();
|
|
9408
|
-
// Without an overlay the sidebar holds no Tab, so with a modal overlay
|
|
9409
|
-
// open under it, the first Tab would take focus back there and leave
|
|
9410
|
-
// the sidebar's other controls out of reach. It leaves focus alone
|
|
9411
|
-
// then, and the cleanup below finds focus outside and leaves it too.
|
|
9412
|
-
if (overlay || !isModalOverlayOpen()) {
|
|
9413
|
-
focusTabStop(element);
|
|
9414
|
-
}
|
|
9415
|
-
return () => {
|
|
9416
|
-
// Only while the sidebar still holds focus: closing one under another
|
|
9417
|
-
// overlay mustn't pull focus out of the one on top, and focus moved
|
|
9418
|
-
// out to the page stays there. A removed subtree leaves focus on
|
|
9419
|
-
// <body>, which still counts as held.
|
|
9420
|
-
const active = getActiveElementInTree(element);
|
|
9421
|
-
if (active === document.body || element.contains(active)) {
|
|
9422
|
-
opener?.focus();
|
|
9423
|
-
}
|
|
9424
|
-
};
|
|
9425
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- `overlay` is read as the sidebar opens, on purpose
|
|
9426
|
-
}, [isOpen, defaultTarget]);
|
|
9501
|
+
// different `defaultTarget` remounts the portaled sidebar: focus is handed
|
|
9502
|
+
// back from the old nodes and moved into the new ones. Whether it takes
|
|
9503
|
+
// focus is decided as it opens, so `overlay` changing while it's open
|
|
9504
|
+
// leaves focus where the user has it. Without an overlay the sidebar holds
|
|
9505
|
+
// no Tab, so with a modal overlay open under it, it leaves focus alone:
|
|
9506
|
+
// the first Tab would take focus back there and leave the sidebar's other
|
|
9507
|
+
// controls out of reach.
|
|
9508
|
+
useOverlayFocus(sidebarRef, isOpen, {
|
|
9509
|
+
modal: overlay,
|
|
9510
|
+
remountKey: defaultTarget,
|
|
9511
|
+
});
|
|
9427
9512
|
// Use combined ref
|
|
9428
9513
|
const combinedRef = React.useCallback((node) => {
|
|
9429
9514
|
sidebarRef.current =
|
|
@@ -10147,6 +10232,81 @@ const DialogContainer = () => {
|
|
|
10147
10232
|
return (jsxRuntime.jsx(Dialog, { isOpen: true, title: options.title, message: options.message, type: options.type, confirmText: options.confirmText, cancelText: options.cancelText, icon: options.icon, showCancel: type === 'confirm', focusCancel: options.focusCancel, onConfirm: () => dialog.close(type === 'confirm' ? true : undefined), onCancel: () => dialog.close(type === 'confirm' ? false : undefined) }));
|
|
10148
10233
|
};
|
|
10149
10234
|
|
|
10235
|
+
// INTERNAL: deliberately not exported from src/index.ts.
|
|
10236
|
+
/**
|
|
10237
|
+
* The media queries Bulma's `fullhd`, `widescreen`, `desktop` and `tablet`
|
|
10238
|
+
* mixins open with, widest first, at Bulma's default breakpoints. A
|
|
10239
|
+
* stylesheet lays a component out with those mixins; these tell its script
|
|
10240
|
+
* which layout that is. A theme that moves Bulma's breakpoints moves the
|
|
10241
|
+
* stylesheet's switch but not these. Narrower than the last one is `mobile`.
|
|
10242
|
+
*/
|
|
10243
|
+
const VIEWPORT_QUERIES = [
|
|
10244
|
+
'screen and (min-width: 1408px)',
|
|
10245
|
+
'screen and (min-width: 1216px)',
|
|
10246
|
+
'screen and (min-width: 1024px)',
|
|
10247
|
+
'screen and (min-width: 769px), print',
|
|
10248
|
+
];
|
|
10249
|
+
/** `useBulmaViewport`'s answer below the tablet breakpoint. */
|
|
10250
|
+
const MOBILE = VIEWPORT_QUERIES.length;
|
|
10251
|
+
/** Where Bulma's `widescreen`, `desktop` and `tablet` sit in `VIEWPORT_QUERIES`. */
|
|
10252
|
+
const VIEWPORT_INDEX = {
|
|
10253
|
+
widescreen: 1,
|
|
10254
|
+
desktop: 2,
|
|
10255
|
+
tablet: 3,
|
|
10256
|
+
};
|
|
10257
|
+
/**
|
|
10258
|
+
* Which of Bulma's viewports the window is in: the index of the widest
|
|
10259
|
+
* `VIEWPORT_QUERIES` entry that matches, `MOBILE` below them all, or `-1`
|
|
10260
|
+
* when that isn't known (on the server, before the first layout effect, and
|
|
10261
|
+
* whenever `enabled` is off, so a caller that needs no answer subscribes to
|
|
10262
|
+
* nothing). It's read in a layout effect, so the first paint after hydration
|
|
10263
|
+
* already has it.
|
|
10264
|
+
*
|
|
10265
|
+
* @function useBulmaViewport
|
|
10266
|
+
* @param enabled - Whether the caller needs the viewport at all.
|
|
10267
|
+
* @returns The viewport, as described above.
|
|
10268
|
+
*/
|
|
10269
|
+
function useBulmaViewport(enabled) {
|
|
10270
|
+
const [viewport, setViewport] = React.useState(-1);
|
|
10271
|
+
useClientLayoutEffect(() => {
|
|
10272
|
+
if (!enabled || typeof window.matchMedia !== 'function')
|
|
10273
|
+
return undefined;
|
|
10274
|
+
const lists = VIEWPORT_QUERIES.map(query => window.matchMedia(query));
|
|
10275
|
+
const update = () => {
|
|
10276
|
+
const index = lists.findIndex(list => list.matches);
|
|
10277
|
+
setViewport(index < 0 ? MOBILE : index);
|
|
10278
|
+
};
|
|
10279
|
+
update();
|
|
10280
|
+
lists.forEach(list => list.addEventListener('change', update));
|
|
10281
|
+
return () => lists.forEach(list => list.removeEventListener('change', update));
|
|
10282
|
+
}, [enabled]);
|
|
10283
|
+
return enabled ? viewport : -1;
|
|
10284
|
+
}
|
|
10285
|
+
/**
|
|
10286
|
+
* Whether the window is at or past one of Bulma's breakpoints, at its default
|
|
10287
|
+
* width, through the one media query Bulma's mixin for it opens with, so a
|
|
10288
|
+
* caller re-renders only when the window crosses that breakpoint. `null`
|
|
10289
|
+
* until it's known: on the server, and before the first layout effect. Where
|
|
10290
|
+
* `matchMedia` isn't there, it's `true`, the wide layout a server renders.
|
|
10291
|
+
*
|
|
10292
|
+
* @function useBulmaBreakpoint
|
|
10293
|
+
* @param breakpoint - The breakpoint: `tablet`, `desktop` or `widescreen`.
|
|
10294
|
+
* @returns Whether the window is at or past it, or `null`.
|
|
10295
|
+
*/
|
|
10296
|
+
function useBulmaBreakpoint(breakpoint) {
|
|
10297
|
+
const [atOrPast, setAtOrPast] = React.useState(null);
|
|
10298
|
+
useClientLayoutEffect(() => {
|
|
10299
|
+
const list = typeof window.matchMedia === 'function'
|
|
10300
|
+
? window.matchMedia(VIEWPORT_QUERIES[VIEWPORT_INDEX[breakpoint]])
|
|
10301
|
+
: null;
|
|
10302
|
+
const update = () => setAtOrPast(list ? list.matches : true);
|
|
10303
|
+
update();
|
|
10304
|
+
list?.addEventListener('change', update);
|
|
10305
|
+
return () => list?.removeEventListener('change', update);
|
|
10306
|
+
}, [breakpoint]);
|
|
10307
|
+
return atOrPast;
|
|
10308
|
+
}
|
|
10309
|
+
|
|
10150
10310
|
/**
|
|
10151
10311
|
* Individual carousel item/slide.
|
|
10152
10312
|
*
|
|
@@ -10174,24 +10334,10 @@ const DefaultPrevIcon = () => (jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org
|
|
|
10174
10334
|
* @returns {JSX.Element} The rendered SVG chevron-right icon.
|
|
10175
10335
|
*/
|
|
10176
10336
|
const DefaultNextIcon = () => (jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsxRuntime.jsx("polyline", { points: "9 18 15 12 9 6" }) }));
|
|
10177
|
-
/**
|
|
10178
|
-
* The media queries Bulma's `fullhd`, `widescreen`, `desktop` and `tablet`
|
|
10179
|
-
* mixins open with, widest first, at Bulma's default breakpoints. The
|
|
10180
|
-
* stylesheet picks the count in view with those mixins; these tell the
|
|
10181
|
-
* script which count that is. Narrower than the last one is `mobile`.
|
|
10182
|
-
*/
|
|
10183
|
-
const VIEWPORT_QUERIES = [
|
|
10184
|
-
'screen and (min-width: 1408px)',
|
|
10185
|
-
'screen and (min-width: 1216px)',
|
|
10186
|
-
'screen and (min-width: 1024px)',
|
|
10187
|
-
'screen and (min-width: 769px), print',
|
|
10188
|
-
];
|
|
10189
|
-
/** `useBulmaViewport`'s answer below the tablet breakpoint. */
|
|
10190
|
-
const MOBILE = VIEWPORT_QUERIES.length;
|
|
10191
10337
|
/**
|
|
10192
10338
|
* The `--bulma-carousel-items-to-show*` variable each count prop writes, in
|
|
10193
|
-
* the order the component lists them:
|
|
10194
|
-
* last `itemsToShow` itself.
|
|
10339
|
+
* the order the component lists them: `useBulmaViewport`'s viewports, widest
|
|
10340
|
+
* first, then mobile, and last `itemsToShow` itself.
|
|
10195
10341
|
*/
|
|
10196
10342
|
const COUNT_SUFFIXES = [
|
|
10197
10343
|
'-fullhd',
|
|
@@ -10201,33 +10347,6 @@ const COUNT_SUFFIXES = [
|
|
|
10201
10347
|
'-mobile',
|
|
10202
10348
|
'',
|
|
10203
10349
|
];
|
|
10204
|
-
/**
|
|
10205
|
-
* Which of Bulma's viewports the window is in: the index of the widest
|
|
10206
|
-
* `VIEWPORT_QUERIES` entry that matches, `MOBILE` below them all, or `-1`
|
|
10207
|
-
* when that isn't known (on the server, before the first layout effect, and
|
|
10208
|
-
* whenever `enabled` is off, so a carousel with one count everywhere
|
|
10209
|
-
* subscribes to nothing).
|
|
10210
|
-
*
|
|
10211
|
-
* @function
|
|
10212
|
-
* @param {boolean} enabled - Whether any viewport-specific count is set.
|
|
10213
|
-
* @returns {number} The viewport, as described above.
|
|
10214
|
-
*/
|
|
10215
|
-
function useBulmaViewport(enabled) {
|
|
10216
|
-
const [viewport, setViewport] = React.useState(-1);
|
|
10217
|
-
useClientLayoutEffect(() => {
|
|
10218
|
-
if (!enabled || typeof window.matchMedia !== 'function')
|
|
10219
|
-
return undefined;
|
|
10220
|
-
const lists = VIEWPORT_QUERIES.map(query => window.matchMedia(query));
|
|
10221
|
-
const update = () => {
|
|
10222
|
-
const index = lists.findIndex(list => list.matches);
|
|
10223
|
-
setViewport(index < 0 ? MOBILE : index);
|
|
10224
|
-
};
|
|
10225
|
-
update();
|
|
10226
|
-
lists.forEach(list => list.addEventListener('change', update));
|
|
10227
|
-
return () => lists.forEach(list => list.removeEventListener('change', update));
|
|
10228
|
-
}, [enabled]);
|
|
10229
|
-
return enabled ? viewport : -1;
|
|
10230
|
-
}
|
|
10231
10350
|
/**
|
|
10232
10351
|
* A count prop as the whole number the carousel works with: rounded down,
|
|
10233
10352
|
* and never below 1, so a zero or a stray `NaN` can't divide by nothing.
|
|
@@ -13125,7 +13244,7 @@ const standalone$2 = {
|
|
|
13125
13244
|
const CommandContext = React.createContext(null);
|
|
13126
13245
|
const useCommand = () => React.useContext(CommandContext) ?? standalone$2;
|
|
13127
13246
|
/** Hands a node to a forwarded ref and to one of the component's own. */
|
|
13128
|
-
function setRefs$
|
|
13247
|
+
function setRefs$3(node, own, forwarded) {
|
|
13129
13248
|
own.current = node;
|
|
13130
13249
|
if (typeof forwarded === 'function')
|
|
13131
13250
|
forwarded(node);
|
|
@@ -13242,7 +13361,7 @@ const CommandParts = React.forwardRef(({ children, label, query: controlledQuery
|
|
|
13242
13361
|
registerHandler,
|
|
13243
13362
|
};
|
|
13244
13363
|
const { rootRef } = engine;
|
|
13245
|
-
const rootRefs = React.useCallback((node) => setRefs$
|
|
13364
|
+
const rootRefs = React.useCallback((node) => setRefs$3(node, rootRef, ref), [rootRef, ref]);
|
|
13246
13365
|
return (jsxRuntime.jsxs("div", { className: classNames(rootClass, bulmaHelperClasses, className), ...rest, ref: rootRefs, children: [jsxRuntime.jsx(CommandContext.Provider, { value: context, children: children }), jsxRuntime.jsx(StatusRegion, { items: announced, describe: describeEmpty })] }));
|
|
13247
13366
|
});
|
|
13248
13367
|
CommandParts.displayName = 'CommandParts';
|
|
@@ -13262,7 +13381,7 @@ const CommandInput = React.forwardRef(({ isLoading, iconLeft, iconLeftName, onKe
|
|
|
13262
13381
|
return () => reportLoading(false);
|
|
13263
13382
|
}, [reportLoading, isLoading]);
|
|
13264
13383
|
const { inputRef } = engine;
|
|
13265
|
-
const inputRefs = React.useCallback((node) => setRefs$
|
|
13384
|
+
const inputRefs = React.useCallback((node) => setRefs$3(node, inputRef, ref), [inputRef, ref]);
|
|
13266
13385
|
const { expanded, highlighted } = engine;
|
|
13267
13386
|
// A name the caller gave the field wins over the Command's `label`.
|
|
13268
13387
|
const callerNamed = !!props['aria-label'] || 'aria-labelledby' in props;
|
|
@@ -18791,7 +18910,7 @@ const standalone$1 = {
|
|
|
18791
18910
|
const ComboboxContext = React.createContext(null);
|
|
18792
18911
|
const useCombobox = () => React.useContext(ComboboxContext) ?? standalone$1;
|
|
18793
18912
|
/** Hands a node to a forwarded ref and to one of the component's own. */
|
|
18794
|
-
function setRefs$
|
|
18913
|
+
function setRefs$2(node, own, forwarded) {
|
|
18795
18914
|
own.current = node;
|
|
18796
18915
|
if (typeof forwarded === 'function')
|
|
18797
18916
|
forwarded(node);
|
|
@@ -18923,7 +19042,7 @@ const ComboboxParts = React.forwardRef(({ children, below, onSettle, open, defau
|
|
|
18923
19042
|
appendToBody,
|
|
18924
19043
|
};
|
|
18925
19044
|
const { rootRef } = engine;
|
|
18926
|
-
const rootRefs = React.useCallback((node) => setRefs$
|
|
19045
|
+
const rootRefs = React.useCallback((node) => setRefs$2(node, rootRef, ref), [rootRef, ref]);
|
|
18927
19046
|
const comboboxElement = (jsxRuntime.jsxs("div", { className: classNames(rootClass, bulmaHelperClasses, className), ...rest, ref: rootRefs, children: [jsxRuntime.jsx(ComboboxContext.Provider, { value: context, children: children }), jsxRuntime.jsx(StatusRegion, { items: announced, describe: describeEmpty })] }));
|
|
18928
19047
|
const fieldElement = below === undefined ? (comboboxElement) : (jsxRuntime.jsxs("div", { children: [comboboxElement, jsxRuntime.jsx("div", { onPointerDownCapture: engine.markInside, onMouseDown: engine.keepFocus, children: below })] }));
|
|
18929
19048
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
@@ -18956,7 +19075,7 @@ const ComboboxInput = React.forwardRef(({ id, isLoading, controlSize, onKeyDown,
|
|
|
18956
19075
|
return () => reportLoading(false);
|
|
18957
19076
|
}, [reportLoading, isLoading]);
|
|
18958
19077
|
const { inputRef } = engine;
|
|
18959
|
-
const inputRefs = React.useCallback((node) => setRefs$
|
|
19078
|
+
const inputRefs = React.useCallback((node) => setRefs$2(node, inputRef, ref), [inputRef, ref]);
|
|
18960
19079
|
const { expanded, highlighted } = engine;
|
|
18961
19080
|
// An `id` of the field's own reaches the label from a layout effect, so
|
|
18962
19081
|
// in server markup, and the first render, the label's `for` still names
|
|
@@ -20253,7 +20372,7 @@ const rgbChannels = (color) => {
|
|
|
20253
20372
|
/** Whether a press should start a drag: any touch or pen, a mouse's main button. */
|
|
20254
20373
|
const startsDrag = (event) => event.pointerType !== 'mouse' || event.button === 0;
|
|
20255
20374
|
/** Hands a node to a forwarded ref and to one of the part's own. */
|
|
20256
|
-
function setRefs(node, own, forwarded) {
|
|
20375
|
+
function setRefs$1(node, own, forwarded) {
|
|
20257
20376
|
own.current = node;
|
|
20258
20377
|
if (typeof forwarded === 'function')
|
|
20259
20378
|
forwarded(node);
|
|
@@ -20385,7 +20504,7 @@ const ColorPickerArea = React.forwardRef(({ className, style, ...props }, ref) =
|
|
|
20385
20504
|
const hiddenOf = (which) => touch || keyed || axis === which ? undefined : true;
|
|
20386
20505
|
if (!picker)
|
|
20387
20506
|
return null;
|
|
20388
|
-
return (jsxRuntime.jsx("div", { ref: node => setRefs(node, rootRef, ref), className: classNames(areaClass, bulmaHelperClasses, className), style: {
|
|
20507
|
+
return (jsxRuntime.jsx("div", { ref: node => setRefs$1(node, rootRef, ref), className: classNames(areaClass, bulmaHelperClasses, className), style: {
|
|
20389
20508
|
...style,
|
|
20390
20509
|
'--color-picker-hue': Math.round(color.h),
|
|
20391
20510
|
'--color-picker-rgb': rgbChannels(color),
|
|
@@ -20459,7 +20578,7 @@ const ChannelSlider = React.forwardRef(({ channel, className, style, ...props },
|
|
|
20459
20578
|
};
|
|
20460
20579
|
if (!picker)
|
|
20461
20580
|
return null;
|
|
20462
|
-
return (jsxRuntime.jsxs("div", { ref: node => setRefs(node, rootRef, ref), className: classNames(sliderClass, bulmaHelperClasses, className), style: {
|
|
20581
|
+
return (jsxRuntime.jsxs("div", { ref: node => setRefs$1(node, rootRef, ref), className: classNames(sliderClass, bulmaHelperClasses, className), style: {
|
|
20463
20582
|
...style,
|
|
20464
20583
|
'--color-picker-hue': Math.round(color.h),
|
|
20465
20584
|
'--color-picker-rgb': rgbChannels(color),
|
|
@@ -20583,7 +20702,7 @@ const ColorPickerSwatches = React.forwardRef(({ className, swatches: ownSwatches
|
|
|
20583
20702
|
};
|
|
20584
20703
|
if (!picker || list.length === 0)
|
|
20585
20704
|
return null;
|
|
20586
|
-
return (jsxRuntime.jsx("div", { ref: node => setRefs(node, rootRef, ref), className: classNames(groupClass, bulmaHelperClasses, className), role: "radiogroup", "aria-label": labels.swatches, "aria-disabled": disabled || undefined, ...rest, children: list.map((entry, i) => {
|
|
20705
|
+
return (jsxRuntime.jsx("div", { ref: node => setRefs$1(node, rootRef, ref), className: classNames(groupClass, bulmaHelperClasses, className), role: "radiogroup", "aria-label": labels.swatches, "aria-disabled": disabled || undefined, ...rest, children: list.map((entry, i) => {
|
|
20587
20706
|
const swatch = colorOf(i);
|
|
20588
20707
|
const isChecked = i === checked;
|
|
20589
20708
|
// The check is drawn in black or white with a ring of the other, so
|
|
@@ -27011,6 +27130,421 @@ function ClientOnly({ children, fallback = null }) {
|
|
|
27011
27130
|
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: typeof children === 'function' ? children() : children });
|
|
27012
27131
|
}
|
|
27013
27132
|
|
|
27133
|
+
const FrameContext = React.createContext(null);
|
|
27134
|
+
/** A width prop as CSS: a number is pixels. */
|
|
27135
|
+
const cssLength = (value) => typeof value === 'number' ? `${value}px` : value;
|
|
27136
|
+
/**
|
|
27137
|
+
* Hands `node` to a part's own ref and to the one forwarded to it. A
|
|
27138
|
+
* forwarded callback that returns a cleanup, as React 19 allows, has it held
|
|
27139
|
+
* in `cleanup` and run on detach in place of a call with `null`, on React 18
|
|
27140
|
+
* as well.
|
|
27141
|
+
*/
|
|
27142
|
+
function setRefs(node, own, forwarded, cleanup) {
|
|
27143
|
+
own.current = node;
|
|
27144
|
+
if (typeof forwarded === 'function') {
|
|
27145
|
+
if (node === null) {
|
|
27146
|
+
const detach = cleanup.current;
|
|
27147
|
+
cleanup.current = null;
|
|
27148
|
+
if (detach)
|
|
27149
|
+
detach();
|
|
27150
|
+
else
|
|
27151
|
+
forwarded(null);
|
|
27152
|
+
return;
|
|
27153
|
+
}
|
|
27154
|
+
const returned = forwarded(node);
|
|
27155
|
+
cleanup.current =
|
|
27156
|
+
typeof returned === 'function' ? returned : null;
|
|
27157
|
+
}
|
|
27158
|
+
else if (forwarded) {
|
|
27159
|
+
forwarded.current = node;
|
|
27160
|
+
}
|
|
27161
|
+
}
|
|
27162
|
+
/**
|
|
27163
|
+
* Warns when a part has an `id` the frame couldn't read, because it isn't a
|
|
27164
|
+
* direct child, so the skip link or the burger points at the wrong one.
|
|
27165
|
+
*/
|
|
27166
|
+
function warnOnUnreadId(part, id, expected) {
|
|
27167
|
+
if (id && id !== expected) {
|
|
27168
|
+
warnOnce(`AppFrame:${part}-id`, `[bestax-bulma] <AppFrame.${part}> has an id <AppFrame> can't read, ` +
|
|
27169
|
+
`so ${part === 'Main' ? 'the skip link' : "the burger's aria-controls"} ` +
|
|
27170
|
+
`misses it. Put AppFrame.${part} directly inside AppFrame.`);
|
|
27171
|
+
}
|
|
27172
|
+
}
|
|
27173
|
+
/**
|
|
27174
|
+
* The top bar of an AppFrame, which sticks while the page scrolls.
|
|
27175
|
+
*
|
|
27176
|
+
* @function
|
|
27177
|
+
* @param {AppFrameHeaderProps} props - Props for AppFrame.Header.
|
|
27178
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<header>`.
|
|
27179
|
+
* @returns {JSX.Element} The rendered header.
|
|
27180
|
+
*/
|
|
27181
|
+
const AppFrameHeader = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
27182
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
27183
|
+
const headerClass = usePrefixedClassNames('app-frame-header');
|
|
27184
|
+
return (jsxRuntime.jsx("header", { ref: ref, className: classNames(headerClass, bulmaHelperClasses, className), ...rest, children: children }));
|
|
27185
|
+
});
|
|
27186
|
+
AppFrameHeader.displayName = 'AppFrame.Header';
|
|
27187
|
+
/**
|
|
27188
|
+
* The navigation of an AppFrame: a column beside the page from the breakpoint up, and a drawer below it.
|
|
27189
|
+
*
|
|
27190
|
+
* @function
|
|
27191
|
+
* @param {AppFrameNavProps} props - Props for AppFrame.Nav.
|
|
27192
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<nav>`.
|
|
27193
|
+
* @returns {JSX.Element} The rendered nav, and the drawer's backdrop.
|
|
27194
|
+
*/
|
|
27195
|
+
const AppFrameNav = React.forwardRef(({ className, style, width, overlay = true, closeOnEscape = true, closeOnClickOutside = true, closeOnNavigate = true, onClick, onPointerDownCapture, children, ...props }, ref) => {
|
|
27196
|
+
const frame = React.useContext(FrameContext);
|
|
27197
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
27198
|
+
const navClass = usePrefixedClassNames('app-frame-nav');
|
|
27199
|
+
const closeClass = usePrefixedClassNames('app-frame-close');
|
|
27200
|
+
const backdropClass = usePrefixedClassNames('app-frame-backdrop');
|
|
27201
|
+
const activeClass = usePrefixedClassNames('is-active');
|
|
27202
|
+
const navRef = React.useRef(null);
|
|
27203
|
+
const backdropRef = React.useRef(null);
|
|
27204
|
+
const navCleanupRef = React.useRef(null);
|
|
27205
|
+
const navRefs = React.useCallback((node) => setRefs(node, navRef, ref, navCleanupRef), [ref]);
|
|
27206
|
+
const navId = rest.id || frame?.state.navId;
|
|
27207
|
+
if (frame)
|
|
27208
|
+
warnOnUnreadId('Nav', rest.id, frame.state.navId);
|
|
27209
|
+
// The drawer is open only below the breakpoint. Above it the nav is a
|
|
27210
|
+
// column, and none of what follows applies.
|
|
27211
|
+
const isDrawer = !!frame?.state.isDrawer;
|
|
27212
|
+
const open = isDrawer && !!frame?.state.navOpen;
|
|
27213
|
+
// The open drawer is Bulma's active state, on the nav as on a navbar's
|
|
27214
|
+
// menu, and on its backdrop.
|
|
27215
|
+
const shown = { [activeClass]: !!frame?.state.navOpen };
|
|
27216
|
+
const modal = open && overlay;
|
|
27217
|
+
const close = () => frame?.state.setNavOpen(false);
|
|
27218
|
+
// The rest of the frame goes inert: the header, the page, the aside and
|
|
27219
|
+
// the footer, but not the backdrop, which takes the presses outside the
|
|
27220
|
+
// drawer rather than let them through to what's under it. Not the whole
|
|
27221
|
+
// page, since a Modal or a toast renders beside the frame, often into an
|
|
27222
|
+
// element that's there before the drawer opens, and a Modal opened from
|
|
27223
|
+
// the drawer has to stay usable. First, so that as the drawer closes it
|
|
27224
|
+
// lets go before focus goes back to the burger: an inert element can't
|
|
27225
|
+
// take focus.
|
|
27226
|
+
useInertSiblings(navRef, modal, false, backdropRef);
|
|
27227
|
+
const isTopmost = useOverlayStack(open, overlay);
|
|
27228
|
+
useScrollLock(modal);
|
|
27229
|
+
// Tab stays inside while the drawer is the topmost modal overlay, as in
|
|
27230
|
+
// Sidebar, so a Modal opened from the drawer holds Tab until it closes.
|
|
27231
|
+
React.useEffect(() => {
|
|
27232
|
+
if (!modal)
|
|
27233
|
+
return undefined;
|
|
27234
|
+
return trapTab(() => navRef.current, window, () => isTopmost('modal'));
|
|
27235
|
+
}, [modal, isTopmost]);
|
|
27236
|
+
useOverlayEscape(open && closeOnEscape, isTopmost, close);
|
|
27237
|
+
// Without an overlay there's no backdrop: a press outside the drawer
|
|
27238
|
+
// closes it, as Popover's does, while it's the topmost overlay, so a
|
|
27239
|
+
// press in a Modal opened over it is the Modal's. A press on a control
|
|
27240
|
+
// that names the nav in its `aria-controls`, such as the burger, is left
|
|
27241
|
+
// to that control, which toggles it.
|
|
27242
|
+
const markInsidePress = useOutsidePress(navRef, open && !overlay && closeOnClickOutside, e => {
|
|
27243
|
+
const id = navRef.current?.id;
|
|
27244
|
+
const controlsNav = (node) => !!node
|
|
27245
|
+
.getAttribute?.('aria-controls')
|
|
27246
|
+
?.split(/\s+/)
|
|
27247
|
+
.includes(id);
|
|
27248
|
+
if (!isTopmost() || e.composedPath().some(controlsNav))
|
|
27249
|
+
return;
|
|
27250
|
+
close();
|
|
27251
|
+
});
|
|
27252
|
+
// Focus moves to the drawer's first tab stop, the close button, as it
|
|
27253
|
+
// opens, and back to whatever had it as it closes, unless it has left
|
|
27254
|
+
// the drawer. After a press outside, the press's own mousedown then takes
|
|
27255
|
+
// it wherever the press goes. When the window widened, the drawer is the
|
|
27256
|
+
// column again, and focus on its links stays; the close button hides
|
|
27257
|
+
// there, so focus on it, or dropped from it to <body>, goes back too.
|
|
27258
|
+
const isDrawerRef = useLatestRef(isDrawer);
|
|
27259
|
+
useOverlayFocus(navRef, open, {
|
|
27260
|
+
modal: overlay,
|
|
27261
|
+
restore: () => {
|
|
27262
|
+
if (isDrawerRef.current)
|
|
27263
|
+
return true;
|
|
27264
|
+
const nav = navRef.current;
|
|
27265
|
+
const active = getActiveElementInTree(nav);
|
|
27266
|
+
return (!nav.contains(active) || !!active?.classList.contains(closeClass));
|
|
27267
|
+
},
|
|
27268
|
+
});
|
|
27269
|
+
const handleClick = (e) => {
|
|
27270
|
+
onClick?.(e);
|
|
27271
|
+
if (!open ||
|
|
27272
|
+
!closeOnNavigate ||
|
|
27273
|
+
e.button !== 0 ||
|
|
27274
|
+
e.metaKey ||
|
|
27275
|
+
e.ctrlKey ||
|
|
27276
|
+
e.shiftKey ||
|
|
27277
|
+
e.altKey) {
|
|
27278
|
+
return;
|
|
27279
|
+
}
|
|
27280
|
+
// Not skipped for a click whose default was prevented: a router's
|
|
27281
|
+
// link prevents it to navigate without a page load. Only a link the nav
|
|
27282
|
+
// holds: one in a Modal portaled from it is the Modal's.
|
|
27283
|
+
const link = e.target.closest('a[href]');
|
|
27284
|
+
if (!link || !navRef.current?.contains(link))
|
|
27285
|
+
return;
|
|
27286
|
+
if (link.hasAttribute('download'))
|
|
27287
|
+
return;
|
|
27288
|
+
const target = link.getAttribute('target');
|
|
27289
|
+
if (target && target !== '_self')
|
|
27290
|
+
return;
|
|
27291
|
+
close();
|
|
27292
|
+
};
|
|
27293
|
+
const navStyle = width === undefined
|
|
27294
|
+
? style
|
|
27295
|
+
: {
|
|
27296
|
+
...style,
|
|
27297
|
+
'--bulma-app-frame-nav-width': cssLength(width),
|
|
27298
|
+
};
|
|
27299
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("nav", { ref: navRefs, className: classNames(navClass, shown, bulmaHelperClasses, className), style: navStyle, onClick: handleClick, onPointerDownCapture: e => {
|
|
27300
|
+
onPointerDownCapture?.(e);
|
|
27301
|
+
markInsidePress(e);
|
|
27302
|
+
}, ...rest, id: navId, "aria-label": rest['aria-label'] ||
|
|
27303
|
+
(rest['aria-labelledby'] ? undefined : frame?.labels.nav), children: [frame && (jsxRuntime.jsx(Delete, { className: closeClass, size: "medium", ariaLabel: frame.labels.closeNav, onClick: close })), children] }), frame && overlay && (jsxRuntime.jsx("div", { ref: backdropRef, className: classNames(backdropClass, shown), "aria-hidden": "true", onClick: closeOnClickOutside
|
|
27304
|
+
? () => {
|
|
27305
|
+
// Not under an overlay opened over the drawer.
|
|
27306
|
+
if (isTopmost())
|
|
27307
|
+
close();
|
|
27308
|
+
}
|
|
27309
|
+
: undefined }))] }));
|
|
27310
|
+
});
|
|
27311
|
+
AppFrameNav.displayName = 'AppFrame.Nav';
|
|
27312
|
+
/**
|
|
27313
|
+
* The page's main content in an AppFrame, which the skip link moves focus to.
|
|
27314
|
+
*
|
|
27315
|
+
* @function
|
|
27316
|
+
* @param {AppFrameMainProps} props - Props for AppFrame.Main.
|
|
27317
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<main>`.
|
|
27318
|
+
* @returns {JSX.Element} The rendered main.
|
|
27319
|
+
*/
|
|
27320
|
+
const AppFrameMain = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
27321
|
+
const frame = React.useContext(FrameContext);
|
|
27322
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
27323
|
+
const mainClass = usePrefixedClassNames('app-frame-main');
|
|
27324
|
+
if (frame)
|
|
27325
|
+
warnOnUnreadId('Main', rest.id, frame.mainId);
|
|
27326
|
+
return (jsxRuntime.jsx("main", { ref: ref, tabIndex: -1, className: classNames(mainClass, bulmaHelperClasses, className), ...rest, id: rest.id || frame?.mainId, children: children }));
|
|
27327
|
+
});
|
|
27328
|
+
AppFrameMain.displayName = 'AppFrame.Main';
|
|
27329
|
+
/**
|
|
27330
|
+
* Content beside the page in an AppFrame, which moves under it below the breakpoint.
|
|
27331
|
+
*
|
|
27332
|
+
* @function
|
|
27333
|
+
* @param {AppFrameAsideProps} props - Props for AppFrame.Aside.
|
|
27334
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<aside>`.
|
|
27335
|
+
* @returns {JSX.Element} The rendered aside.
|
|
27336
|
+
*/
|
|
27337
|
+
const AppFrameAside = React.forwardRef(({ className, style, width, children, ...props }, ref) => {
|
|
27338
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
27339
|
+
const asideClass = usePrefixedClassNames('app-frame-aside');
|
|
27340
|
+
const asideStyle = width === undefined
|
|
27341
|
+
? style
|
|
27342
|
+
: {
|
|
27343
|
+
...style,
|
|
27344
|
+
'--bulma-app-frame-aside-width': cssLength(width),
|
|
27345
|
+
};
|
|
27346
|
+
return (jsxRuntime.jsx("aside", { ref: ref, className: classNames(asideClass, bulmaHelperClasses, className), style: asideStyle, ...rest, children: children }));
|
|
27347
|
+
});
|
|
27348
|
+
AppFrameAside.displayName = 'AppFrame.Aside';
|
|
27349
|
+
/**
|
|
27350
|
+
* The bottom of an AppFrame, under the page.
|
|
27351
|
+
*
|
|
27352
|
+
* @function
|
|
27353
|
+
* @param {AppFrameFooterProps} props - Props for AppFrame.Footer.
|
|
27354
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<footer>`.
|
|
27355
|
+
* @returns {JSX.Element} The rendered footer.
|
|
27356
|
+
*/
|
|
27357
|
+
const AppFrameFooter = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
27358
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
27359
|
+
const footerClass = usePrefixedClassNames('app-frame-footer');
|
|
27360
|
+
return (jsxRuntime.jsx("footer", { ref: ref, className: classNames(footerClass, bulmaHelperClasses, className), ...rest, children: children }));
|
|
27361
|
+
});
|
|
27362
|
+
AppFrameFooter.displayName = 'AppFrame.Footer';
|
|
27363
|
+
/**
|
|
27364
|
+
* The button that opens the drawer, or collapses the column from the breakpoint up: Navbar.Burger, wired to the frame.
|
|
27365
|
+
*
|
|
27366
|
+
* @function
|
|
27367
|
+
* @param {AppFrameBurgerProps} props - Props for AppFrame.Burger.
|
|
27368
|
+
* @param {React.Ref<HTMLButtonElement>} ref - Forwarded ref to the `<button>`.
|
|
27369
|
+
* @returns {JSX.Element} The rendered burger.
|
|
27370
|
+
*/
|
|
27371
|
+
const AppFrameBurger = React.forwardRef(({ className, onClick, ...props }, ref) => {
|
|
27372
|
+
// Outside a frame it's a Navbar.Burger that controls nothing.
|
|
27373
|
+
const frame = React.useContext(FrameContext);
|
|
27374
|
+
const burgerClass = usePrefixedClassNames('app-frame-burger');
|
|
27375
|
+
const state = frame?.state;
|
|
27376
|
+
// Until the frame knows whether the nav is a drawer, the burger says
|
|
27377
|
+
// nothing it can't stand behind: no `aria-expanded`, and its bars. It can't
|
|
27378
|
+
// act before then anyway, since that's before hydration. Outside a frame
|
|
27379
|
+
// it's a plain Navbar.Burger.
|
|
27380
|
+
const known = !!frame?.modeKnown;
|
|
27381
|
+
const expanded = known && !!state && (state.isDrawer ? state.navOpen : !state.navCollapsed);
|
|
27382
|
+
return (jsxRuntime.jsx(NavbarBurger, { ref: ref, ...props, className: classNames(burgerClass, className), "aria-label": props['aria-label'] || frame?.labels.burger, active: expanded, "aria-expanded": frame && !known ? null : expanded, "aria-controls": state?.navId, onClick: e => {
|
|
27383
|
+
onClick?.(e);
|
|
27384
|
+
if (e.defaultPrevented || !frame)
|
|
27385
|
+
return;
|
|
27386
|
+
// Safari, and Firefox on a Mac, don't focus a button that's pressed.
|
|
27387
|
+
// Focused, it's the element the drawer hands focus back to.
|
|
27388
|
+
e.currentTarget.focus();
|
|
27389
|
+
frame.state.toggleNav();
|
|
27390
|
+
} }));
|
|
27391
|
+
});
|
|
27392
|
+
AppFrameBurger.displayName = 'AppFrame.Burger';
|
|
27393
|
+
const isMain = (child) => React.isValidElement(child) && child.type === AppFrameMain;
|
|
27394
|
+
const isNav = (child) => React.isValidElement(child) && child.type === AppFrameNav;
|
|
27395
|
+
/**
|
|
27396
|
+
* 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.
|
|
27397
|
+
*
|
|
27398
|
+
* The header and the column stick while the document scrolls, and a skip
|
|
27399
|
+
* link comes first. The layout is CSS, so the server renders it as the
|
|
27400
|
+
* browser does. It holds two states, each yours to control: whether the
|
|
27401
|
+
* drawer is open, and whether the column is collapsed. `AppFrame.Burger`
|
|
27402
|
+
* and `useAppFrame()` toggle whichever applies at the current width.
|
|
27403
|
+
*
|
|
27404
|
+
* @function
|
|
27405
|
+
* @param {AppFrameProps} props - Props for the AppFrame component.
|
|
27406
|
+
* @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the root `<div>`.
|
|
27407
|
+
* @returns {JSX.Element} The rendered frame.
|
|
27408
|
+
*
|
|
27409
|
+
* @example
|
|
27410
|
+
* <AppFrame>
|
|
27411
|
+
* <AppFrame.Header>
|
|
27412
|
+
* <Navbar aria-label="Site">
|
|
27413
|
+
* <Navbar.Brand>
|
|
27414
|
+
* <Navbar.Item href="/">Acme</Navbar.Item>
|
|
27415
|
+
* <AppFrame.Burger />
|
|
27416
|
+
* </Navbar.Brand>
|
|
27417
|
+
* </Navbar>
|
|
27418
|
+
* </AppFrame.Header>
|
|
27419
|
+
* <AppFrame.Nav>
|
|
27420
|
+
* <Menu>…</Menu>
|
|
27421
|
+
* </AppFrame.Nav>
|
|
27422
|
+
* <AppFrame.Main>…</AppFrame.Main>
|
|
27423
|
+
* </AppFrame>
|
|
27424
|
+
*/
|
|
27425
|
+
const AppFrameRoot = React.forwardRef(({ className, textColor, bgColor, breakpoint = 'desktop', navOpen: navOpenProp, defaultNavOpen = false, onNavOpenChange, navCollapsed: navCollapsedProp, defaultNavCollapsed = false, onNavCollapsedChange, skipLink = true, labels, children, ...props }, ref) => {
|
|
27426
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
27427
|
+
color: textColor,
|
|
27428
|
+
backgroundColor: bgColor,
|
|
27429
|
+
...props,
|
|
27430
|
+
});
|
|
27431
|
+
const rootRef = React.useRef(null);
|
|
27432
|
+
const rootCleanupRef = React.useRef(null);
|
|
27433
|
+
const rootRefs = React.useCallback((node) => setRefs(node, rootRef, ref, rootCleanupRef), [ref]);
|
|
27434
|
+
const [openState, setOpenState] = React.useState(defaultNavOpen);
|
|
27435
|
+
const [collapsedState, setCollapsedState] = React.useState(defaultNavCollapsed);
|
|
27436
|
+
const navOpen = navOpenProp ?? openState;
|
|
27437
|
+
const navCollapsed = navCollapsedProp ?? collapsedState;
|
|
27438
|
+
const openControlled = navOpenProp !== undefined;
|
|
27439
|
+
const collapsedControlled = navCollapsedProp !== undefined;
|
|
27440
|
+
const onNavOpenChangeRef = useLatestRef(onNavOpenChange);
|
|
27441
|
+
const onNavCollapsedChangeRef = useLatestRef(onNavCollapsedChange);
|
|
27442
|
+
const setNavOpen = React.useCallback((open) => {
|
|
27443
|
+
onNavOpenChangeRef.current?.(open);
|
|
27444
|
+
if (!openControlled)
|
|
27445
|
+
setOpenState(open);
|
|
27446
|
+
}, [openControlled, onNavOpenChangeRef]);
|
|
27447
|
+
const setNavCollapsed = React.useCallback((collapsed) => {
|
|
27448
|
+
onNavCollapsedChangeRef.current?.(collapsed);
|
|
27449
|
+
if (!collapsedControlled)
|
|
27450
|
+
setCollapsedState(collapsed);
|
|
27451
|
+
}, [collapsedControlled, onNavCollapsedChangeRef]);
|
|
27452
|
+
// The layout switches in the stylesheet. The script needs the width
|
|
27453
|
+
// only to know which state the burger toggles, and to close the drawer
|
|
27454
|
+
// once the window is at or past the breakpoint.
|
|
27455
|
+
const wide = useBulmaBreakpoint(breakpoint);
|
|
27456
|
+
const isDrawer = wide === false;
|
|
27457
|
+
const isWide = wide === true;
|
|
27458
|
+
React.useEffect(() => {
|
|
27459
|
+
// 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
|
|
27460
|
+
if (isWide && navOpen)
|
|
27461
|
+
setNavOpen(false);
|
|
27462
|
+
}, [isWide, navOpen, setNavOpen]);
|
|
27463
|
+
const toggleNav = React.useCallback(() => {
|
|
27464
|
+
if (isDrawer)
|
|
27465
|
+
setNavOpen(!navOpen);
|
|
27466
|
+
else
|
|
27467
|
+
setNavCollapsed(!navCollapsed);
|
|
27468
|
+
}, [isDrawer, navOpen, navCollapsed, setNavOpen, setNavCollapsed]);
|
|
27469
|
+
// The ids of the parts the skip link and the burger point at, read off
|
|
27470
|
+
// the children so the server renders them too.
|
|
27471
|
+
const baseId = React.useId();
|
|
27472
|
+
const parts = flattenChildren(children);
|
|
27473
|
+
const mainId = parts.find(isMain)?.props.id || `${baseId}-main`;
|
|
27474
|
+
const navId = parts.find(isNav)?.props.id || `${baseId}-nav`;
|
|
27475
|
+
const { skipLink: skipText, nav, closeNav, burger } = labels ?? {};
|
|
27476
|
+
const value = React.useMemo(() => ({
|
|
27477
|
+
state: {
|
|
27478
|
+
navOpen,
|
|
27479
|
+
setNavOpen,
|
|
27480
|
+
navCollapsed,
|
|
27481
|
+
setNavCollapsed,
|
|
27482
|
+
isDrawer,
|
|
27483
|
+
toggleNav,
|
|
27484
|
+
navId,
|
|
27485
|
+
},
|
|
27486
|
+
labels: {
|
|
27487
|
+
skipLink: skipText || 'Skip to main content',
|
|
27488
|
+
nav: nav || 'Sections',
|
|
27489
|
+
closeNav: closeNav || 'Close navigation',
|
|
27490
|
+
burger: burger || undefined,
|
|
27491
|
+
},
|
|
27492
|
+
mainId,
|
|
27493
|
+
modeKnown: wide !== null,
|
|
27494
|
+
}), [
|
|
27495
|
+
navOpen,
|
|
27496
|
+
setNavOpen,
|
|
27497
|
+
navCollapsed,
|
|
27498
|
+
setNavCollapsed,
|
|
27499
|
+
isDrawer,
|
|
27500
|
+
toggleNav,
|
|
27501
|
+
navId,
|
|
27502
|
+
skipText,
|
|
27503
|
+
nav,
|
|
27504
|
+
closeNav,
|
|
27505
|
+
burger,
|
|
27506
|
+
mainId,
|
|
27507
|
+
wide,
|
|
27508
|
+
]);
|
|
27509
|
+
// Its header and footer are the page's banner and contentinfo only
|
|
27510
|
+
// outside `main` and sectioning content, as HTML-AAM maps them.
|
|
27511
|
+
React.useEffect(() => {
|
|
27512
|
+
const host = rootRef.current?.parentElement?.closest('main, article, aside, nav, section');
|
|
27513
|
+
if (host) {
|
|
27514
|
+
warnOnce('AppFrame:nested', `[bestax-bulma] <AppFrame> sits inside a <${host.localName}>, where ` +
|
|
27515
|
+
"its header and footer are no longer the page's banner and " +
|
|
27516
|
+
'contentinfo landmarks. Put AppFrame at the top of the page.');
|
|
27517
|
+
}
|
|
27518
|
+
}, []);
|
|
27519
|
+
const rootClass = usePrefixedClassNames('app-frame', {
|
|
27520
|
+
[`is-${breakpoint}`]: true,
|
|
27521
|
+
'is-nav-collapsed': navCollapsed,
|
|
27522
|
+
});
|
|
27523
|
+
const skipClass = usePrefixedClassNames('app-frame-skip-link');
|
|
27524
|
+
return (jsxRuntime.jsxs("div", { ref: rootRefs, className: classNames(rootClass, bulmaHelperClasses, className), ...rest, children: [skipLink && (jsxRuntime.jsx("a", { className: skipClass, href: `#${mainId}`, children: value.labels.skipLink })), jsxRuntime.jsx(FrameContext.Provider, { value: value, children: children })] }));
|
|
27525
|
+
});
|
|
27526
|
+
/**
|
|
27527
|
+
* The frame's state, for a control of your own inside it: whether the
|
|
27528
|
+
* drawer is open and the column collapsed, setters that go through the
|
|
27529
|
+
* frame's callbacks, and `toggleNav()`, which does what the burger does.
|
|
27530
|
+
* Outside an AppFrame it's `null`, as the library's other context hooks
|
|
27531
|
+
* are, so a control that can sit outside one renders a fallback or nothing.
|
|
27532
|
+
*
|
|
27533
|
+
* @function useAppFrame
|
|
27534
|
+
* @returns The frame's state, or `null` outside an AppFrame.
|
|
27535
|
+
*/
|
|
27536
|
+
function useAppFrame() {
|
|
27537
|
+
return React.useContext(FrameContext)?.state ?? null;
|
|
27538
|
+
}
|
|
27539
|
+
const AppFrame = withSubComponents(AppFrameRoot, {
|
|
27540
|
+
Header: AppFrameHeader,
|
|
27541
|
+
Nav: AppFrameNav,
|
|
27542
|
+
Main: AppFrameMain,
|
|
27543
|
+
Aside: AppFrameAside,
|
|
27544
|
+
Footer: AppFrameFooter,
|
|
27545
|
+
Burger: AppFrameBurger,
|
|
27546
|
+
}, 'AppFrame');
|
|
27547
|
+
|
|
27014
27548
|
/**
|
|
27015
27549
|
* The `.stack` element behind Stack, Group, Center and Flex.
|
|
27016
27550
|
*
|
|
@@ -28020,6 +28554,13 @@ const Stack = React.forwardRef(function Stack(props, ref) {
|
|
|
28020
28554
|
Stack.displayName = 'Stack';
|
|
28021
28555
|
|
|
28022
28556
|
exports.Affix = Affix;
|
|
28557
|
+
exports.AppFrame = AppFrame;
|
|
28558
|
+
exports.AppFrameAside = AppFrameAside;
|
|
28559
|
+
exports.AppFrameBurger = AppFrameBurger;
|
|
28560
|
+
exports.AppFrameFooter = AppFrameFooter;
|
|
28561
|
+
exports.AppFrameHeader = AppFrameHeader;
|
|
28562
|
+
exports.AppFrameMain = AppFrameMain;
|
|
28563
|
+
exports.AppFrameNav = AppFrameNav;
|
|
28023
28564
|
exports.Autocomplete = Autocomplete;
|
|
28024
28565
|
exports.Avatar = Avatar;
|
|
28025
28566
|
exports.Avatars = Avatars;
|
|
@@ -28250,6 +28791,7 @@ exports.radioSizes = radioSizes;
|
|
|
28250
28791
|
exports.switchColors = switchColors;
|
|
28251
28792
|
exports.switchSizes = switchSizes;
|
|
28252
28793
|
exports.toast = toast;
|
|
28794
|
+
exports.useAppFrame = useAppFrame;
|
|
28253
28795
|
exports.useBulmaClasses = useBulmaClasses;
|
|
28254
28796
|
exports.useClassPrefix = useClassPrefix;
|
|
28255
28797
|
exports.useColorClasses = useColorClasses;
|