bestax-mcp 1.16.3 → 1.16.4

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.
@@ -3615,25 +3615,69 @@
3615
3615
  "is-64x64": { "writes": [{ "prop": "size", "value": "64x64" }] },
3616
3616
  "is-96x96": { "writes": [{ "prop": "size", "value": "96x96" }] },
3617
3617
  "is-128x128": { "writes": [{ "prop": "size", "value": "128x128" }] },
3618
- "is-square": { "writes": [{ "prop": "size", "value": "square" }] }
3619
- },
3620
- "omits": {
3621
- "is-1by1": "`size` renders `has-ratio` beside it as well",
3622
- "is-5by4": "`size` renders `has-ratio` beside it as well",
3623
- "is-4by3": "`size` renders `has-ratio` beside it as well",
3624
- "is-3by2": "`size` renders `has-ratio` beside it as well",
3625
- "is-5by3": "`size` renders `has-ratio` beside it as well",
3626
- "is-16by9": "`size` renders `has-ratio` beside it as well",
3627
- "is-2by1": "`size` renders `has-ratio` beside it as well",
3628
- "is-3by1": "`size` renders `has-ratio` beside it as well",
3629
- "is-4by5": "`size` renders `has-ratio` beside it as well",
3630
- "is-3by4": "`size` renders `has-ratio` beside it as well",
3631
- "is-2by3": "`size` renders `has-ratio` beside it as well",
3632
- "is-3by5": "`size` renders `has-ratio` beside it as well",
3633
- "is-9by16": "`size` renders `has-ratio` beside it as well",
3634
- "is-1by2": "`size` renders `has-ratio` beside it as well",
3635
- "is-1by3": "`size` renders `has-ratio` beside it as well"
3618
+ "is-square": { "writes": [{ "prop": "size", "value": "square" }] },
3619
+ "is-1by1": {
3620
+ "writes": [{ "prop": "size", "value": "1by1" }],
3621
+ "rendersTag": "figure"
3622
+ },
3623
+ "is-5by4": {
3624
+ "writes": [{ "prop": "size", "value": "5by4" }],
3625
+ "rendersTag": "figure"
3626
+ },
3627
+ "is-4by3": {
3628
+ "writes": [{ "prop": "size", "value": "4by3" }],
3629
+ "rendersTag": "figure"
3630
+ },
3631
+ "is-3by2": {
3632
+ "writes": [{ "prop": "size", "value": "3by2" }],
3633
+ "rendersTag": "figure"
3634
+ },
3635
+ "is-5by3": {
3636
+ "writes": [{ "prop": "size", "value": "5by3" }],
3637
+ "rendersTag": "figure"
3638
+ },
3639
+ "is-16by9": {
3640
+ "writes": [{ "prop": "size", "value": "16by9" }],
3641
+ "rendersTag": "figure"
3642
+ },
3643
+ "is-2by1": {
3644
+ "writes": [{ "prop": "size", "value": "2by1" }],
3645
+ "rendersTag": "figure"
3646
+ },
3647
+ "is-3by1": {
3648
+ "writes": [{ "prop": "size", "value": "3by1" }],
3649
+ "rendersTag": "figure"
3650
+ },
3651
+ "is-4by5": {
3652
+ "writes": [{ "prop": "size", "value": "4by5" }],
3653
+ "rendersTag": "figure"
3654
+ },
3655
+ "is-3by4": {
3656
+ "writes": [{ "prop": "size", "value": "3by4" }],
3657
+ "rendersTag": "figure"
3658
+ },
3659
+ "is-2by3": {
3660
+ "writes": [{ "prop": "size", "value": "2by3" }],
3661
+ "rendersTag": "figure"
3662
+ },
3663
+ "is-3by5": {
3664
+ "writes": [{ "prop": "size", "value": "3by5" }],
3665
+ "rendersTag": "figure"
3666
+ },
3667
+ "is-9by16": {
3668
+ "writes": [{ "prop": "size", "value": "9by16" }],
3669
+ "rendersTag": "figure"
3670
+ },
3671
+ "is-1by2": {
3672
+ "writes": [{ "prop": "size", "value": "1by2" }],
3673
+ "rendersTag": "figure"
3674
+ },
3675
+ "is-1by3": {
3676
+ "writes": [{ "prop": "size", "value": "1by3" }],
3677
+ "rendersTag": "figure"
3678
+ }
3636
3679
  },
3680
+ "omits": {},
3637
3681
  "wrapsChildren": null,
3638
3682
  "folds": null,
3639
3683
  "absorbs": {
package/data/catalog.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 2,
3
3
  "generatedFrom": {
4
4
  "package": "@allxsmith/bestax-bulma",
5
- "version": "5.40.0"
5
+ "version": "5.41.0"
6
6
  },
7
7
  "docsBase": "https://bestax.io/docs",
8
8
  "categories": [
@@ -738,8 +738,8 @@
738
738
  "slug": "components/menu",
739
739
  "import": "import { Menu } from '@allxsmith/bestax-bulma';",
740
740
  "compound": true,
741
- "propCount": 16,
742
- "exampleCount": 6
741
+ "propCount": 22,
742
+ "exampleCount": 8
743
743
  },
744
744
  {
745
745
  "name": "Message",
@@ -2080,6 +2080,10 @@
2080
2080
  "--bulma-menu-list-link-padding": ["Menu"],
2081
2081
  "--bulma-menu-nested-list-margin": ["Menu"],
2082
2082
  "--bulma-menu-nested-list-padding-left": ["Menu"],
2083
+ "--bulma-menu-section-chevron-color": ["Menu"],
2084
+ "--bulma-menu-section-chevron-duration": ["Menu"],
2085
+ "--bulma-menu-section-chevron-size": ["Menu"],
2086
+ "--bulma-menu-section-gap": ["Menu"],
2083
2087
  "--bulma-message-background-l": ["Message"],
2084
2088
  "--bulma-message-body-border-width": ["Message"],
2085
2089
  "--bulma-message-body-color": ["Message"],
@@ -2801,6 +2805,11 @@
2801
2805
  "path": "Menu.List",
2802
2806
  "purpose": "MenuList applies `menu-list` class only at the top level (not for nested lists)."
2803
2807
  },
2808
+ "MenuSection": {
2809
+ "component": "Menu",
2810
+ "path": "Menu.Section",
2811
+ "purpose": "A section of the menu that opens and closes: its heading is a button drawn like `Menu.Label`, and its `Menu.List` of links shows while it's open."
2812
+ },
2804
2813
  "NavbarBrand": {
2805
2814
  "component": "Navbar",
2806
2815
  "path": "Navbar.Brand",
@@ -97,7 +97,7 @@
97
97
  "name": "size",
98
98
  "type": "'16x16' | '24x24' | '32x32' | '48x48' | '64x64' | '96x96' | '128x128' | 'square' | '1by1' | '5by4' | '4by3' | '3by2' | '5by3' | '16by9' | '2by1' | '3by1' | '4by5' | '3by4' | '2by3' | '3by5' | '9by16' | '1by2' | '1by3'",
99
99
  "default": null,
100
- "description": "Fixed size or aspect ratio modifier for the image container.",
100
+ "description": "Fixed size or aspect ratio modifier for the image container. A ratio (`16by9` and the rest) renders only its `is-<ratio>` class there. Bulma stretches an `<img>` inside to fill the ratio, but any other child, such as an `iframe`, fills it only with `className=\"has-ratio\"` of its own.",
101
101
  "required": false,
102
102
  "inherited": false,
103
103
  "deprecated": false
@@ -21,17 +21,25 @@
21
21
  "title": "Deeply Nested Menu",
22
22
  "code": "<Menu>\n <Menu.Label>Levels</Menu.Label>\n <Menu.List>\n <Menu.Item href=\"#level-1\">\n Level 1\n <Menu.List>\n <Menu.Item href=\"#level-2\">\n Level 2\n <Menu.List>\n <Menu.Item href=\"#level-3\">Level 3</Menu.Item>\n </Menu.List>\n </Menu.Item>\n </Menu.List>\n </Menu.Item>\n </Menu.List>\n</Menu>"
23
23
  },
24
+ {
25
+ "title": "Sections that open and close",
26
+ "code": "<nav aria-label=\"Documentation\">\n <Menu>\n <Menu.Section label=\"Getting started\">\n <Menu.List>\n <Menu.Item href=\"#installation\">Installation</Menu.Item>\n <Menu.Item href=\"#quick-start\">Quick start</Menu.Item>\n </Menu.List>\n </Menu.Section>\n <Menu.Section label=\"Components\" defaultOpen>\n <Menu.List>\n <Menu.Item href=\"#button\">Button</Menu.Item>\n <Menu.Item href=\"#card\" active>\n Card\n </Menu.Item>\n <Menu.Item href=\"#modal\">Modal</Menu.Item>\n </Menu.List>\n </Menu.Section>\n <Menu.Section label=\"Form\">\n <Menu.List>\n <Menu.Item href=\"#input\">Input</Menu.Item>\n <Menu.Item href=\"#select\">Select</Menu.Item>\n </Menu.List>\n </Menu.Section>\n </Menu>\n</nav>"
27
+ },
28
+ {
29
+ "title": "Controlled sections",
30
+ "code": "function example() {\n const groups = {\n Components: ['Button', 'Card', 'Modal'],\n Form: ['Input', 'Select'],\n };\n const [open, setOpen] = React.useState(['Components']);\n\n return (\n <>\n <Buttons mb=\"3\">\n <Button size=\"small\" onClick={() => setOpen(Object.keys(groups))}>\n Open all\n </Button>\n <Button size=\"small\" onClick={() => setOpen([])}>\n Close all\n </Button>\n </Buttons>\n <Menu>\n {Object.entries(groups).map(([label, links]) => (\n <Menu.Section\n key={label}\n label={label}\n open={open.includes(label)}\n onOpenChange={isOpen =>\n setOpen(current =>\n isOpen ? [...current, label] : current.filter(l => l !== label)\n )\n }\n >\n <Menu.List>\n {links.map(link => (\n <Menu.Item key={link} href={`#${link.toLowerCase()}`}>\n {link}\n </Menu.Item>\n ))}\n </Menu.List>\n </Menu.Section>\n ))}\n </Menu>\n </>\n );\n}"
31
+ },
24
32
  {
25
33
  "title": "Forwarded ref",
26
34
  "code": "function example() {\n const itemRef = React.useRef<HTMLAnchorElement>(null);\n\n return (\n <>\n <Button mb=\"3\" onClick={() => itemRef.current?.focus()}>\n Focus Customers\n </Button>\n <Menu>\n <Menu.Label>General</Menu.Label>\n <Menu.List>\n <Menu.Item href=\"#dashboard\">Dashboard</Menu.Item>\n <Menu.Item href=\"#customers\" ref={itemRef}>\n Customers\n </Menu.Item>\n </Menu.List>\n </Menu>\n </>\n );\n}"
27
35
  },
28
36
  {
29
37
  "title": "Compound (dot-notation) usage",
30
- "code": "<Menu>\n <Menu.Label>General</Menu.Label>\n <Menu.List>\n <Menu.Item active href=\"#dashboard\">\n Dashboard\n </Menu.Item>\n <Menu.Item href=\"#customers\">Customers</Menu.Item>\n </Menu.List>\n</Menu>"
38
+ "code": "<Menu>\n <Menu.Label>General</Menu.Label>\n <Menu.List>\n <Menu.Item active href=\"#dashboard\">\n Dashboard\n </Menu.Item>\n <Menu.Item href=\"#customers\">Customers</Menu.Item>\n </Menu.List>\n <Menu.Section label=\"Transactions\">\n <Menu.List>\n <Menu.Item href=\"#payments\">Payments</Menu.Item>\n <Menu.Item href=\"#transfers\">Transfers</Menu.Item>\n </Menu.List>\n </Menu.Section>\n</Menu>"
31
39
  }
32
40
  ],
