@stealthscale/component-a11y 0.1.0 → 0.1.2
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/README.md +54 -38
- package/dist/cyclonedx/bom.json +1 -1
- package/dist/index.d.ts +47 -38
- package/dist/index.js +239 -253
- package/dist/recipe-BM8WGKWH.js +127 -0
- package/dist/theme.d.ts +3 -4
- package/dist/theme.js +4 -5
- package/package.json +28 -25
- package/dist/recipe-BKojQp6r.js +0 -106
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { defineRecipe, defineSlotRecipe, dense } from "@stealthscale/theme/authoring";
|
|
2
|
+
//#region src/roving-focus/recipe.ts
|
|
3
|
+
/**
|
|
4
|
+
* Styles a roving focus group as a flex row or column in the direction its arrow keys move.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* One `orientation` axis sets both the layout and the arrow keys, so a column never responds to
|
|
8
|
+
* the left and right arrows. `both` wraps the row, because a group navigable on both axes runs
|
|
9
|
+
* onto more lines. The recipe has no `palette` or `effect` axis, because the group renders no
|
|
10
|
+
* box of its own.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Defaults to a horizontal group.
|
|
14
|
+
*/
|
|
15
|
+
const recipe$2 = defineSlotRecipe({
|
|
16
|
+
base: {
|
|
17
|
+
item: { minInlineSize: "0" },
|
|
18
|
+
root: { display: "flex" }
|
|
19
|
+
},
|
|
20
|
+
className: "roving-focus",
|
|
21
|
+
defaultVariants: { orientation: "horizontal" },
|
|
22
|
+
jsx: [/^RovingFocus(\.\w+)?$/u],
|
|
23
|
+
slots: ["root", "item"],
|
|
24
|
+
variants: {
|
|
25
|
+
/**
|
|
26
|
+
* Flex direction of the root, matching the arrow keys the group handles.
|
|
27
|
+
*/
|
|
28
|
+
orientation: {
|
|
29
|
+
both: { root: {
|
|
30
|
+
flexDirection: "row",
|
|
31
|
+
flexWrap: "wrap"
|
|
32
|
+
} },
|
|
33
|
+
horizontal: { root: { flexDirection: "row" } },
|
|
34
|
+
vertical: { root: { flexDirection: "column" } }
|
|
35
|
+
} }
|
|
36
|
+
});
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/skip-nav/recipe.ts
|
|
39
|
+
/**
|
|
40
|
+
* Styles a skip link that is clipped until keyboard focus, and the target it moves focus to.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* The link applies the `srOnly` utility at rest. Under `:focus-visible` it resets each property
|
|
44
|
+
* the clipping sets and fixes itself to the window's start corner on the `fill.surface` layer
|
|
45
|
+
* style. It does not use `srOnly: false`, which expands to `position: static` and `padding: 0`
|
|
46
|
+
* and conflicts with the fixed position and the padding. The target sets a scroll margin, so a
|
|
47
|
+
* sticky header does not cover it. The recipe has no axis, because the link has one look and the
|
|
48
|
+
* target renders nothing.
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* Clips the link at rest and offsets the target from the top of the scroll container.
|
|
52
|
+
*/
|
|
53
|
+
const recipe$1 = defineSlotRecipe({
|
|
54
|
+
base: {
|
|
55
|
+
link: {
|
|
56
|
+
_focusVisible: {
|
|
57
|
+
layerStyle: "fill.surface",
|
|
58
|
+
textStyle: "label.md",
|
|
59
|
+
blockSize: "auto",
|
|
60
|
+
borderRadius: "l2",
|
|
61
|
+
clip: "auto",
|
|
62
|
+
inlineSize: "auto",
|
|
63
|
+
insetBlockStart: dense("{spacing.inset.md}"),
|
|
64
|
+
insetInlineStart: dense("{spacing.inset.md}"),
|
|
65
|
+
margin: "0",
|
|
66
|
+
overflow: "visible",
|
|
67
|
+
paddingBlock: dense("{spacing.inset.sm}"),
|
|
68
|
+
paddingInline: dense("{spacing.inset.md}"),
|
|
69
|
+
position: "fixed",
|
|
70
|
+
zIndex: "skipNav"
|
|
71
|
+
},
|
|
72
|
+
srOnly: true
|
|
73
|
+
},
|
|
74
|
+
target: { scrollMarginBlockStart: dense("{spacing.inset.lg}") }
|
|
75
|
+
},
|
|
76
|
+
className: "skip-nav",
|
|
77
|
+
jsx: [/^SkipNav(\.\w+)?$/u],
|
|
78
|
+
slots: ["link", "target"]
|
|
79
|
+
});
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/visually-hidden/recipe.ts
|
|
82
|
+
/**
|
|
83
|
+
* Styles content that a screen reader announces and the browser does not paint.
|
|
84
|
+
*
|
|
85
|
+
* @remarks
|
|
86
|
+
* The base applies the `srOnly` utility, which clips the element to 1px and keeps it in the
|
|
87
|
+
* accessibility tree. `display: none` and `visibility: hidden` remove it from the tree. The
|
|
88
|
+
* `focusable` value cancels the clipping under `:focus-visible` and fixes the element to the
|
|
89
|
+
* window's start corner, so a keyboard user sees the control that holds focus. The recipe has no
|
|
90
|
+
* `palette` or `effect` axis, because the element renders nothing at rest.
|
|
91
|
+
*/
|
|
92
|
+
/**
|
|
93
|
+
* Clips the element at rest.
|
|
94
|
+
*/
|
|
95
|
+
const recipe = defineRecipe({
|
|
96
|
+
base: { srOnly: true },
|
|
97
|
+
className: "visually-hidden",
|
|
98
|
+
jsx: [/^VisuallyHidden$/u],
|
|
99
|
+
variants: {
|
|
100
|
+
/**
|
|
101
|
+
* Reveals the element under keyboard focus, on the `fill.surface` layer style at the window's
|
|
102
|
+
* start corner.
|
|
103
|
+
*
|
|
104
|
+
* @remarks
|
|
105
|
+
* The value resets each property the clipping sets. It does not use `srOnly: false`, which
|
|
106
|
+
* expands to `position: static` and `padding: 0` and conflicts with the fixed position and
|
|
107
|
+
* the padding.
|
|
108
|
+
*/
|
|
109
|
+
focusable: { true: { _focusVisible: {
|
|
110
|
+
layerStyle: "fill.surface",
|
|
111
|
+
textStyle: "label.md",
|
|
112
|
+
blockSize: "auto",
|
|
113
|
+
borderRadius: "l1",
|
|
114
|
+
clip: "auto",
|
|
115
|
+
inlineSize: "auto",
|
|
116
|
+
insetBlockStart: dense("{spacing.inset.md}"),
|
|
117
|
+
insetInlineStart: dense("{spacing.inset.md}"),
|
|
118
|
+
margin: "0",
|
|
119
|
+
overflow: "visible",
|
|
120
|
+
paddingBlock: dense("{spacing.inset.sm}"),
|
|
121
|
+
paddingInline: dense("{spacing.inset.md}"),
|
|
122
|
+
position: "fixed",
|
|
123
|
+
zIndex: "skipNav"
|
|
124
|
+
} } } }
|
|
125
|
+
});
|
|
126
|
+
//#endregion
|
|
127
|
+
export { recipe$1 as n, recipe$2 as r, recipe as t };
|
package/dist/theme.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
//#region src/theme.d.ts
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* to install.
|
|
3
|
+
* Declares the preset an application's compiler installs to pick up this package's recipes.
|
|
5
4
|
*
|
|
6
5
|
* @remarks
|
|
7
|
-
* The list is
|
|
8
|
-
*
|
|
6
|
+
* The list is maintained by hand. Nothing generates it, because the package's own specification
|
|
7
|
+
* fails when a recipe file in `src/` is missing from the preset.
|
|
9
8
|
*/
|
|
10
9
|
declare const _default: import("@stealthscale/theme/authoring").Preset;
|
|
11
10
|
//#endregion
|
package/dist/theme.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { n as recipe$2, r as recipe$1, t as recipe } from "./recipe-
|
|
1
|
+
import { n as recipe$2, r as recipe$1, t as recipe } from "./recipe-BM8WGKWH.js";
|
|
2
2
|
import { definePreset } from "@stealthscale/theme/authoring";
|
|
3
3
|
//#region src/theme.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* to install.
|
|
5
|
+
* Declares the preset an application's compiler installs to pick up this package's recipes.
|
|
7
6
|
*
|
|
8
7
|
* @remarks
|
|
9
|
-
* The list is
|
|
10
|
-
*
|
|
8
|
+
* The list is maintained by hand. Nothing generates it, because the package's own specification
|
|
9
|
+
* fails when a recipe file in `src/` is missing from the preset.
|
|
11
10
|
*/
|
|
12
11
|
var theme_default = definePreset({
|
|
13
12
|
name: "@stealthscale/component-a11y",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stealthscale/component-a11y",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Draws what a keyboard and a screen reader need, and an eye does not.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"a11y",
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
"components",
|
|
9
9
|
"react"
|
|
10
10
|
],
|
|
11
|
-
"homepage": "https://github.com/stealth-scale/
|
|
12
|
-
"bugs": "https://github.com/stealth-scale/
|
|
11
|
+
"homepage": "https://github.com/stealth-scale/scale/tree/main/components/a11y#readme",
|
|
12
|
+
"bugs": "https://github.com/stealth-scale/scale/issues",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "git+https://github.com/stealth-scale/
|
|
16
|
+
"url": "git+https://github.com/stealth-scale/scale.git",
|
|
17
17
|
"directory": "components/a11y"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
@@ -35,30 +35,33 @@
|
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@arethetypeswrong/core": "^0.18
|
|
39
|
-
"@
|
|
40
|
-
"@stealthscale/
|
|
41
|
-
"@stealthscale/
|
|
42
|
-
"@stealthscale/
|
|
43
|
-
"@stealthscale/vite-config
|
|
44
|
-
"@stealthscale/vite-config-
|
|
38
|
+
"@arethetypeswrong/core": "^0.18",
|
|
39
|
+
"@stealthscale/component-actions": "^0.2.1",
|
|
40
|
+
"@stealthscale/component-layout": "^0.2.1",
|
|
41
|
+
"@stealthscale/hooks": "^0.3.0",
|
|
42
|
+
"@stealthscale/theme": "^0.5.0",
|
|
43
|
+
"@stealthscale/vite-config": "^0.8.0",
|
|
44
|
+
"@stealthscale/vite-config-i18n": "^0.2.1",
|
|
45
|
+
"@stealthscale/vite-config-react": "^0.9.1",
|
|
46
|
+
"@stealthscale/vite-config-specimen": "^0.2.1",
|
|
47
|
+
"@stealthscale/vite-config-theme": "^0.2.1",
|
|
45
48
|
"@stealthscale/vite-config-typescript": "^0.3.0",
|
|
46
|
-
"@testing-library/react": "^16.3
|
|
47
|
-
"@types/react": "^19.3
|
|
48
|
-
"@types/react-dom": "^19.3
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"oxc-transform-react": "^0.
|
|
52
|
-
"publint": "^0.3
|
|
53
|
-
"react": "^19.3
|
|
54
|
-
"react-dom": "^19.3
|
|
55
|
-
"typescript": "^7.0
|
|
56
|
-
"vitest": "
|
|
49
|
+
"@testing-library/react": "^16.3",
|
|
50
|
+
"@types/react": "^19.3",
|
|
51
|
+
"@types/react-dom": "^19.3",
|
|
52
|
+
"happy-dom": "^20.14",
|
|
53
|
+
"lucide-react": "^1.52",
|
|
54
|
+
"oxc-transform-react": "^0.153",
|
|
55
|
+
"publint": "^0.3",
|
|
56
|
+
"react": "^19.3",
|
|
57
|
+
"react-dom": "^19.3",
|
|
58
|
+
"typescript": "^7.0",
|
|
59
|
+
"vitest": "5.0.3"
|
|
57
60
|
},
|
|
58
61
|
"peerDependencies": {
|
|
59
|
-
"@stealthscale/hooks": "^0.
|
|
60
|
-
"@stealthscale/theme": "^0.
|
|
61
|
-
"react": "^19.3
|
|
62
|
+
"@stealthscale/hooks": "^0.3.0",
|
|
63
|
+
"@stealthscale/theme": "^0.5.0",
|
|
64
|
+
"react": "^19.3"
|
|
62
65
|
},
|
|
63
66
|
"engines": {
|
|
64
67
|
"node": ">=26.0.0"
|
package/dist/recipe-BKojQp6r.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
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 };
|