bestax-mcp 1.16.1 → 1.16.3

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/data/catalog.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 2,
3
3
  "generatedFrom": {
4
4
  "package": "@allxsmith/bestax-bulma",
5
- "version": "5.39.0"
5
+ "version": "5.40.0"
6
6
  },
7
7
  "docsBase": "https://bestax.io/docs",
8
8
  "categories": [
@@ -1200,8 +1200,8 @@
1200
1200
  "slug": "components/treeview",
1201
1201
  "import": "import { TreeView } from '@allxsmith/bestax-bulma';",
1202
1202
  "compound": true,
1203
- "propCount": 20,
1204
- "exampleCount": 8
1203
+ "propCount": 27,
1204
+ "exampleCount": 9
1205
1205
  },
1206
1206
  {
1207
1207
  "name": "UnorderedList",
@@ -163,7 +163,7 @@
163
163
  "name": "isLoading",
164
164
  "type": "boolean",
165
165
  "default": "false",
166
- "description": "Displays a loading spinner. Under `prefers-reduced-motion: reduce` the spinner stops and stays drawn, and in forced colors (Windows' contrast themes) the label stays hidden behind it (with bestax's CSS loaded).",
166
+ "description": "Displays a loading spinner, with everything inside the button hidden behind it, in forced colors (Windows' contrast themes) too. Under `prefers-reduced-motion: reduce` the spinner stops and stays drawn. Bulma alone hides only the label, so the rest needs bestax's CSS loaded.",
167
167
  "required": false,
168
168
  "inherited": false,
169
169
  "deprecated": false
@@ -193,7 +193,7 @@
193
193
  "name": "isLoading",
194
194
  "type": "boolean",
195
195
  "default": "false",
196
- "description": "Displays a loading spinner. Under `prefers-reduced-motion: reduce` the spinner stops and stays drawn, and in forced colors (Windows' contrast themes) the label stays hidden behind it (with bestax's CSS loaded).",
196
+ "description": "Displays a loading spinner, with everything inside the button hidden behind it, in forced colors (Windows' contrast themes) too. Under `prefers-reduced-motion: reduce` the spinner stops and stays drawn. Bulma alone hides only the label, so the rest needs bestax's CSS loaded.",
197
197
  "required": false,
198
198
  "inherited": false,
199
199
  "deprecated": false
@@ -387,7 +387,7 @@
387
387
  "name": "isLoading",
388
388
  "type": "boolean",
389
389
  "default": "false",
390
- "description": "Displays a loading spinner. Under `prefers-reduced-motion: reduce` the spinner stops and stays drawn, and in forced colors (Windows' contrast themes) the label stays hidden behind it (with bestax's CSS loaded).",
390
+ "description": "Displays a loading spinner, with everything inside the button hidden behind it, in forced colors (Windows' contrast themes) too. Under `prefers-reduced-motion: reduce` the spinner stops and stays drawn. Bulma alone hides only the label, so the rest needs bestax's CSS loaded.",
391
391
  "required": false,
392
392
  "inherited": true,
393
393
  "deprecated": false
@@ -563,7 +563,7 @@
563
563
  "name": "isLoading",
564
564
  "type": "boolean",
565
565
  "default": "false",
566
- "description": "Shows Bulma's spinner in place of the label and holds the toggle where it is: a click, Enter or Space does nothing, so neither `onClick` nor `onPressedChange` fires, until it's cleared. Set it while a change is saving. The button carries `aria-busy` and `aria-disabled` meanwhile, and stays focusable, so focus isn't lost mid-save. Under `prefers-reduced-motion: reduce` the spinner stops and stays drawn, and in forced colors (Windows' contrast themes) the label stays hidden behind it (with bestax's CSS loaded).",
566
+ "description": "Shows Bulma's spinner in place of its content and holds the toggle where it is: a click, Enter or Space does nothing, so neither `onClick` nor `onPressedChange` fires, until it's cleared. Set it while a change is saving. The button carries `aria-busy` and `aria-disabled` meanwhile, and stays focusable, so focus isn't lost mid-save. Everything inside it is hidden behind the spinner, in forced colors (Windows' contrast themes) too, and under `prefers-reduced-motion: reduce` the spinner stops and stays drawn. Bulma alone hides only the label, so the rest needs bestax's CSS loaded.",
567
567
  "required": false,
568
568
  "inherited": false,
569
569
  "deprecated": false
@@ -108,7 +108,7 @@
108
108
  "name": "isLoading",
109
109
  "type": "boolean",
110
110
  "default": "false",
111
- "description": "Displays a loading spinner. Under `prefers-reduced-motion: reduce` the spinner stops and stays drawn, and in forced colors (Windows' contrast themes) the label stays hidden behind it (with bestax's CSS loaded).",
111
+ "description": "Displays a loading spinner, with everything inside the button hidden behind it, in forced colors (Windows' contrast themes) too. Under `prefers-reduced-motion: reduce` the spinner stops and stays drawn. Bulma alone hides only the label, so the rest needs bestax's CSS loaded.",
112
112
  "required": false,
113
113
  "inherited": true,
114
114
  "deprecated": false
@@ -46,7 +46,7 @@
46
46
  "code": "function example() {\n const [open, setOpen] = useState(false);\n const closeModal = () => setOpen(false);\n\n return (\n <>\n <Button color=\"success\" onClick={() => setOpen(true)}>\n Show Modal\n </Button>\n <Modal isActive={open}>\n <Modal.Background onClick={closeModal} />\n <Modal.Content>\n <Box>\n <Title size=\"4\">Custom Content</Title>\n <Paragraph>Using compound components for full control!</Paragraph>\n </Box>\n </Modal.Content>\n <Modal.Close variant=\"floating\" onClick={closeModal} />\n </Modal>\n </>\n );\n}"
47
47
  }