33
- "accessibility": "- The root `Menu` renders as `<aside class=\"menu\">`. A menu that's navigation is better inside a\n `<nav>` with an `aria-label`, which gives assistive tech a navigation landmark to jump to.\n- Every `Menu.List` has `role=\"list\"`, nested ones included. That's the role a `<ul>` already has,\n set again for Safari, whose WebKit stops exposing a `<ul>` as a list once its markers are hidden,\n as Bulma's reset hides them, unless the role is written out.\n- `Menu.Item active` sets `aria-current=\"page\"` on the element `as` renders, the link by\n default, so the current item isn't only a color. An `aria-current` you pass wins: a menu of\n sections on one page can say `\"location\"`, and `aria-current={false}` turns it off.\n- Use semantic nesting (`Menu.List` inside `Menu.Item`) for submenus.\n- Give each item an `href`, a router link through `as`, or `as=\"button\"`, so the keyboard can\n reach it.\n- `Menu.Item as=\"button\"` defaults to `type=\"button\"`, so a menu item inside a form doesn't\n submit it. Pass `type=\"submit\"` or `type=\"reset\"` and yours is used instead; any other value\n renders `type=\"button\"`.\n- In forced colors (Windows' contrast themes), the browser replaces Bulma's fill and text color\n for the active item along with every other color, so bestax's stylesheet marks an\n `is-active` or `is-selected` item with a bar in the system's highlight color along its start\n edge there instead. The item's text, and an icon or a tag inside it, keep the colors your\n contrast theme gives them. That rule ships in `bestax.css` and `extras.css`, not in Bulma's\n own stylesheet.\n\n:::note\nFor router integrations, pass the correct `as` and `to`/`href` props to `Menu.Item`.\n:::",
34
- "related": ["Panel", "Sidebar", "TreeView", "useBulmaClasses"],
41
+ "accessibility": "- The root `Menu` renders as `<aside class=\"menu\">`. A menu that's navigation is better inside a\n `<nav>` with an `aria-label`, which gives assistive tech a navigation landmark to jump to.\n- Every `Menu.List` has `role=\"list\"`, nested ones included. That's the role a `<ul>` already has,\n set again for Safari, whose WebKit stops exposing a `<ul>` as a list once its markers are hidden,\n as Bulma's reset hides them, unless the role is written out.\n- `Menu.Item active` sets `aria-current=\"page\"` on the element `as` renders, the link by\n default, so the current item isn't only a color. An `aria-current` you pass wins: a menu of\n sections on one page can say `\"location\"`, and `aria-current={false}` turns it off.\n- Use semantic nesting (`Menu.List` inside `Menu.Item`) for submenus.\n- Give each item an `href`, a router link through `as`, or `as=\"button\"`, so the keyboard can\n reach it.\n- `Menu.Item as=\"button\"` defaults to `type=\"button\"`, so a menu item inside a form doesn't\n submit it. Pass `type=\"submit\"` or `type=\"reset\"` and yours is used instead; any other value\n renders `type=\"button\"`.\n- In forced colors (Windows' contrast themes), the browser replaces Bulma's fill and text color\n for the active item along with every other color, so bestax's stylesheet marks an\n `is-active` or `is-selected` item with a bar in the system's highlight color along its start\n edge there instead. The item's text, and an icon or a tag inside it, keep the colors your\n contrast theme gives them. That rule ships in `bestax.css` and `extras.css`, not in Bulma's\n own stylesheet.\n\nFor `Menu.Section`, a section that opens and closes:\n\n- It follows APG's\n [Disclosure Navigation Menu example](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation/).\n The heading is a `<button type=\"button\">` with `aria-expanded`, and `aria-controls` naming the\n wrapper around its list. Enter and Space open and close it, as they do any button, and it\n doesn't submit a form it sits in.\n- The links under it are ordinary links. Each is its own tab stop, and there's no roving focus or\n arrow-key handling, which is a tree's model rather than navigation's. They keep their `href`,\n so a link still opens in a new tab, and `active` still sets `aria-current=\"page\"`.\n- A closed section's list is `hidden` and `inert`, so it isn't drawn, Tab skips its links, and\n assistive tech and find-in-page don't reach them. `inert` keeps the links out of reach even if a\n stylesheet sets `display` on the list and shows it. The list stays in the page, so\n `aria-controls` always names an element that exists, and a section that starts closed is\n already closed in the server's HTML, before any script runs.\n- Open the section that holds the current page, with `defaultOpen` or `open`, so the link marked\n `aria-current` can be seen and reached.\n- When the app closes a section while focus is on one of its links, as a sidebar that keeps only\n the current route's section open does after a link is followed, focus moves to that section's\n heading. Without that, the browser drops focus to the page, and the next Tab starts from the\n top.\n- `label` is the button's content and its accessible name. Keep links and other buttons out of\n it.\n- In forced colors the chevron stays visible, since it's drawn as a border, and the focus ring\n takes the system's text color. In a right-to-left page the chevron sits at the left end of the\n heading and points left while closed. It turns with a short transition, which stops when the\n reader asks for reduced motion.\n\n:::note\nFor router integrations, pass the correct `as` and `to`/`href` props to `Menu.Item`.\n:::",
42
+ "related": ["Collapse", "Panel", "Sidebar", "TreeView", "useBulmaClasses"],
35
43
  "storybook": "https://bestax.io/storybook/?path=/story/components-menu--basic",
36
44
  "summary": "The `Menu` component provides Bulma's vertical navigation menu: a simple, accessible sidebar or section menu for your Bulma React UI.",
37
45
  "import": "import { Menu } from '@allxsmith/bestax-bulma';",
@@ -241,9 +249,97 @@
241
249
  ],
242
250
  "catchAll": "Remaining props of the element or component selected by as (default <a>) and Bulma helper props",
243
251
  "types": []
252
+ },
253
+ {
254
+ "path": "Menu.Section",
255
+ "summary": "A section of the menu that opens and closes: its heading is a button drawn like `Menu.Label`, and its `Menu.List` of links shows while it's open. It's APG's disclosure navigation, for a sidebar of link groups: the links stay ordinary links, each its own tab stop, and a closed section's list is `hidden` and `inert`, so Tab and assistive tech skip it. It goes directly in `Menu`, holding a `Menu.List`. Closing it while focus is on one of its links moves focus to its heading.",
256
+ "component": null,
257
+ "props": [
258
+ {
259
+ "name": "className",
260
+ "type": "string",
261
+ "default": null,
262
+ "description": "Additional CSS classes for the wrapping `<div>`.",
263
+ "required": false,
264
+ "inherited": false,
265
+ "deprecated": false
266
+ },
267
+ {
268
+ "name": "label",
269
+ "type": "React.ReactNode",
270
+ "default": null,
271
+ "description": "**Required.** The section's heading, drawn like `Menu.Label` inside the button that opens and closes it. Keep links and buttons out of it, since it's inside one.",
272
+ "required": false,
273
+ "inherited": false,
274
+ "deprecated": false
275
+ },
276
+ {
277
+ "name": "children",
278
+ "type": "React.ReactNode",
279
+ "default": null,
280
+ "description": "The `Menu.List` of links the section shows while it's open. The section itself goes directly in `Menu`, beside its labels and lists: inside a `Menu.Item` it would render inside the item's link, and inside a `Menu.List` it wouldn't be a list item.",
281
+ "required": false,
282
+ "inherited": false,
283
+ "deprecated": false
284
+ },
285
+ {
286
+ "name": "open",
287
+ "type": "boolean",
288
+ "default": null,
289
+ "description": "Controlled open state. If provided, the section is controlled, and its button only reports through `onOpenChange`.",
290
+ "required": false,
291
+ "inherited": false,
292
+ "deprecated": false
293
+ },
294
+ {
295
+ "name": "defaultOpen",
296
+ "type": "boolean",
297
+ "default": "false",
298
+ "description": "Initial open state for uncontrolled usage. Open the section that holds the current page, so the reader sees where they are.",
299
+ "required": false,
300
+ "inherited": false,
301
+ "deprecated": false
302
+ },
303
+ {
304
+ "name": "onOpenChange",
305
+ "type": "(open: boolean) => void",
306
+ "default": null,
307
+ "description": "Called with the state the button asks for (`true` to open) each time it is clicked or activated with Enter or Space, in every mode. Pair it with `open` to let the button drive a controlled section.",
308
+ "required": false,
309
+ "inherited": false,
310
+ "deprecated": false
311
+ }
312
+ ],
313
+ "extraProps": [],
314
+ "catchAll": "All standard <div> attributes and Bulma helper props",
315
+ "types": []
244
316
  }
