@tanstack/devtools-a11y 0.0.1 → 0.1.1
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/dist/esm/core/components/IssueCard.d.ts +10 -0
- package/dist/esm/core/components/IssueCard.js +83 -0
- package/dist/esm/core/components/IssueCard.js.map +1 -0
- package/dist/esm/core/components/IssueList.d.ts +6 -0
- package/dist/esm/core/components/IssueList.js +134 -0
- package/dist/esm/core/components/IssueList.js.map +1 -0
- package/dist/esm/core/components/Settings.d.ts +6 -0
- package/dist/esm/core/components/Settings.js +251 -0
- package/dist/esm/core/components/Settings.js.map +1 -0
- package/dist/esm/core/components/Shell.d.ts +2 -0
- package/dist/esm/core/components/Shell.js +214 -0
- package/dist/esm/core/components/Shell.js.map +1 -0
- package/dist/esm/core/components/index.d.ts +2 -0
- package/dist/esm/core/components/index.js +14 -0
- package/dist/esm/core/components/index.js.map +1 -0
- package/dist/esm/core/contexts/allyContext.d.ts +17 -0
- package/dist/esm/core/contexts/allyContext.js +66 -0
- package/dist/esm/core/contexts/allyContext.js.map +1 -0
- package/dist/esm/core/core.d.ts +19 -0
- package/dist/esm/core/core.js +8 -0
- package/dist/esm/core/core.js.map +1 -0
- package/dist/esm/core/index.d.ts +9 -0
- package/dist/esm/core/index.js +9 -0
- package/dist/esm/core/index.js.map +1 -0
- package/dist/esm/core/production.d.ts +2 -0
- package/dist/esm/core/production.js +4 -0
- package/dist/esm/core/styles/styles.d.ts +85 -0
- package/dist/esm/core/styles/styles.js +547 -0
- package/dist/esm/core/styles/styles.js.map +1 -0
- package/dist/esm/core/types/types.d.ts +141 -0
- package/dist/esm/core/utils/ally-audit.utils.d.ts +19 -0
- package/dist/esm/core/utils/ally-audit.utils.js +226 -0
- package/dist/esm/core/utils/ally-audit.utils.js.map +1 -0
- package/dist/esm/core/utils/config.utils.d.ts +17 -0
- package/dist/esm/core/utils/config.utils.js +63 -0
- package/dist/esm/core/utils/config.utils.js.map +1 -0
- package/dist/esm/core/utils/custom-audit.utils.d.ts +13 -0
- package/dist/esm/core/utils/custom-audit.utils.js +426 -0
- package/dist/esm/core/utils/custom-audit.utils.js.map +1 -0
- package/dist/esm/core/utils/export-audit.uitls.d.ts +17 -0
- package/dist/esm/core/utils/export-audit.uitls.js +83 -0
- package/dist/esm/core/utils/export-audit.uitls.js.map +1 -0
- package/dist/esm/core/utils/ui.utils.d.ts +24 -0
- package/dist/esm/core/utils/ui.utils.js +330 -0
- package/dist/esm/core/utils/ui.utils.js.map +1 -0
- package/dist/esm/react/A11yDevtools.d.ts +5 -0
- package/dist/esm/react/A11yDevtools.js +8 -0
- package/dist/esm/react/A11yDevtools.js.map +1 -0
- package/dist/esm/react/index.d.ts +8 -0
- package/dist/esm/react/index.js +11 -0
- package/dist/esm/react/index.js.map +1 -0
- package/dist/esm/react/plugin.d.ts +12 -0
- package/dist/esm/react/plugin.js +11 -0
- package/dist/esm/react/plugin.js.map +1 -0
- package/dist/esm/react/production/A11yDevtools.d.ts +5 -0
- package/dist/esm/react/production/A11yDevtools.js +8 -0
- package/dist/esm/react/production/A11yDevtools.js.map +1 -0
- package/dist/esm/react/production/plugin.d.ts +7 -0
- package/dist/esm/react/production/plugin.js +11 -0
- package/dist/esm/react/production/plugin.js.map +1 -0
- package/dist/esm/react/production.d.ts +3 -0
- package/dist/esm/react/production.js +5 -0
- package/dist/esm/solid/A11yDevtools.d.ts +5 -0
- package/dist/esm/solid/A11yDevtools.js +8 -0
- package/dist/esm/solid/A11yDevtools.js.map +1 -0
- package/dist/esm/solid/index.d.ts +8 -0
- package/dist/esm/solid/index.js +9 -0
- package/dist/esm/solid/index.js.map +1 -0
- package/dist/esm/solid/plugin.d.ts +12 -0
- package/dist/esm/solid/plugin.js +11 -0
- package/dist/esm/solid/plugin.js.map +1 -0
- package/dist/esm/solid/production/A11yDevtools.d.ts +5 -0
- package/dist/esm/solid/production/A11yDevtools.js +8 -0
- package/dist/esm/solid/production/A11yDevtools.js.map +1 -0
- package/dist/esm/solid/production/plugin.d.ts +7 -0
- package/dist/esm/solid/production/plugin.js +11 -0
- package/dist/esm/solid/production/plugin.js.map +1 -0
- package/dist/esm/solid/production.d.ts +3 -0
- package/dist/esm/solid/production.js +3 -0
- package/package.json +110 -7
- package/src/core/components/IssueCard.tsx +75 -0
- package/src/core/components/IssueList.tsx +155 -0
- package/src/core/components/Settings.tsx +221 -0
- package/src/core/components/Shell.tsx +154 -0
- package/src/core/components/index.tsx +12 -0
- package/src/core/contexts/allyContext.tsx +118 -0
- package/src/core/core.tsx +11 -0
- package/src/core/index.ts +10 -0
- package/src/core/production.ts +5 -0
- package/src/core/styles/styles.ts +556 -0
- package/src/core/types/types.ts +177 -0
- package/src/core/utils/ally-audit.utils.ts +345 -0
- package/src/core/utils/config.utils.ts +68 -0
- package/src/core/utils/custom-audit.utils.ts +643 -0
- package/src/core/utils/export-audit.uitls.ts +180 -0
- package/src/core/utils/ui.utils.ts +483 -0
- package/src/react/A11yDevtools.ts +12 -0
- package/src/react/index.ts +16 -0
- package/src/react/plugin.ts +9 -0
- package/src/react/production/A11yDevtools.ts +11 -0
- package/src/react/production/plugin.ts +9 -0
- package/src/react/production.ts +7 -0
- package/src/solid/A11yDevtools.ts +11 -0
- package/src/solid/index.ts +14 -0
- package/src/solid/plugin.ts +9 -0
- package/src/solid/production/A11yDevtools.ts +10 -0
- package/src/solid/production/plugin.ts +9 -0
- package/src/solid/production.ts +5 -0
- package/README.md +0 -45
package/package.json
CHANGED
|
@@ -1,10 +1,113 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/devtools-a11y",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Accessibility auditing plugin for TanStack Devtools powered by axe-core",
|
|
5
|
+
"author": "TanStack",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/TanStack/devtools.git",
|
|
10
|
+
"directory": "packages/devtools-a11y"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://tanstack.com/devtools",
|
|
13
|
+
"funding": {
|
|
14
|
+
"type": "github",
|
|
15
|
+
"url": "https://github.com/sponsors/tannerlinsley"
|
|
16
|
+
},
|
|
5
17
|
"keywords": [
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
18
|
+
"devtools",
|
|
19
|
+
"accessibility",
|
|
20
|
+
"a11y",
|
|
21
|
+
"wcag",
|
|
22
|
+
"axe-core",
|
|
23
|
+
"audit"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"exports": {
|
|
27
|
+
"./core": {
|
|
28
|
+
"import": {
|
|
29
|
+
"types": "./dist/esm/core/index.d.ts",
|
|
30
|
+
"default": "./dist/esm/core/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"./core/production": {
|
|
34
|
+
"import": {
|
|
35
|
+
"types": "./dist/esm/core/production.d.ts",
|
|
36
|
+
"default": "./dist/esm/core/production.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"./react": {
|
|
40
|
+
"import": {
|
|
41
|
+
"types": "./dist/esm/react/index.d.ts",
|
|
42
|
+
"default": "./dist/esm/react/index.js"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"./react/production": {
|
|
46
|
+
"import": {
|
|
47
|
+
"types": "./dist/esm/react/production.d.ts",
|
|
48
|
+
"default": "./dist/esm/react/production.js"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"./solid": {
|
|
52
|
+
"import": {
|
|
53
|
+
"types": "./dist/esm/solid/index.d.ts",
|
|
54
|
+
"default": "./dist/esm/solid/index.js"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"./solid/production": {
|
|
58
|
+
"import": {
|
|
59
|
+
"types": "./dist/esm/solid/production.d.ts",
|
|
60
|
+
"default": "./dist/esm/solid/production.js"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"./package.json": "./package.json"
|
|
64
|
+
},
|
|
65
|
+
"sideEffects": false,
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=18"
|
|
68
|
+
},
|
|
69
|
+
"files": [
|
|
70
|
+
"dist/",
|
|
71
|
+
"src"
|
|
72
|
+
],
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"axe-core": "^4.10.0",
|
|
75
|
+
"goober": "^2.1.16",
|
|
76
|
+
"@tanstack/devtools-ui": "0.5.1",
|
|
77
|
+
"@tanstack/devtools-utils": "^0.4.0"
|
|
78
|
+
},
|
|
79
|
+
"devDependencies": {
|
|
80
|
+
"vite-plugin-solid": "^2.11.11"
|
|
81
|
+
},
|
|
82
|
+
"peerDependencies": {
|
|
83
|
+
"@types/react": ">=17.0.0",
|
|
84
|
+
"preact": ">=10.0.0",
|
|
85
|
+
"react": ">=17.0.0",
|
|
86
|
+
"solid-js": ">=1.9.7",
|
|
87
|
+
"vue": ">=3.2.0"
|
|
88
|
+
},
|
|
89
|
+
"peerDependenciesMeta": {
|
|
90
|
+
"preact": {
|
|
91
|
+
"optional": true
|
|
92
|
+
},
|
|
93
|
+
"react": {
|
|
94
|
+
"optional": true
|
|
95
|
+
},
|
|
96
|
+
"solid-js": {
|
|
97
|
+
"optional": true
|
|
98
|
+
},
|
|
99
|
+
"vue": {
|
|
100
|
+
"optional": true
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"scripts": {
|
|
104
|
+
"clean": "premove ./build ./dist",
|
|
105
|
+
"lint:fix": "eslint ./src --fix",
|
|
106
|
+
"test:eslint": "eslint ./src",
|
|
107
|
+
"test:lib": "vitest",
|
|
108
|
+
"test:lib:dev": "pnpm test:lib --watch",
|
|
109
|
+
"test:types": "tsc",
|
|
110
|
+
"test:build": "publint --strict",
|
|
111
|
+
"build": "vite build"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** @jsxImportSource solid-js */
|
|
2
|
+
|
|
3
|
+
import { For, Show } from 'solid-js'
|
|
4
|
+
import { Button } from '@tanstack/devtools-ui'
|
|
5
|
+
import { useStyles } from '../styles/styles'
|
|
6
|
+
|
|
7
|
+
// types
|
|
8
|
+
import type { A11yIssue, SeverityThreshold } from '../types/types'
|
|
9
|
+
|
|
10
|
+
interface A11yIssueCardProps {
|
|
11
|
+
issue: A11yIssue
|
|
12
|
+
impact: SeverityThreshold
|
|
13
|
+
selected: boolean
|
|
14
|
+
onSelect: () => void
|
|
15
|
+
onDisableRule: (ruleId: string) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function A11yIssueCard(props: A11yIssueCardProps) {
|
|
19
|
+
const selector = () => props.issue.nodes[0]?.selector || 'unknown'
|
|
20
|
+
const styles = useStyles()
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
class={styles().issueCard}
|
|
25
|
+
classList={{
|
|
26
|
+
[styles().issueCardSelected]: props.selected,
|
|
27
|
+
}}
|
|
28
|
+
onClick={props.onSelect}
|
|
29
|
+
>
|
|
30
|
+
<div class={styles().issueRow}>
|
|
31
|
+
<div class={styles().issueMain}>
|
|
32
|
+
<div class={styles().issueTitleRow}>
|
|
33
|
+
<span class={styles().dot(props.impact)} />
|
|
34
|
+
|
|
35
|
+
<span>{props.issue.ruleId}</span>
|
|
36
|
+
</div>
|
|
37
|
+
<p class={styles().issueMessage}>{props.issue.message}</p>
|
|
38
|
+
|
|
39
|
+
<div class={styles().selector}>{selector()}</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div class={styles().issueAside}>
|
|
43
|
+
<a
|
|
44
|
+
class={styles().helpLink}
|
|
45
|
+
href={props.issue.helpUrl}
|
|
46
|
+
target="_blank"
|
|
47
|
+
rel="noopener noreferrer"
|
|
48
|
+
onClick={(event) => event.stopPropagation()}
|
|
49
|
+
>
|
|
50
|
+
Learn more
|
|
51
|
+
</a>
|
|
52
|
+
|
|
53
|
+
<Button
|
|
54
|
+
variant="secondary"
|
|
55
|
+
ghost
|
|
56
|
+
onClick={(event: MouseEvent) => {
|
|
57
|
+
event.stopPropagation()
|
|
58
|
+
props.onDisableRule(props.issue.ruleId)
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
Disable rule
|
|
62
|
+
</Button>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<Show when={props.issue.wcagTags.length > 0}>
|
|
67
|
+
<div class={styles().tags}>
|
|
68
|
+
<For each={props.issue.wcagTags.slice(0, 3)}>
|
|
69
|
+
{(tag) => <span class={styles().tag}>{tag}</span>}
|
|
70
|
+
</For>
|
|
71
|
+
</div>
|
|
72
|
+
</Show>
|
|
73
|
+
</div>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/** @jsxImportSource solid-js */
|
|
2
|
+
|
|
3
|
+
import { For, Show } from 'solid-js'
|
|
4
|
+
import { useAllyContext } from '../contexts/allyContext'
|
|
5
|
+
import {
|
|
6
|
+
SEVERITY_LABELS,
|
|
7
|
+
clearHighlights,
|
|
8
|
+
highlightAllIssues,
|
|
9
|
+
highlightElement,
|
|
10
|
+
scrollToElement,
|
|
11
|
+
} from '../utils/ui.utils'
|
|
12
|
+
import { IMPACTS } from '../utils/ally-audit.utils'
|
|
13
|
+
import { useStyles } from '../styles/styles'
|
|
14
|
+
import { A11yIssueCard } from './IssueCard'
|
|
15
|
+
|
|
16
|
+
// types
|
|
17
|
+
import type { Signal } from 'solid-js'
|
|
18
|
+
|
|
19
|
+
interface A11yIssueListProps {
|
|
20
|
+
selectedIssueSignal: Signal<string>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function A11yIssueList(props: A11yIssueListProps) {
|
|
24
|
+
const [selectedIssueId, setSelectedIssueId] = props.selectedIssueSignal
|
|
25
|
+
|
|
26
|
+
// hooks
|
|
27
|
+
const styles = useStyles()
|
|
28
|
+
const ally = useAllyContext()
|
|
29
|
+
|
|
30
|
+
// handlers
|
|
31
|
+
const handleIssueClick = (issueId: string) => {
|
|
32
|
+
if (selectedIssueId() === issueId) {
|
|
33
|
+
setSelectedIssueId('')
|
|
34
|
+
clearHighlights()
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
ally.config.showOverlays &&
|
|
38
|
+
ally.allyResult.audit &&
|
|
39
|
+
ally.filteredIssues().length > 0
|
|
40
|
+
) {
|
|
41
|
+
highlightAllIssues(ally.filteredIssues())
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setSelectedIssueId(issueId)
|
|
48
|
+
clearHighlights()
|
|
49
|
+
|
|
50
|
+
const issue = ally.allyResult.audit?.issues.find((i) => i.id === issueId)
|
|
51
|
+
if (!issue || issue.nodes.length === 0) return
|
|
52
|
+
|
|
53
|
+
let scrolled = false
|
|
54
|
+
for (const node of issue.nodes) {
|
|
55
|
+
const selector = node.selector
|
|
56
|
+
if (!selector) continue
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const el = document.querySelector(selector)
|
|
60
|
+
if (el) {
|
|
61
|
+
if (!scrolled) {
|
|
62
|
+
scrollToElement(selector)
|
|
63
|
+
scrolled = true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
highlightElement(selector, issue.impact, {
|
|
67
|
+
showTooltip: true,
|
|
68
|
+
ruleId: issue.ruleId,
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.warn('[A11y Panel] Invalid selector:', selector, error)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div>
|
|
79
|
+
<div class={styles().summaryGrid}>
|
|
80
|
+
<For each={IMPACTS}>
|
|
81
|
+
{(impact) => {
|
|
82
|
+
// Count issues from the reactive filteredIssues memo so counts update when config.threshold changes
|
|
83
|
+
const issuesForImpact = () =>
|
|
84
|
+
ally.filteredIssues().filter((issue) => issue.impact === impact)
|
|
85
|
+
const count = () => issuesForImpact().length || 0
|
|
86
|
+
|
|
87
|
+
const active = () => ally.impactKey() === impact
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<button
|
|
91
|
+
class={styles().summaryButton}
|
|
92
|
+
classList={{
|
|
93
|
+
[styles().summaryButtonActive(impact)]: active(),
|
|
94
|
+
}}
|
|
95
|
+
onClick={() => {
|
|
96
|
+
ally.setImpactKey(
|
|
97
|
+
ally.impactKey() === impact ? 'all' : impact,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
setSelectedIssueId('')
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
<div class={styles().summaryCount(impact)}>{count()}</div>
|
|
104
|
+
<div class={styles().summaryLabel}>
|
|
105
|
+
{SEVERITY_LABELS[impact]}
|
|
106
|
+
</div>
|
|
107
|
+
</button>
|
|
108
|
+
)
|
|
109
|
+
}}
|
|
110
|
+
</For>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<For each={IMPACTS}>
|
|
114
|
+
{(impact) => {
|
|
115
|
+
const issues = () =>
|
|
116
|
+
ally.filteredIssues().filter((issue) => issue.impact === impact)
|
|
117
|
+
|
|
118
|
+
const shouldRender = () => {
|
|
119
|
+
if (ally.impactKey() !== 'all') {
|
|
120
|
+
return ally.impactKey() === impact
|
|
121
|
+
}
|
|
122
|
+
return issues().length > 0
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<Show when={shouldRender()}>
|
|
127
|
+
<div class={styles().section}>
|
|
128
|
+
<h3 class={styles().sectionTitle(impact)}>
|
|
129
|
+
{SEVERITY_LABELS[impact]} ({issues().length})
|
|
130
|
+
</h3>
|
|
131
|
+
|
|
132
|
+
<For each={issues()}>
|
|
133
|
+
{(issue) => (
|
|
134
|
+
<A11yIssueCard
|
|
135
|
+
issue={issue}
|
|
136
|
+
impact={impact}
|
|
137
|
+
selected={selectedIssueId() === issue.id}
|
|
138
|
+
onSelect={() => handleIssueClick(issue.id)}
|
|
139
|
+
onDisableRule={() =>
|
|
140
|
+
ally.setConfig('disabledRules', [
|
|
141
|
+
...ally.config.disabledRules,
|
|
142
|
+
issue.ruleId,
|
|
143
|
+
])
|
|
144
|
+
}
|
|
145
|
+
/>
|
|
146
|
+
)}
|
|
147
|
+
</For>
|
|
148
|
+
</div>
|
|
149
|
+
</Show>
|
|
150
|
+
)
|
|
151
|
+
}}
|
|
152
|
+
</For>
|
|
153
|
+
</div>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/** @jsxImportSource solid-js */
|
|
2
|
+
|
|
3
|
+
import { For, Show, createMemo, createSignal } from 'solid-js'
|
|
4
|
+
import { Button, Input, Select } from '@tanstack/devtools-ui'
|
|
5
|
+
import { getAvailableRules } from '../utils/ally-audit.utils'
|
|
6
|
+
import { useAllyContext } from '../contexts/allyContext'
|
|
7
|
+
import { CATEGORIES, CATEGORY_LABELS, useStyles } from '../styles/styles'
|
|
8
|
+
|
|
9
|
+
// types
|
|
10
|
+
import type {
|
|
11
|
+
RuleCategory,
|
|
12
|
+
RuleSetPreset,
|
|
13
|
+
SeverityThreshold,
|
|
14
|
+
} from '../types/types'
|
|
15
|
+
|
|
16
|
+
interface A11ySettingsOverlayProps {
|
|
17
|
+
onClose: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function A11ySettingsOverlay(props: A11ySettingsOverlayProps) {
|
|
21
|
+
const { config, setConfig } = useAllyContext()
|
|
22
|
+
const styles = useStyles()
|
|
23
|
+
|
|
24
|
+
const disabledRulesSet = createMemo(() => new Set(config.disabledRules))
|
|
25
|
+
const availableRules = createMemo(() => getAvailableRules())
|
|
26
|
+
|
|
27
|
+
const [searchString, setSearchString] = createSignal('')
|
|
28
|
+
const [searchCategory, setSearchCategory] = createSignal<RuleCategory>('all')
|
|
29
|
+
|
|
30
|
+
const filteredRules = createMemo(() => {
|
|
31
|
+
const cat = searchCategory()
|
|
32
|
+
const query = searchString().toLowerCase()
|
|
33
|
+
return availableRules().filter((rule) => {
|
|
34
|
+
if (cat !== 'all' && !rule.tags.includes(cat)) {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!query) return true
|
|
39
|
+
return (
|
|
40
|
+
rule.id.toLowerCase().includes(query) ||
|
|
41
|
+
rule.description.toLowerCase().includes(query)
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div class={styles().settingsOverlay}>
|
|
48
|
+
<div class={styles().settingsHeader}>
|
|
49
|
+
<h3 class={styles().settingsTitle}>Settings</h3>
|
|
50
|
+
<Button variant="secondary" outline onClick={props.onClose}>
|
|
51
|
+
Done
|
|
52
|
+
</Button>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class={styles().settingsContent}>
|
|
56
|
+
<div class={styles().settingsSection}>
|
|
57
|
+
<h4 class={styles().settingsSectionLabel}>General</h4>
|
|
58
|
+
<div class={styles().settingsRowStack}>
|
|
59
|
+
<Select<SeverityThreshold>
|
|
60
|
+
label="Severity Threshold"
|
|
61
|
+
description="Only show issues at or above this level"
|
|
62
|
+
value={config.threshold}
|
|
63
|
+
options={[
|
|
64
|
+
{ value: 'critical', label: 'Critical' },
|
|
65
|
+
{ value: 'serious', label: 'Serious' },
|
|
66
|
+
{ value: 'moderate', label: 'Moderate' },
|
|
67
|
+
{ value: 'minor', label: 'Minor' },
|
|
68
|
+
]}
|
|
69
|
+
onChange={(value: string) => {
|
|
70
|
+
setConfig('threshold', value as SeverityThreshold)
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
<Select<RuleSetPreset>
|
|
74
|
+
label="Rule Set"
|
|
75
|
+
description="WCAG conformance level or standard"
|
|
76
|
+
value={config.ruleSet}
|
|
77
|
+
options={[
|
|
78
|
+
{ value: 'wcag2a', label: 'WCAG 2.0 A' },
|
|
79
|
+
{ value: 'wcag2aa', label: 'WCAG 2.0 AA' },
|
|
80
|
+
{ value: 'wcag21aa', label: 'WCAG 2.1 AA' },
|
|
81
|
+
{ value: 'wcag22aa', label: 'WCAG 2.2 AA' },
|
|
82
|
+
{ value: 'section508', label: 'Section 508' },
|
|
83
|
+
{ value: 'best-practice', label: 'Best Practice' },
|
|
84
|
+
{ value: 'all', label: 'All Rules' },
|
|
85
|
+
]}
|
|
86
|
+
onChange={(value: string) => {
|
|
87
|
+
setConfig('ruleSet', value as RuleSetPreset)
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div>
|
|
94
|
+
<div class={styles().rulesHeaderRow}>
|
|
95
|
+
<h4 class={styles().settingsSectionLabel}>
|
|
96
|
+
Rules ({availableRules().length} total,{' '}
|
|
97
|
+
{config.disabledRules.length} disabled)
|
|
98
|
+
</h4>
|
|
99
|
+
|
|
100
|
+
<div class={styles().rulesHeaderActions}>
|
|
101
|
+
<Button
|
|
102
|
+
variant="success"
|
|
103
|
+
outline
|
|
104
|
+
onClick={() => setConfig('disabledRules', [])}
|
|
105
|
+
>
|
|
106
|
+
Enable All
|
|
107
|
+
</Button>
|
|
108
|
+
|
|
109
|
+
<Button
|
|
110
|
+
variant="danger"
|
|
111
|
+
outline
|
|
112
|
+
onClick={() =>
|
|
113
|
+
setConfig(
|
|
114
|
+
'disabledRules',
|
|
115
|
+
availableRules().map((rule) => rule.id),
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
>
|
|
119
|
+
Disable All
|
|
120
|
+
</Button>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div class={styles().filtersRow}>
|
|
125
|
+
<Select<RuleCategory>
|
|
126
|
+
label="Category"
|
|
127
|
+
value={searchCategory()}
|
|
128
|
+
options={CATEGORIES.map((cat) => ({
|
|
129
|
+
value: cat,
|
|
130
|
+
label: CATEGORY_LABELS[cat],
|
|
131
|
+
}))}
|
|
132
|
+
onChange={(value: string) =>
|
|
133
|
+
setSearchCategory(value as RuleCategory)
|
|
134
|
+
}
|
|
135
|
+
/>
|
|
136
|
+
|
|
137
|
+
<Input
|
|
138
|
+
label="Search"
|
|
139
|
+
placeholder="Search rules..."
|
|
140
|
+
value={searchString()}
|
|
141
|
+
onChange={(value: string) => setSearchString(value)}
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div class={styles().rulesList}>
|
|
146
|
+
<For each={filteredRules()}>
|
|
147
|
+
{(rule, idx) => {
|
|
148
|
+
const isDisabled = () => disabledRulesSet().has(rule.id)
|
|
149
|
+
const isBestPracticeOnly = () =>
|
|
150
|
+
rule.tags.includes('best-practice') &&
|
|
151
|
+
!rule.tags.some(
|
|
152
|
+
(tag) =>
|
|
153
|
+
tag.startsWith('wcag') || tag.startsWith('section508'),
|
|
154
|
+
)
|
|
155
|
+
const categoryTag = () =>
|
|
156
|
+
rule.tags.find((tag) => tag.startsWith('cat.'))
|
|
157
|
+
const hasBorder = () => idx() < filteredRules().length - 1
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<label
|
|
161
|
+
class={styles().ruleRow}
|
|
162
|
+
classList={{
|
|
163
|
+
[styles().ruleRowDisabled]: isDisabled(),
|
|
164
|
+
[styles().ruleRowBorder]: hasBorder(),
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
<input
|
|
168
|
+
class={styles().ruleCheckbox}
|
|
169
|
+
type="checkbox"
|
|
170
|
+
checked={!isDisabled()}
|
|
171
|
+
onChange={() =>
|
|
172
|
+
setConfig('disabledRules', (rules) => {
|
|
173
|
+
if (disabledRulesSet().has(rule.id)) {
|
|
174
|
+
return rules.filter((id) => id !== rule.id)
|
|
175
|
+
} else {
|
|
176
|
+
return [...rules, rule.id]
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
/>
|
|
181
|
+
<div class={styles().ruleInfo}>
|
|
182
|
+
<div class={styles().ruleTop}>
|
|
183
|
+
<span
|
|
184
|
+
class={styles().ruleId}
|
|
185
|
+
classList={{
|
|
186
|
+
[styles().ruleIdDisabled]: isDisabled(),
|
|
187
|
+
}}
|
|
188
|
+
>
|
|
189
|
+
{rule.id}
|
|
190
|
+
</span>
|
|
191
|
+
<Show when={isBestPracticeOnly()}>
|
|
192
|
+
<span
|
|
193
|
+
class={styles().bpBadge}
|
|
194
|
+
title="Best Practice only"
|
|
195
|
+
>
|
|
196
|
+
BP
|
|
197
|
+
</span>
|
|
198
|
+
</Show>
|
|
199
|
+
</div>
|
|
200
|
+
<div class={styles().ruleDesc}>{rule.description}</div>
|
|
201
|
+
<Show when={categoryTag()}>
|
|
202
|
+
{(tag) => (
|
|
203
|
+
<div class={styles().catTagRow}>
|
|
204
|
+
<span class={styles().catTag}>
|
|
205
|
+
{CATEGORY_LABELS[tag() as RuleCategory] ||
|
|
206
|
+
tag().replace('cat.', '')}
|
|
207
|
+
</span>
|
|
208
|
+
</div>
|
|
209
|
+
)}
|
|
210
|
+
</Show>
|
|
211
|
+
</div>
|
|
212
|
+
</label>
|
|
213
|
+
)
|
|
214
|
+
}}
|
|
215
|
+
</For>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
)
|
|
221
|
+
}
|