48
48
  ],
49
- "accessibility": "- The modal root gets `role=\"dialog\"` and `aria-modal=\"true\"` while active — pass an explicit `role` to override (e.g. `Dialog` passes `role=\"presentation\"` on the `Modal` it wraps so its own `role=\"alertdialog\"` is the only one announced).\n- **Give the dialog an accessible name.** With the legacy API, `modalCardTitle` is wired up as `aria-labelledby` for you. With the compound API, pass your own `aria-label` or `aria-labelledby` on `<Modal>` — a `role=\"dialog\"` with no name is announced as just \"dialog\". An explicit `aria-labelledby` always wins over the generated one.\n- Escape closes the modal (calls `onClose`) by default; opt out with `closeOnEscape={false}`. One Escape closes the topmost overlay only, whichever open Modal, [Dialog](https://bestax.io/docs/api/components/dialog), [Sidebar](https://bestax.io/docs/api/components/sidebar) or full-page [Loading](https://bestax.io/docs/api/components/loading) opened last, and the next Escape closes the one under it. A modal on top with `closeOnEscape={false}` still keeps Escape from the layers under it. A [Toast](https://bestax.io/docs/api/components/toast) waits until no overlay is open.\n- The modal calls `preventDefault()` on the Escape it closes on, so a listener of your own that runs after it can tell the key was used.\n- A popup inside the modal takes its own Escape first. With an Autocomplete list, a Dropdown menu, a Popover or a date picker's calendar open, the first Escape closes that and the next one closes the modal.\n- A popup of your own inside a modal should call `preventDefault()` on the Escape it closes on. The modal leaves an Escape that was prevented alone, whether your popup hears it in a React `onKeyDown` or in a listener on `document`. Call it only when the popup is open and the key was pressed in it, so an Escape with nothing to close still closes the modal, and so does one pressed in a modal opened over your popup.\n- Body scroll is locked while the modal is active; opt out with `lockScroll={false}`. The lock is ref-counted and shared with `Dialog`, `Sidebar` and `Loading`, so whichever overlay closes first doesn't unlock the page underneath one that is still open.\n- Focus moves to the first enabled focusable element (or the modal itself) on open, and is restored to the previously focused element on close — unless another modal is still open on top, in which case that one keeps focus.\n- Tab and Shift+Tab cycle within the modal while it is the topmost modal overlay, so the keyboard order agrees with what `aria-modal=\"true\"` tells assistive technology. With another Modal or Dialog, a [Sidebar](https://bestax.io/docs/api/components/sidebar) with an overlay or a full-page Loading opened over it, the modal leaves Tab alone until that one closes. A Sidebar without an overlay isn't modal: opened over the modal, it takes Escape first, but the modal keeps holding Tab and keeps focus, so the sidebar doesn't take focus when it opens, and a Tab from its controls comes back into the modal. A focus trap of your own inside it, such as a portaled panel's, keeps a Tab it calls `preventDefault()` on, and the modal leaves focus where your trap put it. Note that `aria-modal` does not by itself make background content inert, and no `inert` attribute is applied, so background content remains reachable by pointer and by browser UI such as find-in-page.\n- The modal background closes the modal on click (`onClose` required).\n- Close buttons are provided for both modal-card and modal-content variants.\n\n:::note\nAlways provide an `onClose` handler for accessibility and to allow users to dismiss the modal.\n:::",
49
+ "accessibility": "- The modal root gets `role=\"dialog\"` and `aria-modal=\"true\"` while active — pass an explicit `role` to override (e.g. `Dialog` passes `role=\"presentation\"` on the `Modal` it wraps so its own `role=\"alertdialog\"` is the only one announced).\n- **Give the dialog an accessible name.** With the legacy API, `modalCardTitle` is wired up as `aria-labelledby` for you. With the compound API, pass your own `aria-label` or `aria-labelledby` on `<Modal>` — a `role=\"dialog\"` with no name is announced as just \"dialog\". An explicit `aria-labelledby` always wins over the generated one.\n- Escape closes the modal (calls `onClose`) by default; opt out with `closeOnEscape={false}`. One Escape closes the topmost overlay only, whichever open Modal, [Dialog](https://bestax.io/docs/api/components/dialog), [Sidebar](https://bestax.io/docs/api/components/sidebar) or full-page [Loading](https://bestax.io/docs/api/components/loading) opened last, and the next Escape closes the one under it. A modal on top with `closeOnEscape={false}` still keeps Escape from the layers under it. A [Toast](https://bestax.io/docs/api/components/toast) waits until no overlay is open.\n- The modal calls `preventDefault()` on the Escape it closes on, so a listener of your own that runs after it can tell the key was used.\n- A popup inside the modal takes its own Escape first. With an Autocomplete list, a Dropdown menu, a Popover or a date picker's calendar open, the first Escape closes that and the next one closes the modal.\n- A popup of your own inside a modal should call `preventDefault()` on the Escape it closes on. The modal leaves an Escape that was prevented alone, whether your popup hears it in a React `onKeyDown` or in a listener on `document`. Call it only when the popup is open and the key was pressed in it, so an Escape with nothing to close still closes the modal, and so does one pressed in a modal opened over your popup.\n- Body scroll is locked while the modal is active; opt out with `lockScroll={false}`. The lock is ref-counted and shared with `Dialog`, `Sidebar` and `Loading`, so whichever overlay closes first doesn't unlock the page underneath one that is still open.\n- Focus moves to the first tab stop (or the modal itself) on open, and is restored to the previously focused element on close — unless another modal is still open on top, in which case that one keeps focus.\n- Tab and Shift+Tab cycle within the modal while it is the topmost modal overlay, so the keyboard order agrees with what `aria-modal=\"true\"` tells assistive technology. They wrap at the same tab stops [`useFocusTrap`](https://bestax.io/docs/api/helpers/usefocustrap) does, so disabled, hidden and `tabIndex={-1}` elements are skipped, and so is inert content, such as a closed [Sidebar](https://bestax.io/docs/api/components/sidebar) inside the modal, and the content of a closed `<details>`. A radio group counts once. A Tab from outside the modal brings focus back in, and so does a Tab the browser takes out anyway, as Safari does past links when its settings say to. With another Modal or Dialog, a [Sidebar](https://bestax.io/docs/api/components/sidebar) with an overlay or a full-page Loading opened over it, the modal leaves Tab alone until that one closes. A Sidebar without an overlay isn't modal: opened over the modal, it takes Escape first, but the modal keeps holding Tab and keeps focus, so the sidebar doesn't take focus when it opens, and a Tab from its controls comes back into the modal. A focus trap of your own inside it, such as a portaled panel's, keeps a Tab it calls `preventDefault()` on, and the modal leaves focus where your trap put it. Note that `aria-modal` does not by itself make background content inert, and no `inert` attribute is applied, so background content remains reachable by pointer and by browser UI such as find-in-page.\n- The modal background closes the modal on click (`onClose` required).\n- Close buttons are provided for both modal-card and modal-content variants.\n\n:::note\nAlways provide an `onClose` handler for accessibility and to allow users to dismiss the modal.\n:::",
50
50
  "related": [
51
51
  "Button",
52
52
  "Dialog",
@@ -285,7 +285,7 @@
285
285
  "name": "isLoading",
286
286
  "type": "boolean",
287
287
  "default": "false",
288
- "description": "Displays a loading spinner. Under `prefers-reduced-motion: reduce` the spinner stops and stays drawn, and in forced colors (Windows' contrast themes) the label stays hidden behind it (with bestax's CSS loaded).",
288
+ "description": "Displays a loading spinner, with everything inside the button hidden behind it, in forced colors (Windows' contrast themes) too. Under `prefers-reduced-motion: reduce` the spinner stops and stays drawn. Bulma alone hides only the label, so the rest needs bestax's CSS loaded.",
289
289
  "required": false,
290
290
  "inherited": true,
291
291
  "deprecated": false
@@ -125,7 +125,7 @@
125
125
  "name": "isLoading",
126
126
  "type": "boolean",
127
127
  "default": "false",
128
- "description": "Shows Bulma's spinner in place of the label and holds the toggle where it is: a click, Enter or Space does nothing, so neither `onClick` nor `onPressedChange` fires, until it's cleared. Set it while a change is saving. The button carries `aria-busy` and `aria-disabled` meanwhile, and stays focusable, so focus isn't lost mid-save. Under `prefers-reduced-motion: reduce` the spinner stops and stays drawn, and in forced colors (Windows' contrast themes) the label stays hidden behind it (with bestax's CSS loaded).",
128
+ "description": "Shows Bulma's spinner in place of its content and holds the toggle where it is: a click, Enter or Space does nothing, so neither `onClick` nor `onPressedChange` fires, until it's cleared. Set it while a change is saving. The button carries `aria-busy` and `aria-disabled` meanwhile, and stays focusable, so focus isn't lost mid-save. Everything inside it is hidden behind the spinner, in forced colors (Windows' contrast themes) too, and under `prefers-reduced-motion: reduce` the spinner stops and stays drawn. Bulma alone hides only the label, so the rest needs bestax's CSS loaded.",
129
129
  "required": false,
130
130
  "inherited": false,
131
131
  "deprecated": false
@@ -23,7 +23,11 @@
23
23
  },