245
317
  ],
246
318
  "cssVars": [
319
+ {
320
+ "css": "--bulma-menu-section-gap",
321
+ "sass": "$menu-section-gap",
322
+ "default": "0.375em",
323
+ "scope": "root"
324
+ },
325
+ {
326
+ "css": "--bulma-menu-section-chevron-size",
327
+ "sass": "$menu-section-chevron-size",
328
+ "default": "1em",
329
+ "scope": "root"
330
+ },
331
+ {
332
+ "css": "--bulma-menu-section-chevron-color",
333
+ "sass": "$menu-section-chevron-color",
334
+ "default": "currentColor",
335
+ "scope": "root"
336
+ },
337
+ {
338
+ "css": "--bulma-menu-section-chevron-duration",
339
+ "sass": "$menu-section-chevron-duration",
340
+ "default": "var(--bulma-duration)",
341
+ "scope": "root"
342
+ },
247
343
  {
248
344
  "css": "--bulma-menu-item-h",
249
345
  "sass": "$menu-item-h",
@@ -42,7 +42,7 @@
42
42
  "code": "<TreeView\n aria-label=\"Files\"\n selectionMode=\"single\"\n defaultSelected=\"src/App.tsx\"\n defaultExpanded={['src']}\n>\n <TreeView.Item value=\"src\" label=\"src\" icon=\"folder\">\n <TreeView.Item value=\"src/index.ts\" label=\"index.ts\" icon=\"file\" />\n <TreeView.Item value=\"src/App.tsx\" label=\"App.tsx\" icon=\"file\" />\n <TreeView.Item value=\"src/legacy.ts\" label=\"legacy.ts\" disabled />\n </TreeView.Item>\n <TreeView.Item value=\"package.json\" label=\"package.json\" icon=\"file\" />\n</TreeView>"
43
43
  }
44
44
  ],
