@allxsmith/bestax-bulma 5.60.0 → 5.62.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/components/TreeView.js +14 -10
- package/dist/esm/components/TreeView.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 +11 -16
- package/dist/esm/form/Combobox.js.map +1 -1
- package/dist/esm/form/Field.js.map +1 -1
- package/dist/esm/form/TreeSelect.js +395 -0
- package/dist/esm/form/TreeSelect.js.map +1 -0
- package/dist/esm/form/_pickerInternals/PickerPopover.js +5 -1
- package/dist/esm/form/_pickerInternals/PickerPopover.js.map +1 -1
- package/dist/esm/form/_selectInternals/DialogSelect.js +208 -0
- package/dist/esm/form/_selectInternals/DialogSelect.js.map +1 -0
- package/dist/esm/form/_selectInternals/ValueTags.js +28 -0
- package/dist/esm/form/_selectInternals/ValueTags.js.map +1 -0
- package/dist/esm/helpers/overlayStack.js +33 -1
- package/dist/esm/helpers/overlayStack.js.map +1 -1
- package/dist/esm/helpers/treeData.js +97 -0
- package/dist/esm/helpers/treeData.js.map +1 -0
- package/dist/esm/helpers/useBulmaViewport.js +82 -0
- package/dist/esm/helpers/useBulmaViewport.js.map +1 -0
- package/dist/esm/helpers/useComboboxEngine.js +2 -2
- package/dist/esm/helpers/useComboboxEngine.js.map +1 -1
- package/dist/esm/helpers/useControlled.js +30 -0
- package/dist/esm/helpers/useControlled.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/helpers/useTreeView.js +15 -13
- package/dist/esm/helpers/useTreeView.js.map +1 -1
- 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 +1767 -518
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/Navbar.d.ts +4 -3
- package/dist/types/form/Field.d.ts +1 -1
- package/dist/types/form/TreeSelect.d.ts +308 -0
- package/dist/types/form/_pickerInternals/PickerPopover.d.ts +8 -0
- package/dist/types/form/_selectInternals/DialogSelect.d.ts +86 -0
- package/dist/types/form/_selectInternals/ValueTags.d.ts +40 -0
- package/dist/types/helpers/overlayStack.d.ts +17 -0
- package/dist/types/helpers/treeData.d.ts +48 -0
- package/dist/types/helpers/useBulmaViewport.d.ts +34 -0
- package/dist/types/helpers/useControlled.d.ts +14 -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/helpers/useTreeView.d.ts +30 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/layout/AppFrame.d.ts +306 -0
- package/dist/types-cjs/components/Navbar.d.ts +4 -3
- package/dist/types-cjs/form/Field.d.ts +1 -1
- package/dist/types-cjs/form/TreeSelect.d.ts +308 -0
- package/dist/types-cjs/form/_pickerInternals/PickerPopover.d.ts +8 -0
- package/dist/types-cjs/form/_selectInternals/DialogSelect.d.ts +86 -0
- package/dist/types-cjs/form/_selectInternals/ValueTags.d.ts +40 -0
- package/dist/types-cjs/helpers/overlayStack.d.ts +17 -0
- package/dist/types-cjs/helpers/treeData.d.ts +48 -0
- package/dist/types-cjs/helpers/useBulmaViewport.d.ts +34 -0
- package/dist/types-cjs/helpers/useControlled.d.ts +14 -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/helpers/useTreeView.d.ts +30 -0
- package/dist/types-cjs/index.d.ts +2 -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/form/_index.scss +1 -0
- package/src/scss/form/_picker-popover.scss +6 -0
- package/src/scss/form/_treeselect.scss +146 -0
- package/src/scss/layout/_appframe.scss +306 -0
- package/src/scss/layout/_index.scss +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTreeView.js","sources":["../../../src/helpers/useTreeView.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useCallback, useEffect, useReducer, useRef, useState } from 'react';\nimport type React from 'react';\nimport type { TreeViewSelectionMode } from '../components/TreeView';\nimport { useClientLayoutEffect } from './useClientLayoutEffect';\nimport { getActiveElementInTree } from './shadowDom';\nimport { typeaheadMatch, typeaheadText, useTypeahead } from './typeahead';\n\n/** What an item tells the tree about itself when it registers. */\nexport interface TreeViewItemRecord {\n /** The value of the item it's in, or `null` at the top. */\n parent: string | null;\n disabled: boolean;\n /** It has children, or `hasChildren` says it will. */\n isParent: boolean;\n}\n\n/** An item as a key or a click on it sees it. */\nexport interface TreeViewItemInfo extends TreeViewItemRecord {\n value: string;\n open: boolean;\n /**\n * A failed load's row: what Enter, Space and a click on it do, in place of\n * what the mode says.\n */\n retry?: () => void;\n}\n\n/** A box's state: checked, unchecked or mixed. */\nexport type TreeViewCheckState = boolean | 'mixed';\n\n/**\n * Where an item sits among the items beside it, for its `aria-posinset` and\n * `aria-setsize`: its number, from 1, and how many there are.\n */\nexport interface TreeViewPosition {\n posinset: number;\n setsize: number;\n}\n\n/**\n * What an item shows of the selection: `selected` in a `single` tree,\n * `checked` in a `checkbox` or `checkbox-independent` tree, and neither with\n * `none`.\n */\nexport interface TreeViewItemState {\n selected?: boolean;\n checked?: TreeViewCheckState;\n}\n\ntype Action = 'open' | 'select' | 'check';\n\n/** What Enter, Space and a click on the row do in a mode. */\ninterface ModeActions {\n enter: Action[];\n space: Action[];\n click: Action[];\n}\n\n/** The keys and clicks of both checkbox modes, which differ only in what a check reaches. */\nconst CHECKBOX_ACTIONS: ModeActions = {\n enter: ['open'],\n space: ['check'],\n click: ['check'],\n};\n\n/**\n * What Enter, Space and a click on the row do in each mode, from the tables\n * in #1096, with `checkbox-independent` from #1430. `open` opens or closes a\n * parent and does nothing on a leaf. A click on the chevron only ever opens\n * or closes.\n */\nconst MODES: Record<TreeViewSelectionMode, ModeActions> = {\n none: { enter: ['open'], space: ['open'], click: ['open'] },\n single: { enter: ['select'], space: ['select'], click: ['select', 'open'] },\n checkbox: CHECKBOX_ACTIONS,\n 'checkbox-independent': CHECKBOX_ACTIONS,\n};\n\n/** Left and Right, swapped for a right-to-left page. */\nconst MIRRORED: Record<string, string> = {\n ArrowLeft: 'ArrowRight',\n ArrowRight: 'ArrowLeft',\n};\n\n/** An IME is composing, so the key belongs to it (`keyCode` 229 is Safari's word for it). */\nconst isComposing = (event: React.KeyboardEvent): boolean =>\n event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;\n\n/**\n * The items a tree knows, and their elements while they're on the page. A\n * loading item's row, or a failed load's, hands over its element under a key\n * of its own and has no record, so the keys reach it and nothing else counts it.\n */\ninterface Registry {\n records: Map<string, TreeViewItemRecord>;\n // By value, for an item whose ref lets go, which hands over no element.\n elements: Map<string, HTMLLIElement>;\n // Weak, so an element that leaves the page needn't be forgotten by hand.\n values: WeakMap<Element, string>;\n}\n\n/** The registry's items on the page, in document order. */\nconst shownIn = (tree: Element, { values }: Registry): HTMLElement[] =>\n Array.from(tree.querySelectorAll<HTMLElement>('[role=\"treeitem\"]')).filter(\n element => values.has(element)\n );\n\n/** The item an item on the page sits in: its group's `<li>`, or none at the top. */\nfunction parentItemOf(element: Element): HTMLElement | null {\n const group = element.parentElement!;\n return group.getAttribute('role') === 'group' ? group.parentElement : null;\n}\n\n/** No positions: on the server, and until the tree has mounted. */\nconst NO_POSITIONS = new Map<string, TreeViewPosition>();\n\n/**\n * Each item's place among the items beside it, by its key, read off the\n * items on the page: the ones in the same group, or at the top. A loading or\n * failed load's row counts, as it does for a browser that works positions\n * out for itself, so the declared ones say what a browser would.\n */\nfunction positionsOf(\n shown: HTMLElement[],\n { values }: Registry\n): Map<string, TreeViewPosition> {\n const sets = new Map<Element | null, HTMLElement[]>();\n shown.forEach(element => {\n const set = sets.get(element.parentElement);\n if (set) set.push(element);\n else sets.set(element.parentElement, [element]);\n });\n const positions = new Map<string, TreeViewPosition>();\n sets.forEach(set =>\n set.forEach((element, at) =>\n positions.set(values.get(element)!, {\n posinset: at + 1,\n setsize: set.length,\n })\n )\n );\n return positions;\n}\n\n/** Whether two sets of positions say the same for every item. */\nfunction samePositions(\n a: Map<string, TreeViewPosition>,\n b: Map<string, TreeViewPosition>\n): boolean {\n return (\n a.size === b.size &&\n Array.from(a).every(([key, { posinset, setsize }]) => {\n const other = b.get(key);\n return other?.posinset === posinset && other.setsize === setsize;\n })\n );\n}\n\n/** The values of the items an item on the page is in, from the top. */\nfunction pathOf(element: Element, { values }: Registry): string[] {\n const path: string[] = [];\n for (let at = parentItemOf(element); at; at = parentItemOf(at)) {\n path.unshift(values.get(at)!);\n }\n return path;\n}\n\n/**\n * Every known item's check state, by the rules in #1096:\n *\n * - a disabled item is checked only when its own value is listed;\n * - an enabled item with no enabled items anywhere under it is checked when\n * it, or any item above it, is listed;\n * - any other enabled item follows the items of that second kind in its\n * branch: checked when they all are, unchecked when none is, mixed between.\n *\n * Also returns the items of the second kind, which are what a toggle sets.\n */\nfunction readChecks(\n records: Map<string, TreeViewItemRecord>,\n listed: Set<string>\n): { states: Map<string, TreeViewCheckState>; ends: string[] } {\n const kids = new Map<string | null, string[]>();\n records.forEach(({ parent }, value) => {\n kids.set(parent, [...(kids.get(parent) ?? []), value]);\n });\n const states = new Map<string, TreeViewCheckState>();\n const ends: string[] = [];\n // How many enabled ends the branch holds, and how many are checked.\n const visit = (value: string, above: boolean): [number, number] => {\n const marked = above || listed.has(value);\n let total = 0;\n let on = 0;\n for (const kid of kids.get(value) ?? []) {\n const [t, o] = visit(kid, marked);\n total += t;\n on += o;\n }\n if (records.get(value)!.disabled) {\n states.set(value, listed.has(value));\n return [total, on];\n }\n if (total === 0) {\n states.set(value, marked);\n ends.push(value);\n return [1, marked ? 1 : 0];\n }\n states.set(value, on === total ? true : on > 0 && 'mixed');\n return [total, on];\n };\n for (const top of kids.get(null) ?? []) visit(top, false);\n return { states, ends };\n}\n\n/**\n * The list a toggle of `value` leaves: it and every enabled item under it\n * take the opposite of its state (a mixed one checks), and the rest is read\n * back by the rules. That's every item that shows checked, parents\n * included, plus any listed value no item has.\n */\nfunction toggledList(\n records: Map<string, TreeViewItemRecord>,\n list: string[],\n value: string\n): string[] {\n const { states, ends } = readChecks(records, new Set(list));\n const to = states.get(value) !== true;\n // An end was reached from the top, so everything above it is known.\n const under = (end: string) => {\n for (let at: string | null = end; at !== null;) {\n if (at === value) return true;\n at = records.get(at)!.parent;\n }\n return false;\n };\n const seed = new Set(\n ends.filter(end => (under(end) ? to : states.get(end) === true))\n );\n records.forEach(({ disabled }, item) => {\n if (disabled && states.get(item) === true) seed.add(item);\n });\n const after = readChecks(records, seed).states;\n return [\n ...Array.from(after)\n .filter(([, state]) => state === true)\n .map(([item]) => item),\n ...list.filter(item => !records.has(item)),\n ];\n}\n\n/** A value the caller holds, or one this hook holds when the caller doesn't. */\nfunction useControlled<T>(controlled: T | undefined, initial: T) {\n const [own, setOwn] = useState(initial);\n const isControlled = controlled !== undefined;\n const set = (next: T) => {\n if (!isControlled) setOwn(next);\n };\n return [isControlled ? controlled : own, set] as const;\n}\n\n/**\n * The list with `value` added at the end, or, when it's `present`, with\n * every copy of it taken out. Everything else stays as it was, in its order.\n */\nfunction addedOrRemoved(\n list: string[],\n value: string,\n present: boolean\n): string[] {\n return present ? list.filter(v => v !== value) : [...list, value];\n}\n\n/** Options for `useTreeView`: the TreeView's own props. */\nexport interface TreeViewOptions {\n mode: TreeViewSelectionMode;\n selected?: string | null;\n defaultSelected: string | null;\n onSelectedChange?: (value: string) => void;\n checked?: string[];\n defaultChecked: string[];\n onCheckedChange?: (checked: string[]) => void;\n expanded?: string[];\n defaultExpanded: string[];\n onExpandedChange?: (expanded: string[]) => void;\n}\n\n/** What `useTreeView` hands the root and its items. */\nexport interface TreeViewEngine {\n /** For the `<ul role=\"tree\">`. */\n treeRef: React.MutableRefObject<HTMLUListElement | null>;\n /**\n * Whether a closed item still renders its children, drawing nothing, so a\n * check knows everything under it. Only a `checkbox` tree needs to: a check\n * in a `checkbox-independent` tree reaches nothing under it.\n */\n rendersClosed: boolean;\n /** Whether any number of items can be checked, in either checkbox mode. */\n multiselectable: boolean;\n /** The item that holds `tabIndex={0}`. */\n tabStop: string | null;\n isOpen: (value: string) => boolean;\n /**\n * What the item shows of the selection. `path` is the values of the items\n * above it, which decide its box until the items have registered, as on\n * the server.\n */\n stateOf: (\n value: string,\n path: string[],\n disabled: boolean\n ) => TreeViewItemState;\n /**\n * Where the item with this key sits among the items beside it, read off\n * the page after each render and whenever items move. Nothing until the\n * tree has mounted, as on the server.\n */\n positionOf: (key: string) => TreeViewPosition | undefined;\n /** Records an item from its layout effect. Returns the function that forgets it. */\n register: (value: string, record: TreeViewItemRecord) => () => void;\n /** For each item's `<li>` ref: records it, and notices focus leaving with it. */\n setElement: (value: string, element: HTMLLIElement | null) => void;\n /** Focus arrived on the item's own `<li>`. */\n onFocus: (value: string, element: HTMLLIElement) => void;\n /** Focus left the item's own `<li>`. */\n onBlur: () => void;\n /** A key on the item's `<li>`, after its own `onKeyDown`. */\n onKeyDown: (\n event: React.KeyboardEvent<HTMLLIElement>,\n item: TreeViewItemInfo\n ) => void;\n /**\n * A click on the item's row, or with `onToggle` on its chevron. Focus and\n * the tab stop move to the item.\n */\n onClick: (\n element: HTMLLIElement,\n item: TreeViewItemInfo,\n onToggle: boolean\n ) => void;\n}\n\n/**\n * The open state, the selection, the keys, the tab stop, the positions and\n * the registry of a TreeView (#1096). Focus moves for real, with a roving\n * tabindex, rather than through `aria-activedescendant`. Each item's\n * position among the items beside it is declared rather than left to the\n * browser (#1171), read off the page once the tree has mounted.\n *\n * Every item registers its value, its parent and whether it's disabled from\n * a layout effect. In a `checkbox` tree a closed item's children still\n * render and register while drawing nothing, so the registry knows every item\n * and a check reaches all of them. A `checkbox-independent` tree reads each\n * box off the checked list alone, so a check adds or takes out its own value\n * and nothing else. The keys read the items on the page, in document order,\n * each time they need them.\n *\n * @function useTreeView\n * @param options - The TreeView's props.\n * @returns The state and handlers the root and its items wire up.\n */\nexport function useTreeView(options: TreeViewOptions): TreeViewEngine {\n // An own key only, so a name every object inherits, like `toString`, is a\n // mode the tree doesn't know too.\n const mode = Object.prototype.hasOwnProperty.call(MODES, options.mode)\n ? options.mode\n : 'none';\n const actions = MODES[mode];\n\n const [expanded, setExpanded] = useControlled(\n options.expanded,\n options.defaultExpanded\n );\n const [selection, setSelection] = useControlled(\n options.selected,\n options.defaultSelected\n );\n const [checkedList, setChecked] = useControlled(\n options.checked,\n options.defaultChecked\n );\n // In a `checkbox` tree a check reaches the items under it, by the rules. In\n // a `checkbox-independent` one each box is its own item's, read off the list.\n const cascade = mode === 'checkbox';\n const independent = mode === 'checkbox-independent';\n // A box on every item, in either checkbox tree.\n const boxed = cascade || independent;\n // Each is read in its own mode only.\n const selected = mode === 'single' ? selection : null;\n const listed = new Set(boxed ? checkedList : []);\n\n const treeRef = useRef<HTMLUListElement | null>(null);\n const registryRef = useRef<Registry>({\n records: new Map(),\n elements: new Map(),\n values: new WeakMap(),\n });\n // Items register from their own effects, so they ask for a render to be read.\n const [, refresh] = useReducer((n: number) => n + 1, 0);\n const typeahead = useTypeahead();\n\n // The item with focus, and where the stop rests while focus is elsewhere:\n // `undefined` until the tree has mounted. With the values above it, for\n // when it goes away.\n const [focused, setFocused] = useState<string | null>(null);\n const [resting, setResting] = useState<string | null | undefined>();\n const restingPathRef = useRef<string[]>([]);\n // The values above an item that left the page with focus in it.\n const lostRef = useRef<string[] | null>(null);\n // Each item's place among the items beside it, by its key.\n const [positions, setPositions] = useState(NO_POSITIONS);\n\n const { records } = registryRef.current;\n const checks = cascade\n ? // eslint-disable-next-line react-hooks/refs -- read in render on purpose: items write the registry from their layout effects and then ask for this render with `refresh`\n readChecks(records, listed).states\n : undefined;\n const tabStop = focused ?? (resting === undefined ? selected : resting);\n\n // An item's box, decided here alone, so the box drawn, the tab stop and a\n // toggle agree: off the list in a `checkbox-independent` tree, and by the\n // rules in a `checkbox` one, where it's `undefined` until the item\n // registers. `undefined` in a tree with no boxes.\n const checkOf = (value: string): TreeViewCheckState | undefined =>\n independent ? listed.has(value) : checks?.get(value);\n\n const isOpen = (value: string) => expanded.includes(value);\n\n const stateOf = (\n value: string,\n path: string[],\n disabled: boolean\n ): TreeViewItemState => {\n if (mode === 'single') {\n return disabled ? {} : { selected: value === selected };\n }\n if (boxed) {\n // Until the items register in a `checkbox` tree, a value stands for\n // its branch, and a parent can't know it's mixed.\n return {\n checked:\n checkOf(value) ??\n (listed.has(value) || (!disabled && path.some(v => listed.has(v)))),\n };\n }\n return {};\n };\n\n const changeOpen = (next: string[]) => {\n setExpanded(next);\n options.onExpandedChange?.(next);\n };\n\n const run: Record<Action, (item: TreeViewItemInfo) => void> = {\n open: ({ value, isParent, open }) => {\n if (isParent) changeOpen(addedOrRemoved(expanded, value, open));\n },\n select: ({ value }) => {\n if (value === selected) return;\n setSelection(value);\n options.onSelectedChange?.(value);\n },\n check: ({ value }) => {\n // A `checkbox-independent` box is its own item's alone, so its value\n // comes or goes and the rest of the list stays as it was.\n const next = independent\n ? addedOrRemoved(checkedList, value, checkOf(value) === true)\n : toggledList(records, checkedList, value);\n setChecked(next);\n options.onCheckedChange?.(next);\n },\n };\n\n /** Disabled items are never selected, checked, opened or closed. */\n const act = (item: TreeViewItemInfo, list: Action[]) => {\n if (!item.disabled) list.forEach(action => run[action](item));\n };\n\n /** Enter, Space and a click on the row: a failed load's row retries. */\n const press = (item: TreeViewItemInfo, list: Action[]) => {\n if (item.retry) item.retry();\n else act(item, list);\n };\n\n // After every render, read each item's position off the page, and put the\n // stop where it belongs. While focus is in the tree it's the focused item.\n // Otherwise it's the selected item, or the first checked one on the page,\n // and failing that it stays where it was. An item that goes away hands it\n // to its nearest ancestor on the page, or to the first item, and focus goes\n // with it if it had focus.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- after every render on purpose: it reads the page, and sets state only when what it reads has changed\n useClientLayoutEffect(() => {\n const tree = treeRef.current!;\n const registry = registryRef.current;\n // Each item's element is read off the page with its value, rather than\n // out of `elements`, which holds one element per value: two items\n // sharing a value, which they shouldn't, can leave it without one the\n // page still has.\n const shown = shownIn(tree, registry);\n const placed = positionsOf(shown, registry);\n if (!samePositions(positions, placed)) setPositions(placed);\n const onPage = shown.map(element => registry.values.get(element)!);\n const elementOf = (value: string) => shown[onPage.indexOf(value)];\n const isShown = (value: string | null | undefined): value is string =>\n value != null && onPage.includes(value);\n const heir = (path: string[]) =>\n [...path].reverse().find(isShown) ?? onPage[0] ?? null;\n\n const lost = lostRef.current;\n lostRef.current = null;\n if (lost && !tree.contains(getActiveElementInTree(tree))) {\n const to = heir(lost);\n if (to) elementOf(to).focus();\n return;\n }\n if (focused !== null && !isShown(focused)) setFocused(null);\n\n const preferred = onPage.find(\n value => value === selected || checkOf(value) === true\n );\n const next =\n preferred ?? (isShown(resting) ? resting : heir(restingPathRef.current));\n if (next !== resting) {\n setResting(next);\n restingPathRef.current = next ? pathOf(elementOf(next), registry) : [];\n }\n });\n\n // Items can also move without the tree rendering, such as children that a\n // component of the app sorts with state of its own. That changes no record,\n // so the page is watched for items coming, going and moving, and the\n // positions are read again.\n useEffect(() => {\n const tree = treeRef.current!;\n const observer = new MutationObserver(() => {\n const registry = registryRef.current;\n const placed = positionsOf(shownIn(tree, registry), registry);\n setPositions(current =>\n samePositions(current, placed) ? current : placed\n );\n });\n observer.observe(tree, { childList: true, subtree: true });\n return () => observer.disconnect();\n }, []);\n\n const register = useCallback((value: string, record: TreeViewItemRecord) => {\n const { records: known } = registryRef.current;\n known.set(value, record);\n refresh();\n return () => {\n if (known.get(value) !== record) return;\n known.delete(value);\n refresh();\n };\n }, []);\n\n const setElement = useCallback(\n (value: string, element: HTMLLIElement | null) => {\n const registry = registryRef.current;\n if (element) {\n registry.elements.set(value, element);\n registry.values.set(element, value);\n return;\n }\n const gone = registry.elements.get(value);\n if (!gone) return;\n // The ref lets go before the element leaves the page, so it can still\n // say whether focus was in it, and where it sat.\n if (!lostRef.current && gone.contains(getActiveElementInTree(gone))) {\n lostRef.current = [...pathOf(gone, registry), value];\n }\n registry.elements.delete(value);\n },\n []\n );\n\n const onFocus = (value: string, element: HTMLLIElement) => {\n setFocused(value);\n setResting(value);\n restingPathRef.current = pathOf(element, registryRef.current);\n };\n\n const onBlur = () => setFocused(null);\n\n const onKeyDown = (\n event: React.KeyboardEvent<HTMLLIElement>,\n item: TreeViewItemInfo\n ) => {\n const element = event.currentTarget;\n // A key on an item inside this one bubbles here too, and is its own.\n if (\n event.target !== element ||\n event.defaultPrevented ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n isComposing(event)\n ) {\n return;\n }\n const registry = registryRef.current;\n const items = shownIn(treeRef.current!, registry);\n const at = items.indexOf(element);\n const rtl = getComputedStyle(element).direction === 'rtl';\n const key = (rtl && MIRRORED[event.key]) || event.key;\n\n switch (key) {\n case 'ArrowDown':\n items[at + 1]?.focus();\n break;\n case 'ArrowUp':\n items[at - 1]?.focus();\n break;\n case 'Home':\n items[0].focus();\n break;\n case 'End':\n items[items.length - 1].focus();\n break;\n case 'ArrowRight': {\n const next = items[at + 1];\n if (!item.open) act(item, ['open']);\n else if (next && parentItemOf(next) === element) next.focus();\n break;\n }\n case 'ArrowLeft':\n if (item.open && !item.disabled) act(item, ['open']);\n else parentItemOf(element)?.focus();\n break;\n // A held key acts once.\n case 'Enter':\n if (!event.repeat) press(item, actions.enter);\n break;\n case ' ':\n if (!event.repeat) press(item, actions.space);\n break;\n case '*': {\n const closed: string[] = [];\n registry.records.forEach((record, value) => {\n if (\n record.parent === item.parent &&\n record.isParent &&\n !record.disabled &&\n !expanded.includes(value)\n ) {\n closed.push(value);\n }\n });\n if (closed.length) changeOpen([...expanded, ...closed]);\n break;\n }\n default:\n // Type-ahead takes a printable character. Anything else, Escape and\n // Tab included, is left to whatever is around the tree.\n if (event.key.length !== 1) return;\n typeaheadMatch(items, typeahead.type(event.key), at, el =>\n typeaheadText(el.firstElementChild!)\n )?.focus();\n }\n event.preventDefault();\n };\n\n const onClick = (\n element: HTMLLIElement,\n item: TreeViewItemInfo,\n onToggle: boolean\n ) => {\n element.focus();\n if (onToggle) act(item, ['open']);\n else press(item, actions.click);\n };\n\n return {\n treeRef,\n rendersClosed: cascade,\n multiselectable: boxed,\n tabStop,\n isOpen,\n stateOf,\n positionOf: key => positions.get(key),\n register,\n setElement,\n onFocus,\n onBlur,\n onKeyDown,\n onClick,\n };\n}\n"],"names":["isComposing"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useTreeView.js","sources":["../../../src/helpers/useTreeView.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport {\n createContext,\n useCallback,\n useEffect,\n useReducer,\n useRef,\n useState,\n} from 'react';\nimport type React from 'react';\nimport type { TreeViewSelectionMode } from '../components/TreeView';\nimport { useClientLayoutEffect } from './useClientLayoutEffect';\nimport { useControlled } from './useControlled';\nimport { getActiveElementInTree } from './shadowDom';\nimport { typeaheadMatch, typeaheadText, useTypeahead } from './typeahead';\n\n/** What an item tells the tree about itself when it registers. */\nexport interface TreeViewItemRecord {\n /** The value of the item it's in, or `null` at the top. */\n parent: string | null;\n disabled: boolean;\n /** It has children, or `hasChildren` says it will. */\n isParent: boolean;\n}\n\n/** An item as a key or a click on it sees it. */\nexport interface TreeViewItemInfo extends TreeViewItemRecord {\n value: string;\n open: boolean;\n /**\n * A failed load's row: what Enter, Space and a click on it do, in place of\n * what the mode says.\n */\n retry?: () => void;\n}\n\n/** A box's state: checked, unchecked or mixed. */\nexport type TreeViewCheckState = boolean | 'mixed';\n\n/**\n * Where an item sits among the items beside it, for its `aria-posinset` and\n * `aria-setsize`: its number, from 1, and how many there are.\n */\nexport interface TreeViewPosition {\n posinset: number;\n setsize: number;\n}\n\n/**\n * What an item shows of the selection: `selected` in a `single` tree,\n * `checked` in a `checkbox` or `checkbox-independent` tree, and neither with\n * `none`.\n */\nexport interface TreeViewItemState {\n selected?: boolean;\n checked?: TreeViewCheckState;\n}\n\ntype Action = 'open' | 'select' | 'check';\n\n/** What Enter, Space and a click on the row do in a mode. */\ninterface ModeActions {\n enter: Action[];\n space: Action[];\n click: Action[];\n}\n\n/** The keys and clicks of both checkbox modes, which differ only in what a check reaches. */\nconst CHECKBOX_ACTIONS: ModeActions = {\n enter: ['open'],\n space: ['check'],\n click: ['check'],\n};\n\n/**\n * What Enter, Space and a click on the row do in each mode, from the tables\n * in #1096, with `checkbox-independent` from #1430. `open` opens or closes a\n * parent and does nothing on a leaf. A click on the chevron only ever opens\n * or closes.\n */\nconst MODES: Record<TreeViewSelectionMode, ModeActions> = {\n none: { enter: ['open'], space: ['open'], click: ['open'] },\n single: { enter: ['select'], space: ['select'], click: ['select', 'open'] },\n checkbox: CHECKBOX_ACTIONS,\n 'checkbox-independent': CHECKBOX_ACTIONS,\n};\n\n/** Left and Right, swapped for a right-to-left page. */\nconst MIRRORED: Record<string, string> = {\n ArrowLeft: 'ArrowRight',\n ArrowRight: 'ArrowLeft',\n};\n\n/** An IME is composing, so the key belongs to it (`keyCode` 229 is Safari's word for it). */\nconst isComposing = (event: React.KeyboardEvent): boolean =>\n event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;\n\n/**\n * The items a tree knows, and their elements while they're on the page. A\n * loading item's row, or a failed load's, hands over its element under a key\n * of its own and has no record, so the keys reach it and nothing else counts it.\n */\ninterface Registry {\n records: Map<string, TreeViewItemRecord>;\n // By value, for an item whose ref lets go, which hands over no element.\n elements: Map<string, HTMLLIElement>;\n // Weak, so an element that leaves the page needn't be forgotten by hand.\n values: WeakMap<Element, string>;\n}\n\n/** The registry's items on the page, in document order. */\nconst shownIn = (tree: Element, { values }: Registry): HTMLElement[] =>\n Array.from(tree.querySelectorAll<HTMLElement>('[role=\"treeitem\"]')).filter(\n element => values.has(element)\n );\n\n/** The item an item on the page sits in: its group's `<li>`, or none at the top. */\nfunction parentItemOf(element: Element): HTMLElement | null {\n const group = element.parentElement!;\n return group.getAttribute('role') === 'group' ? group.parentElement : null;\n}\n\n/** No positions: on the server, and until the tree has mounted. */\nconst NO_POSITIONS = new Map<string, TreeViewPosition>();\n\n/**\n * Each item's place among the items beside it, by its key, read off the\n * items on the page: the ones in the same group, or at the top. A loading or\n * failed load's row counts, as it does for a browser that works positions\n * out for itself, so the declared ones say what a browser would.\n */\nfunction positionsOf(\n shown: HTMLElement[],\n { values }: Registry\n): Map<string, TreeViewPosition> {\n const sets = new Map<Element | null, HTMLElement[]>();\n shown.forEach(element => {\n const set = sets.get(element.parentElement);\n if (set) set.push(element);\n else sets.set(element.parentElement, [element]);\n });\n const positions = new Map<string, TreeViewPosition>();\n sets.forEach(set =>\n set.forEach((element, at) =>\n positions.set(values.get(element)!, {\n posinset: at + 1,\n setsize: set.length,\n })\n )\n );\n return positions;\n}\n\n/** Whether two sets of positions say the same for every item. */\nfunction samePositions(\n a: Map<string, TreeViewPosition>,\n b: Map<string, TreeViewPosition>\n): boolean {\n return (\n a.size === b.size &&\n Array.from(a).every(([key, { posinset, setsize }]) => {\n const other = b.get(key);\n return other?.posinset === posinset && other.setsize === setsize;\n })\n );\n}\n\n/** The values of the items an item on the page is in, from the top. */\nfunction pathOf(element: Element, { values }: Registry): string[] {\n const path: string[] = [];\n for (let at = parentItemOf(element); at; at = parentItemOf(at)) {\n path.unshift(values.get(at)!);\n }\n return path;\n}\n\n/**\n * The values of the items a tree hides while they stay in it, as TreeSelect's\n * search hides the items that don't match. A hidden item renders as one under\n * a closed parent does: nothing on the page, and in a `checkbox` tree it and\n * its children still register, so a check still reaches them. Internal, for\n * the components that put a TreeView inside them.\n */\nexport const TreeViewHiddenContext = createContext<ReadonlySet<string>>(\n new Set()\n);\n\n/**\n * Every known item's check state, by the rules in #1096:\n *\n * - a disabled item is checked only when its own value is listed;\n * - an enabled item with no enabled items anywhere under it is checked when\n * it, or any item above it, is listed;\n * - any other enabled item follows the items of that second kind in its\n * branch: checked when they all are, unchecked when none is, mixed between.\n *\n * Also returns the items of the second kind, which are what a toggle sets.\n */\nexport function readChecks(\n records: Map<string, TreeViewItemRecord>,\n listed: Set<string>\n): { states: Map<string, TreeViewCheckState>; ends: string[] } {\n const kids = new Map<string | null, string[]>();\n records.forEach(({ parent }, value) => {\n kids.set(parent, [...(kids.get(parent) ?? []), value]);\n });\n const states = new Map<string, TreeViewCheckState>();\n const ends: string[] = [];\n // How many enabled ends the branch holds, and how many are checked.\n const visit = (value: string, above: boolean): [number, number] => {\n const marked = above || listed.has(value);\n let total = 0;\n let on = 0;\n for (const kid of kids.get(value) ?? []) {\n const [t, o] = visit(kid, marked);\n total += t;\n on += o;\n }\n if (records.get(value)!.disabled) {\n states.set(value, listed.has(value));\n return [total, on];\n }\n if (total === 0) {\n states.set(value, marked);\n ends.push(value);\n return [1, marked ? 1 : 0];\n }\n states.set(value, on === total ? true : on > 0 && 'mixed');\n return [total, on];\n };\n for (const top of kids.get(null) ?? []) visit(top, false);\n return { states, ends };\n}\n\n/**\n * The list a toggle of `value` leaves: it and every enabled item under it\n * take the opposite of its state (a mixed one checks), and the rest is read\n * back by the rules. That's every item that shows checked, parents\n * included, plus any listed value no item has.\n */\nexport function toggledList(\n records: Map<string, TreeViewItemRecord>,\n list: string[],\n value: string\n): string[] {\n const { states, ends } = readChecks(records, new Set(list));\n const to = states.get(value) !== true;\n // An end was reached from the top, so everything above it is known.\n const under = (end: string) => {\n for (let at: string | null = end; at !== null;) {\n if (at === value) return true;\n at = records.get(at)!.parent;\n }\n return false;\n };\n const seed = new Set(\n ends.filter(end => (under(end) ? to : states.get(end) === true))\n );\n records.forEach(({ disabled }, item) => {\n if (disabled && states.get(item) === true) seed.add(item);\n });\n const after = readChecks(records, seed).states;\n return [\n ...Array.from(after)\n .filter(([, state]) => state === true)\n .map(([item]) => item),\n ...list.filter(item => !records.has(item)),\n ];\n}\n\n/**\n * The list with `value` added at the end, or, when it's `present`, with\n * every copy of it taken out. Everything else stays as it was, in its order.\n */\nfunction addedOrRemoved(\n list: string[],\n value: string,\n present: boolean\n): string[] {\n return present ? list.filter(v => v !== value) : [...list, value];\n}\n\n/** Options for `useTreeView`: the TreeView's own props. */\nexport interface TreeViewOptions {\n mode: TreeViewSelectionMode;\n selected?: string | null;\n defaultSelected: string | null;\n onSelectedChange?: (value: string) => void;\n checked?: string[];\n defaultChecked: string[];\n onCheckedChange?: (checked: string[]) => void;\n expanded?: string[];\n defaultExpanded: string[];\n onExpandedChange?: (expanded: string[]) => void;\n}\n\n/** What `useTreeView` hands the root and its items. */\nexport interface TreeViewEngine {\n /** For the `<ul role=\"tree\">`. */\n treeRef: React.MutableRefObject<HTMLUListElement | null>;\n /**\n * Whether a closed item still renders its children, drawing nothing, so a\n * check knows everything under it. Only a `checkbox` tree needs to: a check\n * in a `checkbox-independent` tree reaches nothing under it.\n */\n rendersClosed: boolean;\n /** Whether any number of items can be checked, in either checkbox mode. */\n multiselectable: boolean;\n /** The item that holds `tabIndex={0}`. */\n tabStop: string | null;\n isOpen: (value: string) => boolean;\n /**\n * What the item shows of the selection. `path` is the values of the items\n * above it, which decide its box until the items have registered, as on\n * the server.\n */\n stateOf: (\n value: string,\n path: string[],\n disabled: boolean\n ) => TreeViewItemState;\n /**\n * Where the item with this key sits among the items beside it, read off\n * the page after each render and whenever items move. Nothing until the\n * tree has mounted, as on the server.\n */\n positionOf: (key: string) => TreeViewPosition | undefined;\n /** Records an item from its layout effect. Returns the function that forgets it. */\n register: (value: string, record: TreeViewItemRecord) => () => void;\n /** For each item's `<li>` ref: records it, and notices focus leaving with it. */\n setElement: (value: string, element: HTMLLIElement | null) => void;\n /** Focus arrived on the item's own `<li>`. */\n onFocus: (value: string, element: HTMLLIElement) => void;\n /** Focus left the item's own `<li>`. */\n onBlur: () => void;\n /** A key on the item's `<li>`, after its own `onKeyDown`. */\n onKeyDown: (\n event: React.KeyboardEvent<HTMLLIElement>,\n item: TreeViewItemInfo\n ) => void;\n /**\n * A click on the item's row, or with `onToggle` on its chevron. Focus and\n * the tab stop move to the item.\n */\n onClick: (\n element: HTMLLIElement,\n item: TreeViewItemInfo,\n onToggle: boolean\n ) => void;\n}\n\n/**\n * The open state, the selection, the keys, the tab stop, the positions and\n * the registry of a TreeView (#1096). Focus moves for real, with a roving\n * tabindex, rather than through `aria-activedescendant`. Each item's\n * position among the items beside it is declared rather than left to the\n * browser (#1171), read off the page once the tree has mounted.\n *\n * Every item registers its value, its parent and whether it's disabled from\n * a layout effect. In a `checkbox` tree a closed item's children still\n * render and register while drawing nothing, so the registry knows every item\n * and a check reaches all of them. A `checkbox-independent` tree reads each\n * box off the checked list alone, so a check adds or takes out its own value\n * and nothing else. The keys read the items on the page, in document order,\n * each time they need them.\n *\n * @function useTreeView\n * @param options - The TreeView's props.\n * @returns The state and handlers the root and its items wire up.\n */\nexport function useTreeView(options: TreeViewOptions): TreeViewEngine {\n // An own key only, so a name every object inherits, like `toString`, is a\n // mode the tree doesn't know too.\n const mode = Object.prototype.hasOwnProperty.call(MODES, options.mode)\n ? options.mode\n : 'none';\n const actions = MODES[mode];\n\n const [expanded, setExpanded] = useControlled(\n options.expanded,\n options.defaultExpanded\n );\n const [selection, setSelection] = useControlled(\n options.selected,\n options.defaultSelected\n );\n const [checkedList, setChecked] = useControlled(\n options.checked,\n options.defaultChecked\n );\n // In a `checkbox` tree a check reaches the items under it, by the rules. In\n // a `checkbox-independent` one each box is its own item's, read off the list.\n const cascade = mode === 'checkbox';\n const independent = mode === 'checkbox-independent';\n // A box on every item, in either checkbox tree.\n const boxed = cascade || independent;\n // Each is read in its own mode only.\n const selected = mode === 'single' ? selection : null;\n const listed = new Set(boxed ? checkedList : []);\n\n const treeRef = useRef<HTMLUListElement | null>(null);\n const registryRef = useRef<Registry>({\n records: new Map(),\n elements: new Map(),\n values: new WeakMap(),\n });\n // Items register from their own effects, so they ask for a render to be read.\n const [, refresh] = useReducer((n: number) => n + 1, 0);\n const typeahead = useTypeahead();\n\n // The item with focus, and where the stop rests while focus is elsewhere:\n // `undefined` until the tree has mounted. With the values above it, for\n // when it goes away.\n const [focused, setFocused] = useState<string | null>(null);\n const [resting, setResting] = useState<string | null | undefined>();\n const restingPathRef = useRef<string[]>([]);\n // The values above an item that left the page with focus in it.\n const lostRef = useRef<string[] | null>(null);\n // Each item's place among the items beside it, by its key.\n const [positions, setPositions] = useState(NO_POSITIONS);\n\n const { records } = registryRef.current;\n const checks = cascade\n ? // eslint-disable-next-line react-hooks/refs -- read in render on purpose: items write the registry from their layout effects and then ask for this render with `refresh`\n readChecks(records, listed).states\n : undefined;\n const tabStop = focused ?? (resting === undefined ? selected : resting);\n\n // An item's box, decided here alone, so the box drawn, the tab stop and a\n // toggle agree: off the list in a `checkbox-independent` tree, and by the\n // rules in a `checkbox` one, where it's `undefined` until the item\n // registers. `undefined` in a tree with no boxes.\n const checkOf = (value: string): TreeViewCheckState | undefined =>\n independent ? listed.has(value) : checks?.get(value);\n\n const isOpen = (value: string) => expanded.includes(value);\n\n const stateOf = (\n value: string,\n path: string[],\n disabled: boolean\n ): TreeViewItemState => {\n if (mode === 'single') {\n return disabled ? {} : { selected: value === selected };\n }\n if (boxed) {\n // Until the items register in a `checkbox` tree, a value stands for\n // its branch, and a parent can't know it's mixed.\n return {\n checked:\n checkOf(value) ??\n (listed.has(value) || (!disabled && path.some(v => listed.has(v)))),\n };\n }\n return {};\n };\n\n const changeOpen = (next: string[]) => {\n setExpanded(next);\n options.onExpandedChange?.(next);\n };\n\n const run: Record<Action, (item: TreeViewItemInfo) => void> = {\n open: ({ value, isParent, open }) => {\n if (isParent) changeOpen(addedOrRemoved(expanded, value, open));\n },\n select: ({ value }) => {\n if (value === selected) return;\n setSelection(value);\n options.onSelectedChange?.(value);\n },\n check: ({ value }) => {\n // A `checkbox-independent` box is its own item's alone, so its value\n // comes or goes and the rest of the list stays as it was.\n const next = independent\n ? addedOrRemoved(checkedList, value, checkOf(value) === true)\n : toggledList(records, checkedList, value);\n setChecked(next);\n options.onCheckedChange?.(next);\n },\n };\n\n /** Disabled items are never selected, checked, opened or closed. */\n const act = (item: TreeViewItemInfo, list: Action[]) => {\n if (!item.disabled) list.forEach(action => run[action](item));\n };\n\n /** Enter, Space and a click on the row: a failed load's row retries. */\n const press = (item: TreeViewItemInfo, list: Action[]) => {\n if (item.retry) item.retry();\n else act(item, list);\n };\n\n // After every render, read each item's position off the page, and put the\n // stop where it belongs. While focus is in the tree it's the focused item.\n // Otherwise it's the selected item, or the first checked one on the page,\n // and failing that it stays where it was. An item that goes away hands it\n // to its nearest ancestor on the page, or to the first item, and focus goes\n // with it if it had focus.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- after every render on purpose: it reads the page, and sets state only when what it reads has changed\n useClientLayoutEffect(() => {\n const tree = treeRef.current!;\n const registry = registryRef.current;\n // Each item's element is read off the page with its value, rather than\n // out of `elements`, which holds one element per value: two items\n // sharing a value, which they shouldn't, can leave it without one the\n // page still has.\n const shown = shownIn(tree, registry);\n const placed = positionsOf(shown, registry);\n if (!samePositions(positions, placed)) setPositions(placed);\n const onPage = shown.map(element => registry.values.get(element)!);\n const elementOf = (value: string) => shown[onPage.indexOf(value)];\n const isShown = (value: string | null | undefined): value is string =>\n value != null && onPage.includes(value);\n const heir = (path: string[]) =>\n [...path].reverse().find(isShown) ?? onPage[0] ?? null;\n\n const lost = lostRef.current;\n lostRef.current = null;\n if (lost && !tree.contains(getActiveElementInTree(tree))) {\n const to = heir(lost);\n if (to) elementOf(to).focus();\n return;\n }\n if (focused !== null && !isShown(focused)) setFocused(null);\n\n const preferred = onPage.find(\n value => value === selected || checkOf(value) === true\n );\n const next =\n preferred ?? (isShown(resting) ? resting : heir(restingPathRef.current));\n if (next !== resting) {\n setResting(next);\n restingPathRef.current = next ? pathOf(elementOf(next), registry) : [];\n }\n });\n\n // Items can also move without the tree rendering, such as children that a\n // component of the app sorts with state of its own. That changes no record,\n // so the page is watched for items coming, going and moving, and the\n // positions are read again.\n useEffect(() => {\n const tree = treeRef.current!;\n const observer = new MutationObserver(() => {\n const registry = registryRef.current;\n const placed = positionsOf(shownIn(tree, registry), registry);\n setPositions(current =>\n samePositions(current, placed) ? current : placed\n );\n });\n observer.observe(tree, { childList: true, subtree: true });\n return () => observer.disconnect();\n }, []);\n\n const register = useCallback((value: string, record: TreeViewItemRecord) => {\n const { records: known } = registryRef.current;\n known.set(value, record);\n refresh();\n return () => {\n if (known.get(value) !== record) return;\n known.delete(value);\n refresh();\n };\n }, []);\n\n const setElement = useCallback(\n (value: string, element: HTMLLIElement | null) => {\n const registry = registryRef.current;\n if (element) {\n registry.elements.set(value, element);\n registry.values.set(element, value);\n return;\n }\n const gone = registry.elements.get(value);\n if (!gone) return;\n // The ref lets go before the element leaves the page, so it can still\n // say whether focus was in it, and where it sat.\n if (!lostRef.current && gone.contains(getActiveElementInTree(gone))) {\n lostRef.current = [...pathOf(gone, registry), value];\n }\n registry.elements.delete(value);\n },\n []\n );\n\n const onFocus = (value: string, element: HTMLLIElement) => {\n setFocused(value);\n setResting(value);\n restingPathRef.current = pathOf(element, registryRef.current);\n };\n\n const onBlur = () => setFocused(null);\n\n const onKeyDown = (\n event: React.KeyboardEvent<HTMLLIElement>,\n item: TreeViewItemInfo\n ) => {\n const element = event.currentTarget;\n // A key on an item inside this one bubbles here too, and is its own.\n if (\n event.target !== element ||\n event.defaultPrevented ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n isComposing(event)\n ) {\n return;\n }\n const registry = registryRef.current;\n const items = shownIn(treeRef.current!, registry);\n const at = items.indexOf(element);\n const rtl = getComputedStyle(element).direction === 'rtl';\n const key = (rtl && MIRRORED[event.key]) || event.key;\n\n switch (key) {\n case 'ArrowDown':\n items[at + 1]?.focus();\n break;\n case 'ArrowUp':\n items[at - 1]?.focus();\n break;\n case 'Home':\n items[0].focus();\n break;\n case 'End':\n items[items.length - 1].focus();\n break;\n case 'ArrowRight': {\n const next = items[at + 1];\n if (!item.open) act(item, ['open']);\n else if (next && parentItemOf(next) === element) next.focus();\n break;\n }\n case 'ArrowLeft':\n if (item.open && !item.disabled) act(item, ['open']);\n else parentItemOf(element)?.focus();\n break;\n // A held key acts once.\n case 'Enter':\n if (!event.repeat) press(item, actions.enter);\n break;\n case ' ':\n if (!event.repeat) press(item, actions.space);\n break;\n case '*': {\n // The siblings on the page: not the ones a component around the\n // tree hides, which stay registered.\n const closed: string[] = [];\n registry.records.forEach((record, value) => {\n if (\n record.parent === item.parent &&\n record.isParent &&\n !record.disabled &&\n !expanded.includes(value) &&\n registry.elements.has(value)\n ) {\n closed.push(value);\n }\n });\n if (closed.length) changeOpen([...expanded, ...closed]);\n break;\n }\n default:\n // Type-ahead takes a printable character. Anything else, Escape and\n // Tab included, is left to whatever is around the tree.\n if (event.key.length !== 1) return;\n typeaheadMatch(items, typeahead.type(event.key), at, el =>\n typeaheadText(el.firstElementChild!)\n )?.focus();\n }\n event.preventDefault();\n };\n\n const onClick = (\n element: HTMLLIElement,\n item: TreeViewItemInfo,\n onToggle: boolean\n ) => {\n element.focus();\n if (onToggle) act(item, ['open']);\n else press(item, actions.click);\n };\n\n return {\n treeRef,\n rendersClosed: cascade,\n multiselectable: boxed,\n tabStop,\n isOpen,\n stateOf,\n positionOf: key => positions.get(key),\n register,\n setElement,\n onFocus,\n onBlur,\n onKeyDown,\n onClick,\n };\n}\n"],"names":["isComposing"],"mappings":"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"}
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
'use client';
|
|
3
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
import React, { createContext, forwardRef, useContext, useRef, useCallback, useEffect, useState, useId, useMemo } from 'react';
|
|
5
|
+
import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
|
|
6
|
+
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
7
|
+
import { withSubComponents } from '../helpers/withSubComponents.js';
|
|
8
|
+
import { flattenChildren } from '../helpers/flattenChildren.js';
|
|
9
|
+
import { useLatestRef } from '../helpers/useLatestRef.js';
|
|
10
|
+
import { useOverlayStack, useOverlayEscape } from '../helpers/overlayStack.js';
|
|
11
|
+
import { useScrollLock } from '../helpers/scrollLock.js';
|
|
12
|
+
import { useInertSiblings } from '../helpers/useInertSiblings.js';
|
|
13
|
+
import { trapTab } from '../helpers/useFocusTrap.js';
|
|
14
|
+
import { useOverlayFocus } from '../helpers/useOverlayFocus.js';
|
|
15
|
+
import { useOutsidePress } from '../helpers/useOutsidePress.js';
|
|
16
|
+
import { getActiveElementInTree } from '../helpers/shadowDom.js';
|
|
17
|
+
import { useBulmaBreakpoint } from '../helpers/useBulmaViewport.js';
|
|
18
|
+
import { warnOnce } from '../helpers/devWarnings.js';
|
|
19
|
+
import { NavbarBurger } from '../components/Navbar.js';
|
|
20
|
+
import { Delete } from '../elements/Delete.js';
|
|
21
|
+
|
|
22
|
+
const FrameContext = createContext(null);
|
|
23
|
+
/** A width prop as CSS: a number is pixels. */
|
|
24
|
+
const cssLength = (value) => typeof value === 'number' ? `${value}px` : value;
|
|
25
|
+
/**
|
|
26
|
+
* Hands `node` to a part's own ref and to the one forwarded to it. A
|
|
27
|
+
* forwarded callback that returns a cleanup, as React 19 allows, has it held
|
|
28
|
+
* in `cleanup` and run on detach in place of a call with `null`, on React 18
|
|
29
|
+
* as well.
|
|
30
|
+
*/
|
|
31
|
+
function setRefs(node, own, forwarded, cleanup) {
|
|
32
|
+
own.current = node;
|
|
33
|
+
if (typeof forwarded === 'function') {
|
|
34
|
+
if (node === null) {
|
|
35
|
+
const detach = cleanup.current;
|
|
36
|
+
cleanup.current = null;
|
|
37
|
+
if (detach)
|
|
38
|
+
detach();
|
|
39
|
+
else
|
|
40
|
+
forwarded(null);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const returned = forwarded(node);
|
|
44
|
+
cleanup.current =
|
|
45
|
+
typeof returned === 'function' ? returned : null;
|
|
46
|
+
}
|
|
47
|
+
else if (forwarded) {
|
|
48
|
+
forwarded.current = node;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Warns when a part has an `id` the frame couldn't read, because it isn't a
|
|
53
|
+
* direct child, so the skip link or the burger points at the wrong one.
|
|
54
|
+
*/
|
|
55
|
+
function warnOnUnreadId(part, id, expected) {
|
|
56
|
+
if (id && id !== expected) {
|
|
57
|
+
warnOnce(`AppFrame:${part}-id`, `[bestax-bulma] <AppFrame.${part}> has an id <AppFrame> can't read, ` +
|
|
58
|
+
`so ${part === 'Main' ? 'the skip link' : "the burger's aria-controls"} ` +
|
|
59
|
+
`misses it. Put AppFrame.${part} directly inside AppFrame.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The top bar of an AppFrame, which sticks while the page scrolls.
|
|
64
|
+
*
|
|
65
|
+
* @function
|
|
66
|
+
* @param {AppFrameHeaderProps} props - Props for AppFrame.Header.
|
|
67
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<header>`.
|
|
68
|
+
* @returns {JSX.Element} The rendered header.
|
|
69
|
+
*/
|
|
70
|
+
const AppFrameHeader = forwardRef(({ className, children, ...props }, ref) => {
|
|
71
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
72
|
+
const headerClass = usePrefixedClassNames('app-frame-header');
|
|
73
|
+
return (jsx("header", { ref: ref, className: classNames(headerClass, bulmaHelperClasses, className), ...rest, children: children }));
|
|
74
|
+
});
|
|
75
|
+
AppFrameHeader.displayName = 'AppFrame.Header';
|
|
76
|
+
/**
|
|
77
|
+
* The navigation of an AppFrame: a column beside the page from the breakpoint up, and a drawer below it.
|
|
78
|
+
*
|
|
79
|
+
* @function
|
|
80
|
+
* @param {AppFrameNavProps} props - Props for AppFrame.Nav.
|
|
81
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<nav>`.
|
|
82
|
+
* @returns {JSX.Element} The rendered nav, and the drawer's backdrop.
|
|
83
|
+
*/
|
|
84
|
+
const AppFrameNav = forwardRef(({ className, style, width, overlay = true, closeOnEscape = true, closeOnClickOutside = true, closeOnNavigate = true, onClick, onPointerDownCapture, children, ...props }, ref) => {
|
|
85
|
+
const frame = useContext(FrameContext);
|
|
86
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
87
|
+
const navClass = usePrefixedClassNames('app-frame-nav');
|
|
88
|
+
const closeClass = usePrefixedClassNames('app-frame-close');
|
|
89
|
+
const backdropClass = usePrefixedClassNames('app-frame-backdrop');
|
|
90
|
+
const activeClass = usePrefixedClassNames('is-active');
|
|
91
|
+
const navRef = useRef(null);
|
|
92
|
+
const backdropRef = useRef(null);
|
|
93
|
+
const navCleanupRef = useRef(null);
|
|
94
|
+
const navRefs = useCallback((node) => setRefs(node, navRef, ref, navCleanupRef), [ref]);
|
|
95
|
+
const navId = rest.id || frame?.state.navId;
|
|
96
|
+
if (frame)
|
|
97
|
+
warnOnUnreadId('Nav', rest.id, frame.state.navId);
|
|
98
|
+
// The drawer is open only below the breakpoint. Above it the nav is a
|
|
99
|
+
// column, and none of what follows applies.
|
|
100
|
+
const isDrawer = !!frame?.state.isDrawer;
|
|
101
|
+
const open = isDrawer && !!frame?.state.navOpen;
|
|
102
|
+
// The open drawer is Bulma's active state, on the nav as on a navbar's
|
|
103
|
+
// menu, and on its backdrop.
|
|
104
|
+
const shown = { [activeClass]: !!frame?.state.navOpen };
|
|
105
|
+
const modal = open && overlay;
|
|
106
|
+
const close = () => frame?.state.setNavOpen(false);
|
|
107
|
+
// The rest of the frame goes inert: the header, the page, the aside and
|
|
108
|
+
// the footer, but not the backdrop, which takes the presses outside the
|
|
109
|
+
// drawer rather than let them through to what's under it. Not the whole
|
|
110
|
+
// page, since a Modal or a toast renders beside the frame, often into an
|
|
111
|
+
// element that's there before the drawer opens, and a Modal opened from
|
|
112
|
+
// the drawer has to stay usable. First, so that as the drawer closes it
|
|
113
|
+
// lets go before focus goes back to the burger: an inert element can't
|
|
114
|
+
// take focus.
|
|
115
|
+
useInertSiblings(navRef, modal, false, backdropRef);
|
|
116
|
+
const isTopmost = useOverlayStack(open, overlay);
|
|
117
|
+
useScrollLock(modal);
|
|
118
|
+
// Tab stays inside while the drawer is the topmost modal overlay, as in
|
|
119
|
+
// Sidebar, so a Modal opened from the drawer holds Tab until it closes.
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!modal)
|
|
122
|
+
return undefined;
|
|
123
|
+
return trapTab(() => navRef.current, window, () => isTopmost('modal'));
|
|
124
|
+
}, [modal, isTopmost]);
|
|
125
|
+
useOverlayEscape(open && closeOnEscape, isTopmost, close);
|
|
126
|
+
// Without an overlay there's no backdrop: a press outside the drawer
|
|
127
|
+
// closes it, as Popover's does, while it's the topmost overlay, so a
|
|
128
|
+
// press in a Modal opened over it is the Modal's. A press on a control
|
|
129
|
+
// that names the nav in its `aria-controls`, such as the burger, is left
|
|
130
|
+
// to that control, which toggles it.
|
|
131
|
+
const markInsidePress = useOutsidePress(navRef, open && !overlay && closeOnClickOutside, e => {
|
|
132
|
+
const id = navRef.current?.id;
|
|
133
|
+
const controlsNav = (node) => !!node
|
|
134
|
+
.getAttribute?.('aria-controls')
|
|
135
|
+
?.split(/\s+/)
|
|
136
|
+
.includes(id);
|
|
137
|
+
if (!isTopmost() || e.composedPath().some(controlsNav))
|
|
138
|
+
return;
|
|
139
|
+
close();
|
|
140
|
+
});
|
|
141
|
+
// Focus moves to the drawer's first tab stop, the close button, as it
|
|
142
|
+
// opens, and back to whatever had it as it closes, unless it has left
|
|
143
|
+
// the drawer. After a press outside, the press's own mousedown then takes
|
|
144
|
+
// it wherever the press goes. When the window widened, the drawer is the
|
|
145
|
+
// column again, and focus on its links stays; the close button hides
|
|
146
|
+
// there, so focus on it, or dropped from it to <body>, goes back too.
|
|
147
|
+
const isDrawerRef = useLatestRef(isDrawer);
|
|
148
|
+
useOverlayFocus(navRef, open, {
|
|
149
|
+
modal: overlay,
|
|
150
|
+
restore: () => {
|
|
151
|
+
if (isDrawerRef.current)
|
|
152
|
+
return true;
|
|
153
|
+
const nav = navRef.current;
|
|
154
|
+
const active = getActiveElementInTree(nav);
|
|
155
|
+
return (!nav.contains(active) || !!active?.classList.contains(closeClass));
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
const handleClick = (e) => {
|
|
159
|
+
onClick?.(e);
|
|
160
|
+
if (!open ||
|
|
161
|
+
!closeOnNavigate ||
|
|
162
|
+
e.button !== 0 ||
|
|
163
|
+
e.metaKey ||
|
|
164
|
+
e.ctrlKey ||
|
|
165
|
+
e.shiftKey ||
|
|
166
|
+
e.altKey) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
// Not skipped for a click whose default was prevented: a router's
|
|
170
|
+
// link prevents it to navigate without a page load. Only a link the nav
|
|
171
|
+
// holds: one in a Modal portaled from it is the Modal's.
|
|
172
|
+
const link = e.target.closest('a[href]');
|
|
173
|
+
if (!link || !navRef.current?.contains(link))
|
|
174
|
+
return;
|
|
175
|
+
if (link.hasAttribute('download'))
|
|
176
|
+
return;
|
|
177
|
+
const target = link.getAttribute('target');
|
|
178
|
+
if (target && target !== '_self')
|
|
179
|
+
return;
|
|
180
|
+
close();
|
|
181
|
+
};
|
|
182
|
+
const navStyle = width === undefined
|
|
183
|
+
? style
|
|
184
|
+
: {
|
|
185
|
+
...style,
|
|
186
|
+
'--bulma-app-frame-nav-width': cssLength(width),
|
|
187
|
+
};
|
|
188
|
+
return (jsxs(Fragment, { children: [jsxs("nav", { ref: navRefs, className: classNames(navClass, shown, bulmaHelperClasses, className), style: navStyle, onClick: handleClick, onPointerDownCapture: e => {
|
|
189
|
+
onPointerDownCapture?.(e);
|
|
190
|
+
markInsidePress(e);
|
|
191
|
+
}, ...rest, id: navId, "aria-label": rest['aria-label'] ||
|
|
192
|
+
(rest['aria-labelledby'] ? undefined : frame?.labels.nav), children: [frame && (jsx(Delete, { className: closeClass, size: "medium", ariaLabel: frame.labels.closeNav, onClick: close })), children] }), frame && overlay && (jsx("div", { ref: backdropRef, className: classNames(backdropClass, shown), "aria-hidden": "true", onClick: closeOnClickOutside
|
|
193
|
+
? () => {
|
|
194
|
+
// Not under an overlay opened over the drawer.
|
|
195
|
+
if (isTopmost())
|
|
196
|
+
close();
|
|
197
|
+
}
|
|
198
|
+
: undefined }))] }));
|
|
199
|
+
});
|
|
200
|
+
AppFrameNav.displayName = 'AppFrame.Nav';
|
|
201
|
+
/**
|
|
202
|
+
* The page's main content in an AppFrame, which the skip link moves focus to.
|
|
203
|
+
*
|
|
204
|
+
* @function
|
|
205
|
+
* @param {AppFrameMainProps} props - Props for AppFrame.Main.
|
|
206
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<main>`.
|
|
207
|
+
* @returns {JSX.Element} The rendered main.
|
|
208
|
+
*/
|
|
209
|
+
const AppFrameMain = forwardRef(({ className, children, ...props }, ref) => {
|
|
210
|
+
const frame = useContext(FrameContext);
|
|
211
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
212
|
+
const mainClass = usePrefixedClassNames('app-frame-main');
|
|
213
|
+
if (frame)
|
|
214
|
+
warnOnUnreadId('Main', rest.id, frame.mainId);
|
|
215
|
+
return (jsx("main", { ref: ref, tabIndex: -1, className: classNames(mainClass, bulmaHelperClasses, className), ...rest, id: rest.id || frame?.mainId, children: children }));
|
|
216
|
+
});
|
|
217
|
+
AppFrameMain.displayName = 'AppFrame.Main';
|
|
218
|
+
/**
|
|
219
|
+
* Content beside the page in an AppFrame, which moves under it below the breakpoint.
|
|
220
|
+
*
|
|
221
|
+
* @function
|
|
222
|
+
* @param {AppFrameAsideProps} props - Props for AppFrame.Aside.
|
|
223
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<aside>`.
|
|
224
|
+
* @returns {JSX.Element} The rendered aside.
|
|
225
|
+
*/
|
|
226
|
+
const AppFrameAside = forwardRef(({ className, style, width, children, ...props }, ref) => {
|
|
227
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
228
|
+
const asideClass = usePrefixedClassNames('app-frame-aside');
|
|
229
|
+
const asideStyle = width === undefined
|
|
230
|
+
? style
|
|
231
|
+
: {
|
|
232
|
+
...style,
|
|
233
|
+
'--bulma-app-frame-aside-width': cssLength(width),
|
|
234
|
+
};
|
|
235
|
+
return (jsx("aside", { ref: ref, className: classNames(asideClass, bulmaHelperClasses, className), style: asideStyle, ...rest, children: children }));
|
|
236
|
+
});
|
|
237
|
+
AppFrameAside.displayName = 'AppFrame.Aside';
|
|
238
|
+
/**
|
|
239
|
+
* The bottom of an AppFrame, under the page.
|
|
240
|
+
*
|
|
241
|
+
* @function
|
|
242
|
+
* @param {AppFrameFooterProps} props - Props for AppFrame.Footer.
|
|
243
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<footer>`.
|
|
244
|
+
* @returns {JSX.Element} The rendered footer.
|
|
245
|
+
*/
|
|
246
|
+
const AppFrameFooter = forwardRef(({ className, children, ...props }, ref) => {
|
|
247
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
248
|
+
const footerClass = usePrefixedClassNames('app-frame-footer');
|
|
249
|
+
return (jsx("footer", { ref: ref, className: classNames(footerClass, bulmaHelperClasses, className), ...rest, children: children }));
|
|
250
|
+
});
|
|
251
|
+
AppFrameFooter.displayName = 'AppFrame.Footer';
|
|
252
|
+
/**
|
|
253
|
+
* The button that opens the drawer, or collapses the column from the breakpoint up: Navbar.Burger, wired to the frame.
|
|
254
|
+
*
|
|
255
|
+
* @function
|
|
256
|
+
* @param {AppFrameBurgerProps} props - Props for AppFrame.Burger.
|
|
257
|
+
* @param {React.Ref<HTMLButtonElement>} ref - Forwarded ref to the `<button>`.
|
|
258
|
+
* @returns {JSX.Element} The rendered burger.
|
|
259
|
+
*/
|
|
260
|
+
const AppFrameBurger = forwardRef(({ className, onClick, ...props }, ref) => {
|
|
261
|
+
// Outside a frame it's a Navbar.Burger that controls nothing.
|
|
262
|
+
const frame = useContext(FrameContext);
|
|
263
|
+
const burgerClass = usePrefixedClassNames('app-frame-burger');
|
|
264
|
+
const state = frame?.state;
|
|
265
|
+
// Until the frame knows whether the nav is a drawer, the burger says
|
|
266
|
+
// nothing it can't stand behind: no `aria-expanded`, and its bars. It can't
|
|
267
|
+
// act before then anyway, since that's before hydration. Outside a frame
|
|
268
|
+
// it's a plain Navbar.Burger.
|
|
269
|
+
const known = !!frame?.modeKnown;
|
|
270
|
+
const expanded = known && !!state && (state.isDrawer ? state.navOpen : !state.navCollapsed);
|
|
271
|
+
return (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 => {
|
|
272
|
+
onClick?.(e);
|
|
273
|
+
if (e.defaultPrevented || !frame)
|
|
274
|
+
return;
|
|
275
|
+
// Safari, and Firefox on a Mac, don't focus a button that's pressed.
|
|
276
|
+
// Focused, it's the element the drawer hands focus back to.
|
|
277
|
+
e.currentTarget.focus();
|
|
278
|
+
frame.state.toggleNav();
|
|
279
|
+
} }));
|
|
280
|
+
});
|
|
281
|
+
AppFrameBurger.displayName = 'AppFrame.Burger';
|
|
282
|
+
const isMain = (child) => React.isValidElement(child) && child.type === AppFrameMain;
|
|
283
|
+
const isNav = (child) => React.isValidElement(child) && child.type === AppFrameNav;
|
|
284
|
+
/**
|
|
285
|
+
* 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
|
+
* The header and the column stick while the document scrolls, and a skip
|
|
288
|
+
* link comes first. The layout is CSS, so the server renders it as the
|
|
289
|
+
* browser does. It holds two states, each yours to control: whether the
|
|
290
|
+
* drawer is open, and whether the column is collapsed. `AppFrame.Burger`
|
|
291
|
+
* and `useAppFrame()` toggle whichever applies at the current width.
|
|
292
|
+
*
|
|
293
|
+
* @function
|
|
294
|
+
* @param {AppFrameProps} props - Props for the AppFrame component.
|
|
295
|
+
* @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the root `<div>`.
|
|
296
|
+
* @returns {JSX.Element} The rendered frame.
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* <AppFrame>
|
|
300
|
+
* <AppFrame.Header>
|
|
301
|
+
* <Navbar aria-label="Site">
|
|
302
|
+
* <Navbar.Brand>
|
|
303
|
+
* <Navbar.Item href="/">Acme</Navbar.Item>
|
|
304
|
+
* <AppFrame.Burger />
|
|
305
|
+
* </Navbar.Brand>
|
|
306
|
+
* </Navbar>
|
|
307
|
+
* </AppFrame.Header>
|
|
308
|
+
* <AppFrame.Nav>
|
|
309
|
+
* <Menu>…</Menu>
|
|
310
|
+
* </AppFrame.Nav>
|
|
311
|
+
* <AppFrame.Main>…</AppFrame.Main>
|
|
312
|
+
* </AppFrame>
|
|
313
|
+
*/
|
|
314
|
+
const AppFrameRoot = forwardRef(({ className, textColor, bgColor, breakpoint = 'desktop', navOpen: navOpenProp, defaultNavOpen = false, onNavOpenChange, navCollapsed: navCollapsedProp, defaultNavCollapsed = false, onNavCollapsedChange, skipLink = true, labels, children, ...props }, ref) => {
|
|
315
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
316
|
+
color: textColor,
|
|
317
|
+
backgroundColor: bgColor,
|
|
318
|
+
...props,
|
|
319
|
+
});
|
|
320
|
+
const rootRef = useRef(null);
|
|
321
|
+
const rootCleanupRef = useRef(null);
|
|
322
|
+
const rootRefs = useCallback((node) => setRefs(node, rootRef, ref, rootCleanupRef), [ref]);
|
|
323
|
+
const [openState, setOpenState] = useState(defaultNavOpen);
|
|
324
|
+
const [collapsedState, setCollapsedState] = useState(defaultNavCollapsed);
|
|
325
|
+
const navOpen = navOpenProp ?? openState;
|
|
326
|
+
const navCollapsed = navCollapsedProp ?? collapsedState;
|
|
327
|
+
const openControlled = navOpenProp !== undefined;
|
|
328
|
+
const collapsedControlled = navCollapsedProp !== undefined;
|
|
329
|
+
const onNavOpenChangeRef = useLatestRef(onNavOpenChange);
|
|
330
|
+
const onNavCollapsedChangeRef = useLatestRef(onNavCollapsedChange);
|
|
331
|
+
const setNavOpen = useCallback((open) => {
|
|
332
|
+
onNavOpenChangeRef.current?.(open);
|
|
333
|
+
if (!openControlled)
|
|
334
|
+
setOpenState(open);
|
|
335
|
+
}, [openControlled, onNavOpenChangeRef]);
|
|
336
|
+
const setNavCollapsed = useCallback((collapsed) => {
|
|
337
|
+
onNavCollapsedChangeRef.current?.(collapsed);
|
|
338
|
+
if (!collapsedControlled)
|
|
339
|
+
setCollapsedState(collapsed);
|
|
340
|
+
}, [collapsedControlled, onNavCollapsedChangeRef]);
|
|
341
|
+
// The layout switches in the stylesheet. The script needs the width
|
|
342
|
+
// only to know which state the burger toggles, and to close the drawer
|
|
343
|
+
// once the window is at or past the breakpoint.
|
|
344
|
+
const wide = useBulmaBreakpoint(breakpoint);
|
|
345
|
+
const isDrawer = wide === false;
|
|
346
|
+
const isWide = wide === true;
|
|
347
|
+
useEffect(() => {
|
|
348
|
+
// 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
|
|
349
|
+
if (isWide && navOpen)
|
|
350
|
+
setNavOpen(false);
|
|
351
|
+
}, [isWide, navOpen, setNavOpen]);
|
|
352
|
+
const toggleNav = useCallback(() => {
|
|
353
|
+
if (isDrawer)
|
|
354
|
+
setNavOpen(!navOpen);
|
|
355
|
+
else
|
|
356
|
+
setNavCollapsed(!navCollapsed);
|
|
357
|
+
}, [isDrawer, navOpen, navCollapsed, setNavOpen, setNavCollapsed]);
|
|
358
|
+
// The ids of the parts the skip link and the burger point at, read off
|
|
359
|
+
// the children so the server renders them too.
|
|
360
|
+
const baseId = useId();
|
|
361
|
+
const parts = flattenChildren(children);
|
|
362
|
+
const mainId = parts.find(isMain)?.props.id || `${baseId}-main`;
|
|
363
|
+
const navId = parts.find(isNav)?.props.id || `${baseId}-nav`;
|
|
364
|
+
const { skipLink: skipText, nav, closeNav, burger } = labels ?? {};
|
|
365
|
+
const value = useMemo(() => ({
|
|
366
|
+
state: {
|
|
367
|
+
navOpen,
|
|
368
|
+
setNavOpen,
|
|
369
|
+
navCollapsed,
|
|
370
|
+
setNavCollapsed,
|
|
371
|
+
isDrawer,
|
|
372
|
+
toggleNav,
|
|
373
|
+
navId,
|
|
374
|
+
},
|
|
375
|
+
labels: {
|
|
376
|
+
skipLink: skipText || 'Skip to main content',
|
|
377
|
+
nav: nav || 'Sections',
|
|
378
|
+
closeNav: closeNav || 'Close navigation',
|
|
379
|
+
burger: burger || undefined,
|
|
380
|
+
},
|
|
381
|
+
mainId,
|
|
382
|
+
modeKnown: wide !== null,
|
|
383
|
+
}), [
|
|
384
|
+
navOpen,
|
|
385
|
+
setNavOpen,
|
|
386
|
+
navCollapsed,
|
|
387
|
+
setNavCollapsed,
|
|
388
|
+
isDrawer,
|
|
389
|
+
toggleNav,
|
|
390
|
+
navId,
|
|
391
|
+
skipText,
|
|
392
|
+
nav,
|
|
393
|
+
closeNav,
|
|
394
|
+
burger,
|
|
395
|
+
mainId,
|
|
396
|
+
wide,
|
|
397
|
+
]);
|
|
398
|
+
// Its header and footer are the page's banner and contentinfo only
|
|
399
|
+
// outside `main` and sectioning content, as HTML-AAM maps them.
|
|
400
|
+
useEffect(() => {
|
|
401
|
+
const host = rootRef.current?.parentElement?.closest('main, article, aside, nav, section');
|
|
402
|
+
if (host) {
|
|
403
|
+
warnOnce('AppFrame:nested', `[bestax-bulma] <AppFrame> sits inside a <${host.localName}>, where ` +
|
|
404
|
+
"its header and footer are no longer the page's banner and " +
|
|
405
|
+
'contentinfo landmarks. Put AppFrame at the top of the page.');
|
|
406
|
+
}
|
|
407
|
+
}, []);
|
|
408
|
+
const rootClass = usePrefixedClassNames('app-frame', {
|
|
409
|
+
[`is-${breakpoint}`]: true,
|
|
410
|
+
'is-nav-collapsed': navCollapsed,
|
|
411
|
+
});
|
|
412
|
+
const skipClass = usePrefixedClassNames('app-frame-skip-link');
|
|
413
|
+
return (jsxs("div", { ref: rootRefs, className: classNames(rootClass, bulmaHelperClasses, className), ...rest, children: [skipLink && (jsx("a", { className: skipClass, href: `#${mainId}`, children: value.labels.skipLink })), jsx(FrameContext.Provider, { value: value, children: children })] }));
|
|
414
|
+
});
|
|
415
|
+
/**
|
|
416
|
+
* The frame's state, for a control of your own inside it: whether the
|
|
417
|
+
* drawer is open and the column collapsed, setters that go through the
|
|
418
|
+
* frame's callbacks, and `toggleNav()`, which does what the burger does.
|
|
419
|
+
* Outside an AppFrame it's `null`, as the library's other context hooks
|
|
420
|
+
* are, so a control that can sit outside one renders a fallback or nothing.
|
|
421
|
+
*
|
|
422
|
+
* @function useAppFrame
|
|
423
|
+
* @returns The frame's state, or `null` outside an AppFrame.
|
|
424
|
+
*/
|
|
425
|
+
function useAppFrame() {
|
|
426
|
+
return useContext(FrameContext)?.state ?? null;
|
|
427
|
+
}
|
|
428
|
+
const AppFrame = withSubComponents(AppFrameRoot, {
|
|
429
|
+
Header: AppFrameHeader,
|
|
430
|
+
Nav: AppFrameNav,
|
|
431
|
+
Main: AppFrameMain,
|
|
432
|
+
Aside: AppFrameAside,
|
|
433
|
+
Footer: AppFrameFooter,
|
|
434
|
+
Burger: AppFrameBurger,
|
|
435
|
+
}, 'AppFrame');
|
|
436
|
+
|
|
437
|
+
export { AppFrame, AppFrameAside, AppFrameBurger, AppFrameFooter, AppFrameHeader, AppFrameMain, AppFrameNav, useAppFrame };
|
|
438
|
+
//# sourceMappingURL=AppFrame.js.map
|