24
24
  {
25
25
  "title": "Folders That Load",
26
- "code": "function example() {\n interface Folder {\n id: string;\n name: string;\n hasFolders: boolean;\n }\n\n // A pretend server: each folder holds two more, three levels down.\n const listFolders = (id: string) =>\n new Promise<Folder[]>(resolve =>\n setTimeout(\n () =>\n resolve(\n ['a', 'b'].map(name => ({\n id: `${id}/${name}`,\n name: `Folder ${name.toUpperCase()}`,\n hasFolders: id.split('/').length < 3,\n }))\n ),\n 800\n )\n );\n\n const [expanded, setExpanded] = useState<string[]>([]);\n const [loaded, setLoaded] = useState<Record<string, Folder[]>>({});\n\n function changeOpen(next: string[]) {\n setExpanded(next);\n for (const id of next) {\n if (!loaded[id]) {\n listFolders(id).then(found =>\n setLoaded(current => ({ ...current, [id]: found }))\n );\n }\n }\n }\n\n const items = (folders: Folder[]): React.ReactNode =>\n folders.map(folder => (\n <TreeView.Item\n key={folder.id}\n value={folder.id}\n label={folder.name}\n icon=\"folder\"\n hasChildren={folder.hasFolders}\n >\n {loaded[folder.id]\n ? items(loaded[folder.id])\n : expanded.includes(folder.id) && (\n <TreeView.Item\n value={`${folder.id}:loading`}\n label=\"Loading…\"\n disabled\n />\n )}\n </TreeView.Item>\n ));\n\n return (\n <TreeView\n aria-label=\"Folders\"\n expanded={expanded}\n onExpandedChange={changeOpen}\n >\n {items([\n { id: 'home', name: 'Home', hasFolders: true },\n { id: 'shared', name: 'Shared', hasFolders: true },\n ])}\n </TreeView>\n );\n}"
26
+ "code": "function example() {\n interface Folder {\n id: string;\n name: string;\n hasFolders: boolean;\n }\n\n // A pretend server: each folder holds two more, three levels down.\n const listFolders = (id: string, fail: boolean) =>\n new Promise<Folder[]>((resolve, reject) =>\n setTimeout(() => {\n if (fail) {\n reject(new Error(`Couldn't reach ${id}`));\n return;\n }\n resolve(\n ['a', 'b'].map(name => ({\n id: `${id}/${name}`,\n name: `Folder ${name.toUpperCase()}`,\n hasFolders: id.split('/').length < 3,\n }))\n );\n }, 800)\n );\n\n const [expanded, setExpanded] = useState<string[]>([]);\n // A folder's children, or how loading them is going.\n const [found, setFound] = useState<\n Record<string, Folder[] | 'loading' | 'error'>\n >({});\n // Shared fails the first time, so there's a Retry to press.\n const [sharedFails, setSharedFails] = useState(true);\n\n function load(id: string) {\n const fail = id === 'shared' && sharedFails;\n if (fail) setSharedFails(false);\n setFound(current => ({ ...current, [id]: 'loading' }));\n listFolders(id, fail).then(\n folders => setFound(current => ({ ...current, [id]: folders })),\n () => setFound(current => ({ ...current, [id]: 'error' }))\n );\n }\n\n function changeOpen(next: string[]) {\n setExpanded(next);\n for (const id of next) {\n if (!found[id]) load(id);\n }\n }\n\n const items = (folders: Folder[]): React.ReactNode =>\n folders.map(folder => {\n const state = found[folder.id];\n return (\n <TreeView.Item\n key={folder.id}\n value={folder.id}\n label={folder.name}\n icon=\"folder\"\n hasChildren={folder.hasFolders}\n isLoading={state === 'loading'}\n loadError={\n state === 'error' ? `Couldn't load ${folder.name}` : undefined\n }\n onRetry={() => load(folder.id)}\n >\n {Array.isArray(state) && items(state)}\n </TreeView.Item>\n );\n });\n\n return (\n <TreeView\n aria-label=\"Folders\"\n expanded={expanded}\n onExpandedChange={changeOpen}\n >\n {items([\n { id: 'home', name: 'Home', hasFolders: true },\n { id: 'shared', name: 'Shared', hasFolders: true },\n ])}\n </TreeView>\n );\n}"
27
+ },
28
+ {
29
+ "title": "Folders That Load",
30
+ "code": "<TreeView\n aria-label=\"Dossiers\"\n loadingLabel=\"Chargement…\"\n retryLabel=\"Réessayer\"\n loadingAnnouncement={name => `Chargement de ${name}`}\n loadedAnnouncement={name => `${name} chargé`}\n>\n {/* … */}\n</TreeView>"
27
31
  },