45
- "accessibility": "`TreeView` follows the [APG Tree View Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/).\n\n- **Roles:** the root is a `<ul role=\"tree\">`, each item an `<li role=\"treeitem\">`, and an open\n parent's children sit in a `<ul role=\"group\">` inside it. A closed item's children have no\n markup.\n- **A name:** a tree needs one, from `aria-label` or `aria-labelledby`, and a development build\n warns when it has neither. Each item is named by `aria-labelledby` pointing at its own label, and\n declares its `aria-level`, which VoiceOver needs according to\n [GitHub's testing](https://github.blog/engineering/user-experience/considerations-for-making-a-tree-view-component-accessible/).\n `aria-expanded` is on parents only.\n- **One tab stop, with real focus:** one item holds `tabIndex={0}` and the rest `-1`, and focus\n moves from item to item, rather than through `aria-activedescendant`. When focus comes into the\n tree, it lands on the selected item, or in a checkbox tree on the first checked item on the page.\n Without one, it lands where focus last was, or on the first item. If the item holding the stop\n goes away, its nearest ancestor on the page takes it, and focus goes too if it had focus.\n- **Selection:** a `single` tree marks each enabled item `aria-selected`, `true` or `false`. A\n `checkbox` tree marks `aria-checked` on every item, `true`, `false` or `mixed`, disabled ones\n included, and adds `aria-multiselectable=\"true\"` to the tree. A tree never has both, and a tree\n with no `selectionMode` has neither. Selection doesn't follow focus.\n- **The box** is a [`Checkbox`](https://bestax.io/docs/api/form/checkbox) inside an `aria-hidden` span, with\n `tabIndex={-1}`, so it's neither a tab stop nor a second thing for a screen reader to read. The\n item's `aria-checked` is what a screen reader hears.\n- **Disabled items** are `aria-disabled` and stay reachable from the arrow keys, Home, End and\n typing, so a screen reader user finds them. They're never selected, checked, opened or closed.\n One that's already open stays open, so what's under it is still reachable.\n- **A loading item's row** is a `treeitem` that says Loading…, marked `aria-disabled` and reached\n the way a disabled item is, so a screen reader user who moves into the folder hears what's\n happening. It has no value: it's never selected or checked, it has no `aria-selected` or\n `aria-checked`, and a checkbox tree doesn't count it. If it goes with focus on it, focus moves to\n the item it was loading for. Bulma's loader turns in its chevron's place, hidden from assistive\n tech.\n- **A failed load's row** is a `treeitem` named by its message and the Retry button's text, such\n as \"Couldn't load src Retry\". Enter, Space or a click on the row calls `onRetry`, in every mode.\n The button is drawn inside an `aria-hidden` span and takes no focus, the way an item's box is,\n so the tree keeps its one tab stop, and nothing in the message should be a link or a button\n either. A retry that starts loading keeps the same row, so focus stays on it. With no `onRetry`,\n the row is disabled and shows the message alone.\n- **Announcements:** a visually hidden `role=\"status\"` region with `aria-live=\"polite\"` sits right\n after the tree, one for the whole tree, from the moment it mounts, so a screen reader is already\n listening when something is written into it. It says \"Loading src\" when an open item starts\n loading, a failed load's message, and \"src loaded\" when the loading row or a failed load's row\n goes with the item still open.\n The name is the item's label as a screen reader reads it. A closed item, or one in a closed\n branch, says nothing. Each announcement is written a moment after the change and cleared soon\n after, so the page doesn't keep a second copy of the text.\n\nEnter and Space do the item's one thing, which depends on the mode:\n\n| Key | No `selectionMode` | `single` | `checkbox` |\n| ----- | ------------------------ | ---------------- | ------------------------ |\n| Enter | Opens or closes a parent | Selects the item | Opens or closes a parent |\n| Space | Opens or closes a parent | Selects the item | Checks or unchecks it |\n\nOn a failed load's row, both retry, whatever the mode. The rest work the same in every mode:\n\n| Key | What it does |\n| ---------- | ----------------------------------------------------------------------------------------- |\n| Down, Up | The next or previous item on the page, without wrapping |\n| Right | Opens a closed parent, and focus stays. On an open parent, moves to its first child |\n| Left | Closes an open parent. Otherwise moves to its parent |\n| Home, End | The first item, and the last one on the page |\n| `*` | Opens every closed parent beside the focused item, itself included. Focus stays |\n| Characters | Moves to the next item whose label starts with what's typed, several characters adding up |\n| Tab | Leaves the tree |\n\n- **Right to left:** Right and Left swap in a right-to-left page, and the chevron points to the\n left while closed.\n- **Keys it leaves alone:** anything with Control, Alt or Meta held, anything while an input method\n is composing, and Escape, so it reaches a Modal or Popover around the tree. An item's own\n `onKeyDown` runs first, and calling `preventDefault()` there keeps the tree from acting on the\n key.\n- **Clicks:** the chevron only opens and closes. The rest of the row does the item's one thing:\n opens or closes it with no `selectionMode`, selects it (and opens or closes a parent) in a\n `single` tree, and checks it in a `checkbox` tree. A click moves focus and the tab stop to the\n item.\n- **Not for site navigation:** APG's own\n [navigation tree example](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/examples/treeview-navigation/)\n says \"A pattern more suited for typical site navigation with expandable groups of links is the\n disclosure pattern\", and a tree's items aren't links. Keep a sidebar on [`Menu`](https://bestax.io/docs/api/components/menu).\n- **Focus and the selected look differ:** the focus ring sits outside the row, so it shows on the\n selected fill as well as on a plain row.\n- **Forced colors:** in Windows high contrast mode the selected row is the system's Highlight and\n HighlightText, a disabled row or a loading one is GrayText, and the focus ring, the chevrons and\n the guide lines stay visible, since they're outlines and borders. A mixed box keeps its dash, and\n the loader's ring stays open.\n- **Motion:** the chevron turns with a short transition, and the loader spins. Both stop when the\n reader asks for reduced motion, and the loader stays drawn.",
45
+ "accessibility": "`TreeView` follows the [APG Tree View Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/).\n\n- **Roles:** the root is a `<ul role=\"tree\">`, each item an `<li role=\"treeitem\">`, and an open\n parent's children sit in a `<ul role=\"group\">` inside it. A closed item's children have no\n markup.\n- **A name:** a tree needs one, from `aria-label` or `aria-labelledby`, and a development build\n warns when it has neither. Each item is named by `aria-labelledby` pointing at its own label, and\n declares its `aria-level`, which VoiceOver needs according to\n [GitHub's testing](https://github.blog/engineering/user-experience/considerations-for-making-a-tree-view-component-accessible/).\n `aria-expanded` is on parents only.\n- **One tab stop, with real focus:** one item holds `tabIndex={0}` and the rest `-1`, and focus\n moves from item to item, rather than through `aria-activedescendant`. When focus comes into the\n tree, it lands on the selected item, or in a checkbox tree on the first checked item on the page.\n Without one, it lands where focus last was, or on the first item. If the item holding the stop\n goes away, its nearest ancestor on the page takes it, and focus goes too if it had focus.\n- **Selection:** a `single` tree marks each enabled item `aria-selected`, `true` or `false`. A\n `checkbox` tree marks `aria-checked` on every item, `true`, `false` or `mixed`, disabled ones\n included, and adds `aria-multiselectable=\"true\"` to the tree. A tree never has both, and a tree\n with no `selectionMode` has neither. Selection doesn't follow focus.\n- **The box** is a [`Checkbox`](https://bestax.io/docs/api/form/checkbox) inside an `aria-hidden` span, with\n `tabIndex={-1}`, so it's neither a tab stop nor a second thing for a screen reader to read. The\n item's `aria-checked` is what a screen reader hears.\n- **Disabled items** are `aria-disabled` and stay reachable from the arrow keys, Home, End and\n typing, so a screen reader user finds them. They're never selected, checked, opened or closed.\n One that's already open stays open, so what's under it is still reachable.\n- **A loading item's row** is a `treeitem` that says Loading…, marked `aria-disabled` and reached\n the way a disabled item is, so a screen reader user who moves into the folder hears what's\n happening. It has no value: it's never selected or checked, it has no `aria-selected` or\n `aria-checked`, and a checkbox tree doesn't count it. If it goes with focus on it, focus moves to\n the item it was loading for. Bulma's loader turns in its chevron's place, hidden from assistive\n tech.\n- **A failed load's row** is a `treeitem` named by its message and the Retry button's text, such\n as \"Couldn't load src Retry\". Enter, Space or a click on the row calls `onRetry`, in every mode.\n The button is drawn inside an `aria-hidden` span and takes no focus, the way an item's box is,\n so the tree keeps its one tab stop, and nothing in the message should be a link or a button\n either. A retry that starts loading keeps the same row, so focus stays on it. With no `onRetry`,\n the row is disabled and shows the message alone.\n- **Announcements:** a visually hidden `role=\"status\"` region with `aria-live=\"polite\"` sits right\n after the tree, one for the whole tree, from the moment it mounts, so a screen reader is already\n listening when something is written into it. It says \"Loading src\" when an open item starts\n loading, a failed load's message, and \"src loaded\" when the loading row or a failed load's row\n goes with the item still open.\n The name is the item's label as a screen reader reads it. A closed item, or one in a closed\n branch, says nothing. Each announcement is written a moment after the change and cleared soon\n after, so the page doesn't keep a second copy of the text.\n\nEnter and Space do the item's one thing, which depends on the mode:\n\n| Key | No `selectionMode` | `single` | `checkbox` |\n| ----- | ------------------------ | ---------------- | ------------------------ |\n| Enter | Opens or closes a parent | Selects the item | Opens or closes a parent |\n| Space | Opens or closes a parent | Selects the item | Checks or unchecks it |\n\nOn a failed load's row, both retry, whatever the mode. The rest work the same in every mode:\n\n| Key | What it does |\n| ---------- | ----------------------------------------------------------------------------------------- |\n| Down, Up | The next or previous item on the page, without wrapping |\n| Right | Opens a closed parent, and focus stays. On an open parent, moves to its first child |\n| Left | Closes an open parent. Otherwise moves to its parent |\n| Home, End | The first item, and the last one on the page |\n| `*` | Opens every closed parent beside the focused item, itself included. Focus stays |\n| Characters | Moves to the next item whose label starts with what's typed, several characters adding up |\n| Tab | Leaves the tree |\n\n- **Right to left:** Right and Left swap in a right-to-left page, and the chevron points to the\n left while closed.\n- **Keys it leaves alone:** anything with Control, Alt or Meta held, anything while an input method\n is composing, and Escape, so it reaches a Modal or Popover around the tree. An item's own\n `onKeyDown` runs first, and calling `preventDefault()` there keeps the tree from acting on the\n key.\n- **Clicks:** the chevron only opens and closes. The rest of the row does the item's one thing:\n opens or closes it with no `selectionMode`, selects it (and opens or closes a parent) in a\n `single` tree, and checks it in a `checkbox` tree. A click moves focus and the tab stop to the\n item.\n- **Not for site navigation:** APG's own\n [navigation tree example](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/examples/treeview-navigation/)\n says \"A pattern more suited for typical site navigation with expandable groups of links is the\n disclosure pattern\", and a tree's items aren't links. Keep a sidebar on [`Menu`](https://bestax.io/docs/api/components/menu),\n whose `Menu.Section` is that disclosure pattern.\n- **Focus and the selected look differ:** the focus ring sits outside the row, so it shows on the\n selected fill as well as on a plain row.\n- **Forced colors:** in Windows high contrast mode the selected row is the system's Highlight and\n HighlightText, a disabled row or a loading one is GrayText, and the focus ring, the chevrons and\n the guide lines stay visible, since they're outlines and borders. A mixed box keeps its dash, and\n the loader's ring stays open.\n- **Motion:** the chevron turns with a short transition, and the loader spins. Both stop when the\n reader asks for reduced motion, and the loader stays drawn.",
46
46
  "related": [
47
47
  "Checkbox",
48
48
  "Collapse",
@@ -23,8 +23,8 @@
23
23
  "storybook": "https://bestax.io/storybook/?path=/story/helpers-usefocustrap--default",
24
24
  "summary": "`useFocusTrap` keeps keyboard focus inside a container while it is active: focus moves in when the trap turns on, Tab and Shift+Tab wrap between the first and…",
25
25
  "import": "import { useFocusTrap } from '@allxsmith/bestax-bulma';",
26
- "api": "```ts\nfunction useFocusTrap(\n containerRef: RefObject<HTMLElement | null>,\n options?: UseFocusTrapOptions\n): void;\n\ninterface UseFocusTrapOptions {\n active?: boolean;\n initialFocusRef?: RefObject<HTMLElement | null>;\n restoreFocus?: boolean | RefObject<HTMLElement | null>;\n}\n```\n\n### Parameters\n\n| Parameter | Type | Default | Description |\n| ------------------------- | --------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `containerRef` | `RefObject<HTMLElement \\| null>` | — | The element focus stays inside. |\n| `options.active` | `boolean` | `true` | Whether the trap is on. Turning it on moves focus in; turning it off (or unmounting) releases Tab and restores focus. |\n| `options.initialFocusRef` | `RefObject<HTMLElement \\| null>` | — | An element inside the container that can take focus, focused when the trap turns on; otherwise the first tab stop, then the container. |\n| `options.restoreFocus` | `boolean` \\| `RefObject<HTMLElement \\| null>` | `true` | Where focus goes when the trap turns off: `true` for the element focused when it turned on, a ref for that element, `false` to leave focus alone. |\n\nIt returns nothing. On the server it does nothing, and the container renders as written.",
27
- "doc": "# useFocusTrap\n\n## Overview\n\n`useFocusTrap` keeps keyboard focus inside a container while it is active: focus moves in when the trap turns on, Tab and Shift+Tab wrap between the first and last tab stops, and focus goes back where it came from when the trap turns off.\n\nUse it for your own floating or modal content, such as a command palette or a custom overlay. `Modal`, `Dialog`, `Sidebar`, `Popover` and the date and time pickers already manage focus themselves, and a panel opened from a button is what [`Popover`](https://bestax.io/docs/api/components/popover) is for.\n\n---\n\n## Import\n\n```tsx\nimport { useFocusTrap } from '@allxsmith/bestax-bulma';\n```\n\n---\n\n## Usage\n\n### A panel opened from a button\n\nPass a ref to the container and turn the trap on with the panel. Give the container `tabIndex={-1}` so it can hold focus itself when nothing inside can. Open the panel below, then Tab past Cancel or Shift+Tab past the input: focus wraps, skipping the disabled button.\n\n```tsx live\nfunction example() {\n const [open, setOpen] = useState(false);\n const buttonRef = React.useRef<HTMLButtonElement>(null);\n const panelRef = React.useRef<HTMLDivElement>(null);\n useFocusTrap(panelRef, { active: open, restoreFocus: buttonRef });\n\n return (\n <>\n <Button\n ref={buttonRef}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n aria-controls=\"filters-panel\"\n onClick={() => setOpen(o => !o)}\n >\n Filters\n </Button>\n {open && (\n <div\n ref={panelRef}\n id=\"filters-panel\"\n role=\"dialog\"\n aria-label=\"Filters\"\n tabIndex={-1}\n onKeyDown={e => {\n if (e.key !== 'Escape') return;\n e.preventDefault();\n setOpen(false);\n }}\n >\n <Box mt=\"3\">\n <Input label=\"Name contains\" />\n <Buttons>\n <Button color=\"primary\" onClick={() => setOpen(false)}>\n Apply\n </Button>\n <Button disabled>Export</Button>\n <Button onClick={() => setOpen(false)}>Cancel</Button>\n </Buttons>\n </Box>\n </div>\n )}\n </>\n );\n}\n```\n\nThe trap doesn't close anything. Escape, a click outside and the buttons that dismiss the panel are the component's to handle, as the `onKeyDown` above does. Its `preventDefault()` marks the Escape used, so a [`Modal`](https://bestax.io/docs/api/components/modal) or [`Sidebar`](https://bestax.io/docs/api/components/sidebar) around the panel stays open for the next one.\n\n### Where focus starts\n\nOn its own the trap focuses the first tab stop inside the container, or the container when there is none. `initialFocusRef` picks a specific element inside the container instead, such as the search box of a command palette. The element has to be able to take focus, so a heading needs `tabIndex={-1}`. When focusing it doesn't put focus inside the container, because the ref is empty or its element is outside, disabled, hidden or can't take focus, the trap starts on the first tab stop as it would without the ref.\n\n```tsx\nconst searchRef = useRef<HTMLInputElement>(null);\nuseFocusTrap(paletteRef, { active: open, initialFocusRef: searchRef });\n```\n\n### Where focus goes back to\n\n`restoreFocus` decides where focus lands when the trap turns off, whether because `active` went `false`, the container left the page or the component unmounted.\n\n- `true` (the default) returns it to the element that had focus when the trap turned on.\n- A ref sends it to that element instead. Prefer this for a panel opened from a button: some browsers don't focus a button when it is clicked, and content that focuses itself as it mounts (an `autoFocus` input) takes focus before the trap can see where it came from.\n- `false` leaves focus alone.\n\nFocus only moves back while the trap still holds it. If a click has already put focus on something else outside the container, it stays there.\n\n### With portals\n\nA trapped panel is often rendered through a portal, at the end of `document.body`, and a portaled panel can mount a render after the trap turns on: once the page has hydrated, or once a target held in state exists. The trap waits for hydration and looks for its container again each time the component calling it renders, so it attaches to the panel once the panel is there.\n\nPut the ref on the element inside the [`Portal`](https://bestax.io/docs/api/helpers/portal), so the trap holds the portaled content itself. The panel below is declared inside a box that clips its overflow and portals into an element after it, as in the Portal page's [container example](https://bestax.io/docs/api/helpers/portal#into-a-container). Open it, then Tab past Cancel: focus wraps inside the panel, and Escape or either button closes it and returns focus to Share.\n\n```tsx live\nfunction example() {\n const [open, setOpen] = useState(false);\n const [target, setTarget] = useState<HTMLElement | null>(null);\n const buttonRef = React.useRef<HTMLButtonElement>(null);\n const panelRef = React.useRef<HTMLDivElement>(null);\n useFocusTrap(panelRef, { active: open, restoreFocus: buttonRef });\n\n return (\n <>\n <Box overflow=\"clipped\">\n <Button\n ref={buttonRef}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n aria-controls=\"share-panel\"\n onClick={() => setOpen(o => !o)}\n >\n Share\n </Button>\n {open && target && (\n <Portal container={target}>\n <div\n ref={panelRef}\n id=\"share-panel\"\n role=\"dialog\"\n aria-label=\"Share\"\n tabIndex={-1}\n onKeyDown={e => {\n if (e.key !== 'Escape') return;\n e.preventDefault();\n setOpen(false);\n }}\n >\n <Box mt=\"3\">\n <Input label=\"Email\" type=\"email\" />\n <Buttons>\n <Button color=\"primary\" onClick={() => setOpen(false)}>\n Send\n </Button>\n <Button onClick={() => setOpen(false)}>Cancel</Button>\n </Buttons>\n </Box>\n </div>\n </Portal>\n )}\n </Box>\n <div ref={setTarget} />\n </>\n );\n}\n```\n\nThe other way round doesn't work: a portal rendered _inside_ the trapped container puts its content somewhere else in the DOM, outside the container, where Tab from the trap can't reach it and the trap doesn't hold focus. Render a nested overlay inside the container, or give it a trap of its own.\n\n### Which elements are tab stops\n\nThe trap wraps at the first and last elements the browser's Tab would visit, taken in document order:\n\n- links with an `href`, buttons, inputs other than `type=\"hidden\"`, selects and text areas\n- the areas with an `href` of an image map whose image is shown, taken where the `<map>` sits\n- frames: `<iframe>`, `<embed>` and `<object>`\n- `<audio>` and `<video>` with `controls`\n- the summary of a `<details>`, which stays a stop while the details is closed\n- an editable region (`contentEditable`), counted once rather than once per editable element inside it\n- anything with a `tabIndex` of `0` or more\n- the content of an open shadow root inside the container, such as a web component's own controls, with anything slotted into it taken where its slot is\n\nWithin those, what the browser's Tab skips is skipped here too, so none of it can become an end of the trap and let Tab walk out of the container:\n\n- a negative `tabIndex`, such as the unfocused items of a roving-tabindex grid\n- disabled controls, including those inside a disabled `<fieldset>`\n- anything not rendered: `hidden`, `display: none` on the element or an ancestor (for slotted content, an ancestor of its slot counts), `visibility: hidden`, the content of a closed `<details>`, and a child of an open shadow root's host that no slot takes\n- anything inside an `inert` subtree\n- all but one button of a radio group: the checked one, or the first when none is checked\n- every button of a radio group whose checked button is outside the container, because a group is every button that shares its name, form owner and tree (the page, or one shadow root), not only the ones in the container\n\n### When Tab leaves anyway\n\nA browser can leave out a stop the page has, such as links when its settings say to. When a Tab the trap let through still takes focus out of the container, the trap sends it back to the other end. If the container is the last thing on the page, there is nothing outside for focus to land on and the browser can take it to its own toolbar instead, so avoid ending a trap with a link.\n\nOnly Tab is watched. A click or a script that moves focus out of the container is left alone, and the next Tab starts from wherever focus went. Pulling focus back from a click is modal behaviour, and a modal also has to block the page behind it; [`Modal`](https://bestax.io/docs/api/components/modal) does both. For a non-modal panel, close it when focus or a click goes elsewhere.\n\n### What the trap can't see into\n\nSome elements move focus among parts of their own, and the trap sees them as one stop:\n\n- A frame's content is its own document. Once focus is inside, Tab moves through the frame without the trap seeing it, and Tab from the frame's last stop goes on to whatever follows the frame. At the end of the trap, that is outside it: follow a frame with another stop.\n- A native date or time input has a stop for each field. At the edge of the trap, Tab wraps instead of visiting the later fields. Keep it away from the edges, or follow it with another stop.\n- A closed shadow root can't be looked into. The trap sees its host and the host's own children, but not the stops inside the root or which of those children a slot takes. A child that no slot takes isn't rendered and can't take focus, so if it ends up as the trap's first or last stop, turning the trap on or wrapping Tab to it leaves focus where it was. Keep a closed-root host away from the trap's edges, or give it no children that a slot doesn't take.\n- A scrollable region with nothing focusable inside is a stop in some browsers and not in others. Give it `tabIndex={0}` so it is one everywhere, which also lets keyboard users scroll it.\n\n### What the trap takes on trust\n\n- A browser can visit an image map's areas where the `<map>` sits or where its image is. The trap takes them where the map is, so keep the map right after its image, where the two agree.\n- A positive `tabIndex` changes the order the browser's Tab visits, but not the trap's ends, which stay the first and last stops in document order. Where the two disagree, a Tab past the browser's end is let through and then sent to the trap's end instead, which can be the very element it left, so focus seems stuck. Keep positive `tabIndex` out of a trap.\n- The trap listens for Tab on the container. Content inside that stops a Tab keydown from propagating keeps it from the trap, so that Tab isn't wrapped, and focus it takes out isn't sent back.\n\n### Nested traps\n\nThe trap listens for Tab on its container, so a trap inside another one handles Tab first and the outer trap stands aside. A panel that opens a second panel inside it keeps focus in the inner one.\n\n---\n\n## API\n\n```ts\nfunction useFocusTrap(\n containerRef: RefObject<HTMLElement | null>,\n options?: UseFocusTrapOptions\n): void;\n\ninterface UseFocusTrapOptions {\n active?: boolean;\n initialFocusRef?: RefObject<HTMLElement | null>;\n restoreFocus?: boolean | RefObject<HTMLElement | null>;\n}\n```\n\n### Parameters\n\n| Parameter | Type | Default | Description |\n| ------------------------- | --------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `containerRef` | `RefObject<HTMLElement \\| null>` | — | The element focus stays inside. |\n| `options.active` | `boolean` | `true` | Whether the trap is on. Turning it on moves focus in; turning it off (or unmounting) releases Tab and restores focus. |\n| `options.initialFocusRef` | `RefObject<HTMLElement \\| null>` | — | An element inside the container that can take focus, focused when the trap turns on; otherwise the first tab stop, then the container. |\n| `options.restoreFocus` | `boolean` \\| `RefObject<HTMLElement \\| null>` | `true` | Where focus goes when the trap turns off: `true` for the element focused when it turned on, a ref for that element, `false` to leave focus alone. |\n\nIt returns nothing. On the server it does nothing, and the container renders as written.\n\n---\n\n## Accessibility\n\n- Trap focus in content that asks for the reader's attention until it is dismissed: a dialog, a command palette, a panel with its own controls. Content a reader should be able to Tab past, such as a tooltip or a disclosure, shouldn't be trapped.\n- Pair the trap with a way out. The trap handles Tab only, so wire Escape (and any close button) to turn it off.\n- Give the container a role and an accessible name (`role=\"dialog\"` with `aria-label` or `aria-labelledby`), and give the button that opens it `aria-haspopup`, `aria-expanded` and `aria-controls`.\n- Return focus to the element that opened the content when it closes. The default does, and a ref to the trigger makes it reliable.\n- The trap is for the keyboard. It doesn't hide the rest of the page from screen readers or pointers, and it doesn't pull focus back from a click; for fully modal content, use [`Modal`](https://bestax.io/docs/api/components/modal), which sets `aria-modal`.\n\n---\n\n## Related Components\n\n- [`Modal`](https://bestax.io/docs/api/components/modal): A modal overlay with its own focus management.\n- [`Dialog`](https://bestax.io/docs/api/components/dialog): Ready-made confirm and alert dialogs.\n\n---\n\n## Additional Resources\n\n- [Storybook: useFocusTrap Stories](https://bestax.io/storybook/?path=/story/helpers-usefocustrap--default)\n- [WAI-ARIA Authoring Practices: Dialog (Modal) pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)",
26
+ "api": "```ts\nfunction useFocusTrap(\n containerRef: RefObject<HTMLElement | null>,\n options?: UseFocusTrapOptions\n): void;\n\ninterface UseFocusTrapOptions {\n active?: boolean;\n initialFocusRef?: RefObject<HTMLElement | null>;\n restoreFocus?: boolean | RefObject<HTMLElement | null>;\n}\n```\n\n### Parameters\n\n| Parameter | Type | Default | Description |\n| ------------------------- | --------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `containerRef` | `RefObject<HTMLElement \\| null>` | — | The element focus stays inside. |\n| `options.active` | `boolean` | `true` | Whether the trap is on. Turning it on moves focus in; turning it off (or unmounting) releases Tab and restores focus. |\n| `options.initialFocusRef` | `RefObject<HTMLElement \\| null>` | — | An element inside the container that can take focus, focused when the trap turns on; otherwise the first tab stop that takes focus, then the container. |\n| `options.restoreFocus` | `boolean` \\| `RefObject<HTMLElement \\| null>` | `true` | Where focus goes when the trap turns off: `true` for the element focused when it turned on, a ref for that element, `false` to leave focus alone. |\n\nIt returns nothing. On the server it does nothing, and the container renders as written.",
27
+ "doc": "# useFocusTrap\n\n## Overview\n\n`useFocusTrap` keeps keyboard focus inside a container while it is active: focus moves in when the trap turns on, Tab and Shift+Tab wrap between the first and last tab stops, and focus goes back where it came from when the trap turns off.\n\nUse it for your own floating or modal content, such as a command palette or a custom overlay. `Modal`, `Dialog`, `Sidebar`, `Popover` and the date and time pickers already manage focus themselves, and a panel opened from a button is what [`Popover`](https://bestax.io/docs/api/components/popover) is for.\n\n---\n\n## Import\n\n```tsx\nimport { useFocusTrap } from '@allxsmith/bestax-bulma';\n```\n\n---\n\n## Usage\n\n### A panel opened from a button\n\nPass a ref to the container and turn the trap on with the panel. Give the container `tabIndex={-1}` so it can hold focus itself when nothing inside can. Open the panel below, then Tab past Cancel or Shift+Tab past the input: focus wraps, skipping the disabled button.\n\n```tsx live\nfunction example() {\n const [open, setOpen] = useState(false);\n const buttonRef = React.useRef<HTMLButtonElement>(null);\n const panelRef = React.useRef<HTMLDivElement>(null);\n useFocusTrap(panelRef, { active: open, restoreFocus: buttonRef });\n\n return (\n <>\n <Button\n ref={buttonRef}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n aria-controls=\"filters-panel\"\n onClick={() => setOpen(o => !o)}\n >\n Filters\n </Button>\n {open && (\n <div\n ref={panelRef}\n id=\"filters-panel\"\n role=\"dialog\"\n aria-label=\"Filters\"\n tabIndex={-1}\n onKeyDown={e => {\n if (e.key !== 'Escape') return;\n e.preventDefault();\n setOpen(false);\n }}\n >\n <Box mt=\"3\">\n <Input label=\"Name contains\" />\n <Buttons>\n <Button color=\"primary\" onClick={() => setOpen(false)}>\n Apply\n </Button>\n <Button disabled>Export</Button>\n <Button onClick={() => setOpen(false)}>Cancel</Button>\n </Buttons>\n </Box>\n </div>\n )}\n </>\n );\n}\n```\n\nThe trap doesn't close anything. Escape, a click outside and the buttons that dismiss the panel are the component's to handle, as the `onKeyDown` above does. Its `preventDefault()` marks the Escape used, so a [`Modal`](https://bestax.io/docs/api/components/modal) or [`Sidebar`](https://bestax.io/docs/api/components/sidebar) around the panel stays open for the next one.\n\n### Where focus starts\n\nOn its own the trap focuses the first tab stop inside the container that takes focus, or the container when none does. `initialFocusRef` picks a specific element inside the container instead, such as the search box of a command palette. The element has to be able to take focus, so a heading needs `tabIndex={-1}`. When focusing it doesn't put focus inside the container, because the ref is empty or its element is outside, disabled, hidden or can't take focus, the trap starts on the first tab stop as it would without the ref.\n\n```tsx\nconst searchRef = useRef<HTMLInputElement>(null);\nuseFocusTrap(paletteRef, { active: open, initialFocusRef: searchRef });\n```\n\n### Where focus goes back to\n\n`restoreFocus` decides where focus lands when the trap turns off, whether because `active` went `false`, the container left the page or the component unmounted.\n\n- `true` (the default) returns it to the element that had focus when the trap turned on.\n- A ref sends it to that element instead. Prefer this for a panel opened from a button: some browsers don't focus a button when it is clicked, and content that focuses itself as it mounts (an `autoFocus` input) takes focus before the trap can see where it came from.\n- `false` leaves focus alone.\n\nFocus only moves back while the trap still holds it. If a click has already put focus on something else outside the container, it stays there.\n\n### With portals\n\nA trapped panel is often rendered through a portal, at the end of `document.body`, and a portaled panel can mount a render after the trap turns on: once the page has hydrated, or once a target held in state exists. The trap waits for hydration and looks for its container again each time the component calling it renders, so it attaches to the panel once the panel is there.\n\nPut the ref on the element inside the [`Portal`](https://bestax.io/docs/api/helpers/portal), so the trap holds the portaled content itself. The panel below is declared inside a box that clips its overflow and portals into an element after it, as in the Portal page's [container example](https://bestax.io/docs/api/helpers/portal#into-a-container). Open it, then Tab past Cancel: focus wraps inside the panel, and Escape or either button closes it and returns focus to Share.\n\n```tsx live\nfunction example() {\n const [open, setOpen] = useState(false);\n const [target, setTarget] = useState<HTMLElement | null>(null);\n const buttonRef = React.useRef<HTMLButtonElement>(null);\n const panelRef = React.useRef<HTMLDivElement>(null);\n useFocusTrap(panelRef, { active: open, restoreFocus: buttonRef });\n\n return (\n <>\n <Box overflow=\"clipped\">\n <Button\n ref={buttonRef}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n aria-controls=\"share-panel\"\n onClick={() => setOpen(o => !o)}\n >\n Share\n </Button>\n {open && target && (\n <Portal container={target}>\n <div\n ref={panelRef}\n id=\"share-panel\"\n role=\"dialog\"\n aria-label=\"Share\"\n tabIndex={-1}\n onKeyDown={e => {\n if (e.key !== 'Escape') return;\n e.preventDefault();\n setOpen(false);\n }}\n >\n <Box mt=\"3\">\n <Input label=\"Email\" type=\"email\" />\n <Buttons>\n <Button color=\"primary\" onClick={() => setOpen(false)}>\n Send\n </Button>\n <Button onClick={() => setOpen(false)}>Cancel</Button>\n </Buttons>\n </Box>\n </div>\n </Portal>\n )}\n </Box>\n <div ref={setTarget} />\n </>\n );\n}\n```\n\nThe other way round doesn't work: a portal rendered _inside_ the trapped container puts its content somewhere else in the DOM, outside the container, where Tab from the trap can't reach it and the trap doesn't hold focus. Render a nested overlay inside the container, or give it a trap of its own.\n\n### Which elements are tab stops\n\nThe trap wraps at the first and last elements the browser's Tab would visit, taken in document order:\n\n- links with an `href`, buttons, inputs other than `type=\"hidden\"`, selects and text areas\n- the areas with an `href` of an image map whose image is shown, taken where the `<map>` sits\n- frames: `<iframe>`, `<embed>` and `<object>`\n- `<audio>` and `<video>` with `controls`\n- the summary of a `<details>`, which stays a stop while the details is closed\n- an editable region (`contentEditable`), counted once rather than once per editable element inside it\n- anything with a `tabIndex` of `0` or more\n- the content of an open shadow root inside the container, such as a web component's own controls, with anything slotted into it taken where its slot is\n\nWithin those, what the browser's Tab skips is skipped here too, so none of it can become an end of the trap and let Tab walk out of the container:\n\n- a negative `tabIndex`, such as the unfocused items of a roving-tabindex grid\n- disabled controls, including those inside a disabled `<fieldset>`\n- anything not rendered: `hidden`, `display: none` on the element or an ancestor (for slotted content, an ancestor of its slot counts), `visibility: hidden`, the content of a closed `<details>`, and a child of an open shadow root's host that no slot takes\n- anything inside an `inert` subtree\n- all but one button of a radio group: the checked one, or the first when none is checked\n- every button of a radio group whose checked button is outside the container, because a group is every button that shares its name, form owner and tree (the page, or one shadow root), not only the ones in the container\n\n### When Tab leaves anyway\n\nA browser can leave out a stop the page has, such as links when its settings say to. When a Tab the trap let through still takes focus out of the container, the trap sends it back to the other end. If the container is the last thing on the page, there is nothing outside for focus to land on and the browser can take it to its own toolbar instead, so avoid ending a trap with a link.\n\nA browser can also refuse focus to a stop. An `<object>` or `<embed>` with nothing to show is the common one, such as an `<object>` whose file failed to load, and which of them a browser refuses differs between browsers and with what has loaded, so the trap doesn't guess. Whenever it moves focus, as it turns on, when Tab wraps, and when it sends a Tab back in, it passes over a stop that doesn't take focus for the next one, and falls back to the container when none does.\n\nA refused stop at an end of the trap is the one case that still gets out. The trap can't tell it will be refused, so a Tab toward it is left to the browser, which goes past it and out of the container the way it goes past a skipped link. Focus comes back only when there's something outside for it to land on, so don't end a trap that sits last on the page with an `<object>` or `<embed>` that may have nothing to show, any more than with a link.\n\nOnly Tab is watched. A click or a script that moves focus out of the container is left alone, and the next Tab starts from wherever focus went. Pulling focus back from a click is modal behaviour, and a modal also has to block the page behind it; [`Modal`](https://bestax.io/docs/api/components/modal) does both. For a non-modal panel, close it when focus or a click goes elsewhere.\n\n### What the trap can't see into\n\nSome elements move focus among parts of their own, and the trap sees them as one stop:\n\n- A frame's content is its own document. Once focus is inside, Tab moves through the frame without the trap seeing it, and Tab from the frame's last stop goes on to whatever follows the frame. At the end of the trap, that is outside it: follow a frame with another stop.\n- A native date or time input has a stop for each field. At the edge of the trap, Tab wraps instead of visiting the later fields. Keep it away from the edges, or follow it with another stop.\n- A closed shadow root can't be looked into. The trap sees its host and the host's own children, but not the stops inside the root or which of those children a slot takes. A child that no slot takes isn't rendered and can't take focus, so the trap passes over it when it moves focus, as it does any stop the browser refuses. At an end of the trap it's a refused stop like any other, so keep a closed-root host away from the trap's edges, or give it no children that a slot doesn't take.\n- A scrollable region with nothing focusable inside is a stop in some browsers and not in others. Give it `tabIndex={0}` so it is one everywhere, which also lets keyboard users scroll it.\n\n### What the trap takes on trust\n\n- A browser can visit an image map's areas where the `<map>` sits or where its image is. The trap takes them where the map is, so keep the map right after its image, where the two agree.\n- A positive `tabIndex` changes the order the browser's Tab visits, but not the trap's ends, which stay the first and last stops in document order. Where the two disagree, a Tab past the browser's end is let through and then sent to the trap's end instead, which can be the very element it left, so focus seems stuck. Keep positive `tabIndex` out of a trap.\n- The trap listens for Tab on the container. Content inside that stops a Tab keydown from propagating keeps it from the trap, so that Tab isn't wrapped, and focus it takes out isn't sent back.\n\n### Nested traps\n\nThe trap listens for Tab on its container, so a trap inside another one handles Tab first and the outer trap stands aside. A panel that opens a second panel inside it keeps focus in the inner one.\n\n---\n\n## API\n\n```ts\nfunction useFocusTrap(\n containerRef: RefObject<HTMLElement | null>,\n options?: UseFocusTrapOptions\n): void;\n\ninterface UseFocusTrapOptions {\n active?: boolean;\n initialFocusRef?: RefObject<HTMLElement | null>;\n restoreFocus?: boolean | RefObject<HTMLElement | null>;\n}\n```\n\n### Parameters\n\n| Parameter | Type | Default | Description |\n| ------------------------- | --------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `containerRef` | `RefObject<HTMLElement \\| null>` | — | The element focus stays inside. |\n| `options.active` | `boolean` | `true` | Whether the trap is on. Turning it on moves focus in; turning it off (or unmounting) releases Tab and restores focus. |\n| `options.initialFocusRef` | `RefObject<HTMLElement \\| null>` | — | An element inside the container that can take focus, focused when the trap turns on; otherwise the first tab stop that takes focus, then the container. |\n| `options.restoreFocus` | `boolean` \\| `RefObject<HTMLElement \\| null>` | `true` | Where focus goes when the trap turns off: `true` for the element focused when it turned on, a ref for that element, `false` to leave focus alone. |\n\nIt returns nothing. On the server it does nothing, and the container renders as written.\n\n---\n\n## Accessibility\n\n- Trap focus in content that asks for the reader's attention until it is dismissed: a dialog, a command palette, a panel with its own controls. Content a reader should be able to Tab past, such as a tooltip or a disclosure, shouldn't be trapped.\n- Pair the trap with a way out. The trap handles Tab only, so wire Escape (and any close button) to turn it off.\n- Give the container a role and an accessible name (`role=\"dialog\"` with `aria-label` or `aria-labelledby`), and give the button that opens it `aria-haspopup`, `aria-expanded` and `aria-controls`.\n- Return focus to the element that opened the content when it closes. The default does, and a ref to the trigger makes it reliable.\n- The trap is for the keyboard. It doesn't hide the rest of the page from screen readers or pointers, and it doesn't pull focus back from a click; for fully modal content, use [`Modal`](https://bestax.io/docs/api/components/modal), which sets `aria-modal`.\n\n---\n\n## Related Components\n\n- [`Modal`](https://bestax.io/docs/api/components/modal): A modal overlay with its own focus management.\n- [`Dialog`](https://bestax.io/docs/api/components/dialog): Ready-made confirm and alert dialogs.\n\n---\n\n## Additional Resources\n\n- [Storybook: useFocusTrap Stories](https://bestax.io/storybook/?path=/story/helpers-usefocustrap--default)\n- [WAI-ARIA Authoring Practices: Dialog (Modal) pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)",
28
28
  "parts": [],
29
29
  "cssVars": []
30
30
  }
