automatica11y 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +25 -7
  2. package/package.json +5 -3
  3. package/skills/automatica11y-runner/SKILL.md +44 -13
  4. package/skills/automatica11y-runner/references/fixtures.md +38 -2
  5. package/src/commands/common.js +5 -2
  6. package/src/frameworks/index.js +42 -0
  7. package/src/frameworks/react.js +77 -0
  8. package/src/frameworks/vue.js +90 -0
  9. package/src/frameworks/wc.js +55 -0
  10. package/src/globals.d.ts +1 -0
  11. package/src/harness/bundle.js +7 -6
  12. package/src/harness/generate/dialects.js +64 -0
  13. package/src/harness/generate/index.js +12 -0
  14. package/src/harness/generate/jsx-recipes.js +224 -0
  15. package/src/harness/generate/jsx.js +76 -0
  16. package/src/harness/generate/kit.js +50 -0
  17. package/src/harness/generate/marking.js +64 -0
  18. package/src/harness/generate/probe.js +97 -0
  19. package/src/harness/generate/shared.js +12 -0
  20. package/src/harness/generate/wc-recipes.js +132 -0
  21. package/src/harness/npm-install.js +38 -7
  22. package/src/harness/settle.js +17 -0
  23. package/src/harness/storybook.js +1 -0
  24. package/src/harness/url.js +13 -3
  25. package/src/plan/classify.js +11 -4
  26. package/src/plan/mapping.js +10 -7
  27. package/src/plan/resolve-npm.js +15 -9
  28. package/src/plan/subpath.js +133 -0
  29. package/src/report/comparison.js +14 -9
  30. package/src/report/parts.js +36 -12
  31. package/src/run/audit-npm.js +118 -28
  32. package/src/run/generate-fixture.js +74 -0
  33. package/src/run/run-plan.js +19 -4
  34. package/src/run/summary.js +5 -0
  35. package/src/schema.js +30 -6
  36. package/src/tiers/computed/checks.js +44 -5
  37. package/src/tiers/computed/color.js +8 -0
  38. package/src/tiers/computed/index.js +2 -2
  39. package/src/tiers/computed/measure-kit.js +30 -10
  40. package/src/tiers/conditions/checks.js +283 -0
  41. package/src/tiers/conditions/index.js +30 -0
  42. package/src/tiers/conditions/kit.js +133 -0
  43. package/src/tiers/interactions/archetypes.js +111 -0
  44. package/src/tiers/interactions/helpers.js +56 -0
  45. package/src/tiers/interactions/index.js +16 -3
  46. package/src/harness/npm-react.js +0 -39
  47. package/src/harness/npm-wc.js +0 -30
package/README.md CHANGED
@@ -36,7 +36,7 @@ A target is `[label=]<spec>`. The label is optional, and names the target in the
36
36
  | A live page | `https://example.com/page` |
37
37
  | A local page or site | `./page.html` or `./dist`. A path with no prefix is relative to the working folder, so `dist` means `./dist`. Prefixes `../`, `/`, `~`, and `file:` work too. |
38
38
  | A Storybook | Its URL, or a local folder with `index.json` or `stories.json`. |
39
- | An npm package | `npm:name`, `npm:@scope/name`, or `npm:name@version`. React and web component libraries work. |
39
+ | An npm package | `npm:name`, `npm:@scope/name`, or `npm:name@version`. Add a sub-path to test one entry of a package: `npm:@scope/pkg/button` or `npm:@scope/pkg@1.2.3/button/v2`. React, Vue 3, and web component libraries work. |
40
40
 
41
41
  A bare word such as `button` is a path: the folder or file `./button`. Write `npm:button` to pick the package. The prefix is what chooses a package, so a folder with the same name never gets in the way. A bare word that isn't a path fails with a hint, such as "If you meant the npm package, write npm:react."
42
42
 
@@ -44,11 +44,21 @@ A local `.html` file is served over `http://localhost`, never `file://`. A stati
44
44
 
45
45
  ## What it checks.
46
46
 
47
- Four tiers run by default. Use `--tiers` to pick fewer.
47
+ Five tiers run by default. Use `--tiers` to pick fewer.
48
48
 
49
49
  - **Rules.** axe-core and IBM Equal Access run side by side. They overlap, and each catches things the other misses. Their findings are reported separately and never added together. axe-core reports an impact (`minor` to `critical`). IBM reports its Toolkit level, a staged adoption scale where level 1 is essential, high-impact requirements. The two scales aren't comparable.
50
- - **Interactions.** Keyboard and focus checks for nine archetypes: button, link, dialog, menu, tabs, combobox, form-field, accordion, and tooltip. Each check runs on a fresh page.
50
+ - **Interactions.** Keyboard and focus checks for ten archetypes: button, link, dialog, menu, tabs, combobox, form-field, accordion, tooltip, and live-region (a message that appears or changes without moving focus, such as an alert or status message). Each check runs on a fresh page.
51
51
  - **Computed checks.** automatica11y's own measurements from resolved styles in the browser, for the trigger of each archetype fixture: text contrast in rest, hover, keyboard focus, and pressed states (1.4.3), the contrast of the control's edge, fill, or icon (1.4.11), and the contrast and thickness of the focus indicator (1.4.11, and 2.4.13 at level AAA). A page that uses a gradient, an image, or transparency behind the control can't be reduced to one color, so that check reports `undetermined`, which is a gap and never a pass. These results are reported on their own and never added to the rule engines' counts.
52
+ - **Conditions.** How the page holds up under a user's settings and environment. Each check opens fresh copies of the page and says what happened:
53
+ - `prefers-reduced-motion: reduce` (2.3.3, 2.2.2): do animations that move or repeat stop or go away? Motion driven by JavaScript timers isn't visible to this check.
54
+ - `prefers-color-scheme: dark` (1.4.3): if the page changes in dark mode, does every piece of text keep its contrast? A page that doesn't adapt isn't failed.
55
+ - `prefers-contrast: more` (1.4.3, 1.4.6): if the page responds, does the lowest text contrast stay above the minimum and not drop? The check says whether enhanced contrast (7:1) is reached. `prefers-contrast: less` (1.4.3): if the page softens, does text stay above the minimum? A page that doesn't respond isn't failed.
56
+ - `prefers-reduced-transparency: reduce` (1.4.3, 1.4.11): do surfaces that hold text stop being see-through (translucent backgrounds, backdrop blur)? This preference isn't a WCAG requirement. It matters because see-through backgrounds make text contrast unpredictable.
57
+ - Forced colors (1.4.11, 2.4.7): is the focus indicator still visible? A ring drawn with `box-shadow` disappears in forced colors, so use an outline.
58
+ - Reflow at 320 CSS pixels (1.4.10): does the page scroll sideways, or does anything reach past the right edge? Two-dimensional content such as data tables and maps is exempt, so a person judges those.
59
+ - Text spacing (1.4.12): with the spacing the criterion names, does any element cut off its text? Overlapping text isn't checked.
60
+
61
+ Conditions run on whole pages and on component fixtures. Storybook stories are reported as not applicable.
52
62
  - **Virtual screen reader.** The announcements a simulated screen reader makes, recorded as data. The output is simulated. It isn't a real screen reader, and real ones announce things differently.