28
32
  {
29
33
  "title": "An Outline With Expand All and Collapse All",
@@ -38,8 +42,15 @@
38
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>"
39
43
  }
40
44
  ],
41
- "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\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\nThe 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 is GrayText, and the focus ring, the chevrons and the guide lines\n stay visible, since they're outlines and borders. A mixed box keeps its dash.\n- **Motion:** the chevron turns with a short transition, which stops when the reader asks for\n reduced motion.",
42
- "related": ["Checkbox", "Collapse", "Menu", "Sidebar", "useBulmaClasses"],
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.",
46
+ "related": [
47
+ "Checkbox",
48
+ "Collapse",
49
+ "Loader",
50
+ "Menu",
51
+ "Sidebar",
52
+ "useBulmaClasses"
53
+ ],
43
54
  "storybook": "https://bestax.io/storybook/?path=/story/components-treeview--default",
44
55
  "summary": "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. It's the APG Tree View pattern, drawn to look like Bulma's menu. It isn't for site navigation: items aren't links, and a sidebar whose sections open and close is `Menu`'s job. The root owns which items are open, through `expanded`, `defaultExpanded` and `onExpandedChange`. `selectionMode` picks one of three trees. With `none`, nothing is picked. With `single`, one item is, and `selected` says which. With `checkbox`, each item has a box, a check reaches every enabled item under it, closed branches included, and a parent with some of its items checked is mixed. `selected` is read in a `single` tree only, and `checked` in a `checkbox` tree only. Selection never follows focus. Disabled items stay reachable from the keys, so a screen reader finds them. The tree needs a name, from `aria-label` or `aria-labelledby`, and a development build warns when it has neither.",