@@ -116,6 +116,12 @@ Centered; a collection of items → Card grid. For mixed requests, pick the domi
116
116
  while the retry runs: the tree draws the Loading… and Retry rows and announces them, so don't
117
117
  hand-write a placeholder item. Its items aren't
118
118
  links, so the app's sidebar and any list of pages stay on `Menu` with `Menu.Item href`.
119
+ - **A sidebar whose groups of links open and close is `Menu.Section`**, one per group, directly
120
+ in `Menu` (never inside a `Menu.Item` or `Menu.List`):
121
+ `<Menu.Section label="Components" defaultOpen>` around that group's `Menu.List`, with
122
+ `defaultOpen` on the group holding the current page (or `open`/`onOpenChange` to hold the state).
123
+ Its heading is a real button with `aria-expanded`, and its links stay links, each a tab stop.
124
+ Not `TreeView`, and not a `Collapse` inside a hand-written `<li>`.
119
125
  - Link lists (footer nav, sidebars): a bare `UnorderedList` of `ListItem`s is already
120
126
  marker-less and flush — Bulma's reset unstyles `ul` — so no prop or CSS is needed;
121
127
  bullets appear only inside `Content`.
@@ -267,11 +267,13 @@ https://bestax.io/docs/guides/features/routing.
267
267
 