53
63
 
54
64
  Every result says what it ran, or why it didn't. A gap, a failure, or a result that can't be tested is a finding. It never counts as a pass.
@@ -60,10 +70,11 @@ Every result says what it ran, or why it didn't. A gap, a failure, or a result t
60
70
  | `--wcag 2.0\|2.1\|2.2` | `2.2` | The WCAG version. |
61
71
  | `--level A\|AA\|AAA` | `AA` | The conformance level. IBM Equal Access has no AAA rules, so it runs its AA rules and says so. |
62
72
  | `--engine axe,ibm` | both | Which rule engines run. |
63
- | `--tiers rules,interactions,computed,vsr` | all | Which tiers run. |
73
+ | `--tiers rules,interactions,computed,conditions,vsr` | all | Which tiers run. |
64
74
  | `--archetypes a,b` | all | Limit npm and Storybook targets to these archetypes. |
65
75
  | `--lib-a11y on,off` | both | For libraries with opt-in accessibility features. See [the fixture guide](skills/automatica11y-runner/references/fixtures.md). |
66
76
  | `--mapping <file>` | none | A mapping file for npm targets. |
77
+ | `--no-generate` | off | Don't build fixtures for npm packages. Only authored fixtures and the `button` and `link` templates run. |
67
78
  | `--max-stories <n>` | `200` | The Storybook story cap. The cap spreads over components. |
68
79
  | `--out <dir>` | `./a11y-report` | Where results go. |
69
80
  | `--plan` | off | Classify the targets and write `plan.json`, then stop. Nothing installs and no browser launches. |
@@ -96,7 +107,14 @@ One failing target doesn't stop a comparison. It's recorded with its reason, and
96
107
 
97
108
  The tool installs each package into its own temporary folder (with install scripts turned off), loads it in the browser, and finds its exports or custom elements. It writes its guesses to `mapping.json`.
98
109
 
99
- It builds `button` and `link` tests on its own. Every other archetype is built from parts that differ by library (`Dialog.Root`, `Dialog.Trigger`, and so on), so it needs a **fixture**: a small file you or your agent write, following [the fixture guide](skills/automatica11y-runner/references/fixtures.md). Put fixtures at `fixtures/<target id>/<archetype>.jsx` (`.js` for web components) and run again. An archetype without a fixture is a gap in the report.
110
+ Each archetype's fixture comes from the first of these that applies:
111
+
112
+ 1. **Authored.** A file you or your agent write at `fixtures/<target id>/<archetype>.jsx` (`.js` for web components). The same `.jsx` works for React and for Vue 3, following [the fixture guide](skills/automatica11y-runner/references/fixtures.md). It always wins.
113
+ 2. **Template.** The tool builds `button` and `link` tests from the export name alone.
114
+ 3. **Generated.** For dialog, menu, tooltip, tabs, accordion, combobox, form-field, and live-region, the tool builds candidate fixtures from what the package exports. It looks for compound parts by common names (a root, a trigger, a content part, a title, a close part, and so on), either as `Dialog.Root` or as `DialogRoot`, and it tries the usual ways of switching a component on (an `open` prop and a close handler). For web components it reads what each element says about itself: its observed attributes, its members, and its slots. It then bundles each candidate, loads it, and checks that exactly one element is the trigger, that nothing logged an error, that activating the trigger shows a root, and that the root carries a role that fits. The first candidate that passes is used. If none does, the archetype is a gap, and the report lists what was tried and why each attempt failed.
115
+ 4. **Gap.** Anything else is a gap in the report.
116
+
117
+ A generated fixture is a guess about how the library is meant to be assembled, so a failure may come from how it was wired and not from the library. Reports mark generated results and treat them as lower evidence than an authored fixture. The source of each generated fixture is written to `generated/<target id>/<archetype>.jsx` beside the report. Copy one to `fixtures/<target id>/` and edit it to make it an authored fixture. Use `--no-generate` to turn generation off, for example when you want a strict comparison of authored fixtures only.
100
118
 
101
119
  Fixtures are code that the tool bundles and runs in a browser on your machine. Write them from the library's documentation, and read ones you didn't write.
102
120
 
@@ -119,9 +137,9 @@ The guidance ships with the tool, so it always matches the version you run. Tell
119
137
  ## Limits.
120
138
 
121
139
  - Automated rules find only part of what WCAG covers. They can't judge whether alt text is meaningful, whether link and heading text make sense in context, cognitive load, real focus and reading order in use, or how real screen readers behave. A person has to check those.
122
- - Components are tested in the states a fixture shows. Dialogs, menus, tooltips, and comboboxes run closed and open. Other states aren't visited.
140
+ - Components are tested in the states a fixture shows. Dialogs, menus, tooltips, and comboboxes run closed and open, and live regions run before and after the message. Other states aren't visited.
123
141
  - Content on a canvas with no alternative, or inside a closed shadow root, can't be tested, and the report says so. The virtual screen reader can't read inside shadow roots at all.
124
- - Vue, Svelte, Angular, and other frameworks report "unsupported framework."
142
+ - Svelte, Angular, Vue 2, and other frameworks report "unsupported framework." Their Storybooks work, because a Storybook renders stories in a page whatever the framework.
125
143
  - Native screen readers aren't part of this version.
126
144
  - Results are a snapshot. The tools run at their latest versions, and the report records them.
127
145
 
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "automatica11y",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "Test and compare the accessibility of web pages, Storybook builds, and npm component libraries.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
8
- "automatica11y": "bin/automatica11y.js"
8
+ "automatica11y": "bin/automatica11y.js",
9
+ "a11y": "bin/automatica11y.js"
9
10
  },