45
56
  "import": "import { TreeView } from '@allxsmith/bestax-bulma';",
@@ -159,6 +170,42 @@
159
170
  "inherited": false,
160
171
  "deprecated": false
161
172
  },
173
+ {
174
+ "name": "loadingLabel",
175
+ "type": "string",
176
+ "default": "'Loading…'",
177
+ "description": "What the row in a loading item's group says. In your app's language.",
178
+ "required": false,
179
+ "inherited": false,
180
+ "deprecated": false
181
+ },
182
+ {
183
+ "name": "retryLabel",
184
+ "type": "string",
185
+ "default": "'Retry'",
186
+ "description": "The Retry button's text on a failed load's row. In your app's language.",
187
+ "required": false,
188
+ "inherited": false,
189
+ "deprecated": false
190
+ },
191
+ {
192
+ "name": "loadingAnnouncement",
193
+ "type": "(name: string) => string",
194
+ "default": null,
195
+ "description": "What the live region says when an open item starts loading, built from the item's name. In your app's language. Default: `` `Loading ${name}` ``.",
196
+ "required": false,
197
+ "inherited": false,
198
+ "deprecated": false
199
+ },
200
+ {
201
+ "name": "loadedAnnouncement",
202
+ "type": "(name: string) => string",
203
+ "default": null,
204
+ "description": "What the live region says when that load finishes. In your app's language. Default: `` `${name} loaded` ``.",
205
+ "required": false,
206
+ "inherited": false,
207
+ "deprecated": false
208
+ },
162
209
  {
163
210
  "name": "className",
164
211
  "type": "string",
@@ -251,6 +298,33 @@
251
298
  "inherited": false,
252
299
  "deprecated": false
253
300
  },
