@allxsmith/bestax-bulma 5.36.1 → 5.38.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/Card.js +2 -2
- package/dist/esm/components/Carousel.js +14 -6
- package/dist/esm/components/Carousel.js.map +1 -1
- package/dist/esm/components/Dropdown.js +7 -2
- package/dist/esm/components/Dropdown.js.map +1 -1
- package/dist/esm/components/EmptyState.js +2 -2
- package/dist/esm/components/Loading.js +7 -1
- package/dist/esm/components/Loading.js.map +1 -1
- package/dist/esm/components/Panel.js +34 -4
- package/dist/esm/components/Panel.js.map +1 -1
- package/dist/esm/components/Popover.js +7 -2
- package/dist/esm/components/Popover.js.map +1 -1
- package/dist/esm/components/Sidebar.js +46 -17
- package/dist/esm/components/Sidebar.js.map +1 -1
- package/dist/esm/components/Timeline.js +5 -5
- package/dist/esm/components/Timeline.js.map +1 -1
- package/dist/esm/components/TreeView.js +192 -0
- package/dist/esm/components/TreeView.js.map +1 -0
- package/dist/esm/form/Autocomplete.js +28 -18
- package/dist/esm/form/Autocomplete.js.map +1 -1
- package/dist/esm/form/Combobox.js +592 -0
- package/dist/esm/form/Combobox.js.map +1 -0
- package/dist/esm/form/Field.js +3 -3
- package/dist/esm/form/Field.js.map +1 -1
- package/dist/esm/form/_pickerInternals/PickerPopover.js +10 -5
- package/dist/esm/form/_pickerInternals/PickerPopover.js.map +1 -1
- package/dist/esm/form/useAutoLabelId.js.map +1 -1
- package/dist/esm/helpers/Theme.js.map +1 -1
- package/dist/esm/helpers/typeahead.js +82 -0
- package/dist/esm/helpers/typeahead.js.map +1 -0
- package/dist/esm/helpers/useAnchoredPosition.js +35 -6
- package/dist/esm/helpers/useAnchoredPosition.js.map +1 -1
- package/dist/esm/helpers/useComboboxEngine.js +331 -0
- package/dist/esm/helpers/useComboboxEngine.js.map +1 -0
- package/dist/esm/helpers/useFocusTrap.js +87 -49
- package/dist/esm/helpers/useFocusTrap.js.map +1 -1
- package/dist/esm/helpers/useInertSiblings.js +106 -0
- package/dist/esm/helpers/useInertSiblings.js.map +1 -0
- package/dist/esm/helpers/useMenuKeys.js +8 -58
- package/dist/esm/helpers/useMenuKeys.js.map +1 -1
- package/dist/esm/helpers/useTreeView.js +380 -0
- package/dist/esm/helpers/useTreeView.js.map +1 -0
- package/dist/esm/layout/Container.js +2 -2
- package/dist/esm/layout/Container.js.map +1 -1
- package/dist/esm/layout/Hero.js +6 -6
- package/dist/esm/layout/Hero.js.map +1 -1
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +3462 -1732
- 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/Loading.d.ts +16 -2
- package/dist/types/components/Panel.d.ts +45 -5
- package/dist/types/components/Sidebar.d.ts +17 -2
- package/dist/types/components/TreeView.d.ts +115 -0
- package/dist/types/form/Combobox.d.ts +416 -0
- package/dist/types/form/Field.d.ts +5 -5
- package/dist/types/form/useAutoLabelId.d.ts +3 -2
- package/dist/types/helpers/Theme.d.ts +6 -0
- package/dist/types/helpers/typeahead.d.ts +41 -0
- package/dist/types/helpers/useAnchoredPosition.d.ts +25 -2
- package/dist/types/helpers/useComboboxEngine.d.ts +111 -0
- package/dist/types/helpers/useFocusTrap.d.ts +15 -2
- package/dist/types/helpers/useInertSiblings.d.ts +30 -0
- package/dist/types/helpers/useMenuKeys.d.ts +1 -2
- package/dist/types/helpers/useTreeView.d.ts +88 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/layout/Container.d.ts +9 -1
- package/dist/types/layout/Hero.d.ts +27 -2
- package/dist/types-cjs/components/Loading.d.ts +16 -2
- package/dist/types-cjs/components/Panel.d.ts +45 -5
- package/dist/types-cjs/components/Sidebar.d.ts +17 -2
- package/dist/types-cjs/components/TreeView.d.ts +115 -0
- package/dist/types-cjs/form/Combobox.d.ts +416 -0
- package/dist/types-cjs/form/Field.d.ts +5 -5
- package/dist/types-cjs/form/useAutoLabelId.d.ts +3 -2
- package/dist/types-cjs/helpers/Theme.d.ts +6 -0
- package/dist/types-cjs/helpers/typeahead.d.ts +41 -0
- package/dist/types-cjs/helpers/useAnchoredPosition.d.ts +25 -2
- package/dist/types-cjs/helpers/useComboboxEngine.d.ts +111 -0
- package/dist/types-cjs/helpers/useFocusTrap.d.ts +15 -2
- package/dist/types-cjs/helpers/useInertSiblings.d.ts +30 -0
- package/dist/types-cjs/helpers/useMenuKeys.d.ts +1 -2
- package/dist/types-cjs/helpers/useTreeView.d.ts +88 -0
- package/dist/types-cjs/index.d.ts +2 -0
- package/dist/types-cjs/layout/Container.d.ts +9 -1
- package/dist/types-cjs/layout/Hero.d.ts +27 -2
- package/dist/versions/bestax-no-dark-mode.css +1 -1
- package/dist/versions/bestax-no-dark-mode.css.map +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
- package/dist/versions/bestax-no-helpers.css +1 -1
- package/dist/versions/bestax-no-helpers.css.map +1 -1
- package/dist/versions/bestax-prefixed.css +1 -1
- package/dist/versions/bestax-prefixed.css.map +1 -1
- package/package.json +1 -1
- package/src/scss/components/_index.scss +1 -0
- package/src/scss/components/_treeview.scss +252 -0
- package/src/scss/form/_combobox.scss +323 -0
- package/src/scss/form/_index.scss +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Timeline.js","sources":["../../../src/components/Timeline.tsx"],"sourcesContent":["import React, { createContext, forwardRef, useContext } from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\nimport { flattenChildren } from '../helpers/flattenChildren';\nimport { visuallyHidden } from '../helpers/statusRegion';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { Icon } from '../elements/Icon';\n\nconst validTimelineColors = [\n 'primary',\n 'link',\n 'info',\n 'success',\n 'warning',\n 'danger',\n] as const;\n/** The Bulma colors a marker takes. */\nexport type TimelineColor = (typeof validTimelineColors)[number];\n\nconst validTimelineTitleElements = ['h2', 'h3', 'h4', 'h5', 'h6', 'p'] as const;\n/** What each item's title renders as: a heading level, or `p` for a plain line. */\nexport type TimelineTitleElement = (typeof validTimelineTitleElements)[number];\n\n/** Where the rail goes. */\nexport type TimelineAlign = 'start' | 'end' | 'alternate';\n\n/** Where an item is in a progress timeline. */\ntype TimelineState = 'completed' | 'active' | 'upcoming' | 'pending';\n\n/**\n * What the root hands each of its items: the title's element, and in a\n * progress timeline, the item's state and the words read after its title.\n */\ninterface TimelineItemContextValue {\n titleAs: TimelineTitleElement;\n state?: TimelineState;\n label?: string;\n}\n\n// An item outside a Timeline is a record item with an h2 title.\nconst TimelineItemContext = createContext<TimelineItemContextValue>({\n titleAs: 'h2',\n});\n\n/** Whether a prop was given something to render. */\nconst isGiven = (node: React.ReactNode) =>\n node !== undefined &&\n node !== null &&\n typeof node !== 'boolean' &&\n node !== '';\n\nconst isTimelineColor = (color?: string): color is TimelineColor =>\n !!color && (validTimelineColors as readonly string[]).includes(color);\n\n/**\n * Props for the Timeline component. The helper props, the other attributes\n * and the `ref` land on the `<ol>`.\n *\n * @extraProp {React.Ref<HTMLOListElement>} [ref] - Ref forwarded to the `<ol>`.\n */\nexport interface TimelineProps\n extends\n Omit<React.OlHTMLAttributes<HTMLOListElement>, 'color' | 'role'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /**\n * `Timeline.Item`s, in the order they're read: newest first or oldest\n * first for a record, oldest first with `active` or `pending`.\n */\n children?: React.ReactNode;\n /**\n * Marks progress. The item at this index, counting the children from 0, is\n * current; the ones before it are completed and the ones after it are\n * upcoming. Leave it out for a record, where nothing is marked. `-1` marks\n * every item upcoming, and an index past the last marks them all completed.\n * A fraction counts as the item it has reached, so `2.5` marks the third\n * item current. Each child counts as one item, with Fragments and arrays opened up, so\n * render one item per child. A component of your own around a\n * `Timeline.Item` still gets its state, but one that renders nothing still\n * counts.\n */\n active?: number;\n /**\n * What's under way, as a last item after the children: its title, read\n * with `pendingLabel`. Write it as words that say so (\"Out for delivery\").\n */\n pending?: React.ReactNode;\n /**\n * `start`: the rail at the start of the line. `end`: at the end, with the\n * text against it. `alternate`: down the middle, the items taking turns and\n * each time across the rail; below Bulma's tablet breakpoint it's `start`.\n */\n align?: TimelineAlign;\n /**\n * What every item's title renders as: a heading at the level under the\n * heading the timeline sits beneath, or `p` for a busy feed.\n */\n titleAs?: TimelineTitleElement;\n /**\n * The markers' color, and the color of progress with `active`. Neutral\n * without it, and Bulma's `link` for progress. The markers are hidden from\n * assistive tech, so say what a color means in the words too.\n */\n color?: TimelineColor;\n /** Read after a completed item's title, visually hidden. In your app's language. */\n completedLabel?: string;\n /** Read after an upcoming item's title, visually hidden. In your app's language. */\n upcomingLabel?: string;\n /** Read after the pending item's title, visually hidden. In your app's language. */\n pendingLabel?: string;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * Props for Timeline.Item, one event. The helper props, the other attributes\n * and the `ref` land on its `<li>`. The native `title` attribute isn't one of\n * them, since `title` here is the event's title.\n *\n * @extraProp {React.Ref<HTMLLIElement>} [ref] - Ref forwarded to the `<li>`.\n */\nexport interface TimelineItemProps\n extends\n Omit<React.LiHTMLAttributes<HTMLLIElement>, 'color' | 'title'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /** What happened, in a line. It renders as the root's `titleAs`. */\n title?: React.ReactNode;\n /**\n * When it happened, as the reader sees it: \"14 October 2026\", \"2 hours\n * ago\". Put the whole date in the words, since screen readers mostly read\n * the text.\n */\n time?: React.ReactNode;\n /**\n * The same moment for machines, as HTML's `datetime` takes it:\n * \"2026-10-14\", \"2026-10-14T08:12\", \"2026-10\". With it, `time` renders in\n * a `<time>`. Keep it the same moment as `time`, since some readers speak it.\n */\n dateTime?: string;\n /**\n * A picture in the marker. A string is an `Icon` name, drawn from the icon\n * library `ConfigProvider` sets, and anything else renders as it is.\n * Without it, the marker is a dot. It's hidden from assistive tech, so\n * what it means goes in the title.\n */\n icon?: React.ReactNode;\n /** This item's marker color, over the root's. Say what it means in the words too. */\n color?: TimelineColor;\n /** Anything else about the event, under the title and the time, as written. */\n children?: React.ReactNode;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * One event: a marker on the rail, then its title, its time and anything else about it.\n *\n * Outside a `Timeline` it's a record item with an `h2` title, though the\n * styles are registered on `.timeline`, so the rail and the marker need one.\n *\n * @function\n * @param {TimelineItemProps} props - Props for Timeline.Item.\n * @param {React.Ref<HTMLLIElement>} ref - Forwarded ref to the `<li>`.\n * @returns {JSX.Element} The rendered event.\n *\n * @example\n * <Timeline.Item title=\"Shipped\" time=\"14 October 2026\" dateTime=\"2026-10-14\">\n * Left the warehouse in Leeds.\n * </Timeline.Item>\n */\nexport const TimelineItem = forwardRef<HTMLLIElement, TimelineItemProps>(\n (\n {\n className,\n title,\n time,\n dateTime,\n icon,\n color,\n textColor,\n bgColor,\n children,\n ...props\n },\n ref\n ) => {\n const { titleAs: TitleTag, state, label } = useContext(TimelineItemContext);\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const itemClass = usePrefixedClassNames('timeline-item', {\n [`is-${state}`]: !!state,\n [`is-${color}`]: isTimelineColor(color),\n });\n const markerClass = usePrefixedClassNames('timeline-marker', {\n 'is-icon': isGiven(icon),\n });\n const contentClass = usePrefixedClassNames('timeline-content');\n const titleClass = usePrefixedClassNames('timeline-title');\n const timeClass = usePrefixedClassNames('timeline-time');\n\n // The state in words, read after the title so it's in the heading too:\n // \"Packed completed\". The current item has none, since aria-current\n // says it. The styles are inline, so the words stay hidden in a modular\n // build that leaves out Bulma's helper classes.\n const words = label ? (\n <span style={visuallyHidden}>{` ${label}`}</span>\n ) : null;\n\n return (\n <li\n ref={ref}\n className={classNames(itemClass, bulmaHelperClasses, className)}\n {...rest}\n // In a progress timeline the state decides, whatever was passed.\n {...(state && {\n 'aria-current': state === 'active' ? 'step' : undefined,\n })}\n >\n {/* Decoration, hidden with whatever is in it, an Icon's own label\n included: the words say what a color, an icon or a state means. */}\n <div className={markerClass} aria-hidden=\"true\">\n {isGiven(icon) &&\n (typeof icon === 'string' ? <Icon name={icon} /> : icon)}\n </div>\n <div className={contentClass}>\n {isGiven(title) ? (\n <TitleTag className={titleClass}>\n {title}\n {words}\n </TitleTag>\n ) : (\n words\n )}\n {isGiven(time) &&\n (dateTime ? (\n <time className={timeClass} dateTime={dateTime}>\n {time}\n </time>\n ) : (\n <span className={timeClass}>{time}</span>\n ))}\n {children}\n </div>\n </li>\n );\n }\n);\n\nTimelineItem.displayName = 'Timeline.Item';\n\n/**\n * The `Timeline` component renders a list of events in the order they happened, each with a marker on a line down the side.\n *\n * By default it's a record, an order's history, a ticket's activity or a\n * changelog, and it marks nothing. With `active` it marks progress, for\n * something that moves along on its own and is reported to the user: a\n * parcel, a deployment, a claim. That reads oldest first, and `pending` ends\n * it on what's under way. A journey the user moves through themselves, like\n * a wizard or a checkout, is `Steps`.\n *\n * It's an `<ol role=\"list\">`, so it stays a list in Safari with its numbers\n * hidden, and each title is a heading at the level `titleAs` sets. The\n * markers are decoration and hidden from assistive tech, so the states are\n * read as words: `aria-current=\"step\"` on the current item, and \"completed\",\n * \"upcoming\" or \"in progress\" after the other titles, in the language the\n * label props set. Nothing here is a control or a live region, so a timeline\n * that changes on screen keeps a `role=\"status\"` region beside it.\n *\n * @function\n * @param {TimelineProps} props - Props for the Timeline component.\n * @param {React.Ref<HTMLOListElement>} ref - Forwarded ref to the `<ol>`.\n * @returns {JSX.Element} The rendered timeline.\n *\n * @example\n * <Timeline titleAs=\"h3\">\n * <Timeline.Item title=\"Delivered\" time=\"16 October 2026\" dateTime=\"2026-10-16\" />\n * <Timeline.Item title=\"Order placed\" time=\"12 October 2026\" dateTime=\"2026-10-12\" />\n * </Timeline>\n *\n * @example\n * <Timeline titleAs=\"h3\" active={1} pending=\"Out for delivery\">\n * <Timeline.Item title=\"Order placed\" />\n * <Timeline.Item title=\"Shipped\" />\n * </Timeline>\n */\nconst TimelineRoot = forwardRef<HTMLOListElement, TimelineProps>(\n (\n {\n className,\n children,\n active,\n pending,\n align = 'start',\n titleAs = 'h2',\n color,\n completedLabel = 'completed',\n upcomingLabel = 'upcoming',\n pendingLabel = 'in progress',\n textColor,\n bgColor,\n ...props\n },\n ref\n ) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const rootClass = usePrefixedClassNames('timeline', {\n 'is-end': align === 'end',\n 'is-alternate': align === 'alternate',\n [`is-${color}`]: isTimelineColor(color),\n });\n const level = validTimelineTitleElements.includes(titleAs) ? titleAs : 'h2';\n const labels: Record<TimelineState, string | undefined> = {\n completed: completedLabel,\n active: undefined,\n upcoming: upcomingLabel,\n pending: pendingLabel,\n };\n const contextFor = (state?: TimelineState) => ({\n titleAs: level,\n state,\n label: state && labels[state],\n });\n // A fraction counts as the item it has reached, so one item is current.\n const reached = typeof active === 'number' ? Math.floor(active) : undefined;\n const stateAt = (index: number): TimelineState | undefined => {\n if (reached === undefined) return undefined;\n if (index === reached) return 'active';\n return index < reached ? 'completed' : 'upcoming';\n };\n\n // Each child gets its state through context rather than a clone, so an\n // app's own component around a Timeline.Item still gets it. Fragments\n // and arrays are opened up first, so each item inside one counts.\n return (\n <ol\n ref={ref}\n className={classNames(rootClass, bulmaHelperClasses, className)}\n {...rest}\n role=\"list\"\n >\n {flattenChildren(children).map((child, index) => (\n <TimelineItemContext.Provider\n key={React.isValidElement(child) ? child.key : index}\n value={contextFor(stateAt(index))}\n >\n {child}\n </TimelineItemContext.Provider>\n ))}\n {isGiven(pending) && (\n <TimelineItemContext.Provider value={contextFor('pending')}>\n <TimelineItem title={pending} />\n </TimelineItemContext.Provider>\n )}\n </ol>\n );\n }\n);\n\nexport const Timeline = withSubComponents(\n TimelineRoot,\n { Item: TimelineItem },\n 'Timeline'\n);\n\nexport default Timeline;\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Timeline.js","sources":["../../../src/components/Timeline.tsx"],"sourcesContent":["import React, { createContext, forwardRef, useContext } from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\nimport { flattenChildren } from '../helpers/flattenChildren';\nimport { visuallyHidden } from '../helpers/statusRegion';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { Icon } from '../elements/Icon';\n\nconst validTimelineColors = [\n 'primary',\n 'link',\n 'info',\n 'success',\n 'warning',\n 'danger',\n] as const;\n/** The Bulma colors a marker takes. */\nexport type TimelineColor = (typeof validTimelineColors)[number];\n\nconst validTimelineTitleElements = ['h2', 'h3', 'h4', 'h5', 'h6', 'p'] as const;\n/** What each item's title renders as: a heading level, or `p` for a plain line. */\nexport type TimelineTitleElement = (typeof validTimelineTitleElements)[number];\n\n/** Where the rail goes. */\nexport type TimelineAlign = 'start' | 'end' | 'alternate';\n\n/** Where an item is in a progress timeline. */\ntype TimelineState = 'completed' | 'active' | 'upcoming' | 'pending';\n\n/**\n * What the root hands each of its items: the title's element, and in a\n * progress timeline, the item's state and the words read after its title.\n */\ninterface TimelineItemContextValue {\n titleAs: TimelineTitleElement;\n state?: TimelineState;\n label?: string;\n}\n\n// An item outside a Timeline is a record item with an h2 title.\nconst TimelineItemContext = createContext<TimelineItemContextValue>({\n titleAs: 'h2',\n});\n\n/** Whether a prop was given something to render. */\nconst isGiven = (node: React.ReactNode) =>\n node !== undefined &&\n node !== null &&\n typeof node !== 'boolean' &&\n node !== '';\n\nconst isTimelineColor = (color?: string): color is TimelineColor =>\n !!color && (validTimelineColors as readonly string[]).includes(color);\n\n/**\n * Props for the Timeline component. The helper props, the other attributes\n * and the `ref` land on the `<ol>`.\n *\n * @extraProp {React.Ref<HTMLOListElement>} [ref] - Ref forwarded to the `<ol>`.\n */\nexport interface TimelineProps\n extends\n Omit<React.OlHTMLAttributes<HTMLOListElement>, 'color' | 'role'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /**\n * `Timeline.Item`s, in the order they're read: newest first or oldest\n * first for a record, oldest first with `active` or `pending`.\n */\n children?: React.ReactNode;\n /**\n * Marks progress. The item at this index, counting the children from 0, is\n * current; the ones before it are completed and the ones after it are\n * upcoming. Leave it out for a record, where nothing is marked. `-1` marks\n * every item upcoming, and an index past the last marks them all completed.\n * A fraction counts as the item it has reached, so `2.5` marks the third\n * item current. Each child counts as one item, with Fragments and arrays opened up, so\n * render one item per child. A component of your own around a\n * `Timeline.Item` still gets its state, but one that renders nothing still\n * counts.\n */\n active?: number;\n /**\n * What's under way, as a last item after the children: its title, read\n * with `pendingLabel`. Write it as words that say so (\"Out for delivery\").\n */\n pending?: React.ReactNode;\n /**\n * `start`: the rail at the start of the line. `end`: at the end, with the\n * text against it. `alternate`: down the middle, the items taking turns and\n * each time across the rail; below Bulma's tablet breakpoint it's `start`.\n */\n align?: TimelineAlign;\n /**\n * What every item's title renders as: a heading at the level under the\n * heading the timeline sits beneath, or `p` for a busy feed.\n */\n titleAs?: TimelineTitleElement;\n /**\n * The markers' color, and the color of progress with `active`. Neutral\n * without it, and Bulma's `link` for progress. The markers are hidden from\n * assistive tech, so say what a color means in the words too.\n */\n color?: TimelineColor;\n /** Read after a completed item's title, visually hidden. In your app's language. */\n completedLabel?: string;\n /** Read after an upcoming item's title, visually hidden. In your app's language. */\n upcomingLabel?: string;\n /** Read after the pending item's title, visually hidden. In your app's language. */\n pendingLabel?: string;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * Props for Timeline.Item, one event. The helper props, the other attributes\n * and the `ref` land on its `<li>`. The native `title` attribute isn't one of\n * them, since `title` here is the event's title.\n *\n * @extraProp {React.Ref<HTMLLIElement>} [ref] - Ref forwarded to the `<li>`.\n */\nexport interface TimelineItemProps\n extends\n Omit<React.LiHTMLAttributes<HTMLLIElement>, 'color' | 'title'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /** What happened, in a line. It renders as the root's `titleAs`. */\n title?: React.ReactNode;\n /**\n * When it happened, as the reader sees it: \"14 October 2026\", \"2 hours\n * ago\". Put the whole date in the words, since screen readers mostly read\n * the text.\n */\n time?: React.ReactNode;\n /**\n * The same moment for machines, as HTML's `datetime` takes it:\n * \"2026-10-14\", \"2026-10-14T08:12\", \"2026-10\". With it, `time` renders in\n * a `<time>`. Keep it the same moment as `time`, since some readers speak it.\n */\n dateTime?: string;\n /**\n * A picture in the marker. A string is an `Icon` name, drawn from the icon\n * library `ConfigProvider` sets, and anything else renders as it is.\n * Without it, the marker is a dot. It's hidden from assistive tech, so\n * what it means goes in the title.\n */\n icon?: React.ReactNode;\n /** This item's marker color, over the root's. Say what it means in the words too. */\n color?: TimelineColor;\n /** Anything else about the event, under the title and the time, as written. */\n children?: React.ReactNode;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * One event: a marker on the rail, then its title, its time and anything else about it.\n *\n * Outside a `Timeline` it's a record item with an `h2` title, though the\n * styles are registered on `.timeline`, so the rail and the marker need one.\n *\n * @function\n * @param {TimelineItemProps} props - Props for Timeline.Item.\n * @param {React.Ref<HTMLLIElement>} ref - Forwarded ref to the `<li>`.\n * @returns {JSX.Element} The rendered event.\n *\n * @example\n * <Timeline.Item title=\"Shipped\" time=\"14 October 2026\" dateTime=\"2026-10-14\">\n * Left the warehouse in Leeds.\n * </Timeline.Item>\n */\nexport const TimelineItem = forwardRef<HTMLLIElement, TimelineItemProps>(\n (\n {\n className,\n title,\n time,\n dateTime,\n icon,\n color,\n textColor,\n bgColor,\n children,\n ...props\n },\n ref\n ) => {\n const { titleAs: TitleTag, state, label } = useContext(TimelineItemContext);\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const itemClass = usePrefixedClassNames('timeline-item', {\n [`is-${state}`]: !!state,\n [`is-${color}`]: isTimelineColor(color),\n });\n const markerClass = usePrefixedClassNames('timeline-marker', {\n 'is-icon': isGiven(icon),\n });\n const contentClass = usePrefixedClassNames('timeline-content');\n const titleClass = usePrefixedClassNames('timeline-title');\n const timeClass = usePrefixedClassNames('timeline-time');\n\n // The state in words, read after the title so it's in the heading too:\n // \"Packed completed\". The current item has none, since aria-current\n // says it. The styles are inline, so the words stay hidden in a modular\n // build that leaves out Bulma's helper classes.\n const words = label ? (\n <span style={visuallyHidden}>{` ${label}`}</span>\n ) : null;\n\n return (\n <li\n ref={ref}\n className={classNames(itemClass, bulmaHelperClasses, className)}\n {...rest}\n // In a progress timeline the state decides, whatever was passed.\n {...(state && {\n 'aria-current': state === 'active' ? 'step' : undefined,\n })}\n >\n {/* Decoration, hidden with whatever is in it, an Icon's own label\n included: the words say what a color, an icon or a state means. */}\n <div className={markerClass} aria-hidden=\"true\">\n {isGiven(icon) &&\n (typeof icon === 'string' ? <Icon name={icon} /> : icon)}\n </div>\n <div className={contentClass}>\n {isGiven(title) ? (\n <TitleTag className={titleClass}>\n {title}\n {words}\n </TitleTag>\n ) : (\n words\n )}\n {isGiven(time) &&\n (dateTime ? (\n <time className={timeClass} dateTime={dateTime}>\n {time}\n </time>\n ) : (\n <span className={timeClass}>{time}</span>\n ))}\n {children}\n </div>\n </li>\n );\n }\n);\n\nTimelineItem.displayName = 'Timeline.Item';\n\n/**\n * The `Timeline` component renders a list of events in the order they happened, each with a marker on a line down the side.\n *\n * By default it's a record, an order's history, a ticket's activity or a\n * changelog, and it marks nothing. With `active` it marks progress, for\n * something that moves along on its own and is reported to the user: a\n * parcel, a deployment, a claim. That reads oldest first, and `pending` ends\n * it on what's under way. A journey the user moves through themselves, like\n * a wizard or a checkout, is `Steps`.\n *\n * It's an `<ol role=\"list\">`, so it stays a list in Safari with its numbers\n * hidden, and each title is a heading at the level `titleAs` sets. The\n * markers are decoration and hidden from assistive tech, so the states are\n * read as words: `aria-current=\"step\"` on the current item, and \"completed\",\n * \"upcoming\" or \"in progress\" after the other titles, in the language the\n * label props set. Nothing here is a control or a live region, so a timeline\n * that changes on screen keeps a `role=\"status\"` region beside it.\n *\n * @function\n * @param {TimelineProps} props - Props for the Timeline component.\n * @param {React.Ref<HTMLOListElement>} ref - Forwarded ref to the `<ol>`.\n * @returns {JSX.Element} The rendered timeline.\n *\n * @example\n * <Timeline titleAs=\"h3\">\n * <Timeline.Item title=\"Delivered\" time=\"16 October 2026\" dateTime=\"2026-10-16\" />\n * <Timeline.Item title=\"Order placed\" time=\"12 October 2026\" dateTime=\"2026-10-12\" />\n * </Timeline>\n *\n * @example\n * <Timeline titleAs=\"h3\" active={1} pending=\"Out for delivery\">\n * <Timeline.Item title=\"Order placed\" />\n * <Timeline.Item title=\"Shipped\" />\n * </Timeline>\n */\nconst TimelineRoot = forwardRef<HTMLOListElement, TimelineProps>(\n (\n {\n className,\n children,\n active,\n pending,\n align = 'start',\n titleAs = 'h2',\n color,\n completedLabel = 'completed',\n upcomingLabel = 'upcoming',\n pendingLabel = 'in progress',\n textColor,\n bgColor,\n ...props\n },\n ref\n ) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const rootClass = usePrefixedClassNames('timeline', {\n 'is-end': align === 'end',\n 'is-alternate': align === 'alternate',\n [`is-${color}`]: isTimelineColor(color),\n });\n const level = validTimelineTitleElements.includes(titleAs) ? titleAs : 'h2';\n const labels: Record<TimelineState, string | undefined> = {\n completed: completedLabel,\n active: undefined,\n upcoming: upcomingLabel,\n pending: pendingLabel,\n };\n const contextFor = (state?: TimelineState) => ({\n titleAs: level,\n state,\n label: state && labels[state],\n });\n // A fraction counts as the item it has reached, so one item is current.\n const reached = typeof active === 'number' ? Math.floor(active) : undefined;\n const stateAt = (index: number): TimelineState | undefined => {\n if (reached === undefined) return undefined;\n if (index === reached) return 'active';\n return index < reached ? 'completed' : 'upcoming';\n };\n\n // Each child gets its state through context rather than a clone, so an\n // app's own component around a Timeline.Item still gets it. Fragments\n // and arrays are opened up first, so each item inside one counts.\n return (\n <ol\n ref={ref}\n className={classNames(rootClass, bulmaHelperClasses, className)}\n {...rest}\n role=\"list\"\n >\n {flattenChildren(children).map((child, index) => (\n <TimelineItemContext.Provider\n key={React.isValidElement(child) ? child.key : index}\n value={contextFor(stateAt(index))}\n >\n {child}\n </TimelineItemContext.Provider>\n ))}\n {isGiven(pending) && (\n <TimelineItemContext.Provider value={contextFor('pending')}>\n <TimelineItem title={pending} />\n </TimelineItemContext.Provider>\n )}\n </ol>\n );\n }\n);\n\nexport const Timeline = withSubComponents(\n TimelineRoot,\n { Item: TimelineItem },\n 'Timeline'\n);\n\nexport default Timeline;\n"],"names":["isGiven","_jsx","_jsxs"],"mappings":"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"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import React, { createContext, forwardRef, useCallback, useEffect, useContext, useId, useRef, useMemo } from 'react';
|
|
4
|
+
import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
|
|
5
|
+
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
6
|
+
import { withSubComponents } from '../helpers/withSubComponents.js';
|
|
7
|
+
import { warnOnce } from '../helpers/devWarnings.js';
|
|
8
|
+
import { useClientLayoutEffect } from '../helpers/useClientLayoutEffect.js';
|
|
9
|
+
import { useTreeView } from '../helpers/useTreeView.js';
|
|
10
|
+
import { Checkbox } from '../form/Checkbox.js';
|
|
11
|
+
import { Icon } from '../elements/Icon.js';
|
|
12
|
+
|
|
13
|
+
const validTreeViewSizes = ['small', 'medium', 'large'];
|
|
14
|
+
// Not exported, so they stay out of the public API that `index.ts` re-exports.
|
|
15
|
+
const TreeViewContext = createContext(null);
|
|
16
|
+
const TOP = { path: [], hidden: false };
|
|
17
|
+
const BranchContext = createContext(TOP);
|
|
18
|
+
/** The box's input lets the row's click toggle it, so a change on its own does nothing. */
|
|
19
|
+
const ignoreChange = () => { };
|
|
20
|
+
/** Whether an `icon` was given something to render. */
|
|
21
|
+
const isGiven = (node) => node !== undefined &&
|
|
22
|
+
node !== null &&
|
|
23
|
+
typeof node !== 'boolean' &&
|
|
24
|
+
node !== '';
|
|
25
|
+
/**
|
|
26
|
+
* The `TreeView` component renders a tree of items you browse and pick from, the way a file explorer works: one tab stop, the arrow keys to move, and Right and Left to open and close.
|
|
27
|
+
*
|
|
28
|
+
* It's the APG Tree View pattern, drawn to look like Bulma's menu. It isn't
|
|
29
|
+
* for site navigation: items aren't links, and a sidebar whose sections open
|
|
30
|
+
* and close is `Menu`'s job. The root owns which items are open, through
|
|
31
|
+
* `expanded`, `defaultExpanded` and `onExpandedChange`.
|
|
32
|
+
*
|
|
33
|
+
* `selectionMode` picks one of three trees. With `none`, nothing is picked.
|
|
34
|
+
* With `single`, one item is, and `selected` says which. With `checkbox`,
|
|
35
|
+
* each item has a box, a check reaches every enabled item under it, closed
|
|
36
|
+
* branches included, and a parent with some of its items checked is mixed.
|
|
37
|
+
* `selected` is read in a `single` tree only, and `checked` in a `checkbox`
|
|
38
|
+
* tree only. Selection never follows focus.
|
|
39
|
+
*
|
|
40
|
+
* Disabled items stay reachable from the keys, so a screen reader finds
|
|
41
|
+
* them. The tree needs a name, from `aria-label` or `aria-labelledby`, and a
|
|
42
|
+
* development build warns when it has neither.
|
|
43
|
+
*
|
|
44
|
+
* @function
|
|
45
|
+
* @param {TreeViewProps} props - Props for the TreeView component.
|
|
46
|
+
* @param {React.Ref<HTMLUListElement>} ref - Forwarded ref to the `<ul>`.
|
|
47
|
+
* @returns {JSX.Element} The rendered tree.
|
|
48
|
+
* @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ | APG Tree View Pattern}
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* <TreeView aria-label="Files" selectionMode="single" defaultExpanded={['src']}>
|
|
52
|
+
* <TreeView.Item value="src" label="src" icon="folder">
|
|
53
|
+
* <TreeView.Item value="src/index.ts" label="index.ts" icon="file" />
|
|
54
|
+
* </TreeView.Item>
|
|
55
|
+
* <TreeView.Item value="package.json" label="package.json" icon="file" />
|
|
56
|
+
* </TreeView>
|
|
57
|
+
*/
|
|
58
|
+
const TreeViewRoot = forwardRef(function TreeView({ children, selectionMode = 'none', selected, defaultSelected = null, onSelectedChange, checked, defaultChecked = [], onCheckedChange, expanded, defaultExpanded = [], onExpandedChange, size, className, ...props }, ref) {
|
|
59
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
|
|
60
|
+
const engine = useTreeView({
|
|
61
|
+
mode: selectionMode,
|
|
62
|
+
selected,
|
|
63
|
+
defaultSelected,
|
|
64
|
+
onSelectedChange,
|
|
65
|
+
checked,
|
|
66
|
+
defaultChecked,
|
|
67
|
+
onCheckedChange,
|
|
68
|
+
expanded,
|
|
69
|
+
defaultExpanded,
|
|
70
|
+
onExpandedChange,
|
|
71
|
+
});
|
|
72
|
+
const { treeRef } = engine;
|
|
73
|
+
const setRefs = useCallback((node) => {
|
|
74
|
+
treeRef.current = node;
|
|
75
|
+
if (typeof ref === 'function')
|
|
76
|
+
ref(node);
|
|
77
|
+
else if (ref)
|
|
78
|
+
ref.current = node;
|
|
79
|
+
}, [ref, treeRef]);
|
|
80
|
+
const named = Boolean(rest['aria-label'] || rest['aria-labelledby']);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!named) {
|
|
83
|
+
warnOnce('TreeView:unnamed', '[bestax-bulma] <TreeView> has no accessible name. Pass ' +
|
|
84
|
+
'aria-label or aria-labelledby, so a screen reader can say what ' +
|
|
85
|
+
'the tree holds.');
|
|
86
|
+
}
|
|
87
|
+
}, [named]);
|
|
88
|
+
const rootClass = usePrefixedClassNames('tree-view', {
|
|
89
|
+
[`is-${size}`]: !!size && validTreeViewSizes.includes(size),
|
|
90
|
+
});
|
|
91
|
+
// The props are spread first, so the role and the attributes the tree
|
|
92
|
+
// owns win over anything a caller passes along.
|
|
93
|
+
return (jsx(TreeViewContext.Provider, { value: engine, children: jsx(BranchContext.Provider, { value: TOP, children: jsx("ul", { ...rest, ref: setRefs, className: classNames(rootClass, bulmaHelperClasses, className), role: "tree", "aria-multiselectable": engine.rendersClosed || undefined, children: children }) }) }));
|
|
94
|
+
});
|
|
95
|
+
/**
|
|
96
|
+
* One item in the tree: a row with its chevron, its box, its icon and its label, and, while it's open, a group of the items under it.
|
|
97
|
+
*
|
|
98
|
+
* @function
|
|
99
|
+
* @param {TreeViewItemProps} props - Props for TreeView.Item.
|
|
100
|
+
* @param {React.Ref<HTMLLIElement>} ref - Forwarded ref to the `<li>`.
|
|
101
|
+
* @returns {JSX.Element | null} The rendered item, or nothing under a closed one.
|
|
102
|
+
*/
|
|
103
|
+
const TreeViewItem = forwardRef(function TreeViewItem({ value, label, icon, children, hasChildren = false, disabled = false, className, onClick, onKeyDown, onFocus, onBlur, ...props }, ref) {
|
|
104
|
+
const tree = useContext(TreeViewContext);
|
|
105
|
+
const branch = useContext(BranchContext);
|
|
106
|
+
const labelId = useId();
|
|
107
|
+
const hasItems = React.Children.toArray(children).length > 0;
|
|
108
|
+
const isParent = hasItems || hasChildren;
|
|
109
|
+
const open = isParent && !!tree?.isOpen(value);
|
|
110
|
+
const parent = branch.path[branch.path.length - 1] ?? null;
|
|
111
|
+
const itemClass = usePrefixedClassNames('tree-view-item');
|
|
112
|
+
const groupClass = usePrefixedClassNames('tree-view-group');
|
|
113
|
+
const toggleClass = usePrefixedClassNames('tree-view-toggle');
|
|
114
|
+
const checkClass = usePrefixedClassNames('tree-view-check');
|
|
115
|
+
const iconClass = usePrefixedClassNames('tree-view-icon');
|
|
116
|
+
const labelClass = usePrefixedClassNames('tree-view-label');
|
|
117
|
+
const register = tree?.register;
|
|
118
|
+
useClientLayoutEffect(() => {
|
|
119
|
+
if (!register) {
|
|
120
|
+
warnOnce('TreeView.Item:outside', '[bestax-bulma] <TreeView.Item> is outside a <TreeView>, so it ' +
|
|
121
|
+
'renders nothing. Put it inside one.');
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
return register(value, { parent, disabled, isParent });
|
|
125
|
+
}, [register, value, parent, disabled, isParent]);
|
|
126
|
+
const setElement = tree?.setElement;
|
|
127
|
+
const setRefs = useCallback((node) => {
|
|
128
|
+
// Only an item inside a tree renders an element to hand over.
|
|
129
|
+
setElement(value, node);
|
|
130
|
+
if (typeof ref === 'function')
|
|
131
|
+
ref(node);
|
|
132
|
+
else if (ref)
|
|
133
|
+
ref.current = node;
|
|
134
|
+
}, [setElement, value, ref]);
|
|
135
|
+
const rowRef = useRef(null);
|
|
136
|
+
const toggleRef = useRef(null);
|
|
137
|
+
const childBranch = useMemo(() => ({
|
|
138
|
+
path: [...branch.path, value],
|
|
139
|
+
hidden: branch.hidden || !open,
|
|
140
|
+
}), [branch, value, open]);
|
|
141
|
+
const { selected, checked } = tree?.stateOf(value, branch.path, disabled) ?? {};
|
|
142
|
+
const rowClass = usePrefixedClassNames('tree-view-row', {
|
|
143
|
+
'is-selected': selected,
|
|
144
|
+
'is-disabled': disabled,
|
|
145
|
+
});
|
|
146
|
+
if (!tree)
|
|
147
|
+
return null;
|
|
148
|
+
// While it's open the children render in its group. In a checkbox tree
|
|
149
|
+
// they render while it's closed too, drawing nothing, so a check knows
|
|
150
|
+
// what's under it.
|
|
151
|
+
const items = (open || tree.rendersClosed) && (jsx(BranchContext.Provider, { value: childBranch, children: children }));
|
|
152
|
+
if (branch.hidden)
|
|
153
|
+
return jsx(Fragment, { children: items });
|
|
154
|
+
const info = { value, parent, disabled, isParent, open };
|
|
155
|
+
return (jsxs("li", { ...props, ref: setRefs, className: classNames(itemClass, className), role: "treeitem", "aria-labelledby": labelId, "aria-level": branch.path.length + 1, "aria-expanded": isParent ? open : undefined, "aria-disabled": disabled || undefined, "aria-selected": selected, "aria-checked": checked, tabIndex: tree.tabStop === value ? 0 : -1, onKeyDown: event => {
|
|
156
|
+
onKeyDown?.(event);
|
|
157
|
+
tree.onKeyDown(event, info);
|
|
158
|
+
}, onFocus: event => {
|
|
159
|
+
onFocus?.(event);
|
|
160
|
+
if (event.target === event.currentTarget) {
|
|
161
|
+
tree.onFocus(value, event.currentTarget);
|
|
162
|
+
}
|
|
163
|
+
}, onBlur: event => {
|
|
164
|
+
onBlur?.(event);
|
|
165
|
+
if (event.target === event.currentTarget)
|
|
166
|
+
tree.onBlur();
|
|
167
|
+
}, onClick: event => {
|
|
168
|
+
onClick?.(event);
|
|
169
|
+
// A click in its row is its own, and so is one on the item itself,
|
|
170
|
+
// as a test's click on the treeitem is. One in its group belongs
|
|
171
|
+
// to the item inside it, or to nothing. The chevron's box is on
|
|
172
|
+
// every row, but it's a chevron only on a parent: on a leaf it's
|
|
173
|
+
// more of the row.
|
|
174
|
+
const target = event.target;
|
|
175
|
+
if (target === event.currentTarget ||
|
|
176
|
+
rowRef.current.contains(target)) {
|
|
177
|
+
tree.onClick(event.currentTarget, info, isParent && toggleRef.current.contains(target));
|
|
178
|
+
}
|
|
179
|
+
}, children: [jsxs("div", { className: rowClass, ref: rowRef, children: [jsx("span", { className: toggleClass, "aria-hidden": "true", ref: toggleRef }), checked !== undefined && (jsx("span", { className: checkClass, "aria-hidden": "true", onClick: event => {
|
|
180
|
+
// A click on the box's label is followed by one on its
|
|
181
|
+
// input, and both reach the item. The label's is cancelled,
|
|
182
|
+
// so the item toggles once.
|
|
183
|
+
if (!(event.target instanceof HTMLInputElement)) {
|
|
184
|
+
event.preventDefault();
|
|
185
|
+
}
|
|
186
|
+
}, children: jsx(Checkbox, { tabIndex: -1, checked: checked === true, indeterminate: checked === 'mixed', disabled: disabled, onChange: ignoreChange }) })), isGiven(icon) && (jsx("span", { className: iconClass, "aria-hidden": "true", children: typeof icon === 'string' ? jsx(Icon, { name: icon }) : icon })), jsx("span", { className: labelClass, id: labelId, children: label })] }), open && hasItems ? (jsx("ul", { className: groupClass, role: "group", children: items })) : (items)] }));
|
|
187
|
+
});
|
|
188
|
+
TreeViewItem.displayName = 'TreeView.Item';
|
|
189
|
+
const TreeView = withSubComponents(TreeViewRoot, { Item: TreeViewItem }, 'TreeView');
|
|
190
|
+
|
|
191
|
+
export { TreeView, TreeViewItem };
|
|
192
|
+
//# sourceMappingURL=TreeView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TreeView.js","sources":["../../../src/components/TreeView.tsx"],"sourcesContent":["import React, {\n createContext,\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { warnOnce } from '../helpers/devWarnings';\nimport { useClientLayoutEffect } from '../helpers/useClientLayoutEffect';\nimport { useTreeView, type TreeViewEngine } from '../helpers/useTreeView';\nimport { Checkbox } from '../form/Checkbox';\nimport { Icon } from '../elements/Icon';\n\n/**\n * How items are picked: `none`, nothing is; `single`, one item at a time;\n * `checkbox`, any number, with a box on every item and mixed parents.\n */\nexport type TreeViewSelectionMode = 'none' | 'single' | 'checkbox';\n\nconst validTreeViewSizes = ['small', 'medium', 'large'] as const;\n\n// Not exported, so they stay out of the public API that `index.ts` re-exports.\nconst TreeViewContext = createContext<TreeViewEngine | null>(null);\n\n/** Where an item sits: the values of the items above it, and whether one of them is closed. */\ninterface Branch {\n path: string[];\n hidden: boolean;\n}\n\nconst TOP: Branch = { path: [], hidden: false };\nconst BranchContext = createContext<Branch>(TOP);\n\n/** The box's input lets the row's click toggle it, so a change on its own does nothing. */\nconst ignoreChange = () => {};\n\n/** Whether an `icon` was given something to render. */\nconst isGiven = (node: React.ReactNode) =>\n node !== undefined &&\n node !== null &&\n typeof node !== 'boolean' &&\n node !== '';\n\n/**\n * Props for the TreeView component. The helper props, the other attributes\n * and the `ref` land on the `<ul role=\"tree\">`.\n *\n * @extraProp {string} [aria-label] - Names the tree, such as \"Files\". A tree needs a name, so pass this or `aria-labelledby`, and a development build warns when it has neither.\n * @extraProp {string} [aria-labelledby] - The id of a visible heading or label that names the tree, in place of `aria-label`.\n * @extraProp {React.Ref<HTMLUListElement>} [ref] - Ref forwarded to the `<ul>`.\n */\nexport interface TreeViewProps\n extends\n Omit<\n React.HTMLAttributes<HTMLUListElement>,\n | keyof BulmaClassesProps\n | 'role'\n | 'onSelect'\n | 'defaultValue'\n | 'defaultChecked'\n >,\n BulmaClassesProps {\n /**\n * `TreeView.Item`s, nested inside each other for their children. Render\n * an item's children whenever you have them, open or not: the tree hides\n * them while it's closed.\n */\n children?: React.ReactNode;\n /**\n * `none`: nothing is picked, and Enter and Space open and close a parent.\n * `single`: one item, picked with Enter, Space or a click.\n * `checkbox`: any number, checked with Space or a click. A check reaches\n * every enabled item under it, and a parent with some of them checked is mixed.\n */\n selectionMode?: TreeViewSelectionMode;\n /**\n * The selected item's value when you hold the state, or `null` for none.\n * Read with `single` only. Picking an item then only calls\n * `onSelectedChange`, and nothing changes until you pass the new value.\n */\n selected?: string | null;\n /** The item selected to begin with, when the tree holds its own state. Read with `single` only. */\n defaultSelected?: string | null;\n /**\n * Called with the value of the item picked. Not called for the one\n * already selected, and moving focus never calls it.\n */\n onSelectedChange?: (value: string) => void;\n /**\n * The checked items' values when you hold the state. Read with `checkbox`\n * only. A value checks its item, and an enabled item also counts as checked\n * when any item above it is in the list, so a parent's value stands for its\n * whole branch, loaded or not. A disabled item is checked only by its own value.\n */\n checked?: string[];\n /** The values checked to begin with, when the tree holds its own state. Read with `checkbox` only. */\n defaultChecked?: string[];\n /**\n * Called after a check or an uncheck with every item that now shows\n * checked, parents included, plus any earlier value that matches no item\n * the tree knows, left as it was. The order means nothing. Passed back as\n * `checked`, the list shows the same boxes.\n */\n onCheckedChange?: (checked: string[]) => void;\n /**\n * The open items' values when you hold the state. A value stays in the\n * list while its item's parent is closed, so the item reopens as it was.\n */\n expanded?: string[];\n /** The items open to begin with, when the tree holds its own state. */\n defaultExpanded?: string[];\n /** Called with the new list whenever an item opens or closes, `*` included. */\n onExpandedChange?: (expanded: string[]) => void;\n /** The text size, as Bulma's menu sizes it. The rows, the indent and the chevron scale with it. */\n size?: 'small' | 'medium' | 'large';\n}\n\n/**\n * Props for TreeView.Item, one row and, nested in it, its children. The other\n * attributes and the `ref` land on its `<li role=\"treeitem\">`.\n *\n * @extraProp {React.Ref<HTMLLIElement>} [ref] - Ref forwarded to the `<li>`.\n */\nexport interface TreeViewItemProps extends Omit<\n React.LiHTMLAttributes<HTMLLIElement>,\n 'role' | 'value' | 'onSelect' | 'children'\n> {\n /** **Required.** Identifies the item in `selected`, `checked` and `expanded`. Unique in the tree. */\n value: string;\n /**\n * **Required.** What the row shows, and the item's name for assistive\n * tech. Nothing in it should be a link or a button: a tree item does one\n * thing, and nothing else in its row takes focus.\n */\n label: React.ReactNode;\n /** A picture before the label: an `Icon` name, or a node. Hidden from assistive tech. */\n icon?: React.ReactNode;\n /**\n * `TreeView.Item`s under this one. Render them whenever you have them,\n * open or not: the tree hides them while it's closed, and a checkbox tree\n * needs them to know what a check reaches.\n */\n children?: React.ReactNode;\n /** Shows the item as a parent before its children are known, such as a folder that loads when it opens. */\n hasChildren?: boolean;\n /**\n * Reachable with the arrow keys, Home, End and type-ahead, so a screen\n * reader finds it, but never selected, checked, opened or closed. One\n * that's already open stays open.\n */\n disabled?: boolean;\n}\n\n/**\n * The `TreeView` component renders a tree of items you browse and pick from, the way a file explorer works: one tab stop, the arrow keys to move, and Right and Left to open and close.\n *\n * It's the APG Tree View pattern, drawn to look like Bulma's menu. It isn't\n * for site navigation: items aren't links, and a sidebar whose sections open\n * and close is `Menu`'s job. The root owns which items are open, through\n * `expanded`, `defaultExpanded` and `onExpandedChange`.\n *\n * `selectionMode` picks one of three trees. With `none`, nothing is picked.\n * With `single`, one item is, and `selected` says which. With `checkbox`,\n * each item has a box, a check reaches every enabled item under it, closed\n * branches included, and a parent with some of its items checked is mixed.\n * `selected` is read in a `single` tree only, and `checked` in a `checkbox`\n * tree only. Selection never follows focus.\n *\n * Disabled items stay reachable from the keys, so a screen reader finds\n * them. The tree needs a name, from `aria-label` or `aria-labelledby`, and a\n * development build warns when it has neither.\n *\n * @function\n * @param {TreeViewProps} props - Props for the TreeView component.\n * @param {React.Ref<HTMLUListElement>} ref - Forwarded ref to the `<ul>`.\n * @returns {JSX.Element} The rendered tree.\n * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ | APG Tree View Pattern}\n *\n * @example\n * <TreeView aria-label=\"Files\" selectionMode=\"single\" defaultExpanded={['src']}>\n * <TreeView.Item value=\"src\" label=\"src\" icon=\"folder\">\n * <TreeView.Item value=\"src/index.ts\" label=\"index.ts\" icon=\"file\" />\n * </TreeView.Item>\n * <TreeView.Item value=\"package.json\" label=\"package.json\" icon=\"file\" />\n * </TreeView>\n */\nconst TreeViewRoot = forwardRef<HTMLUListElement, TreeViewProps>(\n function TreeView(\n {\n children,\n selectionMode = 'none',\n selected,\n defaultSelected = null,\n onSelectedChange,\n checked,\n defaultChecked = [],\n onCheckedChange,\n expanded,\n defaultExpanded = [],\n onExpandedChange,\n size,\n className,\n ...props\n },\n ref\n ) {\n const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });\n const engine = useTreeView({\n mode: selectionMode,\n selected,\n defaultSelected,\n onSelectedChange,\n checked,\n defaultChecked,\n onCheckedChange,\n expanded,\n defaultExpanded,\n onExpandedChange,\n });\n\n const { treeRef } = engine;\n const setRefs = useCallback(\n (node: HTMLUListElement | null) => {\n treeRef.current = node;\n if (typeof ref === 'function') ref(node);\n else if (ref) ref.current = node;\n },\n [ref, treeRef]\n );\n\n const named = Boolean(rest['aria-label'] || rest['aria-labelledby']);\n useEffect(() => {\n if (!named) {\n warnOnce(\n 'TreeView:unnamed',\n '[bestax-bulma] <TreeView> has no accessible name. Pass ' +\n 'aria-label or aria-labelledby, so a screen reader can say what ' +\n 'the tree holds.'\n );\n }\n }, [named]);\n\n const rootClass = usePrefixedClassNames('tree-view', {\n [`is-${size}`]:\n !!size && (validTreeViewSizes as readonly string[]).includes(size),\n });\n\n // The props are spread first, so the role and the attributes the tree\n // owns win over anything a caller passes along.\n return (\n <TreeViewContext.Provider value={engine}>\n <BranchContext.Provider value={TOP}>\n <ul\n {...rest}\n ref={setRefs}\n className={classNames(rootClass, bulmaHelperClasses, className)}\n role=\"tree\"\n aria-multiselectable={engine.rendersClosed || undefined}\n >\n {children}\n </ul>\n </BranchContext.Provider>\n </TreeViewContext.Provider>\n );\n }\n);\n\n/**\n * One item in the tree: a row with its chevron, its box, its icon and its label, and, while it's open, a group of the items under it.\n *\n * @function\n * @param {TreeViewItemProps} props - Props for TreeView.Item.\n * @param {React.Ref<HTMLLIElement>} ref - Forwarded ref to the `<li>`.\n * @returns {JSX.Element | null} The rendered item, or nothing under a closed one.\n */\nexport const TreeViewItem = forwardRef<HTMLLIElement, TreeViewItemProps>(\n function TreeViewItem(\n {\n value,\n label,\n icon,\n children,\n hasChildren = false,\n disabled = false,\n className,\n onClick,\n onKeyDown,\n onFocus,\n onBlur,\n ...props\n },\n ref\n ) {\n const tree = useContext(TreeViewContext);\n const branch = useContext(BranchContext);\n const labelId = useId();\n const hasItems = React.Children.toArray(children).length > 0;\n const isParent = hasItems || hasChildren;\n const open = isParent && !!tree?.isOpen(value);\n const parent = branch.path[branch.path.length - 1] ?? null;\n\n const itemClass = usePrefixedClassNames('tree-view-item');\n const groupClass = usePrefixedClassNames('tree-view-group');\n const toggleClass = usePrefixedClassNames('tree-view-toggle');\n const checkClass = usePrefixedClassNames('tree-view-check');\n const iconClass = usePrefixedClassNames('tree-view-icon');\n const labelClass = usePrefixedClassNames('tree-view-label');\n\n const register = tree?.register;\n useClientLayoutEffect(() => {\n if (!register) {\n warnOnce(\n 'TreeView.Item:outside',\n '[bestax-bulma] <TreeView.Item> is outside a <TreeView>, so it ' +\n 'renders nothing. Put it inside one.'\n );\n return undefined;\n }\n return register(value, { parent, disabled, isParent });\n }, [register, value, parent, disabled, isParent]);\n\n const setElement = tree?.setElement;\n const setRefs = useCallback(\n (node: HTMLLIElement | null) => {\n // Only an item inside a tree renders an element to hand over.\n setElement!(value, node);\n if (typeof ref === 'function') ref(node);\n else if (ref) ref.current = node;\n },\n [setElement, value, ref]\n );\n const rowRef = useRef<HTMLDivElement | null>(null);\n const toggleRef = useRef<HTMLSpanElement | null>(null);\n\n const childBranch = useMemo(\n () => ({\n path: [...branch.path, value],\n hidden: branch.hidden || !open,\n }),\n [branch, value, open]\n );\n\n const { selected, checked } =\n tree?.stateOf(value, branch.path, disabled) ?? {};\n const rowClass = usePrefixedClassNames('tree-view-row', {\n 'is-selected': selected,\n 'is-disabled': disabled,\n });\n\n if (!tree) return null;\n\n // While it's open the children render in its group. In a checkbox tree\n // they render while it's closed too, drawing nothing, so a check knows\n // what's under it.\n const items = (open || tree.rendersClosed) && (\n <BranchContext.Provider value={childBranch}>\n {children}\n </BranchContext.Provider>\n );\n if (branch.hidden) return <>{items}</>;\n\n const info = { value, parent, disabled, isParent, open };\n\n return (\n <li\n {...props}\n ref={setRefs}\n className={classNames(itemClass, className)}\n role=\"treeitem\"\n aria-labelledby={labelId}\n aria-level={branch.path.length + 1}\n aria-expanded={isParent ? open : undefined}\n aria-disabled={disabled || undefined}\n aria-selected={selected}\n aria-checked={checked}\n tabIndex={tree.tabStop === value ? 0 : -1}\n onKeyDown={event => {\n onKeyDown?.(event);\n tree.onKeyDown(event, info);\n }}\n onFocus={event => {\n onFocus?.(event);\n if (event.target === event.currentTarget) {\n tree.onFocus(value, event.currentTarget);\n }\n }}\n onBlur={event => {\n onBlur?.(event);\n if (event.target === event.currentTarget) tree.onBlur();\n }}\n onClick={event => {\n onClick?.(event);\n // A click in its row is its own, and so is one on the item itself,\n // as a test's click on the treeitem is. One in its group belongs\n // to the item inside it, or to nothing. The chevron's box is on\n // every row, but it's a chevron only on a parent: on a leaf it's\n // more of the row.\n const target = event.target as Node;\n if (\n target === event.currentTarget ||\n rowRef.current!.contains(target)\n ) {\n tree.onClick(\n event.currentTarget,\n info,\n isParent && toggleRef.current!.contains(target)\n );\n }\n }}\n >\n <div className={rowClass} ref={rowRef}>\n <span className={toggleClass} aria-hidden=\"true\" ref={toggleRef} />\n {checked !== undefined && (\n <span\n className={checkClass}\n aria-hidden=\"true\"\n onClick={event => {\n // A click on the box's label is followed by one on its\n // input, and both reach the item. The label's is cancelled,\n // so the item toggles once.\n if (!(event.target instanceof HTMLInputElement)) {\n event.preventDefault();\n }\n }}\n >\n <Checkbox\n tabIndex={-1}\n checked={checked === true}\n indeterminate={checked === 'mixed'}\n disabled={disabled}\n onChange={ignoreChange}\n />\n </span>\n )}\n {isGiven(icon) && (\n <span className={iconClass} aria-hidden=\"true\">\n {typeof icon === 'string' ? <Icon name={icon} /> : icon}\n </span>\n )}\n <span className={labelClass} id={labelId}>\n {label}\n </span>\n </div>\n {open && hasItems ? (\n <ul className={groupClass} role=\"group\">\n {items}\n </ul>\n ) : (\n items\n )}\n </li>\n );\n }\n);\n\nTreeViewItem.displayName = 'TreeView.Item';\n\nexport const TreeView = withSubComponents(\n TreeViewRoot,\n { Item: TreeViewItem },\n 'TreeView'\n);\n\nexport default TreeView;\n"],"names":["_jsx","_Fragment","_jsxs"],"mappings":"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"}
|
|
@@ -9,11 +9,12 @@ import { Field } from './Field.js';
|
|
|
9
9
|
import { useAutoLabelId } from './useAutoLabelId.js';
|
|
10
10
|
import { useControlLoading } from './controlLoading.js';
|
|
11
11
|
import { spokenText, StatusRegion } from '../helpers/statusRegion.js';
|
|
12
|
+
import { useLatestRef } from '../helpers/useLatestRef.js';
|
|
12
13
|
|
|
13
14
|
// What the status region announces: the `empty` content, as the one item
|
|
14
15
|
// it's handed while that content is on screen because of the user's typing.
|
|
15
|
-
const typedNoMatches = [{ id: 'empty' }];
|
|
16
|
-
const nothingToAnnounce = [];
|
|
16
|
+
const typedNoMatches$1 = [{ id: 'empty' }];
|
|
17
|
+
const nothingToAnnounce$1 = [];
|
|
17
18
|
// A press on an option would take focus from the field, since an option
|
|
18
19
|
// can't hold it, and nothing would put it back. Cancelling the press keeps
|
|
19
20
|
// it there, as the ARIA Authoring Practices' combobox does, and the click
|
|
@@ -22,7 +23,7 @@ const keepFocus = (e) => e.preventDefault();
|
|
|
22
23
|
// An IME that's composing owns every key: Enter confirms what it composed,
|
|
23
24
|
// and the arrows move through its candidates. Safari can say so with
|
|
24
25
|
// `keyCode` 229 rather than `isComposing`.
|
|
25
|
-
const isComposing$
|
|
26
|
+
const isComposing$2 = (e) => e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229;
|
|
26
27
|
// Whether an option can be picked. The arrows still reach a disabled one, so
|
|
27
28
|
// a screen reader user finds it.
|
|
28
29
|
const isEnabled = (item) => typeof item === 'string' || !item.disabled;
|
|
@@ -147,6 +148,19 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
147
148
|
setIsActive(true);
|
|
148
149
|
}
|
|
149
150
|
}, [openOnFocus, disabled]);
|
|
151
|
+
// Read when a click lands outside, so the listener stays put while the
|
|
152
|
+
// list is open. What it selects changes as the user types and arrows
|
|
153
|
+
// through suggestions, the filtered list is new on every render, and an
|
|
154
|
+
// inline `onInput` or `onSelect` changes on every render of the parent.
|
|
155
|
+
// Typing leaves the highlight where it was, so a list that narrowed can
|
|
156
|
+
// leave it past the last suggestion, with nothing there to select.
|
|
157
|
+
const selectHighlightedRef = useLatestRef(() => {
|
|
158
|
+
if (selectOnClickOutside &&
|
|
159
|
+
highlightedIndex >= 0 &&
|
|
160
|
+
highlightedIndex < filteredData.length) {
|
|
161
|
+
handleSelect(filteredData[highlightedIndex]);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
150
164
|
// Handle blur (click outside)
|
|
151
165
|
useEffect(() => {
|
|
152
166
|
if (!isActive)
|
|
@@ -158,25 +172,21 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
158
172
|
const path = e.composedPath();
|
|
159
173
|
const isInside = path.includes(containerRef.current);
|
|
160
174
|
if (!isInside) {
|
|
161
|
-
|
|
162
|
-
handleSelect(filteredData[highlightedIndex]);
|
|
163
|
-
}
|
|
175
|
+
selectHighlightedRef.current();
|
|
164
176
|
setIsActive(false);
|
|
165
177
|
}
|
|
166
178
|
};
|
|
167
179
|
document.addEventListener('mousedown', handleClickOutside);
|
|
168
180
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
169
|
-
}, [
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
handleSelect,
|
|
175
|
-
]);
|
|
181
|
+
}, [isActive, selectHighlightedRef]);
|
|
182
|
+
// Read when the dropdown opens or closes, so an inline `onActiveChange`,
|
|
183
|
+
// new on every render of the parent, isn't told again about a state that
|
|
184
|
+
// didn't change.
|
|
185
|
+
const onActiveChangeRef = useLatestRef(onActiveChange);
|
|
176
186
|
// Notify active state change
|
|
177
187
|
useEffect(() => {
|
|
178
|
-
|
|
179
|
-
}, [isActive,
|
|
188
|
+
onActiveChangeRef.current?.(isActive);
|
|
189
|
+
}, [isActive, onActiveChangeRef]);
|
|
180
190
|
// Keep first highlighted
|
|
181
191
|
useEffect(() => {
|
|
182
192
|
if (keepFirst && isActive && filteredData.length > 0) {
|
|
@@ -185,7 +195,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
185
195
|
}, [keepFirst, isActive, filteredData.length, inputValue]);
|
|
186
196
|
// Handle keyboard navigation
|
|
187
197
|
const handleKeyDown = useCallback((e) => {
|
|
188
|
-
if (disabled || isComposing$
|
|
198
|
+
if (disabled || isComposing$2(e))
|
|
189
199
|
return;
|
|
190
200
|
switch (e.key) {
|
|
191
201
|
case 'ArrowDown':
|
|
@@ -341,8 +351,8 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
341
351
|
!!inputValue &&
|
|
342
352
|
!loading &&
|
|
343
353
|
!controlLoading
|
|
344
|
-
? typedNoMatches
|
|
345
|
-
: nothingToAnnounce;
|
|
354
|
+
? typedNoMatches$1
|
|
355
|
+
: nothingToAnnounce$1;
|
|
346
356
|
// The region reads the text on screen, whatever `empty` renders.
|
|
347
357
|
const emptyRef = useRef(null);
|
|
348
358
|
const describeEmpty = useCallback(() => {
|