10
11
  "files": [
11
12
  "bin",
@@ -35,7 +36,8 @@
35
36
  "@types/node": "^26.6.4",
36
37
  "react": "^19.3.0",
37
38
  "react-dom": "^19.3.0",
38
- "typescript": "^7.0.2"
39
+ "typescript": "^7.0.2",
40
+ "vue": "^3.5.43"
39
41
  },
40
42
  "publishConfig": {
41
43
  "access": "public"
@@ -16,13 +16,13 @@ The tool is **not** an attestation or certification tool. Automated checks cover
16
16
 
17
17
  Do sections 1 and 2 before you run an audit. A question to the user about a missing target or an unsupported setting (section 3) can come before or after them.
18
18
 
19
- This skill works with automatica11y **0.3.x**. Run:
19
+ This skill works with automatica11y **0.4.x**. Run:
20
20
 
21
21
  ```bash
22
22
  npx --yes automatica11y@latest --version
23
23
  ```
24
24
 
25
- If the command exits with an error or prints nothing, tell the user it failed, include the error text, and stop. If the output doesn't start with `0.3.`, stop. Tell the user the version you got and the series this copy expects (`0.3.x`). If this copy came from a file, offer to read the matching steps with `npx --yes automatica11y@latest guide skill`. Don't run an audit until the user confirms how to proceed.
25
+ If the command exits with an error or prints nothing, tell the user it failed, include the error text, and stop. If the output doesn't start with `0.4.`, stop. Tell the user the version you got and the series this copy expects (`0.4.x`). If this copy came from a file, offer to read the matching steps with `npx --yes automatica11y@latest guide skill`. Don't run an audit until the user confirms how to proceed.
26
26
 
27
27
  ## 2. Check the setup
28
28
 
@@ -48,7 +48,7 @@ Use `audit` for one target and `compare` for two or more, even when they're diff
48
48
  | A live page | `https://example.com/page` |
49
49
  | A local page or site | `./page.html` or `./dist`. A path with no prefix is relative to the working folder. |
50
50
  | A Storybook | its URL, or a local folder with `index.json` |
51
- | An npm package | `npm:name`, `npm:@scope/name`, or `npm:name@version` |
51
+ | An npm package | `npm:name`, `npm:@scope/name`, or `npm:name@version`. To test one entry of a package, add a sub-path after the version: `npm:@scope/pkg/button` or `npm:@scope/pkg@1.2.3/button/v2`. The sub-path is what a person would import (`import ... from "@scope/pkg/button"`), and it has to be something the package exports. |
52
52
 
53
53
  A bare word such as `button` is a path: the folder or file `./button`. Always write an npm package with the `npm:` prefix. If a word could mean a package or a folder, ask the user which one before you run anything.
54
54
 
@@ -59,10 +59,11 @@ Options you can set, and nothing else:
59
59
  | `--wcag 2.0\|2.1\|2.2` | `2.2` | The user names a WCAG version. |
60
60
  | `--level A\|AA\|AAA` | `AA` | The user names a level. IBM Equal Access has no AAA rules, so it runs its AA rules and says so. |
61
61
  | `--engine axe,ibm` | both | The user wants one rule engine. |
62
- | `--tiers rules,interactions,computed,vsr` | all four | The user wants fewer checks. |
63
- | `--archetypes a,b` | all | The user cares about some components. Choose from button, link, dialog, menu, tabs, combobox, form-field, accordion, tooltip, chart. |
62
+ | `--tiers rules,interactions,computed,conditions,vsr` | all five | The user wants fewer checks. |
63
+ | `--archetypes a,b` | all | The user cares about some components. Choose from button, link, dialog, menu, tabs, combobox, form-field, accordion, tooltip, live-region, chart. |
64
64
  | `--lib-a11y on,off` | both | Only for libraries with opt-in accessibility features. |
65
65
  | `--mapping <file>` | none | You wrote or edited a mapping file. |
66
+ | `--no-generate` | off | The user wants only authored fixtures and the `button` and `link` templates, with no generated ones. |
66
67
  | `--max-stories <n>` | 200 | A large Storybook. |
67
68
  | `--out <dir>` | `./a11y-report` | The user names a folder. |
68
69
  | `--plan` | off | You want to see how targets classify without running anything. |
@@ -80,20 +81,26 @@ Don't set the fail flags unless the user asks for gating. They change the exit c
80
81
 
81
82
  ## 4. npm packages need fixtures
82
83
 
83
- A package's components can't be guessed from its name. The first run installs the package, loads it in the browser, finds its exports (or custom elements), and writes a candidate mapping to `<out>/mapping.json`. It tests `button` and `link` from a template. Every other archetype needs a **fixture**, a small file that assembles the component the way the library intends.
84
+ A package's components can't be guessed from its name alone. The first run installs the package, loads it in the browser, finds its exports (or custom elements), and writes a candidate mapping to `<out>/mapping.json`. It then fills each archetype from the first source that applies:
84
85
 
85
- 1. Run the audit once. Read `<out>/mapping.json` and the report's **Archetypes** table.
86
+ - **Authored.** A **fixture** you write: a small file that assembles the component the way the library intends. It always wins.
87
+ - **Template.** The tool builds `button` and `link` from the export name.
88
+ - **Generated.** For dialog, menu, tooltip, tabs, accordion, combobox, form-field, and live-region, the tool builds candidates from the package's compound parts (or from what a custom element says about itself), runs each in the browser, and keeps the first that behaves: one trigger, no errors, a root that appears with a fitting role. The `--no-generate` option turns this off.
89
+ - **Gap.** If none of those worked, the report lists what was tried and why each attempt failed.
90
+
91
+ 1. Run the audit once. Read `<out>/mapping.json` and the report's **Archetypes** table, which has a **Fixture** column.
86
92
  2. Treat the mapping as a guess. Check each `export` or `tag` against what the user asked about.
87
- 3. Each archetype in `mapping.json` has a status. `needs-fixture` means the tool found the component but can't build it from a template. `no-match` means no export or custom element looks like that archetype. For each archetype marked `needs-fixture` that matters to the request, write `fixtures/<target id>/<archetype>.jsx` (`.js` for web components) in the working directory. Follow the contract and the examples below. `references/fixtures.md` has more: the mapping file, states, and library accessibility options. It should sit next to this file. If you can't open it, run `npx --yes automatica11y@latest guide fixtures` to print it. The contract here is enough for the first fixture.
93
+ 3. Each archetype in `mapping.json` has a status. `generated` means the tool built a fixture that passed its checks, and the source is in `<out>/generated/`. `needs-fixture` means the tool found the component but couldn't build a working fixture. `no-match` means no export or custom element looks like that archetype. For each archetype marked `needs-fixture` that matters to the request, write `fixtures/<target id>/<archetype>.jsx` (`.js` for web components) in the working directory. Follow the contract and the examples below. `references/fixtures.md` has more: the mapping file, states, and library accessibility options. It should sit next to this file. If you can't open it, run `npx --yes automatica11y@latest guide fixtures` to print it. The contract here is enough for the first fixture.
88
94
  4. Run the same command again. The tool finds fixtures in that folder without `--mapping`.
89
- 5. Don't invent fixtures for archetypes the user didn't ask about. For a `no-match` archetype, write a fixture only if the library's documentation names a component, or a documented way, to make it. Otherwise leave it as a gap. A gap is an honest result.
95
+ 5. For an archetype that matters, read the generated source before you rely on it. If it looks wrong for the library, write an authored fixture from the documentation. To keep a generated one, copy it from `<out>/generated/<target id>/` into `fixtures/<target id>/`.
96
+ 6. Don't invent fixtures for archetypes the user didn't ask about. For a `no-match` archetype, write a fixture only if the library's documentation names a component, or a documented way, to make it. Otherwise leave it as a gap. A gap is an honest result.
90
97
 
91
98
  **The fixture contract, in brief.**
92
99
 
93
100
  - The default export renders the archetype in its starting state. For web components, the default export is the function `mount(container)`.
94
101
  - Mark **exactly one** element `data-a11y-trigger`. It's what a person would focus and activate. If nothing can be activated, as with a chart, put both attributes on the same outermost element.
95
102
  - Mark the main surface `data-a11y-root`, on the element that carries the role (`dialog`, `menu`, `tooltip`), not on an overlay or portal wrapper. It may appear only after the trigger fires, and it may render in a portal.
96
- - Mount without console errors. Don't import CSS.
103
+ - Mount without console errors. If the library's documentation says to load a stylesheet, import it. When it comes from another package, list that package under `install` in the mapping file (`references/fixtures.md` has the shape).
97
104
  - The attributes have to reach the DOM. If a component drops unknown props, use the library's documented way to render your own element in its place (such as `asChild` in Radix, or the `as` prop in Headless UI), and mark that element. If the library has none, leave the archetype as a gap. Don't wrap the library's component in an element you add and mark that.
98
105
 
99
106
  A React fixture. JSX works without importing React. Import the library from its package name, and the tool installs it:
@@ -118,6 +125,28 @@ export default function Fixture() {
118
125
  }
119
126
  ```
120
127
 
128
+ A Vue 3 fixture. It's also JSX, and a function default export is a functional component. The tool turns JSX into `h()` calls and supplies `h` and `Fragment`, so **don't import `h`**. Children become the default slot. A fixture that needs a plugin or global setup can also export `setup(app)`, which runs before the app mounts. Import the library from its package name, and the tool installs it along with Vue:
129
+
130
+ ```jsx
131
+ import { DialogClose, DialogContent, DialogDescription, DialogOverlay, DialogPortal, DialogRoot, DialogTitle, DialogTrigger } from "reka-ui";
132
+
133
+ export default function Fixture() {
134
+ return (
135
+ <DialogRoot>
136
+ <DialogTrigger data-a11y-trigger>Open dialog</DialogTrigger>
137
+ <DialogPortal>
138
+ <DialogOverlay />
139
+ <DialogContent data-a11y-root>
140
+ <DialogTitle>Edit profile</DialogTitle>
141
+ <DialogDescription>Update your details.</DialogDescription>
142
+ <DialogClose>Close</DialogClose>
143
+ </DialogContent>
144
+ </DialogPortal>
145
+ </DialogRoot>
146
+ );
147
+ }
148
+ ```
149
+
121
150
  A web component fixture. The tool imports the package first, so its elements are defined before `mount` runs. The `data-a11y-trigger` and `data-a11y-root` attributes can sit on a host, a slotted child, or an element inside an open shadow root. A closed shadow root hides its content from every tool:
122
151
 
123
152
  ```js
@@ -164,8 +193,10 @@ Write the narrative from `results.json`. Never write from memory, and never repe
164
193
  10. If the comparison mixes component targets and page targets, open with a warning that the evidence isn't equivalent.
165
194
  11. Label every rule finding with its engine. Report axe-core and IBM Equal Access separately. Never add their counts together. Impact is axe-core's own label. IBM Toolkit level is IBM's staged adoption scale (1 is essential, high-impact requirements). Don't convert one into the other.
166
195
  12. Report computed checks (contrast measured from resolved styles) in their own section. Give the measured ratio next to the ratio the criterion needs, and name the state or the method. Never add them to axe-core or IBM counts. Treat `undetermined` as a gap, never as a pass. When a control has visible text and a pale edge, the result is `not-applicable`, because the text identifies the control. Say that, and leave it for a person to confirm.
167
- 13. When you name a WCAG criterion, use the number and name exactly as `report.md` prints them, and keep its "WCAG data" credit to the W3C. Don't write criterion names or levels from memory.
168
- 14. End with a plain method note. Say what automated tools can't catch: whether alt text is meaningful, whether link and heading text make sense in context, cognitive load, real focus and reading order in use, and how real screen readers behave. Those need a person.
196
+ 13. Report conditions checks (reduced motion, dark mode, more and less contrast, reduced transparency, forced colors, reflow at 320 pixels, text spacing) in their own section, with the numbers from `results.json`. Never add them to axe-core or IBM counts. "Not applicable" means the page doesn't use the feature, such as a page with no dark theme, so don't list it as a failure or a pass. Say what each check can't see: JavaScript-driven motion, overlapping text, and two-dimensional content that WCAG exempts from reflow.
197
+ 14. Say which results come from generated fixtures. A generated fixture is a guess about how the library is assembled, so a failure may come from the wiring and not from the library. Call those results lower evidence than an authored fixture, name the recipe from the **Fixture** column, and don't compare a generated result with an authored one as if they were equal. If the user wants a result to stand on its own, write an authored fixture for it.
198
+ 15. When you name a WCAG criterion, use the number and name exactly as `report.md` prints them, and keep its "WCAG data" credit to the W3C. Don't write criterion names or levels from memory.
199
+ 16. End with a plain method note. Say what automated tools can't catch: whether alt text is meaningful, whether link and heading text make sense in context, cognitive load, real focus and reading order in use, and how real screen readers behave. Those need a person.
169
200
 
170
201
  Use the structure of `report.md`. Quote selectors and rule IDs exactly as `results.json` has them.
171
202
 
@@ -173,7 +204,7 @@ Use the structure of `report.md`. Quote selectors and rule IDs exactly as `resul
173
204
 
174
205
  Say these things plainly. Don't soften them, and don't fill in a result.
175
206
 
176
- - **Unsupported framework.** The package needs a framework other than React or web components. Name it. v1 covers React and web components.
207
+ - **Unsupported framework.** The package needs a framework other than React, Vue 3, or web components. Name it, and say which version is unsupported (Vue 2, for example). This version covers React, Vue 3, and web components. A Storybook for the library still works, whatever the framework.
177
208
  - **Not applicable.** The package has no rendering surface, such as a utility library. There's nothing to test.
178
209
  - **Not testable.** The content is a canvas with no alternative, or sits in a closed shadow root. The rule engines can't see it, so the result is untested, not clean. The virtual screen reader also can't read open shadow roots.
179
210
  - **Gap.** The archetype has no usable fixture or no matching export. Say what the archetype needs.
@@ -6,7 +6,7 @@ Write fixtures from the library's public documentation. Don't copy from memory w
6
6
 
7
7
  ## Where fixtures go
8
8
 
9
- `fixtures/<target id>/<archetype>.jsx` for React, in the working folder: the folder where you run `automatica11y audit` or `automatica11y compare`. Use `.js` for web components. The target id is the label (`radix=npm:@radix-ui/react-dialog` has the id `radix`), or the package name with `/` turned into `-`, such as `radix-ui-react-dialog`. The report's **Targets** list shows each id.
9
+ `fixtures/<target id>/<archetype>.jsx` for React and Vue 3, in the working folder: the folder where you run `automatica11y audit` or `automatica11y compare`. Use `.js` for web components. The target id is the label (`radix=npm:@radix-ui/react-dialog` has the id `radix`), or the package name with `/` turned into `-`, such as `radix-ui-react-dialog`. The report's **Targets** list shows each id.
10
10
 
11
11
  To keep a fixture somewhere else, name it in a mapping file and pass `--mapping`:
12
12
 
@@ -21,13 +21,19 @@ To keep a fixture somewhere else, name it in a mapping file and pass `--mapping`
21
21
 
22
22
  The keys are target ids, then archetypes. `export` (React) or `tag` (web components) changes which export or element a template uses.
23
23
 
24
+ ## Start from a generated fixture
25
+
26
+ You often don't need to write a fixture from nothing. For dialog, menu, tooltip, tabs, accordion, combobox, form-field, and live-region, the tool tries to build one from the package's exports and keeps it only if it works. The source is written to `<out>/generated/<target id>/<archetype>.jsx` (`.js` for web components). To make one an authored fixture, copy it to `fixtures/<target id>/` and edit it. It will carry a block of marking code at the top (`startMarking`). That code marks the trigger and root by role, because a generated fixture can't know how the library forwards props. In an authored fixture you can drop it and put `data-a11y-trigger` and `data-a11y-root` on the right elements yourself, which is clearer and doesn't depend on timing.
27
+
28
+ If the tool couldn't build a working fixture, the report's **Archetypes** table says what it tried and why each attempt failed. That's a good place to start reading.
29
+
24
30
  ## The contract
25
31
 
26
32
  1. The file's default export renders the archetype in its **initial state**.
27
33
  2. Mark **exactly one** element with `data-a11y-trigger`. This is what a person would focus and activate: the button that opens a dialog, the first tab, the combobox input, the form control. If nothing in the archetype can be activated, as with a chart, put `data-a11y-trigger` and `data-a11y-root` on the same element: the outermost one the library renders.
28
34
  3. Mark the **primary surface** with `data-a11y-root`. Put it on the element that carries the role (`role="dialog"`, `role="menu"`, `role="tooltip"`), not on an overlay or a portal wrapper. It's fine if the root doesn't exist until the trigger fires, and fine if it renders in a portal. The tool looks in the whole document.
29
35
  4. Mount without console errors. The tool treats errors as a broken fixture.
30
- 5. Don't import CSS. The tool doesn't link it.
36
+ 5. A stylesheet the fixture imports is bundled and linked. If the library needs one (a token file, a theme) and its documentation says to load it, import it. If that stylesheet is in another package, list the package under `install` in the mapping (see below). Without the stylesheet the library renders unstyled, and the results describe that, not the library.
31
37
  6. Include every part the library's documentation marks as required (for example, a dialog's title and description), so the component doesn't log warnings.
32
38
 
33
39
  The attributes have to reach the DOM. Pass `data-a11y-trigger` to the component that renders the real element. If a component drops unknown props, use the library's documented way to render your own element in its place (for example, `asChild` in Radix, or the `as` prop in Headless UI), and put the attribute on that native element. If the library has no such way, leave the archetype as a gap. Don't wrap the library's component in an element you add and mark that element, because the fixture would then test your element and not the library.
@@ -38,6 +44,8 @@ A React example and a web component example are in `SKILL.md`, in the section on
38
44
 
39
45
  For React, JSX works without importing React. Import the library from its package name. The tool installs it for you.
40
46
 
47
+ For Vue 3, a fixture is also JSX, and the default export is a component: a function that returns JSX is a functional component, or you can export a component object. The tool turns JSX into `h()` calls and supplies `h` and `Fragment` itself, so don't import them. Importing `h` fails the bundle with "symbol already declared". JSX children become the default slot. Vue's reactivity works, so `import { ref } from "vue"` for state, and wrap the JSX in `defineComponent({ setup() { ... return () => (<jsx/>) } })`. A library that needs a plugin, a theme, or global components can export a function named `setup(app)`, which runs before the app mounts, for example `export function setup(app) { app.use(plugin) }`. Named slots and `v-model` aren't JSX syntax. Pass a slots object as the children, or set `modelValue` and `onUpdate:modelValue` with a spread, for example `{...{ "onUpdate:open": (next) => (open.value = next) }}`.
48
+
41
49
  For web components, make `mount(container)` the file's default export. It adds the archetype to the container. The tool imports the package first, so its elements are defined before `mount` runs. The `data-a11y-trigger` and `data-a11y-root` attributes can sit on a host element, a slotted child, or an element inside an **open** shadow root. A **closed** shadow root hides its content from every tool, so the report lists it as not testable.
42
50
 
43
51
  ## Library accessibility options
@@ -50,10 +58,38 @@ export default function Fixture({ libA11y }) {
50
58
  }
51
59
  ```
52
60
 
61
+ ## Companion packages
62
+
63
+ Some libraries need another package beside them, such as a design token stylesheet or a theme. Name it under `install` in the mapping file, next to `fixture`. The first key is the target id, and the second is the archetype. The tool installs it into the same folder as the library, with install scripts off, and the fixture can import it:
64
+
65
+ ```json
66
+ {
67
+ "lib": {
68
+ "live-region": { "fixture": "fixtures/lib/live-region.js", "install": ["@scope/tokens"] }
69
+ }
70
+ }
71
+ ```
72
+
73
+ ```js
74
+ import "@scope/tokens/style.css";
75
+ ```
76
+
77
+ List only what the library's documentation asks you to load. Each entry has to be a package name with an optional version. Flags, paths, and URLs are refused.
78
+
53
79
  ## States
54
80
 
55
81
  For a dialog, menu, tooltip, or combobox, the tool tests the closed state, then activates the trigger and tests the open state. For an accordion it tests collapsed, then expanded. The tool activates the trigger by clicking it. For a tooltip it focuses the trigger instead, so a tooltip has to open on focus. If it opens only on pointer hover, the open state never appears. Make sure activating the trigger really opens the surface, and that the element with `data-a11y-root` is then attached to the document, isn't `display: none` or `hidden`, and isn't `visibility: hidden`. The tool also counts the surface as open if the trigger has `aria-expanded="true"`. If neither holds, the open state is reported as failed, with the reason. The interaction checks also press keys and hover, but they don't change how the open state is reached.
56
82
 
83
+ ## Live regions
84
+
85
+ The `live-region` archetype covers any message that appears, changes, or goes away without moving focus: alerts, status messages, toasts, snackbars, and other `role="alert"`, `role="status"`, `role="log"`, or `aria-live` content. A library's alert component is a fixture for this archetype. The archetype isn't named after any one component.
86
+
87
+ - `data-a11y-trigger` goes on the control that makes the message appear or change, such as a "Save" button. It can't be the message itself. A message that's on the page from the start has nothing to announce, so the tool reports a gap and asks for a trigger.
88
+ - `data-a11y-root` goes on the message: the element that carries the role, or the element whose text changes. It may already be in the page and empty, or the trigger may insert it.
89
+ - If the message has a dismiss control, put it inside the root. The tool presses Enter on it and checks that the message goes away and that focus lands somewhere sensible.
90
+ - The tool checks that the message sits in a live region, that the region was in the page before the message arrived (except for `role="alert"`, which is announced on insertion), that the politeness fits the role, and that focus stays on the trigger.
91
+ - The states are "before message" and "message shown". The tool activates the trigger by clicking it, then waits for the message to have text.
92
+
57
93
  ## Check your fixture
58
94
 
59
95
  Run the audit and look at the report's **Archetypes** table. `ran` means it worked. A `gap` row says what went wrong, such as "didn't render an element with data-a11y-trigger" or "didn't bundle." Fix the file and run again.
@@ -26,6 +26,7 @@ const OPTION_DEFS = /** @type {const} */ ({
26
26
  "lib-a11y": { type: "string" },
27
27
  mapping: { type: "string" },
28
28
  plan: { type: "boolean" },
29
+ "no-generate": { type: "boolean" },
29
30
  out: { type: "string" },
30
31
  "max-stories": { type: "string" },
31
32
  "fail-on-axe": { type: "string" },
@@ -110,6 +111,7 @@ export function parseRunArgs(command, argv) {
110
111
  archetypes: /** @type {any} */ (archetypes),
111
112
  mapping: values.mapping ?? null,
112
113
  maxStories,
114
+ generate: !values["no-generate"],
113
115
  out: values.out ?? "./a11y-report",
114
116
  fail,
115
117
  };
@@ -120,7 +122,7 @@ export function parseRunArgs(command, argv) {
120
122
  export function describeTarget(target) {
121
123
  if (target.status === "failed") return `failed: ${target.reason}`;
122
124
  const r = target.resolved ?? {};
123
- if (target.kind?.startsWith("npm")) return `${r.name}@${r.version ?? r.requested ?? "latest"}${r.framework ? ` (${r.framework})` : ""}`;
125
+ if (target.kind?.startsWith("npm")) return `${r.name}@${r.version ?? r.requested ?? "latest"}${r.subpath ? `/${r.subpath}` : ""}${r.framework ? ` (${r.framework})` : ""}`;
124
126
  return r.url ?? r.path ?? "";
125
127
  }
126
128
 
@@ -237,12 +239,13 @@ export async function runSavedPlan(argv, io) {
237
239
  const FLAGS = `Options:
238
240
  --wcag <2.0|2.1|2.2> WCAG version. Default 2.2.
239
241
  --level <A|AA|AAA> Conformance level. Default AA.
240
- --tiers <list> rules, interactions, computed, vsr. Default all four.
242
+ --tiers <list> rules, interactions, computed, conditions, vsr. Default all five.
241
243
  --engine <list> axe, ibm. Default both.
242
244
  --archetypes <list> Limit npm and Storybook targets to these archetypes.
243
245
  --lib-a11y <list> on, off. Default both.
244
246
  --mapping <file> Archetype mapping file.
245
247
  --max-stories <n> Storybook story cap. Default 200.
248
+ --no-generate Don't build fixtures for npm packages. Only authored fixtures and the button and link templates run.
246
249
  --plan Resolve and print the plan, then stop.
247
250
  --out <dir> Output directory. Default ./a11y-report.
248
251
  --fail-on-axe <impact> minor, moderate, serious, or critical.
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The framework adapters. Each one says how to recognize a package built for its framework, install the framework's runtime,
3
+ * bundle and mount a fixture, list a package's exports, template the simple archetypes, generate the rest, and describe the
4
+ * result in a report. Everything after that (the browser, the tiers, the probe, the reports) knows nothing about a framework.
5
+ *
6
+ * To add a framework, write a module with the same shape as react.js, add it here, and add its id to FLAVORS and its kind to
7
+ * TARGET_KINDS in schema.js. A test checks that the two lists agree.
8
+ *
9
+ * @typedef {{
10
+ * id: string,
11
+ * label: string,
12
+ * kind: string,
13
+ * noun: string,
14
+ * extension: string,
15
+ * runtime: string[],
16
+ * detect: (meta: any) => { kind: string, framework: string, reason: string } | null,
17
+ * bundle: (workDir: string) => { alias: Record<string, string>, define?: Record<string, string>, esbuild: Record<string, any> },
18
+ * entry: (fixturePath: string, pkg: string) => string,
19
+ * discoverEntry: (pkg: string) => string,
20
+ * template: (archetype: string, pkg: string, name?: string) => string | null,
21
+ * generate: (input: any) => { candidates: Array<{ id: string, summary: string, source: string, used: string[] }>, reason: string | null },
22
+ * describe: (npm: any) => string,
23
+ * }} Adapter
24
+ */
25
+ import react from "./react.js";
26
+ import vue from "./vue.js";
27
+ import wc from "./wc.js";
28
+
29
+ /** @type {Record<string, Adapter>} */
30
+ export const ADAPTERS = { react, vue, wc };
31
+
32
+ /** The adapter for a flavor (`react`, `vue`, or `wc`). */
33
+ export function adapterFor(id) {
34
+ const adapter = ADAPTERS[id];
35
+ if (!adapter) throw new Error(`There's no adapter for "${id}".`);
36
+ return adapter;
37
+ }
38
+
39
+ /** The adapter for a plan target's kind, such as `npm-vue`, or null for a kind no adapter owns. */
40
+ export function adapterForKind(kind) {
41
+ return Object.values(ADAPTERS).find((adapter) => adapter.kind === kind) ?? null;
42
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The React adapter: how to recognize a React package, mount a fixture, list its exports, template the simple archetypes,
3
+ * generate the rest, and tell esbuild to use one copy of React. Every framework adapter has this shape (see index.js).
4
+ */
5
+ import { join } from "node:path";
6
+ import { reactDialect } from "../harness/generate/dialects.js";
7
+ import { generateJsx } from "../harness/generate/jsx.js";
8
+
9
+ /** Mounts a fixture's default export. */
10
+ export const entry = (fixturePath, _pkg) => `import { createElement } from "react";
11
+ import { createRoot } from "react-dom/client";
12
+ import Fixture from ${JSON.stringify(fixturePath)};
13
+ const libA11y = new URLSearchParams(location.search).get("libA11y") === "on";
14
+ createRoot(document.getElementById("root")).render(createElement(Fixture, { libA11y }));
15
+ `;
16
+
17
+ /** Loads the whole package, lists its exports, and records compound parts such as Dialog.Trigger. */
18
+ export const discoverEntry = (pkg) => `import * as lib from ${JSON.stringify(pkg)};
19
+ const out = [];
20
+ for (const name of Object.keys(lib)) {
21
+ const value = lib[name];
22
+ const type = typeof value;
23
+ if (value === null || (type !== "function" && type !== "object")) continue;
24
+ const parts = Object.keys(value).filter((key) => /^[A-Z]/.test(key)).slice(0, 30);
25
+ out.push({ name, type, parts });
26
+ }
27
+ window.__a11yExports = out;
28
+ `;
29
+
30
+ /**
31
+ * A fixture for the simple archetypes, from the export name alone.
32
+ * Compound components (dialog, tabs, menu) can't be guessed, so they come from generation or from a fixture someone writes.
33
+ */
34
+ export function template(archetype, pkg, exportName) {
35
+ const body = {
36
+ button: `<Component data-a11y-trigger data-a11y-root type="button">Save</Component>`,
37
+ link: `<Component data-a11y-trigger data-a11y-root href="#top">Read more</Component>`,
38
+ }[archetype];
39
+ if (!body) return null;
40
+ return `import { ${exportName} as Component } from ${JSON.stringify(pkg)};
41
+ export default function Fixture() {
42
+ return ${body};
43
+ }
44
+ `;
45
+ }
46
+
47
+ export default {
48
+ id: "react",
49
+ label: "React",
50
+ kind: "npm-react",
51
+ /** What the mapping calls a thing the package provides. */
52
+ noun: "export",
53
+ /** The extension of an authored fixture. */
54
+ extension: "jsx",
55
+ /** Packages the fixture and the library must share one copy of, and that the install makes sure are there. */
56
+ runtime: ["react", "react-dom"],
57
+ /** Is this package React, from its registry metadata alone? */
58
+ detect(meta) {
59
+ const peers = meta.peerDependencies ?? {};
60
+ const deps = meta.dependencies ?? {};
61
+ if (!("react" in peers || "react-dom" in peers || "react" in deps)) return null;
62
+ const how = "react" in peers || "react-dom" in peers ? "peer dependency" : "dependency";
63
+ return { kind: "npm-react", framework: "React", reason: `The package lists react as a ${how}.` };
64
+ },
65
+ /** esbuild settings for this framework. */
66
+ bundle: (workDir) => ({
67
+ // One copy of React for the library and the fixture, or hooks break.
68
+ alias: { react: join(workDir, "node_modules", "react"), "react-dom": join(workDir, "node_modules", "react-dom") },
69
+ esbuild: { jsx: "automatic" },
70
+ }),
71
+ entry,
72
+ discoverEntry,
73
+ template,
74
+ generate: (input) => generateJsx(reactDialect, input),
75
+ /** One phrase for the report: what the package was run as. */
76
+ describe: (npm) => `React${npm.react ? ` (react ${npm.react})` : ""}`,
77
+ };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The Vue adapter, for Vue 3 packages. Libraries ship compiled components, so nothing here compiles single-file components.
3
+ * Fixtures are JSX that esbuild turns into `h()` calls (a small shim supplies Vue's `h` and `Fragment`, so a fixture doesn't import them),
4
+ * and a fixture's default export is a component. An optional `setup(app)` export installs plugins before the app mounts.
5
+ * Every framework adapter has this shape (see index.js).
6
+ */
7
+ import { writeFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { vueDialect } from "../harness/generate/dialects.js";
10
+ import { generateJsx } from "../harness/generate/jsx.js";
11
+
12
+ /** Mounts a fixture's default export as the root component, after its optional setup(app) has installed what it needs. */
13
+ export const entry = (fixturePath, _pkg) => `import { createApp } from "vue";
14
+ import * as fixture from ${JSON.stringify(fixturePath)};
15
+ const libA11y = new URLSearchParams(location.search).get("libA11y") === "on";
16
+ const app = createApp(fixture.default, { libA11y });
17
+ if (typeof fixture.setup === "function") await fixture.setup(app);
18
+ app.mount(document.getElementById("root"));
19
+ `;
20
+
21
+ /** Loads the whole package, lists its exports, and records compound parts such as Dialog.Trigger. Same as for React. */
22
+ export const discoverEntry = (pkg) => `import * as lib from ${JSON.stringify(pkg)};
23
+ const out = [];
24
+ for (const name of Object.keys(lib)) {
25
+ const value = lib[name];
26
+ const type = typeof value;
27
+ if (value === null || (type !== "function" && type !== "object")) continue;
28
+ const parts = Object.keys(value).filter((key) => /^[A-Z]/.test(key)).slice(0, 30);
29
+ out.push({ name, type, parts });
30
+ }
31
+ window.__a11yExports = out;
32
+ `;
33
+
34
+ /** A fixture for the simple archetypes, from the export name alone. A function default export is a functional component. */
35
+ export function template(archetype, pkg, exportName) {
36
+ const body = {
37
+ button: `<Component data-a11y-trigger data-a11y-root type="button">Save</Component>`,
38
+ link: `<Component data-a11y-trigger data-a11y-root href="#top">Read more</Component>`,
39
+ }[archetype];
40
+ if (!body) return null;
41
+ return `import { ${exportName} as Component } from ${JSON.stringify(pkg)};
42
+ export default function Fixture() {
43
+ return ${body};
44
+ }
45
+ `;
46
+ }
47
+
48
+ /** Vue 3 or later? A range like "^3.2.0 || ^2.7" counts when any part allows 3. A bare "*" counts too. */
49
+ function allowsVue3(range) {
50
+ return String(range)
51
+ .split("||")
52
+ .some((part) => {
53
+ const major = /(\d+)/.exec(part);
54
+ return !major || Number(major[1]) >= 3 || /^[\s*x]*$/.test(part);
55
+ });
56
+ }
57
+
58
+ export default {
59
+ id: "vue",
60
+ label: "Vue",
61
+ kind: "npm-vue",
62
+ noun: "export",
63
+ extension: "jsx",
64
+ runtime: ["vue"],
65
+ detect(meta) {
66
+ const peers = meta.peerDependencies ?? {};
67
+ const deps = meta.dependencies ?? {};
68
+ const range = peers.vue ?? deps.vue;
69
+ if (range === undefined) return null;
70
+ if (!allowsVue3(range)) return { kind: "npm-unsupported", framework: "Vue 2", reason: `The package needs Vue ${range}. Only Vue 3 is supported.` };
71
+ return { kind: "npm-vue", framework: "Vue", reason: `The package lists vue as a ${"vue" in peers ? "peer dependency" : "dependency"}.` };
72
+ },
73
+ /** Writes the shim that supplies `h` and `Fragment`, then returns the settings. */
74
+ bundle(workDir) {
75
+ const shim = join(workDir, "a11y-vue-jsx-shim.js");
76
+ writeFileSync(shim, 'export { h, Fragment } from "vue";\n');
77
+ return {
78
+ // One copy of Vue for the library and the fixture, or reactivity breaks.
79
+ alias: { vue: join(workDir, "node_modules", "vue") },
80
+ define: { __VUE_OPTIONS_API__: "true", __VUE_PROD_DEVTOOLS__: "false", __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "false" },
81
+ // Vue has no JSX runtime of its own, so JSX becomes h() calls. The shim is injected wherever h or Fragment is used without being declared.
82
+ esbuild: { jsx: "transform", jsxFactory: "h", jsxFragment: "Fragment", inject: [shim] },
83
+ };
84
+ },
85
+ entry,
86
+ discoverEntry,
87
+ template,
88
+ generate: (input) => generateJsx(vueDialect, input),
89
+ describe: (npm) => `Vue${npm.vue ? ` (vue ${npm.vue})` : ""}`,
90
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The web component adapter: framework-free entries, templates, and generation from what an element says about itself.
3
+ * Every framework adapter has this shape (see index.js).
4
+ */
5
+ import { generateWc } from "../harness/generate/wc-recipes.js";
6
+
7
+
8
+ /** Loads the package so its custom elements get defined, then calls the fixture's default export, `mount(container)`. */
9
+ export const entry = (fixturePath, pkg) => `// A package's own "sideEffects" list can mark its entry as removable, which would drop a bare import and leave its elements undefined.
10
+ // Using the namespace keeps the package and its registration code.
11
+ import * as library from ${JSON.stringify(pkg)};
12
+ globalThis.__a11yLibrary = library;
13
+ import mount from ${JSON.stringify(fixturePath)};
14
+ const libA11y = new URLSearchParams(location.search).get("libA11y") === "on";
15
+ await mount(document.getElementById("root"), { libA11y });
16
+ `;
17
+
18
+ /** Loads the whole package. The page's init script records every custom element the package defines. */
19
+ export const discoverEntry = (pkg) => `import * as lib from ${JSON.stringify(pkg)};
20
+ window.__a11yExports = Object.keys(lib).map((name) => ({ name, type: typeof lib[name], parts: [] }));
21
+ `;
22
+
23
+ /** A fixture for the simple archetypes, from the tag name alone. */
24
+ export function template(archetype, _pkg, tag) {
25
+ const attrs = {
26
+ button: `el.textContent = "Save";`,
27
+ link: `el.setAttribute("href", "#top");\n el.textContent = "Read more";`,
28
+ }[archetype];
29
+ if (!attrs) return null;
30
+ return `export default function mount(container) {
31
+ const el = document.createElement(${JSON.stringify(tag)});
32
+ el.setAttribute("data-a11y-trigger", "");
33
+ el.setAttribute("data-a11y-root", "");
34
+ ${attrs}
35
+ container.append(el);
36
+ }
37
+ `;
38
+ }
39
+
40
+ export default {
41
+ id: "wc",
42
+ label: "Web components",
43
+ kind: "npm-wc",
44
+ noun: "custom element",
45
+ extension: "js",
46
+ runtime: [],
47
+ /** A package is detected as web components by a manifest or a base library, which index.js orders against the other adapters. */
48
+ detect: () => null,
49
+ bundle: () => ({ alias: {}, esbuild: {} }),
50
+ entry,
51
+ discoverEntry,
52
+ template,
53
+ generate: (input) => generateWc(input),
54
+ describe: (npm) => `web components (${npm.tags?.length ? npm.tags.slice(0, 6).join(", ") : "no tags found"})`,
55
+ };