268
268
  ## Menu
269
269
 
270
- `<Menu>` is a vertical sidebar menu. Subcomponents: `Menu.Label`, `Menu.List`, `Menu.Item`.
270
+ `<Menu>` is a vertical sidebar menu. Subcomponents: `Menu.Label`, `Menu.List`, `Menu.Item`,
271
+ `Menu.Section`.
271
272
 
272
- | Prop | Type |
273
- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
274
- | `Menu.Item` | `active?` (the current page: `is-active` plus `aria-current="page"`, so don't add your own), `href?`, `as?` (custom link component); nest a `Menu.List` inside an item for submenus |
273
+ | Prop | Type |
274
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
275
+ | `Menu.Item` | `active?` (the current page: `is-active` plus `aria-current="page"`, so don't add your own), `href?`, `as?` (custom link component); nest a `Menu.List` inside an item for submenus |
276
+ | `Menu.Section` | `label` (the heading, a `ReactNode`), `defaultOpen?`, or `open?` with `onOpenChange?(open)` to hold the state yourself, as `Collapse` does; its child is the group's `Menu.List`, and a closed section's links are out of reach |
275
277
 
276
278
  ```tsx
277
279
  <Menu>
@@ -285,6 +287,34 @@ https://bestax.io/docs/guides/features/routing.
285
287
  </Menu>
286
288
  ```
287
289
 
290
+ A sidebar whose groups of links open and close (a docs sidebar, an admin app's sections) puts each
291
+ group's `Menu.List` in a `Menu.Section`. Its heading is a real button drawn like `Menu.Label`, with
292
+ `aria-expanded` and a chevron, and its links stay ordinary links, each a tab stop. Open the
293
+ section that holds the current page. A section goes directly in `Menu`, never inside a `Menu.Item`
294
+ (it would render inside the item's link) or a `Menu.List` (it isn't a list item). Don't build this
295
+ from `TreeView` (its items aren't links) or from a `Collapse` in a hand-written `<li>` (its trigger
296
+ is a `div` with `role="button"`).
297
+
298
+ ```tsx
299
+ <nav aria-label="Documentation">
300
+ <Menu>
301
+ <Menu.Section label="Getting started">
302
+ <Menu.List>
303
+ <Menu.Item href="/docs/install">Installation</Menu.Item>
304
+ </Menu.List>
305
+ </Menu.Section>
306
+ <Menu.Section label="Components" defaultOpen>
307
+ <Menu.List>
308
+ <Menu.Item href="/docs/button" active>
309
+ Button
310
+ </Menu.Item>
311
+ <Menu.Item href="/docs/card">Card</Menu.Item>
312
+ </Menu.List>
313
+ </Menu.Section>
314
+ </Menu>
315
+ </nav>
316
+ ```
317
+
288
318
  ## Card
289
319
 
290
320
  `<Card>` for catalog/grid items. Use the props form (`header`, `image`, `footer`) or the compound
@@ -272,7 +272,8 @@ go straight inside `Breadcrumb`, and `<figure className="image is-64x64"><img sr
272
272
  one, so the `<ul>` carries nothing and the `.breadcrumb` needs an `aria-label` of its own.
273
273
  - `Image` renders its `<img>` from `src` and `alt`, so the `<img>` carries nothing else, and
274
274
  `is-rounded` on it becomes `isRounded`. The `.image` keeps its own attributes. A ratio
275
- (`is-4by3`) stays a class, since `size` would add `has-ratio` as well. Around anything else (an
275
+ (`is-4by3`) becomes `size` too. `Image` renders a ratio on a `<figure>`, so a `<figure>` needs no
276
+ `as` there and a `<div>` keeps `as="div"`. Around anything else (an
276
277
  `<img>` with more attributes, an `<iframe>`), `Image` renders its children as given, so the
277
278
  `.image` converts around them and they stay as written, as long as they're HTML elements written
278
279
  out. An empty `.image` stays markup, since `Image` would render an `<img>` of its own, and so does
@@ -103,7 +103,8 @@ renders the same `<h2>`.
103
103
  | the `<select>` inside `.select` | `is-hovered`, `is-focused` | `isHovered`, `isFocused` |
104
104
  | `.hero` → `Hero` | `is-primary`, `is-link`, `is-info`, `is-success`, `is-warning`, `is-danger`, `is-black`, `is-white`, `is-light`, `is-dark` | `color` |
105
105
  | `.hero` → `Hero` | `is-small`, `is-medium`, `is-large`, `is-fullheight`, `is-fullheight-with-navbar` | `size` |
106
- | `.image` → `Image` | `is-16x16`, `is-24x24`, `is-32x32`, `is-48x48`, `is-64x64`, `is-96x96`, `is-128x128`, `is-square` | `size` (a ratio such as `is-4by3` stays a class, since `size` would add `has-ratio`) |
106
+ | `.image` → `Image` | `is-16x16`, `is-24x24`, `is-32x32`, `is-48x48`, `is-64x64`, `is-96x96`, `is-128x128`, `is-square` | `size` |
107
+ | `.image` → `Image` | `is-{ratio}`, for each of the Ratios under Helper classes (`is-16by9`), but not `is-square` (see below) | `size`, on the `<figure>` it renders with no `as` (see below) |
107
108
  | the `<img>` inside `.image` | `is-rounded` | `isRounded` |
108
109
  | `.icon` → `Icon` | `is-small`, `is-medium`, `is-large` | `size` |
109
110
  | `.tabs` → `Tabs` | `is-centered`, `is-right`, `is-left` | `align` |
@@ -130,6 +131,16 @@ renders the same `<h2>`.
130
131
  | `.tag` → `Tag` | `is-light`, `is-rounded`, `is-hoverable` | `isLight`, `isRounded`, `isHoverable` |
131
132
  | `.tag` → `Tag` | `is-medium`, `is-large` | `size` |
132
133
 
134
+ `is-square` sits with the fixed sizes, not the ratios. Bulma sizes it like `is-1by1`, but `Image`
135
+ renders `size="square"` on a `<div>`, as it does a fixed size. So
136
+ `<figure className="image is-square">` becomes `<Image as="figure" size="square">`, while
137
+ `<figure className="image is-1by1">` becomes `<Image size="1by1">`.
138
+
139
+ A ratio converts to identical markup only on a bestax-bulma whose `Image` writes nothing but the
140
+ ratio class on its container. An older one also writes `has-ratio` there, which Bulma reads only
141
+ on a child, so the page looks the same, unless that `.image` sits inside another ratio `.image`,
142
+ which then stretches it to fill. Upgrade bestax-bulma, or keep that ratio as a class.
143
+
133
144
  `Progress` takes `value` and `max` as numbers: `value="40"` becomes `value={40}`. An
134
145
  expression (`value={percent}`) carries over as written, so if it holds a string the migrated
135
146
  file fails to typecheck while still rendering the same; wrap it in `Number(…)`. `Delete`
@@ -232,8 +243,8 @@ Some classes stay put because of how bestax renders them:
232
243
  ## Classes that stay classes
233
244
 
234
245
  Color shades (`has-text-primary-65`), the `-touch` and `-only` breakpoints of text size and
235
- alignment, Grid and `.image` modifiers away from their own element, an `.image` ratio, v1's
236
- `marginless` and `paddingless` (the rules of `m-0` and `p-0` under other names), and the Bulma
237
- helpers bestax has no prop for (`is-display-*`, `is-visibility-*`, `is-float-*`, `is-clear-*`,
238
- …) stay in `className`. They render exactly as before. An `.image` size or ratio away from its
239
- `.image` also gets a `modifier:image` TODO, since Bulma styles it only there.
246
+ alignment, Grid and `.image` modifiers away from their own element, v1's `marginless` and
247
+ `paddingless` (the rules of `m-0` and `p-0` under other names), and the Bulma helpers bestax has
248
+ no prop for (`is-display-*`, `is-visibility-*`, `is-float-*`, `is-clear-*`, …) stay in
249
+ `className`. They render exactly as before. An `.image` size or ratio away from its `.image` also
250
+ gets a `modifier:image` TODO, since Bulma styles it only there.
@@ -107,8 +107,9 @@ Notes:
107
107
  @use '@allxsmith/bestax-bulma/scss/components/tabs' as bestax-tabs;
108
108
  ```
109
109
 
110
- - The `menu` partial **extends** stock Bulma menu (it keeps the active item marked in forced
111
- colors, Windows' contrast themes), so an app using `Menu` needs both, in either order:
110
+ - The `menu` partial **extends** stock Bulma menu (it draws `Menu.Section`'s heading and chevron,
111
+ and keeps the active item marked in forced colors, Windows' contrast themes), so an app using
112
+ `Menu` needs both, in either order:
112
113
 
113
114
  ```scss
114
115
  @use 'bulma/sass/components/menu';
@@ -462,6 +462,17 @@ Declared on the compound `.button.link-button` — see the compound-selector exc
462
462
  `--bulma-loading-overlay-opacity-opaque`, `--bulma-loading-text-color`,
463
463
  `--bulma-loading-text-size`
464
464
 
465
+ ### Menu
466
+
467
+ `Menu.Section`'s, declared on the root, `.menu`, where Bulma declares its own `--bulma-menu-*`:
468
+ the room between a section heading's words and its chevron, and the chevron's box, color and
469
+ turn. The heading itself reads Bulma's label variables (`--bulma-menu-label-color`,
470
+ `--bulma-menu-label-font-size`, `--bulma-menu-label-letter-spacing`,
471
+ `--bulma-menu-label-spacing`), so retheming the menu's labels restyles section headings too.
472
+
473
+ `--bulma-menu-section-chevron-color`, `--bulma-menu-section-chevron-duration`,
474
+ `--bulma-menu-section-chevron-size`, `--bulma-menu-section-gap`
475
+
465
476
  ### Notification
466
477
 
467
478
  `--bulma-notification-delete-padding-inline-end` is the end padding of a notification with a close
package/data/skills.json CHANGED
@@ -64,7 +64,7 @@
64
64
  {
65
65
  "id": "layout-components",
66
66
  "file": "references/layout-components.md",
67
- "bytes": 17448
67
+ "bytes": 18956
68
68
  }
69
69
  ],
70
70
  "examples": [
@@ -103,12 +103,12 @@
103
103
  {
104
104
  "id": "bulma-classes-component-map",
105
105
  "file": "references/bulma-classes/component-map.md",
106
- "bytes": 29661
106
+ "bytes": 29733
107
107
  },
108
108
  {
109
109
  "id": "bulma-classes-prop-map",
110
110
  "file": "references/bulma-classes/prop-map.md",
111
- "bytes": 36213
111
+ "bytes": 37225
112
112
  },
113
113
  {
114
114
  "id": "bulma-classes-unmappables",
@@ -162,7 +162,7 @@
162
162
  {
163
163
  "id": "modular-build",
164
164
  "file": "references/modular-build.md",
165
- "bytes": 12025
165
+ "bytes": 12075
166
166
  }
167
167
  ],
168
168
  "examples": []
@@ -176,7 +176,7 @@
176
176
  {
177
177
  "id": "css-variables",
178
178
  "file": "references/css-variables.md",
179
- "bytes": 44975
179
+ "bytes": 45565
180
180
  },
181
181
  {
182
182
  "id": "themeable-components",
@@ -34,6 +34,12 @@ export interface Modifier {
34
34
  name: string;
35
35
  value: string;
36
36
  };
37
+ /**
38
+ * The tag the component renders with this class's prop and no `as`, in
39
+ * place of the root's own (`Image` puts a ratio on a <figure>). Only one
40
+ * such class converts on an element.
41
+ */
42
+ rendersTag?: string;
37
43
  }
38
44
  export interface RootRecord {
39
45
  status: 'mapped' | 'todo' | 'plain' | 'fold';
@@ -1 +1 @@
1
- {"version":3,"file":"bulma-classes.d.ts","sourceRoot":"","sources":["../src/bulma-classes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bulma-classes.d.ts","sourceRoot":"","sources":["../src/bulma-classes.ts"],"names":[],"mappings":"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"}
@@ -112,9 +112,16 @@ function listTags(tags) {
112
112
  ? `${named.slice(0, -1).join(', ')} or ${named[named.length - 1]}`
113
113
  : named[0];
114
114
  }
115
- function reaches(entry) {
115
+ function reaches(entry, retagged) {
116
+ // The tag it renders with no `as`: the one a class picks, else its own.
117
+ const plain = retagged
118
+ ? `<${retagged}> with these classes`
119
+ : `<${entry.tag}>`;
116
120
  if (entry.as === 'any')
117
- return `<${entry.tag}>, or any tag through \`as\``;
121
+ return `${plain}, or any tag through \`as\``;
122
+ if (retagged && entry.as) {
123
+ return `${plain}, or ${listTags(entry.as)} through \`as\``;
124
+ }
118
125
  if (entry.sizeDrivesTag && entry.as) {
119
126
  return `the heading its \`size\` names (<${entry.tag}> with none), or ${listTags(entry.as)} through \`as\``;
120
127
  }
@@ -300,6 +307,8 @@ export function lookupClasses(table, input, tag) {
300
307
  write.prop,
301
308
  { value: write.value ?? true, token: root ?? '' },
302
309
  ]));
310
+ /** The one class that picks the component's tag, as the codemod takes it. */
311
+ let retaggedBy;
303
312
  const claiming = [
304
313
  ...tokens.filter(token => !own(table.replaced, token)),
305
314
  ...tokens.filter(token => own(table.replaced, token)),
@@ -320,9 +329,16 @@ export function lookupClasses(table, input, tag) {
320
329
  stays(token, `\`${taken.prop}\` is already set by \`${writes.get(taken.prop).token}\``);
321
330
  continue;
322
331
  }
332
+ // Only one class picks the tag, as only one sets a prop.
333
+ if (modifier.rendersTag && retaggedBy) {
334
+ stays(token, `\`${retaggedBy}\` already picks the tag`);
335
+ continue;
336
+ }
323
337
  for (const write of modifier.writes) {
324
338
  writes.set(write.prop, { value: write.value ?? true, token });
325
339
  }
340
+ if (modifier.rendersTag)
341
+ retaggedBy = token;
326
342
  // A class the component renders from an attribute of the element
327
343
  // inside (`is-multiple` beside a `<select multiple>`).
328
344
  const paired = Object.entries(entry.absorbs?.pairs ?? {}).find(([, pairedToken]) => pairedToken === token)?.[0];
@@ -451,15 +467,26 @@ export function lookupClasses(table, input, tag) {
451
467
  });
452
468
  }
453
469
  const size = writes.get('size')?.value;
454
- const renders = entry.sizeDrivesTag && tag !== 'p' && typeof size === 'string'
455
- ? `h${size}`
456
- : entry.tag;
470
+ // A class whose prop puts the component on another tag when no `as` is
471
+ // given (`Image`'s ratios on a <figure>).
472
+ const retagged = retaggedBy
473
+ ? own(entry.modifiers, retaggedBy)?.rendersTag
474
+ : undefined;
475
+ const renders = retagged ??
476
+ (entry.sizeDrivesTag && tag !== 'p' && typeof size === 'string'
477
+ ? `h${size}`
478
+ : entry.tag);
457
479
  const reachable = !tag ||
458
480
  renders === tag ||
459
481
  entry.as === 'any' ||
460
482
  (Array.isArray(entry.as) && entry.as.includes(tag));
461
483
  if (!reachable) {
462
- return result({ kind: 'wrong-tag', target, tag, reaches: reaches(entry) });
484
+ return result({
485
+ kind: 'wrong-tag',
486
+ target,
487
+ tag,
488
+ reaches: reaches(entry, retagged),
489
+ });
463
490
  }
464
491
  // A `className` it's given replaces its own class, so a class left over
465
492
  // keeps the element as markup.
@@ -488,7 +515,7 @@ export function lookupClasses(table, input, tag) {
488
515
  return result({
489
516
  kind: 'component',
490
517
  target,
491
- renders: reaches(entry),
518
+ renders: reaches(entry, retagged),
492
519
  ...about,
493
520
  });
494
521
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bestax-mcp",
3
- "version": "1.16.3",
3
+ "version": "1.16.4",
4
4
  "mcpName": "io.github.allxsmith/bestax-mcp",
5
5
  "description": "MCP server for @allxsmith/bestax-bulma — component props, examples, CSS variables and agent skills, served offline from a version-pinned index",
6
6
  "type": "module",
@@ -42,7 +42,7 @@
42
42
  "dependencies": {
43
43
  "@modelcontextprotocol/sdk": "^1.31.0",
44
44
  "zod": "^4.4.3",
45
- "@allxsmith/bestax-bulma": "^5.40.0"
45
+ "@allxsmith/bestax-bulma": "^5.41.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@jest/globals": "^30.0.0",