@stealthscale/component-a11y 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/cyclonedx/bom.json +1 -0
- package/dist/index.d.ts +141 -0
- package/dist/index.js +598 -0
- package/dist/recipe-BKojQp6r.js +106 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/theme.d.ts +12 -0
- package/dist/theme.js +23 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stealth Scale B.V.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @stealthscale/component-a11y
|
|
2
|
+
|
|
3
|
+
Draws what a keyboard and a screen reader need: words read out and drawn nowhere, a way past the
|
|
4
|
+
navigation, and one tab stop over a set of controls. Every component binds a recipe and draws
|
|
5
|
+
nothing of its own, so a theme restyles all of them by extending the recipe. The preset under
|
|
6
|
+
`./theme` registers the recipes with an application's compiler.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm add @stealthscale/component-a11y
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The package peers on `react`, `@stealthscale/hooks` and `@stealthscale/theme`. An application lists
|
|
15
|
+
the preset under `./theme` among the presets its compiler installs.
|
|
16
|
+
|
|
17
|
+
## VisuallyHidden
|
|
18
|
+
|
|
19
|
+
Exposes its words to a screen reader and draws them nowhere. The words stay in the accessibility
|
|
20
|
+
tree, which `display: none` and `visibility: hidden` both take them out of. A control a keyboard can
|
|
21
|
+
reach is `focusable`, so it comes into view while focus is on it and a sighted reader tabbing
|
|
22
|
+
through the page keeps their place.
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { VisuallyHidden } from "@stealthscale/component-a11y";
|
|
26
|
+
|
|
27
|
+
<button type="button">
|
|
28
|
+
<Icon viewBox="0 0 24 24">…</Icon>
|
|
29
|
+
<VisuallyHidden>Close</VisuallyHidden>
|
|
30
|
+
</button>;
|
|
31
|
+
<VisuallyHidden as="h2">Sections</VisuallyHidden>;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
| Axis | Values | Default |
|
|
35
|
+
| ----------- | ------ | ------- |
|
|
36
|
+
| `focusable` | `true` | off |
|
|
37
|
+
|
|
38
|
+
## SkipNav
|
|
39
|
+
|
|
40
|
+
Carries a keyboard past the navigation, composed as `SkipNav.Link` at the top of the document and
|
|
41
|
+
`SkipNav.Target` where the content starts. The link is hidden until focus reaches it, so a reader
|
|
42
|
+
who tabs meets it first and a reader who never tabs never sees it. The target takes a tab index of
|
|
43
|
+
minus one, because a browser moves focus to a fragment only where the target can hold focus.
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { SkipNav } from "@stealthscale/component-a11y";
|
|
47
|
+
|
|
48
|
+
<SkipNav.Link>Skip to content</SkipNav.Link>;
|
|
49
|
+
<SkipNav.Target as="main">…</SkipNav.Target>;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The link points at `#content` and the target carries that id, so neither states the other. A page
|
|
53
|
+
with more than one landing place names its own: `<SkipNav.Link href="#search">` beside
|
|
54
|
+
`<SkipNav.Target id="search">`.
|
|
55
|
+
|
|
56
|
+
## RovingFocus
|
|
57
|
+
|
|
58
|
+
Holds one tab stop for a set of controls, composed as `RovingFocus.Root` holding `RovingFocus.Item`.
|
|
59
|
+
A reader reaches the set with Tab and moves inside it with the arrows, which is what the toolbar,
|
|
60
|
+
tablist and menubar patterns ask for. Home and End go to the ends, a disabled item is passed over,
|
|
61
|
+
and the arrows run the other way where the line runs right to left.
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { RovingFocus } from "@stealthscale/component-a11y";
|
|
65
|
+
import { Button } from "@stealthscale/component-actions";
|
|
66
|
+
|
|
67
|
+
<RovingFocus.Root role="toolbar">
|
|
68
|
+
<RovingFocus.Item as={Button} variant="ghost">
|
|
69
|
+
Cut
|
|
70
|
+
</RovingFocus.Item>
|
|
71
|
+
<RovingFocus.Item as={Button} variant="ghost">
|
|
72
|
+
Copy
|
|
73
|
+
</RovingFocus.Item>
|
|
74
|
+
</RovingFocus.Root>;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The root carries no role of its own, because naming a set of controls is the caller's decision. It
|
|
78
|
+
reports the orientation to a screen reader only where the caller gave it a role, since
|
|
79
|
+
`aria-orientation` means nothing on a plain element.
|
|
80
|
+
|
|
81
|
+
| Axis | Values | Default |
|
|
82
|
+
| ------------- | -------------------------------- | ------------ |
|
|
83
|
+
| `orientation` | `horizontal`, `vertical`, `both` | `horizontal` |
|
|
84
|
+
|
|
85
|
+
`wrap` joins the ends together. `activeId` sets which item holds the stop from outside, and
|
|
86
|
+
`onActiveIdChange` reports the stop moving.
|
|
87
|
+
|
|
88
|
+
## Licence
|
|
89
|
+
|
|
90
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"http://cyclonedx.org/schema/bom-1.7.schema.json","bomFormat":"CycloneDX","specVersion":"1.7","version":1,"metadata":{"lifecycles":[{"phase":"build"}],"tools":{"components":[{"type":"application","name":"rolldown","version":"1.2.7"},{"type":"application","name":"vite","version":"8.2.2"},{"type":"library","name":"core","group":"@arethetypeswrong","version":"0.18.5","bom-ref":"pkg:npm/%40arethetypeswrong/core@0.18.5","author":"Andrew Branch","description":"The API behind arethetypeswrong.github.io","hashes":[{"alg":"SHA-512","content":"f72b63cc6c318e6f54cfb23d6af7dbb79be542deae93db914444b3263aabce797a58abc8e9dc184e8faf277534d96aeafe64778aa97f3f386f1e129d2480230d"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40arethetypeswrong/core@0.18.5","externalReferences":[{"url":"https://github.com/arethetypeswrong/arethetypeswrong.github.io.git#packages/core","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=20"}]},{"type":"library","name":"hooks","group":"@stealthscale","version":"0.1.0","bom-ref":"pkg:npm/%40stealthscale/hooks@0.1.0","description":"The React hooks a component uses to read the page it draws into: how the page is read, what it measures, and what a value was on the previous render.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/hooks@0.1.0","externalReferences":[{"url":"https://github.com/stealth-scale/config/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/config.git#foundations/hooks","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/config/tree/main/foundations/hooks#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"theme","group":"@stealthscale","version":"0.3.0","bom-ref":"pkg:npm/%40stealthscale/theme@0.3.0","description":"Publishes the foundation every recipe is written against, the runtime every component binds its recipe with, and the vocabulary a theme is written in.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/theme@0.3.0","externalReferences":[{"url":"https://github.com/stealth-scale/config/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/config.git#foundations/theme","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/config/tree/main/foundations/theme#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"vite-config-react","group":"@stealthscale","version":"0.8.0","bom-ref":"pkg:npm/%40stealthscale/vite-config-react@0.8.0","description":"Adds the JSX transform, the React lint rules, a test document and an MDX compiler to a package that renders.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/vite-config-react@0.8.0","externalReferences":[{"url":"https://github.com/stealth-scale/config/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/config.git#packages/vite-config-react","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/config/tree/main/packages/vite-config-react#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"vite-config-theme","group":"@stealthscale","version":"0.1.0","bom-ref":"pkg:npm/%40stealthscale/vite-config-theme@0.1.0","description":"Adds the runtime generator to a design-system package and the stylesheet compiler to an application.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/vite-config-theme@0.1.0","externalReferences":[{"url":"https://github.com/stealth-scale/config/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/config.git#packages/vite-config-theme","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/config/tree/main/packages/vite-config-theme#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"vite-config-typescript","group":"@stealthscale","version":"0.3.0","bom-ref":"pkg:npm/%40stealthscale/vite-config-typescript@0.3.0","description":"Sets the TypeScript options every stealthscale package compiles with.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/vite-config-typescript@0.3.0","externalReferences":[{"url":"https://github.com/stealth-scale/config/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/config.git#packages/vite-config-typescript","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/config/tree/main/packages/vite-config-typescript#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"vite-config","group":"@stealthscale","version":"0.6.0","bom-ref":"pkg:npm/%40stealthscale/vite-config@0.6.0","description":"Configures a package from five Vite+ tiers and a named layer for every block.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/vite-config@0.6.0","externalReferences":[{"url":"https://github.com/stealth-scale/config/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/config.git#packages/vite-config","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/config/tree/main/packages/vite-config#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"react","group":"@testing-library","version":"16.3.3","bom-ref":"pkg:npm/%40testing-library/react@16.3.3","author":"Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)","description":"Simple and complete React DOM testing utilities that encourage good testing practices.","hashes":[{"alg":"SHA-512","content":"528d7ddcd8106cf333ea5aeb86d45040570c0ba45efc42cb15b6ee54bdf4583959c65a597fdfe53074c05713d12f0d6ad62bbd389991d5cd812e210d46cb5d06"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40testing-library/react@16.3.3","externalReferences":[{"url":"https://github.com/testing-library/react-testing-library/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs.url\""},{"url":"https://github.com/testing-library/react-testing-library","type":"vcs","comment":"as detected from PackageJson property \"repository.url\""},{"url":"https://github.com/testing-library/react-testing-library#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=18"}]},{"type":"library","name":"react-dom","group":"@types","version":"19.3.0","bom-ref":"pkg:npm/%40types/react-dom@19.3.0","description":"TypeScript definitions for react-dom","hashes":[{"alg":"SHA-512","content":"648edb538da66575ca1e7fea34b130d88adb888354ed7e7ebdf81d8b10479023696165e32a07d0fcffaec866f90a338b07d51c9d37a0debca542d5768729b8e1"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40types/react-dom@19.3.0","externalReferences":[{"url":"https://github.com/DefinitelyTyped/DefinitelyTyped.git#types/react-dom","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-dom","type":"website","comment":"as detected from PackageJson property \"homepage\""}]},{"type":"library","name":"react","group":"@types","version":"19.3.0","bom-ref":"pkg:npm/%40types/react@19.3.0","description":"TypeScript definitions for react","hashes":[{"alg":"SHA-512","content":"374ac50ae1fd628c46f7f9bad65f4c7e924a7e62ce554d1e9bb8a9233e9346049292f45e2c1f6f2fce4607ecabf01b3995eaa9be0f7a252c05e194b5b38be242"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40types/react@19.3.0","externalReferences":[{"url":"https://github.com/DefinitelyTyped/DefinitelyTyped.git#types/react","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react","type":"website","comment":"as detected from PackageJson property \"homepage\""}]},{"type":"library","name":"babel-plugin-react-compiler","version":"1.0.0","bom-ref":"pkg:npm/babel-plugin-react-compiler@1.0.0","description":"Babel plugin for React Compiler.","hashes":[{"alg":"SHA-512","content":"2319bcb457e828a20f61d0822984ecaaff85778209d0342a3321229a8fa9c5438c511f5c54f9704eb16df40beefb771be99a77980ce5fadd4cac6d9fc712acc7"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/babel-plugin-react-compiler@1.0.0","externalReferences":[{"url":"git+https://github.com/facebook/react.git#compiler/packages/babel-plugin-react-compiler","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""}]},{"type":"library","name":"happy-dom","version":"20.14.5","bom-ref":"pkg:npm/happy-dom@20.14.5","author":"David Ortner","description":"Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. It includes many web standards from WHATWG DOM and HTML.","hashes":[{"alg":"SHA-512","content":"c7f47392958ee346d38c8a13df4890b6deb814b2e61ff89171408dd97fff6cbc7b1f789f91d7cf5f2aacae8fce62dab32008622cc300ee69a2391f42dcd38a8d"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/happy-dom@20.14.5","externalReferences":[{"url":"https://github.com/capricorn86/happy-dom","type":"vcs","comment":"as detected from PackageJson property \"repository\""},{"url":"https://github.com/capricorn86/happy-dom","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=20.0.0"}]},{"type":"library","name":"oxc-transform-react","version":"0.149.0","bom-ref":"pkg:npm/oxc-transform-react@0.149.0","author":"Boshen and oxc contributors","description":"Oxc React Compiler Node API","hashes":[{"alg":"SHA-512","content":"69bdddc5c3ed90349f211f6d35dc4752d9a974bd258479ddb010fd214014e41c6d26dc061bff39367473b367f62b0a2e3641e5edc692b3030b41e52fc3714447"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/oxc-transform-react@0.149.0","externalReferences":[{"url":"https://github.com/oxc-project/oxc/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/oxc-project/oxc.git#napi/transform-react","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://oxc.rs","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":"^20.19.0 || >=22.12.0"}]},{"type":"library","name":"react-dom","version":"19.3.0","bom-ref":"pkg:npm/react-dom@19.3.0","description":"React package for working with the DOM.","hashes":[{"alg":"SHA-512","content":"24393c76089fe753a31680c4ef4f8e4fd202c98afeebd1e59a284dc29d7e36c7db9dadede6c22209af59264b43990e3fd5bfeb9f6ea1200476b985c332be6bd1"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/react-dom@19.3.0","externalReferences":[{"url":"https://github.com/react/react/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs.url\""},{"url":"https://github.com/react/react.git#packages/react-dom","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://react.dev/","type":"website","comment":"as detected from PackageJson property \"homepage\""}]},{"type":"library","name":"react","version":"19.3.0","bom-ref":"pkg:npm/react@19.3.0","description":"React is a JavaScript library for building user interfaces.","hashes":[{"alg":"SHA-512","content":"13c2d471bb415addb46db9766281dfc7864305dc554df38ab42667f5c0d227897afe7ba8029729201723e3bb76c19a155fc836647b8c1db892860091d53e52a2"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/react@19.3.0","externalReferences":[{"url":"https://github.com/react/react/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"https://github.com/react/react.git#packages/react","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://react.dev/","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=0.10.0"}]},{"type":"library","name":"typescript","version":"7.0.2","bom-ref":"pkg:npm/typescript@7.0.2","author":"Microsoft Corp.","description":"TypeScript is a language for application scale JavaScript development","hashes":[{"alg":"SHA-512","content":"f0561abbdea8dcd28e85b8ca8bfa8dbc6fd6e63873c646dd9b9b23f406d9ff94f9b16aa7de12602df1b1dbbb11299593bf2cc23fc74b4414dfe6d05349155434"}],"licenses":[{"license":{"id":"Apache-2.0"}}],"purl":"pkg:npm/typescript@7.0.2","externalReferences":[{"url":"https://github.com/microsoft/TypeScript/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs.url\""},{"url":"https://github.com/microsoft/TypeScript.git","type":"vcs","comment":"as detected from PackageJson property \"repository.url\""},{"url":"https://www.typescriptlang.org/","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=16.20.0"}]},{"type":"library","name":"vitest","version":"4.1.11","bom-ref":"pkg:npm/vitest@4.1.11","author":"Anthony Fu <anthonyfu117@hotmail.com>","description":"Next generation testing framework powered by Vite","hashes":[{"alg":"SHA-512","content":"7e1002acd5d489d21b19206be4595bb8190eed5582d59c8b9740cee025360eb42800fc57f3862cc6cf877864298a2e656565754388016534eee3d985f80e8477"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/vitest@4.1.11","externalReferences":[{"url":"https://github.com/vitest-dev/vitest/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs.url\""},{"url":"git+https://github.com/vitest-dev/vitest.git#packages/vitest","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://vitest.dev","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":"^20.0.0 || ^22.0.0 || >=24.0.0"}]}]},"component":{"type":"library","name":"component-a11y","group":"@stealthscale","version":"0.1.0","bom-ref":"pkg:npm/%40stealthscale/component-a11y@0.1.0","description":"Draws what a keyboard and a screen reader need, and an eye does not.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/component-a11y@0.1.0","externalReferences":[{"url":"https://github.com/stealth-scale/config/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/config.git#components/a11y","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/config/tree/main/components/a11y#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}],"evidence":{"licenses":[{"license":{"name":"file: LICENSE","text":{"content":"TUlUIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBTdGVhbHRoIFNjYWxlIEIuVi4KClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCg==","contentType":"text/plain","encoding":"base64"}}}]}},"supplier":{"name":"Stealth Scale B.V.","url":["https://stealthscale.io"]}},"components":[],"dependencies":[{"ref":"pkg:npm/%40stealthscale/component-a11y@0.1.0"}]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { ComponentProps, ReactElement, ReactNode, Ref } from "react";
|
|
2
|
+
//#region src/roving-focus/item.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Draws the element an item is laid out on.
|
|
5
|
+
*/
|
|
6
|
+
declare const Shell$1: import("@stealthscale/theme").StyledComponent<"div", {}>;
|
|
7
|
+
/**
|
|
8
|
+
* Describes what an item takes beside everything a styled element takes.
|
|
9
|
+
*/
|
|
10
|
+
interface ItemProps extends ComponentProps<typeof Shell$1> {
|
|
11
|
+
/**
|
|
12
|
+
* Whether the arrows pass over the item.
|
|
13
|
+
*/
|
|
14
|
+
disabled?: boolean | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* The id the item answers to, which the group generates where a caller states none.
|
|
17
|
+
*/
|
|
18
|
+
id?: string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Where a caller wants the element.
|
|
21
|
+
*/
|
|
22
|
+
ref?: Ref<HTMLDivElement> | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Takes the tab stop when the arrows reach it, and leaves the tab order otherwise.
|
|
26
|
+
*
|
|
27
|
+
* @throws {@link Error} When it is drawn outside a group.
|
|
28
|
+
*/
|
|
29
|
+
declare function Item(props: ItemProps): ReactElement;
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/roving-focus/use-roving-focus.d.ts
|
|
32
|
+
/**
|
|
33
|
+
* Selects which arrows move focus through the group.
|
|
34
|
+
*/
|
|
35
|
+
type Orientation = "both" | "horizontal" | "vertical";
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/roving-focus/root.d.ts
|
|
38
|
+
/**
|
|
39
|
+
* Draws the element the group is laid out on.
|
|
40
|
+
*/
|
|
41
|
+
declare const Shell: import("@stealthscale/theme").StyledComponent<"div", {
|
|
42
|
+
readonly orientation?: "both" | "horizontal" | "vertical";
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Describes what a group takes beside the variants its recipe offers.
|
|
46
|
+
*/
|
|
47
|
+
interface RootProps extends Omit<ComponentProps<typeof Shell>, "orientation"> {
|
|
48
|
+
/**
|
|
49
|
+
* Which item holds the tab stop, where something above decides it.
|
|
50
|
+
*/
|
|
51
|
+
activeId?: string | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* The items the group lays out.
|
|
54
|
+
*/
|
|
55
|
+
children?: ReactNode | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Which item Tab first enters, where the group decides for itself.
|
|
58
|
+
*/
|
|
59
|
+
defaultActiveId?: string | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Hears that the tab stop moved.
|
|
62
|
+
*/
|
|
63
|
+
onActiveIdChange?: ((activeId: string | undefined) => void) | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Which arrows move focus, which is along the line unless a caller says otherwise.
|
|
66
|
+
*/
|
|
67
|
+
orientation?: Orientation | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Whether the ends join up.
|
|
70
|
+
*/
|
|
71
|
+
wrap?: boolean | undefined;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Keeps one tab stop for the items inside it, and moves it under the arrow keys.
|
|
75
|
+
*/
|
|
76
|
+
declare function Root(props: RootProps): ReactElement;
|
|
77
|
+
declare namespace index_d_exports {
|
|
78
|
+
export { Item, ItemProps, Orientation, Root, RootProps };
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/skip-nav/link.d.ts
|
|
82
|
+
/**
|
|
83
|
+
* The fragment the link points at and the target answers to, where neither states one.
|
|
84
|
+
*/
|
|
85
|
+
declare const SKIP_NAV_TARGET = "content";
|
|
86
|
+
/**
|
|
87
|
+
* Jumps a keyboard past the navigation to the page's content.
|
|
88
|
+
*/
|
|
89
|
+
declare const Link: import("@stealthscale/theme").StyledComponent<"a", {}>;
|
|
90
|
+
/**
|
|
91
|
+
* Describes what the link takes: everything a styled anchor element takes.
|
|
92
|
+
*/
|
|
93
|
+
type LinkProps = ComponentProps<typeof Link>;
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/skip-nav/target.d.ts
|
|
96
|
+
/**
|
|
97
|
+
* Takes the focus the skip link sends, and draws nothing.
|
|
98
|
+
*/
|
|
99
|
+
declare const Target: import("@stealthscale/theme").StyledComponent<"div", {}>;
|
|
100
|
+
/**
|
|
101
|
+
* Describes what the target takes: everything a styled div element takes.
|
|
102
|
+
*/
|
|
103
|
+
type TargetProps = ComponentProps<typeof Target>;
|
|
104
|
+
declare namespace index_d_exports$1 {
|
|
105
|
+
export { Link, LinkProps, SKIP_NAV_TARGET, Target, TargetProps };
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/visually-hidden/context.d.ts
|
|
109
|
+
/**
|
|
110
|
+
* Binds the hidden text's recipe to the element that draws it.
|
|
111
|
+
*
|
|
112
|
+
* @remarks
|
|
113
|
+
* Apart from the recipe, because an application's compiler reads the recipe at build time and
|
|
114
|
+
* the binding needs the runtime. A recipe file that also bound one would put the runtime behind
|
|
115
|
+
* every compiler configuration that reads it.
|
|
116
|
+
*/
|
|
117
|
+
/**
|
|
118
|
+
* Binds the recipe once, for the hidden text and for whatever sets its variants from above.
|
|
119
|
+
*/
|
|
120
|
+
declare const PropsProvider: import("react").Provider<import("@stealthscale/theme").DataAttrs & Partial<{
|
|
121
|
+
readonly focusable?: boolean;
|
|
122
|
+
}>>, withContext: <Tag extends import("react").ElementType>(Component: Tag, options?: import("@stealthscale/theme").JsxFactoryOptions<import("@stealthscale/theme").ComponentProps<Tag> & Partial<{
|
|
123
|
+
readonly focusable?: boolean;
|
|
124
|
+
}>> | undefined) => import("@stealthscale/theme").StyledComponent<Tag, {
|
|
125
|
+
readonly focusable?: boolean;
|
|
126
|
+
}>;
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/visually-hidden/visually-hidden.d.ts
|
|
129
|
+
/**
|
|
130
|
+
* Reads its words to a screen reader and draws them nowhere.
|
|
131
|
+
*/
|
|
132
|
+
export declare const VisuallyHidden: import("@stealthscale/theme").StyledComponent<"span", {
|
|
133
|
+
readonly focusable?: boolean;
|
|
134
|
+
}>;
|
|
135
|
+
/**
|
|
136
|
+
* Describes what hidden text takes: the variants its recipe offers, and everything a styled span
|
|
137
|
+
* element takes.
|
|
138
|
+
*/
|
|
139
|
+
type VisuallyHiddenProps = ComponentProps<typeof VisuallyHidden>;
|
|
140
|
+
//#endregion
|
|
141
|
+
export { index_d_exports as RovingFocus, index_d_exports$1 as SkipNav, type VisuallyHiddenProps, PropsProvider as VisuallyHiddenPropsProvider };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
import { n as recipe$1, r as recipe, t as recipe$2 } from "./recipe-BKojQp6r.js";
|
|
3
|
+
import { c } from "react/compiler-runtime";
|
|
4
|
+
import { createContext, use, useEffect, useId, useRef, useState } from "react";
|
|
5
|
+
import { useCallbackRef, useControllableState } from "@stealthscale/hooks";
|
|
6
|
+
import { createRecipeContext, createSlotRecipeContext } from "@stealthscale/theme";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
//#region src/roving-focus/context.ts
|
|
9
|
+
/**
|
|
10
|
+
* Binds the group's recipe to the parts that draw it.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* Apart from the recipe, because an application's compiler reads the recipe at build time and
|
|
14
|
+
* the binding needs the runtime. The root carries the orientation, and every item below it reads
|
|
15
|
+
* its classes from the root through a context.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Binds the recipe once: the root takes the orientation, and each item draws its slot in it.
|
|
19
|
+
*/
|
|
20
|
+
const { withContext: withContext$1, withProvider: withProvider$1 } = createSlotRecipeContext(recipe);
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/roving-focus/use-roving-focus.ts
|
|
23
|
+
/**
|
|
24
|
+
* Moves one tab stop through a set of controls, so a reader reaches the set with Tab and moves
|
|
25
|
+
* inside it with the arrows.
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* A toolbar, a row of tabs and a menu bar are all one stop in the tab order, and the arrows move
|
|
29
|
+
* which control that stop is on. The items register themselves rather than being counted by the
|
|
30
|
+
* root, because a root cannot see through whatever composes its children. Registration order is
|
|
31
|
+
* the order the effects ran, which is source order until something is conditional, so the list
|
|
32
|
+
* is sorted by where the items sit in the document: what the arrows follow is what the reader
|
|
33
|
+
* sees.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Maps the arrows that run down the page to the step each takes.
|
|
37
|
+
*/
|
|
38
|
+
const DOWN_THE_PAGE = {
|
|
39
|
+
ArrowDown: 1,
|
|
40
|
+
ArrowUp: -1
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Maps the arrows that run along a line to the step each takes.
|
|
44
|
+
*/
|
|
45
|
+
const ALONG_THE_LINE = {
|
|
46
|
+
ArrowLeft: -1,
|
|
47
|
+
ArrowRight: 1
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Carries the group down to its items.
|
|
51
|
+
*/
|
|
52
|
+
const RovingFocusContext = createContext(void 0);
|
|
53
|
+
/**
|
|
54
|
+
* Orders two items by where they sit in the document.
|
|
55
|
+
*/
|
|
56
|
+
function byDocumentPosition(first, second) {
|
|
57
|
+
return (first.element.compareDocumentPosition(second.element) & Node.DOCUMENT_POSITION_FOLLOWING) === 0 ? 1 : -1;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Reads what a key asks for, given the arrows the group binds and which way the line runs.
|
|
61
|
+
*
|
|
62
|
+
* @param key - The key pressed.
|
|
63
|
+
* @param orientation - Which arrows the group moves on.
|
|
64
|
+
* @param forward - One where the line runs left to right, minus one where it runs the other way.
|
|
65
|
+
* @returns The step or the edge to move to, or nothing where the group does not claim the key.
|
|
66
|
+
*/
|
|
67
|
+
function intentOf(key, orientation, forward) {
|
|
68
|
+
if (key === "Home") return "start";
|
|
69
|
+
if (key === "End") return "end";
|
|
70
|
+
const block = DOWN_THE_PAGE[key];
|
|
71
|
+
if (block !== void 0) return orientation === "horizontal" ? void 0 : block;
|
|
72
|
+
const inline = ALONG_THE_LINE[key];
|
|
73
|
+
if (inline !== void 0) return orientation === "vertical" ? void 0 : inline * forward;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Marks out the one item the group moves to, as a slice of the list it is in.
|
|
77
|
+
*
|
|
78
|
+
* @remarks
|
|
79
|
+
* A slice rather than an index, because the list is read at an index that is always inside it
|
|
80
|
+
* and a lookup would still be typed as possibly absent. A slice of one is the item, and a slice
|
|
81
|
+
* of an empty list is empty, so the caller writes no test for a case that cannot arise.
|
|
82
|
+
* @param list - The items, in the order the reader sees them.
|
|
83
|
+
* @param intent - The edge or the step the key asked for.
|
|
84
|
+
* @param from - The item that holds the tab stop.
|
|
85
|
+
* @param wrap - Whether the ends join up.
|
|
86
|
+
* @returns The first and last index of the slice holding the item to move to.
|
|
87
|
+
*/
|
|
88
|
+
function spanOf(list, intent, from, wrap) {
|
|
89
|
+
const last = list.length - 1;
|
|
90
|
+
const at = list.findIndex((item) => item.id === from);
|
|
91
|
+
const here = at === -1 ? 0 : at;
|
|
92
|
+
const to = intent === "start" ? 0 : intent === "end" ? last : wrap ? (here + intent + list.length) % list.length : Math.min(last, Math.max(0, here + intent));
|
|
93
|
+
return [to, to + 1];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Keeps the items in a ref rather than in state.
|
|
97
|
+
*
|
|
98
|
+
* @remarks
|
|
99
|
+
* Nothing drawn depends on the list, only on which item holds the stop, so putting it in state
|
|
100
|
+
* would draw the whole group again every time one item mounted, for a change nobody could see.
|
|
101
|
+
*/
|
|
102
|
+
function useRegistry() {
|
|
103
|
+
const $ = c(6);
|
|
104
|
+
if ($[0] !== "6879b11696c2d2a5e563183941bb99f80ab3ad4b04bbc8a7ef4c43a80f829e8d") {
|
|
105
|
+
for (let $i = 0; $i < 6; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
106
|
+
$[0] = "6879b11696c2d2a5e563183941bb99f80ab3ad4b04bbc8a7ef4c43a80f829e8d";
|
|
107
|
+
}
|
|
108
|
+
let t0;
|
|
109
|
+
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
|
110
|
+
t0 = [];
|
|
111
|
+
$[1] = t0;
|
|
112
|
+
} else t0 = $[1];
|
|
113
|
+
const items = useRef(t0);
|
|
114
|
+
let t1;
|
|
115
|
+
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
|
116
|
+
t1 = (registration) => {
|
|
117
|
+
items.current = [...items.current, registration];
|
|
118
|
+
};
|
|
119
|
+
$[2] = t1;
|
|
120
|
+
} else t1 = $[2];
|
|
121
|
+
const add = t1;
|
|
122
|
+
let t2;
|
|
123
|
+
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
|
124
|
+
t2 = (node) => items.current.some((item) => item.element.contains(node));
|
|
125
|
+
$[3] = t2;
|
|
126
|
+
} else t2 = $[3];
|
|
127
|
+
const holds = t2;
|
|
128
|
+
let t3;
|
|
129
|
+
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
|
|
130
|
+
t3 = () => items.current.toSorted(byDocumentPosition);
|
|
131
|
+
$[4] = t3;
|
|
132
|
+
} else t3 = $[4];
|
|
133
|
+
const ordered = t3;
|
|
134
|
+
let t4;
|
|
135
|
+
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
|
|
136
|
+
const remove = (id) => {
|
|
137
|
+
items.current = items.current.filter((item_0) => item_0.id !== id);
|
|
138
|
+
};
|
|
139
|
+
t4 = {
|
|
140
|
+
add,
|
|
141
|
+
holds,
|
|
142
|
+
ordered,
|
|
143
|
+
remove
|
|
144
|
+
};
|
|
145
|
+
$[5] = t4;
|
|
146
|
+
} else t4 = $[5];
|
|
147
|
+
return t4;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Keeps which item holds the tab stop, driven from above where a caller drives it.
|
|
151
|
+
*/
|
|
152
|
+
function useTabStop(props) {
|
|
153
|
+
const $ = c(13);
|
|
154
|
+
if ($[0] !== "6879b11696c2d2a5e563183941bb99f80ab3ad4b04bbc8a7ef4c43a80f829e8d") {
|
|
155
|
+
for (let $i = 0; $i < 13; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
156
|
+
$[0] = "6879b11696c2d2a5e563183941bb99f80ab3ad4b04bbc8a7ef4c43a80f829e8d";
|
|
157
|
+
}
|
|
158
|
+
const { activeId: driven, defaultActiveId, onActiveIdChange } = props;
|
|
159
|
+
let t0;
|
|
160
|
+
if ($[1] !== defaultActiveId || $[2] !== driven || $[3] !== onActiveIdChange) {
|
|
161
|
+
t0 = {
|
|
162
|
+
defaultValue: defaultActiveId,
|
|
163
|
+
onChange: onActiveIdChange,
|
|
164
|
+
value: driven
|
|
165
|
+
};
|
|
166
|
+
$[1] = defaultActiveId;
|
|
167
|
+
$[2] = driven;
|
|
168
|
+
$[3] = onActiveIdChange;
|
|
169
|
+
$[4] = t0;
|
|
170
|
+
} else t0 = $[4];
|
|
171
|
+
const [activeId, setActiveId] = useControllableState(t0);
|
|
172
|
+
const stop = useRef(activeId);
|
|
173
|
+
let t1;
|
|
174
|
+
let t2;
|
|
175
|
+
if ($[5] !== activeId) {
|
|
176
|
+
t1 = () => {
|
|
177
|
+
stop.current = activeId;
|
|
178
|
+
};
|
|
179
|
+
t2 = [activeId];
|
|
180
|
+
$[5] = activeId;
|
|
181
|
+
$[6] = t1;
|
|
182
|
+
$[7] = t2;
|
|
183
|
+
} else {
|
|
184
|
+
t1 = $[6];
|
|
185
|
+
t2 = $[7];
|
|
186
|
+
}
|
|
187
|
+
useEffect(t1, t2);
|
|
188
|
+
let t3;
|
|
189
|
+
if ($[8] !== setActiveId) {
|
|
190
|
+
t3 = (next) => {
|
|
191
|
+
stop.current = next;
|
|
192
|
+
setActiveId(next);
|
|
193
|
+
};
|
|
194
|
+
$[8] = setActiveId;
|
|
195
|
+
$[9] = t3;
|
|
196
|
+
} else t3 = $[9];
|
|
197
|
+
const claim = useCallbackRef(t3);
|
|
198
|
+
let t4;
|
|
199
|
+
if ($[10] !== activeId || $[11] !== claim) {
|
|
200
|
+
t4 = {
|
|
201
|
+
activeId,
|
|
202
|
+
claim,
|
|
203
|
+
stop
|
|
204
|
+
};
|
|
205
|
+
$[10] = activeId;
|
|
206
|
+
$[11] = claim;
|
|
207
|
+
$[12] = t4;
|
|
208
|
+
} else t4 = $[12];
|
|
209
|
+
return t4;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Moves one tab stop through the items that register with it.
|
|
213
|
+
*
|
|
214
|
+
* @remarks
|
|
215
|
+
* The first item to register takes the stop, so a group is reachable by Tab from its first
|
|
216
|
+
* render. An item that leaves while it holds the stop hands it to whatever is first, so the
|
|
217
|
+
* group never drops out of the tab order.
|
|
218
|
+
* @returns The group the items read, and the handler the root binds.
|
|
219
|
+
*/
|
|
220
|
+
function useRovingFocus(props) {
|
|
221
|
+
const $ = c(23);
|
|
222
|
+
if ($[0] !== "6879b11696c2d2a5e563183941bb99f80ab3ad4b04bbc8a7ef4c43a80f829e8d") {
|
|
223
|
+
for (let $i = 0; $i < 23; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
224
|
+
$[0] = "6879b11696c2d2a5e563183941bb99f80ab3ad4b04bbc8a7ef4c43a80f829e8d";
|
|
225
|
+
}
|
|
226
|
+
const { orientation, wrap } = props;
|
|
227
|
+
const { add, holds, ordered, remove } = useRegistry();
|
|
228
|
+
const { activeId, claim, stop } = useTabStop(props);
|
|
229
|
+
let t0;
|
|
230
|
+
if ($[1] !== add || $[2] !== claim || $[3] !== ordered || $[4] !== remove || $[5] !== stop.current) {
|
|
231
|
+
t0 = (registration) => {
|
|
232
|
+
add(registration);
|
|
233
|
+
if (stop.current === void 0) claim(registration.id);
|
|
234
|
+
return () => {
|
|
235
|
+
remove(registration.id);
|
|
236
|
+
if (stop.current === registration.id) claim(ordered()[0]?.id);
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
$[1] = add;
|
|
240
|
+
$[2] = claim;
|
|
241
|
+
$[3] = ordered;
|
|
242
|
+
$[4] = remove;
|
|
243
|
+
$[5] = stop.current;
|
|
244
|
+
$[6] = t0;
|
|
245
|
+
} else t0 = $[6];
|
|
246
|
+
const register = useCallbackRef(t0);
|
|
247
|
+
let t1;
|
|
248
|
+
if ($[7] !== claim || $[8] !== holds || $[9] !== ordered || $[10] !== orientation || $[11] !== stop.current || $[12] !== wrap) {
|
|
249
|
+
t1 = (event) => {
|
|
250
|
+
if (event.ctrlKey || event.metaKey || event.altKey) return;
|
|
251
|
+
const { target } = event;
|
|
252
|
+
if (!(target instanceof Node) || !holds(target)) return;
|
|
253
|
+
const rightToLeft = globalThis.getComputedStyle(event.currentTarget).direction === "rtl";
|
|
254
|
+
const intent = intentOf(event.key, orientation, rightToLeft ? -1 : 1);
|
|
255
|
+
if (intent === void 0) return;
|
|
256
|
+
for (const moving of ordered().slice(...spanOf(ordered(), intent, stop.current, wrap))) {
|
|
257
|
+
claim(moving.id);
|
|
258
|
+
moving.element.focus();
|
|
259
|
+
}
|
|
260
|
+
event.preventDefault();
|
|
261
|
+
};
|
|
262
|
+
$[7] = claim;
|
|
263
|
+
$[8] = holds;
|
|
264
|
+
$[9] = ordered;
|
|
265
|
+
$[10] = orientation;
|
|
266
|
+
$[11] = stop.current;
|
|
267
|
+
$[12] = wrap;
|
|
268
|
+
$[13] = t1;
|
|
269
|
+
} else t1 = $[13];
|
|
270
|
+
const onKeyDown = useCallbackRef(t1);
|
|
271
|
+
let t2;
|
|
272
|
+
if ($[14] !== claim) {
|
|
273
|
+
t2 = (id) => {
|
|
274
|
+
claim(id);
|
|
275
|
+
};
|
|
276
|
+
$[14] = claim;
|
|
277
|
+
$[15] = t2;
|
|
278
|
+
} else t2 = $[15];
|
|
279
|
+
const onFocus = useCallbackRef(t2);
|
|
280
|
+
let t3;
|
|
281
|
+
if ($[16] !== activeId || $[17] !== onFocus || $[18] !== register) {
|
|
282
|
+
t3 = {
|
|
283
|
+
activeId,
|
|
284
|
+
onFocus,
|
|
285
|
+
register
|
|
286
|
+
};
|
|
287
|
+
$[16] = activeId;
|
|
288
|
+
$[17] = onFocus;
|
|
289
|
+
$[18] = register;
|
|
290
|
+
$[19] = t3;
|
|
291
|
+
} else t3 = $[19];
|
|
292
|
+
const t4 = t3;
|
|
293
|
+
let t5;
|
|
294
|
+
if ($[20] !== onKeyDown || $[21] !== t4) {
|
|
295
|
+
t5 = {
|
|
296
|
+
group: t4,
|
|
297
|
+
onKeyDown
|
|
298
|
+
};
|
|
299
|
+
$[20] = onKeyDown;
|
|
300
|
+
$[21] = t4;
|
|
301
|
+
$[22] = t5;
|
|
302
|
+
} else t5 = $[22];
|
|
303
|
+
return t5;
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/roving-focus/item.tsx
|
|
307
|
+
/**
|
|
308
|
+
* Draws one item of the group, which holds the tab stop when the arrows reach it.
|
|
309
|
+
*
|
|
310
|
+
* @remarks
|
|
311
|
+
* Exactly one item is reachable by Tab and every other is at minus one, which is what makes the
|
|
312
|
+
* whole group one stop in the tab order. A disabled item registers with nothing, so the arrows
|
|
313
|
+
* pass over it, and it does not claim the stop when a pointer focuses it: an item that claimed
|
|
314
|
+
* the stop without registering would leave the stop pointing at nothing and take the group out
|
|
315
|
+
* of the tab order. The element is kept in state rather than in a ref, because the effect that
|
|
316
|
+
* registers it has to run again once the element is there, and a ref changing starts no effect.
|
|
317
|
+
*/
|
|
318
|
+
/**
|
|
319
|
+
* Draws the element an item is laid out on.
|
|
320
|
+
*/
|
|
321
|
+
const Shell$1 = withContext$1("div", "item");
|
|
322
|
+
/**
|
|
323
|
+
* Takes the tab stop when the arrows reach it, and leaves the tab order otherwise.
|
|
324
|
+
*
|
|
325
|
+
* @throws {@link Error} When it is drawn outside a group.
|
|
326
|
+
*/
|
|
327
|
+
function Item(props) {
|
|
328
|
+
const $ = c(27);
|
|
329
|
+
if ($[0] !== "906ba9f383c3499089f80a6d094880677f9a2e49a204ba65b1f93dfa19d6065b") {
|
|
330
|
+
for (let $i = 0; $i < 27; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
331
|
+
$[0] = "906ba9f383c3499089f80a6d094880677f9a2e49a204ba65b1f93dfa19d6065b";
|
|
332
|
+
}
|
|
333
|
+
let handed;
|
|
334
|
+
let named;
|
|
335
|
+
let rest;
|
|
336
|
+
let t0;
|
|
337
|
+
if ($[1] !== props) {
|
|
338
|
+
({disabled: t0, id: named, ref: handed, ...rest} = props);
|
|
339
|
+
$[1] = props;
|
|
340
|
+
$[2] = handed;
|
|
341
|
+
$[3] = named;
|
|
342
|
+
$[4] = rest;
|
|
343
|
+
$[5] = t0;
|
|
344
|
+
} else {
|
|
345
|
+
handed = $[2];
|
|
346
|
+
named = $[3];
|
|
347
|
+
rest = $[4];
|
|
348
|
+
t0 = $[5];
|
|
349
|
+
}
|
|
350
|
+
const disabled = t0 === void 0 ? false : t0;
|
|
351
|
+
const group = use(RovingFocusContext);
|
|
352
|
+
if (group === void 0) throw new Error("RovingFocus.Item is drawn inside RovingFocus.Root and nowhere else.");
|
|
353
|
+
const generated = useId();
|
|
354
|
+
const id = named ?? generated;
|
|
355
|
+
const { activeId, onFocus, register } = group;
|
|
356
|
+
const [element, setElement] = useState(null);
|
|
357
|
+
let t1;
|
|
358
|
+
if ($[6] !== handed) {
|
|
359
|
+
t1 = (node) => {
|
|
360
|
+
setElement(node);
|
|
361
|
+
if (typeof handed === "function") handed(node);
|
|
362
|
+
else if (handed !== null && handed !== void 0) handed.current = node;
|
|
363
|
+
};
|
|
364
|
+
$[6] = handed;
|
|
365
|
+
$[7] = t1;
|
|
366
|
+
} else t1 = $[7];
|
|
367
|
+
const attach = useCallbackRef(t1);
|
|
368
|
+
let t2;
|
|
369
|
+
if ($[8] !== disabled || $[9] !== id || $[10] !== onFocus) {
|
|
370
|
+
t2 = () => {
|
|
371
|
+
if (!disabled) onFocus(id);
|
|
372
|
+
};
|
|
373
|
+
$[8] = disabled;
|
|
374
|
+
$[9] = id;
|
|
375
|
+
$[10] = onFocus;
|
|
376
|
+
$[11] = t2;
|
|
377
|
+
} else t2 = $[11];
|
|
378
|
+
const claim = useCallbackRef(t2);
|
|
379
|
+
let t3;
|
|
380
|
+
let t4;
|
|
381
|
+
if ($[12] !== disabled || $[13] !== element || $[14] !== id || $[15] !== register) {
|
|
382
|
+
t3 = () => {
|
|
383
|
+
if (element === null || disabled) return;
|
|
384
|
+
return register({
|
|
385
|
+
element,
|
|
386
|
+
id
|
|
387
|
+
});
|
|
388
|
+
};
|
|
389
|
+
t4 = [
|
|
390
|
+
disabled,
|
|
391
|
+
element,
|
|
392
|
+
id,
|
|
393
|
+
register
|
|
394
|
+
];
|
|
395
|
+
$[12] = disabled;
|
|
396
|
+
$[13] = element;
|
|
397
|
+
$[14] = id;
|
|
398
|
+
$[15] = register;
|
|
399
|
+
$[16] = t3;
|
|
400
|
+
$[17] = t4;
|
|
401
|
+
} else {
|
|
402
|
+
t3 = $[16];
|
|
403
|
+
t4 = $[17];
|
|
404
|
+
}
|
|
405
|
+
useEffect(t3, t4);
|
|
406
|
+
const t5 = disabled || void 0;
|
|
407
|
+
const t6 = activeId === id ? "" : void 0;
|
|
408
|
+
const t7 = disabled ? "" : void 0;
|
|
409
|
+
const t8 = !disabled && activeId === id ? 0 : -1;
|
|
410
|
+
let t9;
|
|
411
|
+
if ($[18] !== attach || $[19] !== claim || $[20] !== id || $[21] !== rest || $[22] !== t5 || $[23] !== t6 || $[24] !== t7 || $[25] !== t8) {
|
|
412
|
+
t9 = /* @__PURE__ */ jsx(Shell$1, {
|
|
413
|
+
"aria-disabled": t5,
|
|
414
|
+
"data-active": t6,
|
|
415
|
+
"data-disabled": t7,
|
|
416
|
+
id,
|
|
417
|
+
onFocus: claim,
|
|
418
|
+
ref: attach,
|
|
419
|
+
tabIndex: t8,
|
|
420
|
+
...rest
|
|
421
|
+
});
|
|
422
|
+
$[18] = attach;
|
|
423
|
+
$[19] = claim;
|
|
424
|
+
$[20] = id;
|
|
425
|
+
$[21] = rest;
|
|
426
|
+
$[22] = t5;
|
|
427
|
+
$[23] = t6;
|
|
428
|
+
$[24] = t7;
|
|
429
|
+
$[25] = t8;
|
|
430
|
+
$[26] = t9;
|
|
431
|
+
} else t9 = $[26];
|
|
432
|
+
return t9;
|
|
433
|
+
}
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/roving-focus/root.tsx
|
|
436
|
+
/**
|
|
437
|
+
* Draws the element the group is laid out on.
|
|
438
|
+
*/
|
|
439
|
+
const Shell = withProvider$1("div", "root");
|
|
440
|
+
/**
|
|
441
|
+
* Keeps one tab stop for the items inside it, and moves it under the arrow keys.
|
|
442
|
+
*/
|
|
443
|
+
function Root(props) {
|
|
444
|
+
const $ = c(27);
|
|
445
|
+
if ($[0] !== "16a674b835753bf9fd806b5c17b8426cf42995a3c3e2732778987a1e4a0700e7") {
|
|
446
|
+
for (let $i = 0; $i < 27; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
447
|
+
$[0] = "16a674b835753bf9fd806b5c17b8426cf42995a3c3e2732778987a1e4a0700e7";
|
|
448
|
+
}
|
|
449
|
+
let activeId;
|
|
450
|
+
let children;
|
|
451
|
+
let defaultActiveId;
|
|
452
|
+
let onActiveIdChange;
|
|
453
|
+
let rest;
|
|
454
|
+
let t0;
|
|
455
|
+
let t1;
|
|
456
|
+
if ($[1] !== props) {
|
|
457
|
+
({activeId, children, defaultActiveId, onActiveIdChange, orientation: t0, wrap: t1, ...rest} = props);
|
|
458
|
+
$[1] = props;
|
|
459
|
+
$[2] = activeId;
|
|
460
|
+
$[3] = children;
|
|
461
|
+
$[4] = defaultActiveId;
|
|
462
|
+
$[5] = onActiveIdChange;
|
|
463
|
+
$[6] = rest;
|
|
464
|
+
$[7] = t0;
|
|
465
|
+
$[8] = t1;
|
|
466
|
+
} else {
|
|
467
|
+
activeId = $[2];
|
|
468
|
+
children = $[3];
|
|
469
|
+
defaultActiveId = $[4];
|
|
470
|
+
onActiveIdChange = $[5];
|
|
471
|
+
rest = $[6];
|
|
472
|
+
t0 = $[7];
|
|
473
|
+
t1 = $[8];
|
|
474
|
+
}
|
|
475
|
+
const orientation = t0 === void 0 ? "horizontal" : t0;
|
|
476
|
+
const wrap = t1 === void 0 ? false : t1;
|
|
477
|
+
let t2;
|
|
478
|
+
if ($[9] !== activeId || $[10] !== defaultActiveId || $[11] !== onActiveIdChange || $[12] !== orientation || $[13] !== wrap) {
|
|
479
|
+
t2 = {
|
|
480
|
+
activeId,
|
|
481
|
+
defaultActiveId,
|
|
482
|
+
onActiveIdChange,
|
|
483
|
+
orientation,
|
|
484
|
+
wrap
|
|
485
|
+
};
|
|
486
|
+
$[9] = activeId;
|
|
487
|
+
$[10] = defaultActiveId;
|
|
488
|
+
$[11] = onActiveIdChange;
|
|
489
|
+
$[12] = orientation;
|
|
490
|
+
$[13] = wrap;
|
|
491
|
+
$[14] = t2;
|
|
492
|
+
} else t2 = $[14];
|
|
493
|
+
const { group, onKeyDown } = useRovingFocus(t2);
|
|
494
|
+
let t3;
|
|
495
|
+
if ($[15] !== orientation || $[16] !== rest.role) {
|
|
496
|
+
t3 = rest.role !== void 0 && orientation !== "both" ? { "aria-orientation": orientation } : void 0;
|
|
497
|
+
$[15] = orientation;
|
|
498
|
+
$[16] = rest.role;
|
|
499
|
+
$[17] = t3;
|
|
500
|
+
} else t3 = $[17];
|
|
501
|
+
const told = t3;
|
|
502
|
+
let t4;
|
|
503
|
+
if ($[18] !== children || $[19] !== onKeyDown || $[20] !== orientation || $[21] !== rest || $[22] !== told) {
|
|
504
|
+
t4 = /* @__PURE__ */ jsx(Shell, {
|
|
505
|
+
onKeyDown,
|
|
506
|
+
orientation,
|
|
507
|
+
...told,
|
|
508
|
+
...rest,
|
|
509
|
+
children
|
|
510
|
+
});
|
|
511
|
+
$[18] = children;
|
|
512
|
+
$[19] = onKeyDown;
|
|
513
|
+
$[20] = orientation;
|
|
514
|
+
$[21] = rest;
|
|
515
|
+
$[22] = told;
|
|
516
|
+
$[23] = t4;
|
|
517
|
+
} else t4 = $[23];
|
|
518
|
+
let t5;
|
|
519
|
+
if ($[24] !== group || $[25] !== t4) {
|
|
520
|
+
t5 = /* @__PURE__ */ jsx(RovingFocusContext, {
|
|
521
|
+
value: group,
|
|
522
|
+
children: t4
|
|
523
|
+
});
|
|
524
|
+
$[24] = group;
|
|
525
|
+
$[25] = t4;
|
|
526
|
+
$[26] = t5;
|
|
527
|
+
} else t5 = $[26];
|
|
528
|
+
return t5;
|
|
529
|
+
}
|
|
530
|
+
//#endregion
|
|
531
|
+
//#region src/roving-focus/index.ts
|
|
532
|
+
var roving_focus_exports = /* @__PURE__ */ __exportAll({
|
|
533
|
+
Item: () => Item,
|
|
534
|
+
Root: () => Root
|
|
535
|
+
});
|
|
536
|
+
//#endregion
|
|
537
|
+
//#region src/skip-nav/context.ts
|
|
538
|
+
/**
|
|
539
|
+
* Binds the skip link's recipe to the parts that draw it.
|
|
540
|
+
*
|
|
541
|
+
* @remarks
|
|
542
|
+
* Apart from the recipe, because an application's compiler reads the recipe at build time and
|
|
543
|
+
* the binding needs the runtime. Neither part takes a variant, so each is bound on its own
|
|
544
|
+
* rather than one providing for the other: a page puts the link at the top and the target
|
|
545
|
+
* further down, and the two are never nested.
|
|
546
|
+
*/
|
|
547
|
+
/**
|
|
548
|
+
* Binds the recipe once, for the link and for the target it jumps to.
|
|
549
|
+
*/
|
|
550
|
+
const { withProvider } = createSlotRecipeContext(recipe$1);
|
|
551
|
+
//#endregion
|
|
552
|
+
//#region src/skip-nav/link.ts
|
|
553
|
+
/**
|
|
554
|
+
* The fragment the link points at and the target answers to, where neither states one.
|
|
555
|
+
*/
|
|
556
|
+
const SKIP_NAV_TARGET = "content";
|
|
557
|
+
/**
|
|
558
|
+
* Jumps a keyboard past the navigation to the page's content.
|
|
559
|
+
*/
|
|
560
|
+
const Link = withProvider("a", "link", { defaultProps: { href: `#${SKIP_NAV_TARGET}` } });
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/skip-nav/target.ts
|
|
563
|
+
/**
|
|
564
|
+
* Takes the focus the skip link sends, and draws nothing.
|
|
565
|
+
*/
|
|
566
|
+
const Target = withProvider("div", "target", { defaultProps: {
|
|
567
|
+
id: SKIP_NAV_TARGET,
|
|
568
|
+
tabIndex: -1
|
|
569
|
+
} });
|
|
570
|
+
//#endregion
|
|
571
|
+
//#region src/skip-nav/index.ts
|
|
572
|
+
var skip_nav_exports = /* @__PURE__ */ __exportAll({
|
|
573
|
+
Link: () => Link,
|
|
574
|
+
SKIP_NAV_TARGET: () => SKIP_NAV_TARGET,
|
|
575
|
+
Target: () => Target
|
|
576
|
+
});
|
|
577
|
+
//#endregion
|
|
578
|
+
//#region src/visually-hidden/context.ts
|
|
579
|
+
/**
|
|
580
|
+
* Binds the hidden text's recipe to the element that draws it.
|
|
581
|
+
*
|
|
582
|
+
* @remarks
|
|
583
|
+
* Apart from the recipe, because an application's compiler reads the recipe at build time and
|
|
584
|
+
* the binding needs the runtime. A recipe file that also bound one would put the runtime behind
|
|
585
|
+
* every compiler configuration that reads it.
|
|
586
|
+
*/
|
|
587
|
+
/**
|
|
588
|
+
* Binds the recipe once, for the hidden text and for whatever sets its variants from above.
|
|
589
|
+
*/
|
|
590
|
+
const { PropsProvider, withContext } = createRecipeContext(recipe$2);
|
|
591
|
+
//#endregion
|
|
592
|
+
//#region src/visually-hidden/visually-hidden.ts
|
|
593
|
+
/**
|
|
594
|
+
* Reads its words to a screen reader and draws them nowhere.
|
|
595
|
+
*/
|
|
596
|
+
const VisuallyHidden = withContext("span");
|
|
597
|
+
//#endregion
|
|
598
|
+
export { roving_focus_exports as RovingFocus, skip_nav_exports as SkipNav, VisuallyHidden, PropsProvider as VisuallyHiddenPropsProvider };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { defineRecipe, defineSlotRecipe } from "@stealthscale/theme/authoring";
|
|
2
|
+
//#region src/roving-focus/recipe.ts
|
|
3
|
+
/**
|
|
4
|
+
* States what a set of controls under one tab stop is: a row or a column of items, laid out the
|
|
5
|
+
* way the arrows move through them.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* The orientation is one axis because it decides two things that have to agree: which arrows
|
|
9
|
+
* move focus, which the root reads, and which way the items run, which the recipe draws. A group
|
|
10
|
+
* the arrows move through in both directions wraps, because a set a reader moves through on two
|
|
11
|
+
* axes is a set that runs onto a second line.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Lays the items out in a row the arrows run along until a caller says otherwise.
|
|
15
|
+
*/
|
|
16
|
+
const recipe$2 = defineSlotRecipe({
|
|
17
|
+
base: {
|
|
18
|
+
item: { minInlineSize: "0" },
|
|
19
|
+
root: { display: "flex" }
|
|
20
|
+
},
|
|
21
|
+
className: "roving-focus",
|
|
22
|
+
defaultVariants: { orientation: "horizontal" },
|
|
23
|
+
jsx: [/^RovingFocus(\.\w+)?$/u],
|
|
24
|
+
slots: ["root", "item"],
|
|
25
|
+
variants: { orientation: {
|
|
26
|
+
both: { root: {
|
|
27
|
+
flexDirection: "row",
|
|
28
|
+
flexWrap: "wrap"
|
|
29
|
+
} },
|
|
30
|
+
horizontal: { root: { flexDirection: "row" } },
|
|
31
|
+
vertical: { root: { flexDirection: "column" } }
|
|
32
|
+
} }
|
|
33
|
+
});
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/skip-nav/recipe.ts
|
|
36
|
+
/**
|
|
37
|
+
* States what a way past the navigation is: a link a keyboard reaches first and an eye sees only
|
|
38
|
+
* while focus is on it, and the place on the page it jumps to.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* The link is hidden the way the hidden text is hidden, and comes into view under focus, so a
|
|
42
|
+
* reader tabbing into the page meets it and a reader who never tabs never sees it. The target
|
|
43
|
+
* states nothing but its own class, because a page decides what it looks like and the target's
|
|
44
|
+
* whole job is to be somewhere focus can land.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* Draws the link over the top of the page while focus is on it, and leaves the target alone.
|
|
48
|
+
*/
|
|
49
|
+
const recipe$1 = defineSlotRecipe({
|
|
50
|
+
base: {
|
|
51
|
+
link: {
|
|
52
|
+
_focusVisible: {
|
|
53
|
+
borderRadius: "l2",
|
|
54
|
+
insetBlockStart: "inset.md",
|
|
55
|
+
insetInlineStart: "inset.md",
|
|
56
|
+
layerStyle: "fill.surface",
|
|
57
|
+
paddingBlock: "inset.sm",
|
|
58
|
+
paddingInline: "inset.md",
|
|
59
|
+
position: "fixed",
|
|
60
|
+
srOnly: false,
|
|
61
|
+
textStyle: "label.md",
|
|
62
|
+
zIndex: "skipNav"
|
|
63
|
+
},
|
|
64
|
+
srOnly: true
|
|
65
|
+
},
|
|
66
|
+
target: { scrollMarginBlockStart: "inset.lg" }
|
|
67
|
+
},
|
|
68
|
+
className: "skip-nav",
|
|
69
|
+
jsx: [/^SkipNav(\.\w+)?$/u],
|
|
70
|
+
slots: ["link", "target"]
|
|
71
|
+
});
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/visually-hidden/recipe.ts
|
|
74
|
+
/**
|
|
75
|
+
* States what visually hidden text is: words a screen reader reads and an eye never sees, and
|
|
76
|
+
* which come into view where a keyboard reaches them.
|
|
77
|
+
*
|
|
78
|
+
* @remarks
|
|
79
|
+
* The compiler's `srOnly` utility writes the hiding, which takes the words out of sight without
|
|
80
|
+
* taking them out of the accessibility tree the way `display: none` and `visibility: hidden`
|
|
81
|
+
* both do. A control a reader can reach by keyboard has to be visible once focus is on it, or a
|
|
82
|
+
* sighted reader tabbing through the page loses their place, so the focusable value undoes the
|
|
83
|
+
* hiding for as long as focus is there.
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* Hides what it holds from sight and leaves it to a screen reader.
|
|
87
|
+
*/
|
|
88
|
+
const recipe = defineRecipe({
|
|
89
|
+
base: { srOnly: true },
|
|
90
|
+
className: "visually-hidden",
|
|
91
|
+
jsx: [/^VisuallyHidden$/u],
|
|
92
|
+
variants: { focusable: { true: { _focusVisible: {
|
|
93
|
+
borderRadius: "l1",
|
|
94
|
+
insetBlockStart: "inset.md",
|
|
95
|
+
insetInlineStart: "inset.md",
|
|
96
|
+
layerStyle: "fill.surface",
|
|
97
|
+
paddingBlock: "inset.sm",
|
|
98
|
+
paddingInline: "inset.md",
|
|
99
|
+
position: "fixed",
|
|
100
|
+
srOnly: false,
|
|
101
|
+
textStyle: "label.md",
|
|
102
|
+
zIndex: "skipNav"
|
|
103
|
+
} } } }
|
|
104
|
+
});
|
|
105
|
+
//#endregion
|
|
106
|
+
export { recipe$1 as n, recipe$2 as r, recipe as t };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { __exportAll as t };
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/theme.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Publishes the preset that registers every recipe in this package, for an application's compiler
|
|
4
|
+
* to install.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* The list is written by hand. The package's own specification reports a recipe file the list
|
|
8
|
+
* leaves out, so no generator runs here.
|
|
9
|
+
*/
|
|
10
|
+
declare const _default: import("@stealthscale/theme/authoring").Preset;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { _default as default };
|
package/dist/theme.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { n as recipe$2, r as recipe$1, t as recipe } from "./recipe-BKojQp6r.js";
|
|
2
|
+
import { definePreset } from "@stealthscale/theme/authoring";
|
|
3
|
+
//#region src/theme.ts
|
|
4
|
+
/**
|
|
5
|
+
* Publishes the preset that registers every recipe in this package, for an application's compiler
|
|
6
|
+
* to install.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* The list is written by hand. The package's own specification reports a recipe file the list
|
|
10
|
+
* leaves out, so no generator runs here.
|
|
11
|
+
*/
|
|
12
|
+
var theme_default = definePreset({
|
|
13
|
+
name: "@stealthscale/component-a11y",
|
|
14
|
+
theme: { extend: {
|
|
15
|
+
recipes: { visuallyHidden: recipe },
|
|
16
|
+
slotRecipes: {
|
|
17
|
+
rovingFocus: recipe$1,
|
|
18
|
+
skipNav: recipe$2
|
|
19
|
+
}
|
|
20
|
+
} }
|
|
21
|
+
});
|
|
22
|
+
//#endregion
|
|
23
|
+
export { theme_default as default };
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stealthscale/component-a11y",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Draws what a keyboard and a screen reader need, and an eye does not.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"a11y",
|
|
7
|
+
"accessibility",
|
|
8
|
+
"components",
|
|
9
|
+
"react"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/stealth-scale/config/tree/main/components/a11y#readme",
|
|
12
|
+
"bugs": "https://github.com/stealth-scale/config/issues",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/stealth-scale/config.git",
|
|
17
|
+
"directory": "components/a11y"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"imports": {
|
|
27
|
+
"#*": "./src/*"
|
|
28
|
+
},
|
|
29
|
+
"exports": {
|
|
30
|
+
".": "./dist/index.js",
|
|
31
|
+
"./theme": "./dist/theme.js",
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@arethetypeswrong/core": "^0.18.5",
|
|
39
|
+
"@rolldown/plugin-babel": "^0.2.4",
|
|
40
|
+
"@stealthscale/hooks": "^0.1.0",
|
|
41
|
+
"@stealthscale/theme": "^0.3.0",
|
|
42
|
+
"@stealthscale/vite-config": "^0.6.0",
|
|
43
|
+
"@stealthscale/vite-config-react": "^0.8.0",
|
|
44
|
+
"@stealthscale/vite-config-theme": "^0.1.0",
|
|
45
|
+
"@stealthscale/vite-config-typescript": "^0.3.0",
|
|
46
|
+
"@testing-library/react": "^16.3.0",
|
|
47
|
+
"@types/react": "^19.3.0",
|
|
48
|
+
"@types/react-dom": "^19.3.0",
|
|
49
|
+
"babel-plugin-react-compiler": "^1.0.0",
|
|
50
|
+
"happy-dom": "^20.14.3",
|
|
51
|
+
"oxc-transform-react": "^0.149.0",
|
|
52
|
+
"publint": "^0.3.24",
|
|
53
|
+
"react": "^19.3.0",
|
|
54
|
+
"react-dom": "^19.3.0",
|
|
55
|
+
"typescript": "^7.0.2",
|
|
56
|
+
"vitest": "4.1.11"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@stealthscale/hooks": "^0.1.0",
|
|
60
|
+
"@stealthscale/theme": "^0.3.0",
|
|
61
|
+
"react": "^19.3.0"
|
|
62
|
+
},
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=26.0.0"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"build": "vp pack",
|
|
68
|
+
"check": "vp check",
|
|
69
|
+
"test": "vp test"
|
|
70
|
+
}
|
|
71
|
+
}
|