@allxsmith/bestax-bulma 5.36.0 → 5.37.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/EmptyState.js +2 -2
- package/dist/esm/components/Menu.js +9 -2
- package/dist/esm/components/Menu.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/helpers/typeahead.js +82 -0
- package/dist/esm/helpers/typeahead.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/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +1954 -1370
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +1 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/Menu.d.ts +5 -0
- package/dist/types/components/TreeView.d.ts +115 -0
- package/dist/types/helpers/typeahead.d.ts +41 -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 +1 -0
- package/dist/types-cjs/components/Menu.d.ts +5 -0
- package/dist/types-cjs/components/TreeView.d.ts +115 -0
- package/dist/types-cjs/helpers/typeahead.d.ts +41 -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 +1 -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/components/_index.scss +1 -0
- package/src/scss/components/_treeview.scss +252 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Menu.js","sources":["../../../src/components/Menu.tsx"],"sourcesContent":["import React, { createContext, forwardRef, useContext } from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { omitAttrs } from '../helpers/anchorAttrs';\nimport { buttonType } from '../helpers/buttonType';\nimport {\n isCustomElement,\n type PolymorphicComponent,\n} from '../helpers/polymorphic';\n\n// Context to track MenuList nesting level\nconst MenuListLevelContext = createContext(0);\n\n/**\n * Props for the Menu component.\n */\nexport interface MenuProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** Menu content (labels, lists, items, etc). */\n children: React.ReactNode;\n}\n\n/**\n * The `Menu` component provides Bulma's vertical navigation menu: a simple, accessible sidebar or section menu for your Bulma React UI.\n *\n * @function\n * @param {MenuProps} props - Props for the Menu component.\n * @returns {JSX.Element} The rendered menu.\n * @see {@link https://bulma.io/documentation/components/menu/ | Bulma Menu documentation}\n */\nconst MenuComponent: React.FC<MenuProps> = ({\n className,\n children,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n // Generate Bulma classes with prefix\n const bulmaClasses = usePrefixedClassNames('menu');\n\n return (\n <aside\n className={classNames(bulmaClasses, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </aside>\n );\n};\n\n/**\n * Props for the MenuLabel component.\n */\nexport interface MenuLabelProps\n extends\n Omit<React.HTMLAttributes<HTMLParagraphElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** Label content. */\n children: React.ReactNode;\n}\n\n/**\n * Bulma Menu label component.\n *\n * @function\n * @param {MenuLabelProps} props - Props for the MenuLabel component.\n * @returns {JSX.Element} The rendered menu label.\n */\nexport const MenuLabel: React.FC<MenuLabelProps> = ({\n className,\n children,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n return (\n <p\n className={classNames(\n usePrefixedClassNames('menu-label'),\n className,\n bulmaHelperClasses\n )}\n {...rest}\n >\n {children}\n </p>\n );\n};\n\n/**\n * Props for the MenuList component.\n */\nexport interface MenuListProps\n extends\n Omit<React.HTMLAttributes<HTMLUListElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** List items. */\n children: React.ReactNode;\n}\n\n/**\n * MenuList applies `menu-list` class only at the top level (not for nested lists).\n *\n * @function\n * @param {MenuListProps} props - Props for the MenuList component.\n * @returns {JSX.Element} The rendered menu list.\n */\nexport const MenuList: React.FC<MenuListProps> = ({\n className,\n children,\n ...props\n}) => {\n const level = useContext(MenuListLevelContext);\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n const ulClass = classNames(className, bulmaHelperClasses, {\n [usePrefixedClassNames('menu-list')]: level === 0,\n });\n\n // Increment level for nested MenuLists\n return (\n <MenuListLevelContext.Provider value={level + 1}>\n <ul className={ulClass || undefined} {...rest}>\n {children}\n </ul>\n </MenuListLevelContext.Provider>\n );\n};\n\n/**\n * The MenuItem component's own props.\n *\n * A menu item is two elements: a wrapping `<li>` and, inside it, the element\n * `as` names. The props here are the ones the `<li>` consumes; everything else\n * follows `as` onto the inner element.\n */\nexport interface MenuItemOwnProps extends BulmaClassesProps {\n /** Additional CSS classes for the wrapping `<li>`. */\n className?: string;\n /** Item content and optional nested MenuList. */\n children: React.ReactNode;\n /**\n * Marks the item as the current one. It adds Bulma's `is-active` class, and\n * `aria-current=\"page\"` on the element `as` renders, so assistive tech can\n * tell which item is current too. An `aria-current` you pass wins, even\n * `undefined`: use it to say something else (`\"location\"` for a section of\n * one page, say), or `aria-current={false}` to turn it off. A menu should\n * mark one page, so when the section above the current page is `active` too,\n * give it `aria-current=\"location\"`, and give an `active` item that isn't\n * navigation, such as an `as=\"button\"` filter, `aria-current={false}`.\n */\n active?: boolean;\n /** Inline styles for the wrapping `<li>`. */\n style?: React.CSSProperties;\n /** `id` for the wrapping `<li>`. */\n id?: string;\n /** `title` for the wrapping `<li>`. */\n title?: string;\n /** ARIA role for the wrapping `<li>`. */\n role?: React.AriaRole;\n /** Tab index for the wrapping `<li>`. */\n tabIndex?: number;\n /** Test id for the wrapping `<li>`. */\n 'data-testid'?: string;\n}\n\n/**\n * Props for the MenuItem component. Everything the `<li>` does not consume\n * follows `as` onto the inner element: with the default `'a'` that means `href`\n * and the other anchor attributes, and with `as={Link}` it means that\n * component's own props.\n *\n * @extraProp {PolymorphicRef<React.ElementType>} [ref] - Ref forwarded to the inner element `as` renders, not the wrapping `<li>`, typed from `as`: the DOM node for an intrinsic tag, or whatever handle a custom component exposes.\n */\nexport type MenuItemProps<T extends React.ElementType = 'a'> =\n MenuItemOwnProps &\n Omit<React.ComponentPropsWithoutRef<T>, keyof MenuItemOwnProps | 'as'> & {\n /**\n * Custom link component (e.g. `Link` from router).\n *\n * `'button'` renders `type=\"button\"` unless you pass `type=\"submit\"` or `type=\"reset\"`, so\n * an item inside a form does not submit it.\n */\n as?: T;\n };\n\n/**\n * `href` alone — annotated so a typo is a compile error rather than a silent\n * no-op, the way both siblings' sets are.\n */\nconst STRIP_FROM_NON_LINK: Readonly<Record<'href', true>> = { href: true };\n\n/**\n * The shape the implementation destructures. The public contract is the generic\n * `MenuItemProps<T>` above — the body cannot see through `T`.\n */\ntype MenuItemImplProps = MenuItemOwnProps & {\n as?: React.ElementType;\n type?: React.ButtonHTMLAttributes<HTMLButtonElement>['type'];\n};\n\n/**\n * MenuItem supports `as` prop for custom link components, e.g., react-router-dom Link.\n *\n * Give each item an `href`, a router link through `as`, or `as=\"button\"`, so\n * the keyboard can reach it: an `<a>` with no `href` isn't a link, and Tab\n * skips it.\n *\n * @function\n * @param {MenuItemProps} props - Props for the MenuItem component.\n * @param {React.Ref} ref - Forwarded ref to the inner element `as` renders.\n * @returns {JSX.Element} The rendered menu item.\n */\nexport const MenuItem = forwardRef(function MenuItem(\n itemProps: MenuItemProps,\n ref: React.Ref<HTMLElement>\n) {\n const {\n className,\n children,\n active,\n as: Component = 'a',\n 'data-testid': testId,\n style,\n id,\n title,\n role,\n tabIndex,\n ...rest\n } = itemProps as MenuItemImplProps;\n const { bulmaHelperClasses, rest: forwarded } = useBulmaClasses(rest);\n // `href` reaches an anchor or a custom component (which owns its own prop\n // contract), but not another intrinsic tag — `<span href>` is invalid HTML.\n // The same rule Avatar applies through `isLinkLike` and Button through its\n // intrinsic-only strip. Before #641 `href` was an own prop re-applied only\n // for `as=\"a\"`; deriving it from `as` must not quietly widen that.\n const isLinkLike =\n Component === 'a' ||\n typeof Component !== 'string' ||\n isCustomElement(Component);\n // `href` ALONE. `Dropdown.Item` strips every anchor-only attribute except\n // `type`; `Level.Item` strips all of them, plus `rel`.\n //\n // Not because nothing else can arrive: `as` here is an open `React.ElementType`,\n // and a non-fresh spread skips excess-property checking, so a typed caller\n // CAN deliver `target` or `download` and they are forwarded. The set stays at\n // `href` because widening it would be wrong for an open `as` — `referrerPolicy`\n // on `as=\"img\"`, `target` on `as=\"form\"` and `media` on `as=\"source\"` are all\n // legal, and a flat strip would delete them. Getting that right needs a\n // per-attribute-per-element rule, which `bestax-migrate` already models in\n // `LINK_ATTR_ELEMENTS`.\n const linkProps = isLinkLike\n ? forwarded\n : omitAttrs(forwarded, STRIP_FROM_NON_LINK);\n const itemClass = classNames(\n { [usePrefixedClassNames('is-active')]: active },\n bulmaHelperClasses\n );\n\n // Split children into label and nested MenuList(s)\n const labelChildren: React.ReactNode[] = [];\n const nestedMenuLists: React.ReactNode[] = [];\n React.Children.forEach(children, child => {\n if (React.isValidElement(child) && child.type === MenuList) {\n nestedMenuLists.push(child);\n } else {\n labelChildren.push(child);\n }\n });\n\n return (\n <li\n className={className}\n data-testid={testId}\n style={style}\n id={id}\n title={title}\n role={role}\n tabIndex={tabIndex}\n >\n <Component\n ref={ref}\n className={itemClass || undefined}\n // The active item is the current page, the way Pagination marks its\n // page. Before the spread, so an `aria-current` the caller passes\n // decides, `false` and `undefined` included. bestax-migrate converts\n // `is-active` to `active` only beside an `aria-current` of the\n // element's own, and relies on that to render the same markup.\n aria-current={active ? 'page' : undefined}\n {...linkProps}\n // A menu item inside a form must not submit it, and `<button>` defaults\n // to type=\"submit\". A sidebar menu sitting in a form is ordinary. A\n // caller's `submit` or `reset` still wins. Anything else becomes\n // `button`, since HTML reads a value it does not define for a button as\n // submit too; `buttonType` says more.\n //\n // After `linkProps`, reading through it rather than before it: React\n // treats `type={undefined}` as \"remove the attribute\", and a spread\n // carrying the key with no value is how that arrives, so a default\n // spread first would be erased by it. `Dropdown.Item` and `Avatar`\n // apply their button type after the spread too.\n {...(Component === 'button'\n ? { type: buttonType(linkProps.type) }\n : {})}\n >\n {labelChildren}\n </Component>\n {nestedMenuLists}\n </li>\n );\n}) as PolymorphicComponent<MenuItemOwnProps, 'a'>;\n\nMenuItem.displayName = 'MenuItem';\n\n// Attach static subcomponents\nexport const Menu = withSubComponents(\n MenuComponent,\n {\n Label: MenuLabel,\n List: MenuList,\n Item: MenuItem,\n },\n 'Menu'\n);\n\nexport default Menu;\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Menu.js","sources":["../../../src/components/Menu.tsx"],"sourcesContent":["import React, { createContext, forwardRef, useContext } from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { omitAttrs } from '../helpers/anchorAttrs';\nimport { buttonType } from '../helpers/buttonType';\nimport {\n isCustomElement,\n type PolymorphicComponent,\n} from '../helpers/polymorphic';\n\n// Context to track MenuList nesting level\nconst MenuListLevelContext = createContext(0);\n\n/**\n * Props for the Menu component.\n */\nexport interface MenuProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** Menu content (labels, lists, items, etc). */\n children: React.ReactNode;\n}\n\n/**\n * The `Menu` component provides Bulma's vertical navigation menu: a simple, accessible sidebar or section menu for your Bulma React UI.\n *\n * @function\n * @param {MenuProps} props - Props for the Menu component.\n * @returns {JSX.Element} The rendered menu.\n * @see {@link https://bulma.io/documentation/components/menu/ | Bulma Menu documentation}\n */\nconst MenuComponent: React.FC<MenuProps> = ({\n className,\n children,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n // Generate Bulma classes with prefix\n const bulmaClasses = usePrefixedClassNames('menu');\n\n return (\n <aside\n className={classNames(bulmaClasses, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </aside>\n );\n};\n\n/**\n * Props for the MenuLabel component.\n */\nexport interface MenuLabelProps\n extends\n Omit<React.HTMLAttributes<HTMLParagraphElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** Label content. */\n children: React.ReactNode;\n}\n\n/**\n * Bulma Menu label component.\n *\n * @function\n * @param {MenuLabelProps} props - Props for the MenuLabel component.\n * @returns {JSX.Element} The rendered menu label.\n */\nexport const MenuLabel: React.FC<MenuLabelProps> = ({\n className,\n children,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n return (\n <p\n className={classNames(\n usePrefixedClassNames('menu-label'),\n className,\n bulmaHelperClasses\n )}\n {...rest}\n >\n {children}\n </p>\n );\n};\n\n/**\n * Props for the MenuList component.\n * @extraProp {React.AriaRole} [role] - Ignored: every level renders `role=\"list\"` in its place, so a menu or tree role can't go here.\n */\nexport interface MenuListProps\n extends\n Omit<React.HTMLAttributes<HTMLUListElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** List items. */\n children: React.ReactNode;\n}\n\n/**\n * MenuList applies `menu-list` class only at the top level (not for nested lists).\n *\n * Every level renders `role=\"list\"`, which Safari needs: WebKit stops\n * exposing a `<ul>` as a list once its markers are hidden, and Bulma's reset\n * hides them.\n *\n * @function\n * @param {MenuListProps} props - Props for the MenuList component.\n * @returns {JSX.Element} The rendered menu list.\n */\nexport const MenuList: React.FC<MenuListProps> = ({\n className,\n children,\n ...props\n}) => {\n const level = useContext(MenuListLevelContext);\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n const ulClass = classNames(className, bulmaHelperClasses, {\n [usePrefixedClassNames('menu-list')]: level === 0,\n });\n\n // Increment level for nested MenuLists. role=\"list\" restates the <ul>'s\n // own role, for Safari: WebKit stops exposing a list as one once its\n // markers are hidden, as Bulma's reset hides them, unless the role is set\n // explicitly. After the spread, so a caller's role can't take it away.\n return (\n <MenuListLevelContext.Provider value={level + 1}>\n <ul className={ulClass || undefined} {...rest} role=\"list\">\n {children}\n </ul>\n </MenuListLevelContext.Provider>\n );\n};\n\n/**\n * The MenuItem component's own props.\n *\n * A menu item is two elements: a wrapping `<li>` and, inside it, the element\n * `as` names. The props here are the ones the `<li>` consumes; everything else\n * follows `as` onto the inner element.\n */\nexport interface MenuItemOwnProps extends BulmaClassesProps {\n /** Additional CSS classes for the wrapping `<li>`. */\n className?: string;\n /** Item content and optional nested MenuList. */\n children: React.ReactNode;\n /**\n * Marks the item as the current one. It adds Bulma's `is-active` class, and\n * `aria-current=\"page\"` on the element `as` renders, so assistive tech can\n * tell which item is current too. An `aria-current` you pass wins, even\n * `undefined`: use it to say something else (`\"location\"` for a section of\n * one page, say), or `aria-current={false}` to turn it off. A menu should\n * mark one page, so when the section above the current page is `active` too,\n * give it `aria-current=\"location\"`, and give an `active` item that isn't\n * navigation, such as an `as=\"button\"` filter, `aria-current={false}`.\n */\n active?: boolean;\n /** Inline styles for the wrapping `<li>`. */\n style?: React.CSSProperties;\n /** `id` for the wrapping `<li>`. */\n id?: string;\n /** `title` for the wrapping `<li>`. */\n title?: string;\n /** ARIA role for the wrapping `<li>`. */\n role?: React.AriaRole;\n /** Tab index for the wrapping `<li>`. */\n tabIndex?: number;\n /** Test id for the wrapping `<li>`. */\n 'data-testid'?: string;\n}\n\n/**\n * Props for the MenuItem component. Everything the `<li>` does not consume\n * follows `as` onto the inner element: with the default `'a'` that means `href`\n * and the other anchor attributes, and with `as={Link}` it means that\n * component's own props.\n *\n * @extraProp {PolymorphicRef<React.ElementType>} [ref] - Ref forwarded to the inner element `as` renders, not the wrapping `<li>`, typed from `as`: the DOM node for an intrinsic tag, or whatever handle a custom component exposes.\n */\nexport type MenuItemProps<T extends React.ElementType = 'a'> =\n MenuItemOwnProps &\n Omit<React.ComponentPropsWithoutRef<T>, keyof MenuItemOwnProps | 'as'> & {\n /**\n * Custom link component (e.g. `Link` from router).\n *\n * `'button'` renders `type=\"button\"` unless you pass `type=\"submit\"` or `type=\"reset\"`, so\n * an item inside a form does not submit it.\n */\n as?: T;\n };\n\n/**\n * `href` alone — annotated so a typo is a compile error rather than a silent\n * no-op, the way both siblings' sets are.\n */\nconst STRIP_FROM_NON_LINK: Readonly<Record<'href', true>> = { href: true };\n\n/**\n * The shape the implementation destructures. The public contract is the generic\n * `MenuItemProps<T>` above — the body cannot see through `T`.\n */\ntype MenuItemImplProps = MenuItemOwnProps & {\n as?: React.ElementType;\n type?: React.ButtonHTMLAttributes<HTMLButtonElement>['type'];\n};\n\n/**\n * MenuItem supports `as` prop for custom link components, e.g., react-router-dom Link.\n *\n * Give each item an `href`, a router link through `as`, or `as=\"button\"`, so\n * the keyboard can reach it: an `<a>` with no `href` isn't a link, and Tab\n * skips it.\n *\n * @function\n * @param {MenuItemProps} props - Props for the MenuItem component.\n * @param {React.Ref} ref - Forwarded ref to the inner element `as` renders.\n * @returns {JSX.Element} The rendered menu item.\n */\nexport const MenuItem = forwardRef(function MenuItem(\n itemProps: MenuItemProps,\n ref: React.Ref<HTMLElement>\n) {\n const {\n className,\n children,\n active,\n as: Component = 'a',\n 'data-testid': testId,\n style,\n id,\n title,\n role,\n tabIndex,\n ...rest\n } = itemProps as MenuItemImplProps;\n const { bulmaHelperClasses, rest: forwarded } = useBulmaClasses(rest);\n // `href` reaches an anchor or a custom component (which owns its own prop\n // contract), but not another intrinsic tag — `<span href>` is invalid HTML.\n // The same rule Avatar applies through `isLinkLike` and Button through its\n // intrinsic-only strip. Before #641 `href` was an own prop re-applied only\n // for `as=\"a\"`; deriving it from `as` must not quietly widen that.\n const isLinkLike =\n Component === 'a' ||\n typeof Component !== 'string' ||\n isCustomElement(Component);\n // `href` ALONE. `Dropdown.Item` strips every anchor-only attribute except\n // `type`; `Level.Item` strips all of them, plus `rel`.\n //\n // Not because nothing else can arrive: `as` here is an open `React.ElementType`,\n // and a non-fresh spread skips excess-property checking, so a typed caller\n // CAN deliver `target` or `download` and they are forwarded. The set stays at\n // `href` because widening it would be wrong for an open `as` — `referrerPolicy`\n // on `as=\"img\"`, `target` on `as=\"form\"` and `media` on `as=\"source\"` are all\n // legal, and a flat strip would delete them. Getting that right needs a\n // per-attribute-per-element rule, which `bestax-migrate` already models in\n // `LINK_ATTR_ELEMENTS`.\n const linkProps = isLinkLike\n ? forwarded\n : omitAttrs(forwarded, STRIP_FROM_NON_LINK);\n const itemClass = classNames(\n { [usePrefixedClassNames('is-active')]: active },\n bulmaHelperClasses\n );\n\n // Split children into label and nested MenuList(s)\n const labelChildren: React.ReactNode[] = [];\n const nestedMenuLists: React.ReactNode[] = [];\n React.Children.forEach(children, child => {\n if (React.isValidElement(child) && child.type === MenuList) {\n nestedMenuLists.push(child);\n } else {\n labelChildren.push(child);\n }\n });\n\n return (\n <li\n className={className}\n data-testid={testId}\n style={style}\n id={id}\n title={title}\n role={role}\n tabIndex={tabIndex}\n >\n <Component\n ref={ref}\n className={itemClass || undefined}\n // The active item is the current page, the way Pagination marks its\n // page. Before the spread, so an `aria-current` the caller passes\n // decides, `false` and `undefined` included. bestax-migrate converts\n // `is-active` to `active` only beside an `aria-current` of the\n // element's own, and relies on that to render the same markup.\n aria-current={active ? 'page' : undefined}\n {...linkProps}\n // A menu item inside a form must not submit it, and `<button>` defaults\n // to type=\"submit\". A sidebar menu sitting in a form is ordinary. A\n // caller's `submit` or `reset` still wins. Anything else becomes\n // `button`, since HTML reads a value it does not define for a button as\n // submit too; `buttonType` says more.\n //\n // After `linkProps`, reading through it rather than before it: React\n // treats `type={undefined}` as \"remove the attribute\", and a spread\n // carrying the key with no value is how that arrives, so a default\n // spread first would be erased by it. `Dropdown.Item` and `Avatar`\n // apply their button type after the spread too.\n {...(Component === 'button'\n ? { type: buttonType(linkProps.type) }\n : {})}\n >\n {labelChildren}\n </Component>\n {nestedMenuLists}\n </li>\n );\n}) as PolymorphicComponent<MenuItemOwnProps, 'a'>;\n\nMenuItem.displayName = 'MenuItem';\n\n// Attach static subcomponents\nexport const Menu = withSubComponents(\n MenuComponent,\n {\n Label: MenuLabel,\n List: MenuList,\n Item: MenuItem,\n },\n 'Menu'\n);\n\nexport default Menu;\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|
|
@@ -22,7 +22,7 @@ const TimelineItemContext = createContext({
|
|
|
22
22
|
titleAs: 'h2',
|
|
23
23
|
});
|
|
24
24
|
/** Whether a prop was given something to render. */
|
|
25
|
-
const isGiven = (node) => node !== undefined &&
|
|
25
|
+
const isGiven$1 = (node) => node !== undefined &&
|
|
26
26
|
node !== null &&
|
|
27
27
|
typeof node !== 'boolean' &&
|
|
28
28
|
node !== '';
|
|
@@ -55,7 +55,7 @@ const TimelineItem = forwardRef(({ className, title, time, dateTime, icon, color
|
|
|
55
55
|
[`is-${color}`]: isTimelineColor(color),
|
|
56
56
|
});
|
|
57
57
|
const markerClass = usePrefixedClassNames('timeline-marker', {
|
|
58
|
-
'is-icon': isGiven(icon),
|
|
58
|
+
'is-icon': isGiven$1(icon),
|
|
59
59
|
});
|
|
60
60
|
const contentClass = usePrefixedClassNames('timeline-content');
|
|
61
61
|
const titleClass = usePrefixedClassNames('timeline-title');
|
|
@@ -67,8 +67,8 @@ const TimelineItem = forwardRef(({ className, title, time, dateTime, icon, color
|
|
|
67
67
|
const words = label ? (jsx("span", { style: visuallyHidden, children: ` ${label}` })) : null;
|
|
68
68
|
return (jsxs("li", { ref: ref, className: classNames(itemClass, bulmaHelperClasses, className), ...rest, ...(state && {
|
|
69
69
|
'aria-current': state === 'active' ? 'step' : undefined,
|
|
70
|
-
}), children: [jsx("div", { className: markerClass, "aria-hidden": "true", children: isGiven(icon) &&
|
|
71
|
-
(typeof icon === 'string' ? jsx(Icon, { name: icon }) : icon) }), jsxs("div", { className: contentClass, children: [isGiven(title) ? (jsxs(TitleTag, { className: titleClass, children: [title, words] })) : (words), isGiven(time) &&
|
|
70
|
+
}), children: [jsx("div", { className: markerClass, "aria-hidden": "true", children: isGiven$1(icon) &&
|
|
71
|
+
(typeof icon === 'string' ? jsx(Icon, { name: icon }) : icon) }), jsxs("div", { className: contentClass, children: [isGiven$1(title) ? (jsxs(TitleTag, { className: titleClass, children: [title, words] })) : (words), isGiven$1(time) &&
|
|
72
72
|
(dateTime ? (jsx("time", { className: timeClass, dateTime: dateTime, children: time })) : (jsx("span", { className: timeClass, children: time }))), children] })] }));
|
|
73
73
|
});
|
|
74
74
|
TimelineItem.displayName = 'Timeline.Item';
|
|
@@ -142,7 +142,7 @@ const TimelineRoot = forwardRef(({ className, children, active, pending, align =
|
|
|
142
142
|
// Each child gets its state through context rather than a clone, so an
|
|
143
143
|
// app's own component around a Timeline.Item still gets it. Fragments
|
|
144
144
|
// and arrays are opened up first, so each item inside one counts.
|
|
145
|
-
return (jsxs("ol", { ref: ref, className: classNames(rootClass, bulmaHelperClasses, className), ...rest, role: "list", children: [flattenChildren(children).map((child, index) => (jsx(TimelineItemContext.Provider, { value: contextFor(stateAt(index)), children: child }, React.isValidElement(child) ? child.key : index))), isGiven(pending) && (jsx(TimelineItemContext.Provider, { value: contextFor('pending'), children: jsx(TimelineItem, { title: pending }) }))] }));
|
|
145
|
+
return (jsxs("ol", { ref: ref, className: classNames(rootClass, bulmaHelperClasses, className), ...rest, role: "list", children: [flattenChildren(children).map((child, index) => (jsx(TimelineItemContext.Provider, { value: contextFor(stateAt(index)), children: child }, React.isValidElement(child) ? child.key : index))), isGiven$1(pending) && (jsx(TimelineItemContext.Provider, { value: contextFor('pending'), children: jsx(TimelineItem, { title: pending }) }))] }));
|
|
146
146
|
});
|
|
147
147
|
const Timeline = withSubComponents(TimelineRoot, { Item: TimelineItem }, 'Timeline');
|
|
148
148
|
|
|
@@ -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":";;;;;;;;;;AAYA,MAAM,mBAAmB,GAAG;IAC1B,SAAS;IACT,MAAM;IACN,MAAM;IACN,SAAS;IACT,SAAS;IACT,QAAQ;CACA;AAIV,MAAM,0BAA0B,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAU;AAoB/E;AACA,MAAM,mBAAmB,GAAG,aAAa,CAA2B;AAClE,IAAA,OAAO,EAAE,IAAI;AACd,CAAA,CAAC;AAEF;AACA,MAAMA,SAAO,GAAG,CAAC,IAAqB,KACpC,IAAI,KAAK,SAAS;AAClB,IAAA,IAAI,KAAK,IAAI;IACb,OAAO,IAAI,KAAK,SAAS;IACzB,IAAI,KAAK,EAAE;AAEb,MAAM,eAAe,GAAG,CAAC,KAAc,KACrC,CAAC,CAAC,KAAK,IAAK,mBAAyC,CAAC,QAAQ,CAAC,KAAK,CAAC;AA6GvE;;;;;;;;;;;;;;;AAeG;AACI,MAAM,YAAY,GAAG,UAAU,CACpC,CACE,EACE,SAAS,EACT,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,SAAS,EACT,OAAO,EACP,QAAQ,EACR,GAAG,KAAK,EACT,EACD,GAAG,KACD;AACF,IAAA,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,UAAU,CAAC,mBAAmB,CAAC;AAC3E,IAAA,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC;AACnD,QAAA,KAAK,EAAE,SAAS;AAChB,QAAA,eAAe,EAAE,OAAO;AACxB,QAAA,GAAG,KAAK;AACT,KAAA,CAAC;AACF,IAAA,MAAM,SAAS,GAAG,qBAAqB,CAAC,eAAe,EAAE;AACvD,QAAA,CAAC,MAAM,KAAK,CAAA,CAAE,GAAG,CAAC,CAAC,KAAK;QACxB,CAAC,CAAA,GAAA,EAAM,KAAK,CAAA,CAAE,GAAG,eAAe,CAAC,KAAK,CAAC;AACxC,KAAA,CAAC;AACF,IAAA,MAAM,WAAW,GAAG,qBAAqB,CAAC,iBAAiB,EAAE;AAC3D,QAAA,SAAS,EAAEA,SAAO,CAAC,IAAI,CAAC;AACzB,KAAA,CAAC;AACF,IAAA,MAAM,YAAY,GAAG,qBAAqB,CAAC,kBAAkB,CAAC;AAC9D,IAAA,MAAM,UAAU,GAAG,qBAAqB,CAAC,gBAAgB,CAAC;AAC1D,IAAA,MAAM,SAAS,GAAG,qBAAqB,CAAC,eAAe,CAAC;;;;;IAMxD,MAAM,KAAK,GAAG,KAAK,IACjBC,cAAM,KAAK,EAAE,cAAc,EAAA,QAAA,EAAG,CAAA,CAAA,EAAI,KAAK,CAAA,CAAE,EAAA,CAAQ,IAC/C,IAAI;IAER,QACEC,aACE,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,UAAU,CAAC,SAAS,EAAE,kBAAkB,EAAE,SAAS,CAAC,KAC3D,IAAI,EAAA,IAEH,KAAK,IAAI;YACZ,cAAc,EAAE,KAAK,KAAK,QAAQ,GAAG,MAAM,GAAG,SAAS;SACxD,CAAC,EAAA,QAAA,EAAA,CAIFD,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,WAAW,EAAA,aAAA,EAAc,MAAM,EAAA,QAAA,EAC5CD,SAAO,CAAC,IAAI,CAAC;qBACX,OAAO,IAAI,KAAK,QAAQ,GAAGC,GAAA,CAAC,IAAI,EAAA,EAAC,IAAI,EAAE,IAAI,GAAI,GAAG,IAAI,CAAC,EAAA,CACtD,EACNC,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,YAAY,EAAA,QAAA,EAAA,CACzBF,SAAO,CAAC,KAAK,CAAC,IACbE,IAAA,CAAC,QAAQ,EAAA,EAAC,SAAS,EAAE,UAAU,EAAA,QAAA,EAAA,CAC5B,KAAK,EACL,KAAK,CAAA,EAAA,CACG,KAEX,KAAK,CACN,EACAF,SAAO,CAAC,IAAI,CAAC;AACZ,yBAAC,QAAQ,IACPC,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAA,QAAA,EAC3C,IAAI,EAAA,CACA,KAEPA,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAE,SAAS,EAAA,QAAA,EAAG,IAAI,EAAA,CAAQ,CAC1C,CAAC,EACH,QAAQ,CAAA,EAAA,CACL,CAAA,EAAA,CACH;AAET,CAAC;AAGH,YAAY,CAAC,WAAW,GAAG,eAAe;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCG;AACH,MAAM,YAAY,GAAG,UAAU,CAC7B,CACE,EACE,SAAS,EACT,QAAQ,EACR,MAAM,EACN,OAAO,EACP,KAAK,GAAG,OAAO,EACf,OAAO,GAAG,IAAI,EACd,KAAK,EACL,cAAc,GAAG,WAAW,EAC5B,aAAa,GAAG,UAAU,EAC1B,YAAY,GAAG,aAAa,EAC5B,SAAS,EACT,OAAO,EACP,GAAG,KAAK,EACT,EACD,GAAG,KACD;AACF,IAAA,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC;AACnD,QAAA,KAAK,EAAE,SAAS;AAChB,QAAA,eAAe,EAAE,OAAO;AACxB,QAAA,GAAG,KAAK;AACT,KAAA,CAAC;AACF,IAAA,MAAM,SAAS,GAAG,qBAAqB,CAAC,UAAU,EAAE;QAClD,QAAQ,EAAE,KAAK,KAAK,KAAK;QACzB,cAAc,EAAE,KAAK,KAAK,WAAW;QACrC,CAAC,CAAA,GAAA,EAAM,KAAK,CAAA,CAAE,GAAG,eAAe,CAAC,KAAK,CAAC;AACxC,KAAA,CAAC;AACF,IAAA,MAAM,KAAK,GAAG,0BAA0B,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,IAAI;AAC3E,IAAA,MAAM,MAAM,GAA8C;AACxD,QAAA,SAAS,EAAE,cAAc;AACzB,QAAA,MAAM,EAAE,SAAS;AACjB,QAAA,QAAQ,EAAE,aAAa;AACvB,QAAA,OAAO,EAAE,YAAY;KACtB;AACD,IAAA,MAAM,UAAU,GAAG,CAAC,KAAqB,MAAM;AAC7C,QAAA,OAAO,EAAE,KAAK;QACd,KAAK;AACL,QAAA,KAAK,EAAE,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC;AAC9B,KAAA,CAAC;;AAEF,IAAA,MAAM,OAAO,GAAG,OAAO,MAAM,KAAK,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS;AAC3E,IAAA,MAAM,OAAO,GAAG,CAAC,KAAa,KAA+B;QAC3D,IAAI,OAAO,KAAK,SAAS;AAAE,YAAA,OAAO,SAAS;QAC3C,IAAI,KAAK,KAAK,OAAO;AAAE,YAAA,OAAO,QAAQ;QACtC,OAAO,KAAK,GAAG,OAAO,GAAG,WAAW,GAAG,UAAU;AACnD,IAAA,CAAC;;;;IAKD,QACEC,aACE,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,UAAU,CAAC,SAAS,EAAE,kBAAkB,EAAE,SAAS,CAAC,EAAA,GAC3D,IAAI,EACR,IAAI,EAAC,MAAM,EAAA,QAAA,EAAA,CAEV,eAAe,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,MAC1CD,IAAC,mBAAmB,CAAC,QAAQ,EAAA,EAE3B,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,YAEhC,KAAK,EAAA,EAHD,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,KAAK,CAIvB,CAChC,CAAC,EACDD,SAAO,CAAC,OAAO,CAAC,KACfC,GAAA,CAAC,mBAAmB,CAAC,QAAQ,IAAC,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,YACxDA,GAAA,CAAC,YAAY,EAAA,EAAC,KAAK,EAAE,OAAO,GAAI,EAAA,CACH,CAChC,CAAA,EAAA,CACE;AAET,CAAC,CACF;AAEM,MAAM,QAAQ,GAAG,iBAAiB,CACvC,YAAY,EACZ,EAAE,IAAI,EAAE,YAAY,EAAE,EACtB,UAAU;;"}
|
|
@@ -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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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
import { useRef, useEffect, useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
// INTERNAL: deliberately not exported from src/index.ts.
|
|
5
|
+
/** How long typed characters keep adding up to one search, in milliseconds. */
|
|
6
|
+
const TYPEAHEAD_TIMEOUT = 500;
|
|
7
|
+
/**
|
|
8
|
+
* The text typing finds an element by: what it says, without what's hidden
|
|
9
|
+
* from assistive technology, such as an icon whose glyph is a ligature's name.
|
|
10
|
+
*/
|
|
11
|
+
function typeaheadText(element) {
|
|
12
|
+
let text = '';
|
|
13
|
+
const read = (node) => {
|
|
14
|
+
node.childNodes.forEach(child => {
|
|
15
|
+
if (child.nodeType === 3)
|
|
16
|
+
text += child.textContent;
|
|
17
|
+
else if (child.nodeType === 1 &&
|
|
18
|
+
child.getAttribute('aria-hidden') !== 'true') {
|
|
19
|
+
read(child);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
read(element);
|
|
24
|
+
return text.trim().toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The item typing `search` moves to, or none. A single character looks from
|
|
28
|
+
* the item after the focused one, so typing the same letter again cycles
|
|
29
|
+
* through the items that start with it, and so does a run of one letter
|
|
30
|
+
* ("dd"). A longer search looks from the focused item, so typing more of
|
|
31
|
+
* its own text keeps it.
|
|
32
|
+
*
|
|
33
|
+
* @param items - The items typing can reach, in the order they're shown.
|
|
34
|
+
* @param search - What's been typed, lowercased.
|
|
35
|
+
* @param current - The focused item's index in `items`, or -1 for none.
|
|
36
|
+
* @param textOf - The text an item is found by. By default it's the item's
|
|
37
|
+
* own, read by `typeaheadText`.
|
|
38
|
+
*/
|
|
39
|
+
function typeaheadMatch(items, search, current, textOf = typeaheadText) {
|
|
40
|
+
const repeated = Array.from(search).every(char => char === search[0]);
|
|
41
|
+
const wanted = repeated ? search[0] : search;
|
|
42
|
+
const start = current < 0 ? 0 : current + (wanted.length === 1 ? 1 : 0);
|
|
43
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
44
|
+
const item = items[(start + i) % items.length];
|
|
45
|
+
if (textOf(item).startsWith(wanted))
|
|
46
|
+
return item;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Keeps what has been typed for a type-ahead search, and forgets it once
|
|
52
|
+
* `TYPEAHEAD_TIMEOUT` passes with nothing typed. Pair it with
|
|
53
|
+
* `typeaheadMatch` to find the item the search names.
|
|
54
|
+
*
|
|
55
|
+
* @function useTypeahead
|
|
56
|
+
* @returns A way to add a character to the search, and a way to forget it.
|
|
57
|
+
*/
|
|
58
|
+
function useTypeahead() {
|
|
59
|
+
const typedRef = useRef({ text: '' });
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const typed = typedRef.current;
|
|
62
|
+
return () => clearTimeout(typed.timer);
|
|
63
|
+
}, []);
|
|
64
|
+
return useMemo(() => {
|
|
65
|
+
const reset = () => {
|
|
66
|
+
const typed = typedRef.current;
|
|
67
|
+
clearTimeout(typed.timer);
|
|
68
|
+
typed.text = '';
|
|
69
|
+
};
|
|
70
|
+
const type = (char) => {
|
|
71
|
+
const typed = typedRef.current;
|
|
72
|
+
typed.text += char.toLowerCase();
|
|
73
|
+
clearTimeout(typed.timer);
|
|
74
|
+
typed.timer = setTimeout(reset, TYPEAHEAD_TIMEOUT);
|
|
75
|
+
return typed.text;
|
|
76
|
+
};
|
|
77
|
+
return { type, reset };
|
|
78
|
+
}, []);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { TYPEAHEAD_TIMEOUT, typeaheadMatch, typeaheadText, useTypeahead };
|
|
82
|
+
//# sourceMappingURL=typeahead.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeahead.js","sources":["../../../src/helpers/typeahead.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useEffect, useMemo, useRef } from 'react';\n\n/** How long typed characters keep adding up to one search, in milliseconds. */\nexport const TYPEAHEAD_TIMEOUT = 500;\n\n/**\n * The text typing finds an element by: what it says, without what's hidden\n * from assistive technology, such as an icon whose glyph is a ligature's name.\n */\nexport function typeaheadText(element: Element): string {\n let text = '';\n const read = (node: Node) => {\n node.childNodes.forEach(child => {\n if (child.nodeType === 3) text += child.textContent;\n else if (\n child.nodeType === 1 &&\n (child as Element).getAttribute('aria-hidden') !== 'true'\n ) {\n read(child);\n }\n });\n };\n read(element);\n return text.trim().toLowerCase();\n}\n\n/**\n * The item typing `search` moves to, or none. A single character looks from\n * the item after the focused one, so typing the same letter again cycles\n * through the items that start with it, and so does a run of one letter\n * (\"dd\"). A longer search looks from the focused item, so typing more of\n * its own text keeps it.\n *\n * @param items - The items typing can reach, in the order they're shown.\n * @param search - What's been typed, lowercased.\n * @param current - The focused item's index in `items`, or -1 for none.\n * @param textOf - The text an item is found by. By default it's the item's\n * own, read by `typeaheadText`.\n */\nexport function typeaheadMatch<T extends Element>(\n items: T[],\n search: string,\n current: number,\n textOf: (item: T) => string = typeaheadText\n): T | undefined {\n const repeated = Array.from(search).every(char => char === search[0]);\n const wanted = repeated ? search[0] : search;\n const start = current < 0 ? 0 : current + (wanted.length === 1 ? 1 : 0);\n for (let i = 0; i < items.length; i += 1) {\n const item = items[(start + i) % items.length];\n if (textOf(item).startsWith(wanted)) return item;\n }\n return undefined;\n}\n\n/** What `useTypeahead` returns. */\nexport interface Typeahead {\n /**\n * Adds a typed character to the search and returns the whole search,\n * lowercased. Characters typed within `TYPEAHEAD_TIMEOUT` of each other\n * add up, and after that the next one starts a new search.\n */\n type: (char: string) => string;\n /** Forgets what has been typed, so the next character starts a new search. */\n reset: () => void;\n}\n\n/**\n * Keeps what has been typed for a type-ahead search, and forgets it once\n * `TYPEAHEAD_TIMEOUT` passes with nothing typed. Pair it with\n * `typeaheadMatch` to find the item the search names.\n *\n * @function useTypeahead\n * @returns A way to add a character to the search, and a way to forget it.\n */\nexport function useTypeahead(): Typeahead {\n const typedRef = useRef<{\n text: string;\n timer?: ReturnType<typeof setTimeout>;\n }>({ text: '' });\n\n useEffect(() => {\n const typed = typedRef.current;\n return () => clearTimeout(typed.timer);\n }, []);\n\n return useMemo<Typeahead>(() => {\n const reset = () => {\n const typed = typedRef.current;\n clearTimeout(typed.timer);\n typed.text = '';\n };\n const type = (char: string) => {\n const typed = typedRef.current;\n typed.text += char.toLowerCase();\n clearTimeout(typed.timer);\n typed.timer = setTimeout(reset, TYPEAHEAD_TIMEOUT);\n return typed.text;\n };\n return { type, reset };\n }, []);\n}\n"],"names":[],"mappings":";;;AAAA;AAGA;AACO,MAAM,iBAAiB,GAAG;AAEjC;;;AAGG;AACG,SAAU,aAAa,CAAC,OAAgB,EAAA;IAC5C,IAAI,IAAI,GAAG,EAAE;AACb,IAAA,MAAM,IAAI,GAAG,CAAC,IAAU,KAAI;AAC1B,QAAA,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,IAAG;AAC9B,YAAA,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC;AAAE,gBAAA,IAAI,IAAI,KAAK,CAAC,WAAW;AAC9C,iBAAA,IACH,KAAK,CAAC,QAAQ,KAAK,CAAC;gBACnB,KAAiB,CAAC,YAAY,CAAC,aAAa,CAAC,KAAK,MAAM,EACzD;gBACA,IAAI,CAAC,KAAK,CAAC;YACb;AACF,QAAA,CAAC,CAAC;AACJ,IAAA,CAAC;IACD,IAAI,CAAC,OAAO,CAAC;AACb,IAAA,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;AAClC;AAEA;;;;;;;;;;;;AAYG;AACG,SAAU,cAAc,CAC5B,KAAU,EACV,MAAc,EACd,OAAe,EACf,MAAA,GAA8B,aAAa,EAAA;IAE3C,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC;AACrE,IAAA,MAAM,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM;AAC5C,IAAA,MAAM,KAAK,GAAG,OAAO,GAAG,CAAC,GAAG,CAAC,GAAG,OAAO,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACvE,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE;AACxC,QAAA,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC;QAC9C,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;AAAE,YAAA,OAAO,IAAI;IAClD;AACA,IAAA,OAAO,SAAS;AAClB;AAcA;;;;;;;AAOG;SACa,YAAY,GAAA;IAC1B,MAAM,QAAQ,GAAG,MAAM,CAGpB,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IAEhB,SAAS,CAAC,MAAK;AACb,QAAA,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO;QAC9B,OAAO,MAAM,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC;IACxC,CAAC,EAAE,EAAE,CAAC;IAEN,OAAO,OAAO,CAAY,MAAK;QAC7B,MAAM,KAAK,GAAG,MAAK;AACjB,YAAA,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO;AAC9B,YAAA,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC;AACzB,YAAA,KAAK,CAAC,IAAI,GAAG,EAAE;AACjB,QAAA,CAAC;AACD,QAAA,MAAM,IAAI,GAAG,CAAC,IAAY,KAAI;AAC5B,YAAA,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO;AAC9B,YAAA,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,EAAE;AAChC,YAAA,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC;YACzB,KAAK,CAAC,KAAK,GAAG,UAAU,CAAC,KAAK,EAAE,iBAAiB,CAAC;YAClD,OAAO,KAAK,CAAC,IAAI;AACnB,QAAA,CAAC;AACD,QAAA,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE;IACxB,CAAC,EAAE,EAAE,CAAC;AACR;;"}
|