301
+ {
302
+ "name": "isLoading",
303
+ "type": "boolean",
304
+ "default": "false",
305
+ "description": "Its children are on their way. While it's open, its group ends in a row that says so, and the tree's live region says it's loading, then that it's loaded once this goes back to `false`. A parent only: pair it with `hasChildren`, or with the children it already has.",
306
+ "required": false,
307
+ "inherited": false,
308
+ "deprecated": false
309
+ },
310
+ {
311
+ "name": "loadError",
312
+ "type": "React.ReactNode",
313
+ "default": null,
314
+ "description": "Its children didn't load. While it's open, its group ends in a row with this message, and a Retry button when there's an `onRetry`, and the live region reads the message out, so name the item in it (\"Couldn't load src\"), then says it's loaded once this goes with the item still open. A parent only, like `isLoading`. Nothing in it should be a link or a button: the row is what's pressed. `isLoading` wins while both are given.",
315
+ "required": false,
316
+ "inherited": false,
317
+ "deprecated": false
318
+ },
319
+ {
320
+ "name": "onRetry",
321
+ "type": "() => void",
322
+ "default": null,
323
+ "description": "Called by the Retry button on a failed load's row, and by Enter or Space there. Set `isLoading` while the retry runs, so the live region says it's loading, and reads the message out again if it fails again. The same message passed a second time with no loading between is no change the tree can see.",
324
+ "required": false,
325
+ "inherited": false,
326
+ "deprecated": false
327
+ },
254
328
  {
255
329
  "name": "disabled",
256
330
  "type": "boolean",
@@ -110,8 +110,12 @@ Centered; a collection of items → Card grid. For mixed requests, pick the domi
110
110
  other: `<TreeView aria-label="Files" selectionMode="single">` with `selected`/`onSelectedChange`
111
111
  picks one, and `selectionMode="checkbox"` with `checked`/`onCheckedChange` draws boxes with
112
112
  mixed parents, where a parent's value checks its whole branch. Map the data with a recursive
113
- function, and render an item's children whether it's open or not. Its items aren't links, so
114
- the app's sidebar and any list of pages stay on `Menu` with `Menu.Item href`.
113
+ function, and render an item's children whether it's open or not. A folder that loads when it
114
+ opens takes `hasChildren`, `isLoading` while its children are on their way, and
115
+ `loadError="Couldn't load src"` with `onRetry` when the load fails, setting `isLoading` again
116
+ while the retry runs: the tree draws the Loading… and Retry rows and announces them, so don't
117
+ hand-write a placeholder item. Its items aren't
118
+ links, so the app's sidebar and any list of pages stay on `Menu` with `Menu.Item href`.
115
119
  - Link lists (footer nav, sidebars): a bare `UnorderedList` of `ListItem`s is already
116
120
  marker-less and flush — Bulma's reset unstyles `ul` — so no prop or CSS is needed;
117
121
  bullets appear only inside `Content`.
@@ -400,7 +400,8 @@ Run on an HTML page (`bestax-migrate bulma-classes page.html`, or a folder with
400
400
  codemod writes a module beside the page and runs everything above on it, so a page gets the same
401
401
  TODOs its markup would as JSX. What a component can't carry gets an `html-*` rule. TODOs about the
402
402
  whole page sit above the component, the rest above its `return`, and each ends with the page and
403
- the line it came from (`(index.html:42)`). The page itself stays as it was, so the code a TODO
403
+ the line it came from (`(index.html:42)`), or every line, for a TODO several elements share
404
+ (`(index.html:42, 57)`). The page itself stays as it was, so the code a TODO
404
405
  points at is still there to read. A rule names only a tag, an attribute or a fixed word, never
405
406
  anything the page says.
406
407
 
@@ -414,6 +415,11 @@ A component renders into the `<body>` of the app's own document, so nothing in t
414
415
  it into the head; React 18 leaves it in the body, so use `document.title` in an effect there.
415
416
  - **`html-head:meta`**: `charset` and `viewport` go once in the app's `index.html`. A page's own
416
417
  (a description, Open Graph tags) go where the app sets each page's head.
418
+ - **`html-head:csp`**: a `<meta http-equiv="Content-Security-Policy">`. Its policy was written for
419
+ what the page loads, and copied into `index.html` as it is, it can block what the app loads
420
+ instead: a Vite dev server runs an inline script for React's fast refresh and opens a socket for
421
+ hot reloading, and the build serves the app's own scripts, styles and fonts. Write the app's own
422
+ policy, best sent as a response header, and try it under both the dev server and the build.
417
423
  - **`html-head:link`**: icons, preconnects and preloads are app-wide, so keep them in
418
424
  `index.html`; a `canonical` is per page.
419
425
  - **`html-head:style`**: the `<style>` block was dropped. Move its rules into the app's stylesheet.
@@ -586,7 +592,9 @@ read for the burger, a carousel and a theme toggle too, and its TODO names the f
586
592
  app loads paints the right one, and the toggle's state starts from what it wrote. The
587
593
  bestax-theming skill covers `Theme` and `colorMode`, and its `examples/dark-mode.tsx` has the
588
594
  script and the app's side.
589
- - **`html-script:json-ld`** is structured data for search engines. Render it where the app sets
595
+ - **`html-script:json-ld`** is structured data for search engines. Data about the whole site, or
596
+ about a single-page app's one page, goes in the app's `index.html` as it is, where a crawler that
597
+ runs no JavaScript still reads it. Data that differs from page to page goes where the app sets
590
598
  each page's head, as
591
599
  `<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }} />`.
592
600
  - **`html-script`**: any other inline script, or a `<script type>` that isn't code (a template,
@@ -604,8 +612,9 @@ The app's document has its own `<html>` and `<body>`, which a component can't re
604
612
  bestax's `Navbar` adds none there, whatever its `fixed` says.
605
613
  - **`html-document:data-theme`**, **`html-document:theme-dark`**, **`html-document:theme-light`**:
606
614
  the page's color scheme. Put it on the `<html>` in `index.html`, where the first paint has it.
607
- `<Theme colorMode="dark">` writes `data-theme` on `<html>` too, but from an effect, after a
608
- first frame in the visitor's OS scheme, so keep it for a scheme the visitor switches.
615
+ For a scheme the visitor switches, `Theme colorMode` writes the choice only after that paint,
616
+ so write a saved one before it too, as `html-script:theme` says and the bestax-theming skill's
617
+ `examples/dark-mode.tsx` does.
609
618
  - **`html-document:lang`**, **`html-document:dir`**: set them on the `<html>` in `index.html`.
610
619
  - **`html-document:class`**: the page's own classes on `<html>` or `<body>`. Put them on the
611
620
  app's own `<body>` (or `<html>`), or on an element around the page's content if the styles
@@ -656,6 +665,12 @@ than `this`. A `<body onload>` becomes an effect.
656
665
  - **`html-attr:name`**: an attribute JSX can't write (`@click`, `:class`, `x-on:click`), or one the
657
666
  element already had under another spelling, was left out. A framework's directive is code to port
658
667
  into the component.
668
+ - **`html-attr:stray`**: a stray character in a tag, with no letter or digit in it (`\` in
669
+ `<figure class="image" \>`, a typo for `/>`), which the browser read as the name of an empty
670
+ attribute. It was left out, so there is nothing to port; delete it from the page too.
671
+ - **`html-attr:ignored`**: an attribute HTML gives the element no meaning for, so the browser
672
+ ignored it, and React's types reject it there: `type` on a `<textarea>`. It was left out, so the
673
+ module typechecks. Nothing reads it, unless a stylesheet or script of the page's selects on it.
659
674
  - **`html-attr:unknown`**: an attribute React has no prop for by that name, written as it was.
660
675
  React renders it. Whether a TypeScript build takes it depends on the element (React's types
661
676
  list `valign` on a `<td>` and not on a `<th>`), so the TODO doesn't say; what to do depends on
@@ -133,9 +133,10 @@ Notes:
133
133
  `bulma/sass/components/dropdown` next to `scss/components/contextmenu`.
134
134
  - `Loader` is stock Bulma styled by `bulma/sass/elements/loader`, and the `isLoading`
135
135
  spinners on `Button`, `Control` and `Select` are Bulma's too. The `loader` extras partial
136
- stops all of them under `prefers-reduced-motion: reduce`, keeps each ring open in forced
137
- colors (Windows' contrast themes), and keeps a loading button's label hidden there, so
138
- include it (`as bestax-loader`) next to the Bulma modules in any build that shows one. Its
136
+ stops all of them under `prefers-reduced-motion: reduce`, hides everything inside a loading
137
+ button where Bulma hides only the label, and keeps each ring open in forced colors (Windows'
138
+ contrast themes), so include it (`as bestax-loader`) next to the Bulma modules in any build
139
+ that shows one. Its
139
140
  rules win over Bulma's either way, so the order of the two `@use` lines does not matter.
140
141
  - `Progress` and the skeletons are stock Bulma too (`bulma/sass/elements/progress`,
141
142
  `bulma/sass/base/skeleton`). The `progress` and `skeleton` extras partials
package/data/skills.json CHANGED
@@ -113,7 +113,7 @@
113
113
  {
114
114
  "id": "bulma-classes-unmappables",
115
115
  "file": "references/bulma-classes/unmappables.md",
116
- "bytes": 49495
116
+ "bytes": 50861
117
117
  },
118
118
  {
119
119
  "id": "css-migration",
@@ -162,7 +162,7 @@
162
162
  {
163
163
  "id": "modular-build",
164
164
  "file": "references/modular-build.md",
165
- "bytes": 11993
165
+ "bytes": 12025
166
166
  }
167
167
  ],
168
168
  "examples": []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bestax-mcp",
3
- "version": "1.16.1",
3
+ "version": "1.16.3",
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.39.0"
45
+ "@allxsmith/bestax-bulma": "^5.40.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@jest/globals": "^30.0.0",