@a11yfred/neighbor 0.2.0 → 1.0.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/CHANGELOG.md +80 -3
- package/CONTRIBUTING.md +115 -0
- package/README.md +174 -77
- package/RULES-CONTENT.md +296 -0
- package/RULES-CSS.md +61 -0
- package/RULES-MARKUP.md +156 -0
- package/RULES.md +55 -0
- package/lib/content-rules.js +858 -0
- package/lib/helpers-angular.js +146 -146
- package/lib/helpers-jsx.js +193 -193
- package/lib/helpers-vue.js +151 -151
- package/lib/helpers.js +37 -37
- package/lib/rules.js +2413 -2411
- package/lib/ulam-rules.js +301 -301
- package/neighbor-content.mjs +80 -0
- package/neighbor-eslint-angular.mjs +68 -68
- package/neighbor-eslint-vue.mjs +48 -48
- package/neighbor-eslint.mjs +56 -56
- package/neighbor-stylelint.mjs +257 -196
- package/package.json +18 -5
package/neighbor-stylelint.mjs
CHANGED
|
@@ -1,196 +1,257 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @a11yfred/neighbor
|
|
3
|
-
*
|
|
4
|
-
* Rules:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
current.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
atRule.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
'
|
|
135
|
-
'
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @a11yfred/neighbor - Stylelint plugin
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* neighbor/user-preferences - Warn when motion, transparency, or alpha colors
|
|
6
|
+
* are used without @media (prefers-*) fallbacks.
|
|
7
|
+
* neighbor/no-outline-none - Disallow bare outline:none/0 outside :focus selectors.
|
|
8
|
+
* neighbor/no-forced-colors-none - Disallow forced-color-adjust:none inside @media (forced-colors).
|
|
9
|
+
*
|
|
10
|
+
* Sources and credits:
|
|
11
|
+
* WCAG 2.1 / 2.2 w3.org/TR/WCAG21, w3.org/TR/WCAG22
|
|
12
|
+
* WebAIM webaim.org
|
|
13
|
+
* double-great/stylelint-a11y github.com/double-great/stylelint-a11y
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const defined = (x) => x !== undefined && x !== null;
|
|
17
|
+
|
|
18
|
+
/** True if the node or any ancestor is a prefers-* / forced-colors media block */
|
|
19
|
+
function insidePreferencesMedia(node) {
|
|
20
|
+
let current = node.parent;
|
|
21
|
+
while (defined(current)) {
|
|
22
|
+
if (
|
|
23
|
+
current.type === 'atrule' &&
|
|
24
|
+
current.name === 'media' &&
|
|
25
|
+
/prefers-|forced-colors/.test(current.params)
|
|
26
|
+
) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
current = current.parent;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** True if the value string contains an alpha channel (rgb/hsl with slash, or 8-digit hex) */
|
|
35
|
+
function hasAlphaChannel(value) {
|
|
36
|
+
// rgb(r g b / a) or rgba() or hsl(h s l / a)
|
|
37
|
+
if (/\b(rgb|hsl)a?\s*\(/.test(value) && /\/\s*[01]?\.?\d+[^)]*\)/.test(value)) return true;
|
|
38
|
+
// 8-digit hex #rrggbbaa
|
|
39
|
+
if (/#[0-9a-fA-F]{8}\b/.test(value)) return true;
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** True if the opacity value is a structural endpoint (0 or 1), not a dim */
|
|
44
|
+
function isStructuralOpacity(value) {
|
|
45
|
+
const n = parseFloat(value.trim());
|
|
46
|
+
return n === 0 || n === 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const ruleName = 'neighbor/user-preferences';
|
|
50
|
+
|
|
51
|
+
const messages = {
|
|
52
|
+
opacity: (value) =>
|
|
53
|
+
`opacity: ${value} creates a transparency effect. Add a fallback in @media (prefers-reduced-transparency: reduce) that uses an explicit color token instead. See src/components/ui/user-preferences.css.`,
|
|
54
|
+
animation: (prop, value) =>
|
|
55
|
+
`${prop}: ${value} uses motion. Add a fallback in @media (prefers-reduced-motion: reduce) that disables or stills this animation. See src/components/ui/user-preferences.css.`,
|
|
56
|
+
alpha: (value) =>
|
|
57
|
+
`Color value "${value}" uses an alpha channel. Add an opaque fallback in @media (prefers-reduced-transparency: reduce). See src/components/ui/user-preferences.css.`,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const meta = { url: 'https://github.com/a11yfred/neighbor' };
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Collect all selectors that appear inside a prefers-reduced-* or forced-colors
|
|
64
|
+
* media block anywhere in the file. Used to suppress warnings when an override exists.
|
|
65
|
+
* Splits comma-separated selector lists so each individual selector is tracked.
|
|
66
|
+
*/
|
|
67
|
+
function collectCoveredSelectors(root) {
|
|
68
|
+
const covered = new Set();
|
|
69
|
+
root.walkAtRules('media', (atRule) => {
|
|
70
|
+
if (!/prefers-|forced-colors/.test(atRule.params)) return;
|
|
71
|
+
atRule.walkRules((ruleNode) => {
|
|
72
|
+
// Split comma-separated selector lists
|
|
73
|
+
for (const part of ruleNode.selector.split(',')) {
|
|
74
|
+
const sel = part.trim();
|
|
75
|
+
covered.add(sel);
|
|
76
|
+
// Also add bare selector without pseudo-classes/pseudo-elements
|
|
77
|
+
covered.add(sel.replace(/::[^,\s{]+|:[^,\s{(]+(\([^)]*\))?/g, '').trim());
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
return covered;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** True if the given selector (or its bare form) is in the covered set */
|
|
85
|
+
function isCovered(selector, covered) {
|
|
86
|
+
// Split comma lists in the base selector too
|
|
87
|
+
for (const part of selector.split(',')) {
|
|
88
|
+
const norm = part.trim();
|
|
89
|
+
if (covered.has(norm)) return true;
|
|
90
|
+
const bare = norm.replace(/::[^,\s{]+|:[^,\s{(]+(\([^)]*\))?/g, '').trim();
|
|
91
|
+
if (covered.has(bare)) return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** @type {import('stylelint').Rule} */
|
|
97
|
+
function rule(primaryOption) {
|
|
98
|
+
return (root, result) => {
|
|
99
|
+
// Only enforce inside src/components/ui/
|
|
100
|
+
const filePath = (root.source?.input?.file ?? '').replace(/\\/g, '/');
|
|
101
|
+
if (!filePath.includes('src/components/ui')) return;
|
|
102
|
+
// Never enforce inside user-preferences.css itself
|
|
103
|
+
if (filePath.includes('user-preferences.css')) return;
|
|
104
|
+
|
|
105
|
+
// Pre-scan: collect selectors already covered by prefers overrides in this file
|
|
106
|
+
const covered = collectCoveredSelectors(root);
|
|
107
|
+
|
|
108
|
+
root.walkDecls((decl) => {
|
|
109
|
+
if (insidePreferencesMedia(decl)) return;
|
|
110
|
+
|
|
111
|
+
// If the containing rule's selector is already overridden in a prefers block, skip
|
|
112
|
+
const parentSelector = decl.parent?.selector ?? '';
|
|
113
|
+
if (parentSelector && isCovered(parentSelector, covered)) return;
|
|
114
|
+
|
|
115
|
+
const prop = decl.prop.toLowerCase();
|
|
116
|
+
const value = decl.value;
|
|
117
|
+
|
|
118
|
+
// opacity - warn on non-structural values (i.e. dims like 0.5, 0.75)
|
|
119
|
+
if (prop === 'opacity' && !isStructuralOpacity(value)) {
|
|
120
|
+
decl.warn(result, messages.opacity(value), { rule: ruleName });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// animation or transition
|
|
125
|
+
if (prop === 'animation' || prop === 'transition' || prop === 'animation-name') {
|
|
126
|
+
// Skip "none" values - they're already the reduced state
|
|
127
|
+
if (/^none\b/i.test(value.trim())) return;
|
|
128
|
+
decl.warn(result, messages.animation(prop, value), { rule: ruleName });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Alpha-channel color values on visual properties
|
|
133
|
+
const visualProps = new Set([
|
|
134
|
+
'background', 'background-color', 'color', 'border', 'border-color',
|
|
135
|
+
'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color',
|
|
136
|
+
'outline-color', 'box-shadow', 'text-shadow', 'fill', 'stroke',
|
|
137
|
+
]);
|
|
138
|
+
if (visualProps.has(prop) && hasAlphaChannel(value)) {
|
|
139
|
+
decl.warn(result, messages.alpha(value), { rule: ruleName });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const userPreferences = { ruleName, rule, meta };
|
|
146
|
+
|
|
147
|
+
// ─── Rule: neighbor/no-outline-none ──────────────────────────────────────────
|
|
148
|
+
// outline: none / outline: 0 removes the browser's default keyboard focus
|
|
149
|
+
// indicator. This is one of the most common keyboard accessibility failures -
|
|
150
|
+
// keyboard users lose all visual indication of where focus is.
|
|
151
|
+
//
|
|
152
|
+
// Only fires when the declaration is NOT inside a :focus-visible, :focus, or
|
|
153
|
+
// :focus-within selector, and no sibling :focus-visible rule overrides it in
|
|
154
|
+
// the same block.
|
|
155
|
+
//
|
|
156
|
+
// Ref: WCAG 2.4.7 (Focus Visible); WebAIM; Roselli; cross-practitioner consensus
|
|
157
|
+
|
|
158
|
+
const noOutlineNoneRuleName = 'neighbor/no-outline-none';
|
|
159
|
+
|
|
160
|
+
const noOutlineNoneMessages = {
|
|
161
|
+
removed: (value) =>
|
|
162
|
+
`outline: ${value} removes the keyboard focus indicator. Add a :focus-visible rule with a visible outline or custom focus style. (WCAG 2.4.7 / WebAIM)`,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const noOutlineNoneMeta = { url: 'https://github.com/a11yfred/neighbor' };
|
|
166
|
+
|
|
167
|
+
/** Returns true if the selector string targets a focus state. */
|
|
168
|
+
function isFocusSelector(selector) {
|
|
169
|
+
return /:focus(?:-visible|-within)?/i.test(selector);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** @type {import('stylelint').Rule} */
|
|
173
|
+
function noOutlineNoneRule(_primaryOption) {
|
|
174
|
+
return (root, result) => {
|
|
175
|
+
root.walkDecls(/^outline$/i, (decl) => {
|
|
176
|
+
const value = decl.value.trim().toLowerCase();
|
|
177
|
+
if (value !== 'none' && value !== '0') return;
|
|
178
|
+
|
|
179
|
+
// If this declaration is already inside a :focus / :focus-visible rule, it's fine -
|
|
180
|
+
// the author is intentionally restyling focus, which is acceptable as long as they
|
|
181
|
+
// provide an alternative (we can't verify the alternative statically, so we allow it).
|
|
182
|
+
const parent = decl.parent;
|
|
183
|
+
if (parent?.type === 'rule' && isFocusSelector(parent.selector ?? '')) return;
|
|
184
|
+
|
|
185
|
+
// Flag it
|
|
186
|
+
decl.warn(result, noOutlineNoneMessages.removed(decl.value), { rule: noOutlineNoneRuleName });
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const noOutlineNone = {
|
|
192
|
+
ruleName: noOutlineNoneRuleName,
|
|
193
|
+
rule: noOutlineNoneRule,
|
|
194
|
+
meta: noOutlineNoneMeta,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// ─── Rule: neighbor/no-forced-colors-none ────────────────────────────────────
|
|
198
|
+
// forced-color-adjust: none inside @media (forced-colors) actively opts out of
|
|
199
|
+
// Windows High Contrast Mode and other forced-colors user settings. For users
|
|
200
|
+
// who depend on forced colors this is their last resort for viewing content -
|
|
201
|
+
// overriding it is a serious accessibility regression.
|
|
202
|
+
//
|
|
203
|
+
// Legitimate narrow exceptions exist (e.g. color pickers where all swatches
|
|
204
|
+
// would collapse to CanvasText). Those should be scoped tightly to the specific
|
|
205
|
+
// element, not a whole section, and are typically few enough to suppress inline.
|
|
206
|
+
//
|
|
207
|
+
// Ref: Sarah Higley - forced-color-adjust: none (sarahmhigley.com)
|
|
208
|
+
// Adrian Roselli - WHCM and System Colors (adrianroselli.com)
|
|
209
|
+
// WCAG SC 1.4.11 Non-text Contrast; SC 1.4.3 Contrast (Minimum)
|
|
210
|
+
|
|
211
|
+
const noForcedColorsNoneRuleName = 'neighbor/no-forced-colors-none';
|
|
212
|
+
|
|
213
|
+
const noForcedColorsNoneMessages = {
|
|
214
|
+
none: (selector) =>
|
|
215
|
+
`forced-color-adjust: none on "${selector}" inside @media (forced-colors) opts out of ` +
|
|
216
|
+
`Windows High Contrast Mode, removing all forced-color overrides for these elements. ` +
|
|
217
|
+
`Users who depend on forced colors lose visibility entirely. ` +
|
|
218
|
+
`Remove forced-color-adjust: none, or scope it to the narrowest possible element ` +
|
|
219
|
+
`(e.g. a color-picker swatch) and add a comment explaining why. ` +
|
|
220
|
+
`(Higley / Roselli - WCAG SC 1.4.11 / SC 1.4.3)`,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const noForcedColorsNoneMeta = { url: 'https://github.com/a11yfred/neighbor' };
|
|
224
|
+
|
|
225
|
+
/** Returns true if the node is directly inside a @media (forced-colors) block. */
|
|
226
|
+
function insideForcedColorsMedia(node) {
|
|
227
|
+
let current = node.parent;
|
|
228
|
+
while (current) {
|
|
229
|
+
if (
|
|
230
|
+
current.type === 'atrule' &&
|
|
231
|
+
current.name === 'media' &&
|
|
232
|
+
/forced-colors/.test(current.params)
|
|
233
|
+
) return true;
|
|
234
|
+
current = current.parent;
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** @type {import('stylelint').Rule} */
|
|
240
|
+
function noForcedColorsNoneRule(_primaryOption) {
|
|
241
|
+
return (root, result) => {
|
|
242
|
+
root.walkDecls(/^forced-color-adjust$/i, (decl) => {
|
|
243
|
+
if (decl.value.trim().toLowerCase() !== 'none') return;
|
|
244
|
+
if (!insideForcedColorsMedia(decl)) return;
|
|
245
|
+
const selector = decl.parent?.selector ?? decl.parent?.name ?? '(unknown)';
|
|
246
|
+
decl.warn(result, noForcedColorsNoneMessages.none(selector), { rule: noForcedColorsNoneRuleName });
|
|
247
|
+
});
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const noForcedColorsNone = {
|
|
252
|
+
ruleName: noForcedColorsNoneRuleName,
|
|
253
|
+
rule: noForcedColorsNoneRule,
|
|
254
|
+
meta: noForcedColorsNoneMeta,
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
export default [userPreferences, noOutlineNone, noForcedColorsNone];
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a11yfred/neighbor",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Accessibility linting
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Accessibility linting for a11yfred - ESLint (markup + content), Stylelint (CSS). Won't you be my neighbor?",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"files": ["lib", "*.mjs", "LICENSE", "README.md", "CHANGELOG.md"],
|
|
7
|
+
"files": ["lib", "*.mjs", "LICENSE", "README.md", "CHANGELOG.md", "CONTRIBUTING.md", "RULES.md", "RULES-MARKUP.md", "RULES-CSS.md", "RULES-CONTENT.md"],
|
|
8
8
|
"main": "./neighbor-stylelint.mjs",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"./stylelint": "./neighbor-stylelint.mjs",
|
|
20
20
|
"./eslint": "./neighbor-eslint.mjs",
|
|
21
21
|
"./eslint-vue": "./neighbor-eslint-vue.mjs",
|
|
22
|
-
"./eslint-angular": "./neighbor-eslint-angular.mjs"
|
|
22
|
+
"./eslint-angular": "./neighbor-eslint-angular.mjs",
|
|
23
|
+
"./content": "./neighbor-content.mjs"
|
|
23
24
|
},
|
|
24
25
|
"keywords": [
|
|
25
26
|
"stylelint",
|
|
@@ -30,7 +31,11 @@
|
|
|
30
31
|
"css",
|
|
31
32
|
"aria",
|
|
32
33
|
"accessibility",
|
|
33
|
-
"a11y"
|
|
34
|
+
"a11y",
|
|
35
|
+
"content",
|
|
36
|
+
"plain-language",
|
|
37
|
+
"inclusive-language",
|
|
38
|
+
"ableist"
|
|
34
39
|
],
|
|
35
40
|
"peerDependencies": {
|
|
36
41
|
"@angular-eslint/eslint-plugin-template": ">=17",
|
|
@@ -43,6 +48,9 @@
|
|
|
43
48
|
"eslint": {
|
|
44
49
|
"optional": true
|
|
45
50
|
},
|
|
51
|
+
"eslint-plugin-jsx-a11y": {
|
|
52
|
+
"optional": true
|
|
53
|
+
},
|
|
46
54
|
"eslint-plugin-vuejs-accessibility": {
|
|
47
55
|
"optional": true
|
|
48
56
|
},
|
|
@@ -53,6 +61,11 @@
|
|
|
53
61
|
"optional": true
|
|
54
62
|
}
|
|
55
63
|
},
|
|
64
|
+
"overrides": {
|
|
65
|
+
"eslint-plugin-jsx-a11y": {
|
|
66
|
+
"eslint": ">=8"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
56
69
|
"devDependencies": {
|
|
57
70
|
"eslint": "^9.39.4",
|
|
58
71
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|