@elastic/eslint-plugin-eui 2.0.0 → 2.2.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/README.md +16 -1
- package/lib/cjs/index.js +15 -2
- package/lib/cjs/rules/a11y/consistent_is_invalid_props.d.ts +3 -0
- package/lib/cjs/rules/a11y/consistent_is_invalid_props.d.ts.map +1 -0
- package/lib/cjs/rules/a11y/consistent_is_invalid_props.js +81 -0
- package/lib/cjs/rules/a11y/prefer_eui_icon_tip.d.ts +3 -0
- package/lib/cjs/rules/a11y/prefer_eui_icon_tip.d.ts.map +1 -0
- package/lib/cjs/rules/a11y/prefer_eui_icon_tip.js +42 -0
- package/lib/cjs/rules/a11y/require_aria_label_for_modals.d.ts +3 -0
- package/lib/cjs/rules/a11y/require_aria_label_for_modals.d.ts.map +1 -0
- package/lib/cjs/rules/a11y/require_aria_label_for_modals.js +69 -0
- package/lib/cjs/rules/a11y/sr_output_disabled_tooltip.d.ts +3 -0
- package/lib/cjs/rules/a11y/sr_output_disabled_tooltip.d.ts.map +1 -0
- package/lib/cjs/rules/a11y/sr_output_disabled_tooltip.js +71 -0
- package/lib/cjs/utils/get_attr_value.d.ts +3 -0
- package/lib/cjs/utils/get_attr_value.d.ts.map +1 -0
- package/lib/cjs/utils/get_attr_value.js +42 -0
- package/lib/esm/index.js +13 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/rules/a11y/consistent_is_invalid_props.d.ts +2 -0
- package/lib/esm/rules/a11y/consistent_is_invalid_props.js +93 -0
- package/lib/esm/rules/a11y/consistent_is_invalid_props.js.map +1 -0
- package/lib/esm/rules/a11y/prefer_eui_icon_tip.d.ts +2 -0
- package/lib/esm/rules/a11y/prefer_eui_icon_tip.js +42 -0
- package/lib/esm/rules/a11y/prefer_eui_icon_tip.js.map +1 -0
- package/lib/esm/rules/a11y/require_aria_label_for_modals.d.ts +2 -0
- package/lib/esm/rules/a11y/require_aria_label_for_modals.js +107 -0
- package/lib/esm/rules/a11y/require_aria_label_for_modals.js.map +1 -0
- package/lib/esm/rules/a11y/sr_output_disabled_tooltip.d.ts +2 -0
- package/lib/esm/rules/a11y/sr_output_disabled_tooltip.js +75 -0
- package/lib/esm/rules/a11y/sr_output_disabled_tooltip.js.map +1 -0
- package/lib/esm/utils/get_attr_value.d.ts +2 -0
- package/lib/esm/utils/get_attr_value.js +41 -0
- package/lib/esm/utils/get_attr_value.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,9 +125,24 @@ function MyComponent() {
|
|
|
125
125
|
)
|
|
126
126
|
}
|
|
127
127
|
```
|
|
128
|
-
|
|
129
128
|
It's worth pointing out that although the examples provided are specific to EUI components, this rule applies to all JSX elements.
|
|
130
129
|
|
|
130
|
+
### `@elastic/eui/require-aria-label-for-modals`
|
|
131
|
+
|
|
132
|
+
Ensures that EUI modal components (`EuiModal`, `EuiFlyout`, `EuiConfirmModal`) have either an `aria-label` or `aria-labelledby` prop for accessibility. This helps screen reader users understand the purpose and content of modal dialogs.
|
|
133
|
+
|
|
134
|
+
### `@elastic/eui/consistent-is-invalid-props`
|
|
135
|
+
|
|
136
|
+
Ensures that form control components within `EuiFormRow` components have matching `isInvalid` prop values. This maintains consistent validation state between parent form rows and their child form controls, leading to a more predictable and accessible user experience.
|
|
137
|
+
|
|
138
|
+
### `@elastic/eui/sr-output-disabled-tooltip`
|
|
139
|
+
|
|
140
|
+
Ensures `disableScreenReaderOutput` is set when `EuiToolTip` content matches `EuiButtonIcon` "aria-label".
|
|
141
|
+
|
|
142
|
+
### `@elastic/eui/prefer-eui-icon-tip`
|
|
143
|
+
|
|
144
|
+
Ensure `EuiIconTip` is used rather than `<EuiToolTip><EuiIcon/></EuiToolTip>`, as it provides better accessibility and improved support for assistive technologies.
|
|
145
|
+
|
|
131
146
|
## Testing
|
|
132
147
|
|
|
133
148
|
### Running unit tests
|
package/lib/cjs/index.js
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
var _href_or_on_click = require("./rules/href_or_on_click");
|
|
4
4
|
var _no_restricted_eui_imports = require("./rules/no_restricted_eui_imports");
|
|
5
5
|
var _no_css_color = require("./rules/no_css_color");
|
|
6
|
+
var _require_aria_label_for_modals = require("./rules/a11y/require_aria_label_for_modals");
|
|
7
|
+
var _consistent_is_invalid_props = require("./rules/a11y/consistent_is_invalid_props");
|
|
8
|
+
var _sr_output_disabled_tooltip = require("./rules/a11y/sr_output_disabled_tooltip");
|
|
9
|
+
var _prefer_eui_icon_tip = require("./rules/a11y/prefer_eui_icon_tip");
|
|
6
10
|
/*
|
|
7
11
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
8
12
|
* license agreements. See the NOTICE file distributed with
|
|
@@ -26,7 +30,11 @@ const config = {
|
|
|
26
30
|
rules: {
|
|
27
31
|
'href-or-on-click': _href_or_on_click.HrefOnClick,
|
|
28
32
|
'no-restricted-eui-imports': _no_restricted_eui_imports.NoRestrictedEuiImports,
|
|
29
|
-
'no-css-color': _no_css_color.NoCssColor
|
|
33
|
+
'no-css-color': _no_css_color.NoCssColor,
|
|
34
|
+
'require-aria-label-for-modals': _require_aria_label_for_modals.RequireAriaLabelForModals,
|
|
35
|
+
'consistent-is-invalid-props': _consistent_is_invalid_props.ConsistentIsInvalidProps,
|
|
36
|
+
'sr-output-disabled-tooltip': _sr_output_disabled_tooltip.ScreenReaderOutputDisabledTooltip,
|
|
37
|
+
'prefer-eui-icon-tip': _prefer_eui_icon_tip.PreferEuiIconTip
|
|
30
38
|
},
|
|
31
39
|
configs: {
|
|
32
40
|
recommended: {
|
|
@@ -34,7 +42,12 @@ const config = {
|
|
|
34
42
|
rules: {
|
|
35
43
|
'@elastic/eui/href-or-on-click': 'warn',
|
|
36
44
|
'@elastic/eui/no-restricted-eui-imports': 'warn',
|
|
37
|
-
'@elastic/eui/no-css-color': 'warn'
|
|
45
|
+
'@elastic/eui/no-css-color': 'warn',
|
|
46
|
+
'@elastic/eui/require-aria-label-for-modals': 'warn',
|
|
47
|
+
'@elastic/eui/consistent-is-invalid-props': 'warn',
|
|
48
|
+
'@elastic/eui/sr-output-disabled-tooltip': 'warn',
|
|
49
|
+
'@elastic/eui/no-css_color': 'warn',
|
|
50
|
+
'@elastic/eui/prefer-eui-icon-tip': 'warn'
|
|
38
51
|
}
|
|
39
52
|
}
|
|
40
53
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consistent_is_invalid_props.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/consistent_is_invalid_props.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAiB,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAetE,eAAO,MAAM,wBAAwB,wFA6FnC,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ConsistentIsInvalidProps = void 0;
|
|
7
|
+
var _utils = require("@typescript-eslint/utils");
|
|
8
|
+
var _get_attr_value = require("../../utils/get_attr_value");
|
|
9
|
+
/*
|
|
10
|
+
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
11
|
+
* license agreements. See the NOTICE file distributed with
|
|
12
|
+
* this work for additional information regarding copyright
|
|
13
|
+
* ownership. Elasticsearch B.V. licenses this file to you under
|
|
14
|
+
* the Apache License, Version 2.0 (the "License"); you may
|
|
15
|
+
* not use this file except in compliance with the License.
|
|
16
|
+
* You may obtain a copy of the License at
|
|
17
|
+
*
|
|
18
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
19
|
+
*
|
|
20
|
+
* Unless required by applicable law or agreed to in writing,
|
|
21
|
+
* software distributed under the License is distributed on an
|
|
22
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
23
|
+
* KIND, either express or implied. See the License for the
|
|
24
|
+
* specific language governing permissions and limitations
|
|
25
|
+
* under the License.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const formControlComponent = 'EuiFormRow';
|
|
29
|
+
const formControlChildComponents = ['EuiFieldNumber', 'EuiFilePicker', 'EuiFieldText', 'EuiComboBox', 'EuiTextArea', 'EuiFormControlLayoutDelimited', 'SingleFieldSelect', 'EuiSelect'];
|
|
30
|
+
const ConsistentIsInvalidProps = exports.ConsistentIsInvalidProps = _utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
31
|
+
create(context) {
|
|
32
|
+
return {
|
|
33
|
+
JSXElement(node) {
|
|
34
|
+
const openingElement = node.openingElement;
|
|
35
|
+
if (openingElement?.name.type !== 'JSXIdentifier' || openingElement.name.name !== formControlComponent || openingElement.attributes.length === 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const formRowIsInvalid = (0, _get_attr_value.getAttrValue)(context, openingElement.attributes, 'isInvalid');
|
|
39
|
+
if (formRowIsInvalid === undefined) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const childElement = node.children.find(child => child.type === 'JSXElement' && child.openingElement?.name.type === 'JSXIdentifier' && formControlChildComponents.includes(child.openingElement.name.name));
|
|
43
|
+
if (!childElement) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const childIsInvalid = (0, _get_attr_value.getAttrValue)(context, childElement.openingElement.attributes, 'isInvalid');
|
|
47
|
+
if (childIsInvalid !== formRowIsInvalid) {
|
|
48
|
+
const componentName = childElement.openingElement.name.name;
|
|
49
|
+
context.report({
|
|
50
|
+
node: childElement,
|
|
51
|
+
messageId: 'inconsistentIsInvalid',
|
|
52
|
+
fix: fixer => {
|
|
53
|
+
const childIsInvalidAttr = childElement.openingElement.attributes.find(attr => attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier' && attr.name.name === 'isInvalid');
|
|
54
|
+
if (childIsInvalidAttr) {
|
|
55
|
+
return fixer.replaceText(childIsInvalidAttr, `isInvalid={${formRowIsInvalid}}`);
|
|
56
|
+
}
|
|
57
|
+
const insertPosition = childElement.openingElement.name.range[1];
|
|
58
|
+
return fixer.insertTextAfterRange([insertPosition, insertPosition], ` isInvalid={${formRowIsInvalid}}`);
|
|
59
|
+
},
|
|
60
|
+
data: {
|
|
61
|
+
formControlComponent: formControlComponent,
|
|
62
|
+
component: componentName
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
meta: {
|
|
70
|
+
type: 'problem',
|
|
71
|
+
docs: {
|
|
72
|
+
description: `Ensure {{component}} inherit "isInvalid" prop from parent {{formControlComponent}}`
|
|
73
|
+
},
|
|
74
|
+
fixable: 'code',
|
|
75
|
+
schema: [],
|
|
76
|
+
messages: {
|
|
77
|
+
inconsistentIsInvalid: `{{component}} should have the same "isInvalid" prop value as its parent {{formControlComponent}}.`
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
defaultOptions: []
|
|
81
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer_eui_icon_tip.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/prefer_eui_icon_tip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAMtE,eAAO,MAAM,gBAAgB,mFA0C3B,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.PreferEuiIconTip = void 0;
|
|
7
|
+
var _utils = require("@typescript-eslint/utils");
|
|
8
|
+
const TOOLTIP_COMPONENT = 'EuiToolTip';
|
|
9
|
+
const ICON_COMPONENT = 'EuiIcon';
|
|
10
|
+
const ICON_TIP_COMPONENT = 'EuiIconTip';
|
|
11
|
+
const PreferEuiIconTip = exports.PreferEuiIconTip = _utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
12
|
+
create(context) {
|
|
13
|
+
return {
|
|
14
|
+
JSXElement(node) {
|
|
15
|
+
const openingElement = node.openingElement;
|
|
16
|
+
if (openingElement.name.type !== 'JSXIdentifier' || openingElement.name.name !== TOOLTIP_COMPONENT) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Find first JSX child
|
|
21
|
+
const firstChild = node.children.find(child => child.type === 'JSXElement');
|
|
22
|
+
if (firstChild && firstChild.openingElement.name.type === 'JSXIdentifier' && firstChild.openingElement.name.name === ICON_COMPONENT) {
|
|
23
|
+
context.report({
|
|
24
|
+
node: firstChild,
|
|
25
|
+
messageId: 'preferEuiIconTip'
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
meta: {
|
|
32
|
+
type: 'suggestion',
|
|
33
|
+
docs: {
|
|
34
|
+
description: `Prefer using ${ICON_TIP_COMPONENT} over <${TOOLTIP_COMPONENT}><${ICON_COMPONENT}/></${TOOLTIP_COMPONENT}>, as it delivers better accessibility and improved support for assistive technologies.`
|
|
35
|
+
},
|
|
36
|
+
schema: [],
|
|
37
|
+
messages: {
|
|
38
|
+
preferEuiIconTip: `Ensure ${ICON_TIP_COMPONENT} is used rather than <${TOOLTIP_COMPONENT}><${ICON_COMPONENT}/></${TOOLTIP_COMPONENT}>, as it delivers better accessibility and improved support for assistive technologies.`
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
defaultOptions: []
|
|
42
|
+
});
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const RequireAriaLabelForModals: ESLintUtils.RuleModule<"modalAriaMissing" | "confirmModalAriaMissing", [], unknown, ESLintUtils.RuleListener>;
|
|
3
|
+
//# sourceMappingURL=require_aria_label_for_modals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require_aria_label_for_modals.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/require_aria_label_for_modals.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAY,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAKjE,eAAO,MAAM,yBAAyB,+GA0FpC,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.RequireAriaLabelForModals = void 0;
|
|
7
|
+
var _utils = require("@typescript-eslint/utils");
|
|
8
|
+
/*
|
|
9
|
+
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
10
|
+
* license agreements. See the NOTICE file distributed with
|
|
11
|
+
* this work for additional information regarding copyright
|
|
12
|
+
* ownership. Elasticsearch B.V. licenses this file to you under
|
|
13
|
+
* the Apache License, Version 2.0 (the "License"); you may
|
|
14
|
+
* not use this file except in compliance with the License.
|
|
15
|
+
* You may obtain a copy of the License at
|
|
16
|
+
*
|
|
17
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
18
|
+
*
|
|
19
|
+
* Unless required by applicable law or agreed to in writing,
|
|
20
|
+
* software distributed under the License is distributed on an
|
|
21
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
22
|
+
* KIND, either express or implied. See the License for the
|
|
23
|
+
* specific language governing permissions and limitations
|
|
24
|
+
* under the License.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const modalComponents = ['EuiModal', 'EuiFlyout'];
|
|
28
|
+
const confirmModalComponents = ['EuiConfirmModal'];
|
|
29
|
+
const RequireAriaLabelForModals = exports.RequireAriaLabelForModals = _utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
30
|
+
create(context) {
|
|
31
|
+
function checkAttributes(node, componentName, messageId) {
|
|
32
|
+
const hasAriaLabel = node.attributes.some(attr => attr.type === 'JSXAttribute' && typeof attr.name.name === 'string' && ['aria-label', 'aria-labelledby'].includes(attr.name.name));
|
|
33
|
+
if (!hasAriaLabel) {
|
|
34
|
+
context.report({
|
|
35
|
+
node,
|
|
36
|
+
messageId: messageId,
|
|
37
|
+
data: {
|
|
38
|
+
component: componentName
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
JSXOpeningElement(node) {
|
|
45
|
+
if (node.name.type === 'JSXIdentifier') {
|
|
46
|
+
if (modalComponents.includes(node.name.name)) {
|
|
47
|
+
checkAttributes(node, node.name.name, 'modalAriaMissing');
|
|
48
|
+
}
|
|
49
|
+
if (confirmModalComponents.includes(node.name.name)) {
|
|
50
|
+
checkAttributes(node, node.name.name, 'confirmModalAriaMissing');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
meta: {
|
|
58
|
+
type: 'problem',
|
|
59
|
+
docs: {
|
|
60
|
+
description: 'Ensure modals have \'aria-label\' or \'aria-labelledby\''
|
|
61
|
+
},
|
|
62
|
+
schema: [],
|
|
63
|
+
messages: {
|
|
64
|
+
modalAriaMissing: ['{{ component }} must have either \'aria-label\' or \'aria-labelledby\' prop for accessibility.', '\n', 'Option 1: Using \'aria-labelledby\' (preferred):', '1. Import \'useGeneratedHtmlId\':', ' import { useGeneratedHtmlId } from \'@elastic/eui\';', '2. Update your component:', ' const modalTitleId = useGeneratedHtmlId();', ' ...', ' <{{ component }}', ' aria-labelledby={modalTitleId}', ' {...props} ', ' />', ' <{{ component }}Header>', ' <EuiTitle id={modalTitleId}>', ' {\'Descriptive title for the {{ component }}\'}', ' </EuiTitle>', ' </{ component }}Header>', ' ...', ' </{{ component }}>', '\n', 'Option 2: Using \'aria-label\':', ' <{{ component }} aria-label="Descriptive title for the {{ component }}" {...props} />'].join('\n'),
|
|
65
|
+
confirmModalAriaMissing: ['{{ component }} must have either \'aria-label\' or \'aria-labelledby\' prop for accessibility.', '\n', 'Option 1: Using \'aria-labelledby\' (preferred):', '1. Import \'useGeneratedHtmlId\':', ' import { useGeneratedHtmlId } from \'@elastic/eui\';', '2. Update your component:', ' const modalTitleId = useGeneratedHtmlId();', ' ...', ' <{{ component }}', ' title="Descriptive title for the {{ component }}"', ' aria-labelledby={modalTitleId}', ' titleProps={({id: modalTitleId })}', ' {...props} ', ' />', '\n', 'Option 2: Using \'aria-label\':', ' <{{ component }} aria-label="Descriptive title for the {{ component }}" {...props} />'].join('\n')
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
defaultOptions: []
|
|
69
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sr_output_disabled_tooltip.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/sr_output_disabled_tooltip.ts"],"names":[],"mappings":"AAQA,OAAO,EAAiB,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAStE,eAAO,MAAM,iCAAiC,6FA6F1C,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ScreenReaderOutputDisabledTooltip = void 0;
|
|
7
|
+
var _utils = require("@typescript-eslint/utils");
|
|
8
|
+
var _get_attr_value = require("../../utils/get_attr_value");
|
|
9
|
+
/*
|
|
10
|
+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
11
|
+
* or more contributor license agreements. Licensed under the Elastic License
|
|
12
|
+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
13
|
+
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
14
|
+
* Side Public License, v 1.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const tooltipComponent = 'EuiToolTip';
|
|
18
|
+
const disabledTooltipComponentProp = 'disableScreenReaderOutput';
|
|
19
|
+
const buttonComponents = ['EuiButtonIcon'];
|
|
20
|
+
const normalizeAttrString = str => str?.trim().replace(/\s+/g, ' ');
|
|
21
|
+
const ScreenReaderOutputDisabledTooltip = exports.ScreenReaderOutputDisabledTooltip = _utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
22
|
+
create(context) {
|
|
23
|
+
return {
|
|
24
|
+
JSXElement(node) {
|
|
25
|
+
const openingElement = node.openingElement;
|
|
26
|
+
if (openingElement?.name.type !== 'JSXIdentifier' || openingElement.name.name !== tooltipComponent) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const tooltipContent = (0, _get_attr_value.getAttrValue)(context, openingElement.attributes, 'content');
|
|
30
|
+
const hasDisableScreenReader = openingElement.attributes.some(attr => attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier' && attr.name.name === disabledTooltipComponentProp);
|
|
31
|
+
if (hasDisableScreenReader) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const buttonElement = node.children.find(child => child.type === 'JSXElement' && child.openingElement?.name.type === 'JSXIdentifier' && buttonComponents.includes(child.openingElement.name.name));
|
|
35
|
+
if (!buttonElement) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const ariaLabel = (0, _get_attr_value.getAttrValue)(context, buttonElement.openingElement.attributes, 'aria-label');
|
|
39
|
+
if (tooltipContent && ariaLabel && normalizeAttrString(tooltipContent) === normalizeAttrString(ariaLabel)) {
|
|
40
|
+
const buttonElementName = buttonElement.openingElement.name.name;
|
|
41
|
+
context.report({
|
|
42
|
+
node: openingElement,
|
|
43
|
+
messageId: 'requireDisableScreenReader',
|
|
44
|
+
fix: fixer => {
|
|
45
|
+
const lastAttr = openingElement.attributes[openingElement.attributes.length - 1];
|
|
46
|
+
const insertPosition = lastAttr ? lastAttr.range[1] : openingElement.name.range[1];
|
|
47
|
+
return fixer.insertTextAfterRange([insertPosition, insertPosition], ` ${disabledTooltipComponentProp}`);
|
|
48
|
+
},
|
|
49
|
+
data: {
|
|
50
|
+
tooltipComponent,
|
|
51
|
+
disabledTooltipComponentProp,
|
|
52
|
+
buttonElementName
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
meta: {
|
|
60
|
+
type: 'problem',
|
|
61
|
+
docs: {
|
|
62
|
+
description: 'Ensure "{{disabledTooltipComponentProp}}" attribute is set when {{tooltipComponent}} content matches {{buttonElementName}} "aria-label"'
|
|
63
|
+
},
|
|
64
|
+
fixable: 'code',
|
|
65
|
+
schema: [],
|
|
66
|
+
messages: {
|
|
67
|
+
requireDisableScreenReader: '{{tooltipComponent}} should include "{{disabledTooltipComponentProp}}" attribute when its content matches the "aria-label" of {{buttonElementName}} to avoid redundant announcements.'
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
defaultOptions: []
|
|
71
|
+
});
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type TSESTree, type TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
export declare function getAttrValue<TContext extends TSESLint.RuleContext<string, unknown[]>>(context: TContext, attributes: TSESTree.JSXOpeningElement['attributes'], attrName: string): string | undefined;
|
|
3
|
+
//# sourceMappingURL=get_attr_value.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get_attr_value.d.ts","sourceRoot":"","sources":["../../../src/utils/get_attr_value.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAEvE,wBAAgB,YAAY,CAC1B,QAAQ,SAAS,QAAQ,CAAC,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAExD,OAAO,EAAE,QAAQ,EACjB,UAAU,EAAE,QAAQ,CAAC,iBAAiB,CAAC,YAAY,CAAC,EACpD,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,SAAS,CA2BpB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getAttrValue = getAttrValue;
|
|
7
|
+
/*
|
|
8
|
+
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
9
|
+
* license agreements. See the NOTICE file distributed with
|
|
10
|
+
* this work for additional information regarding copyright
|
|
11
|
+
* ownership. Elasticsearch B.V. licenses this file to you under
|
|
12
|
+
* the Apache License, Version 2.0 (the "License"); you may
|
|
13
|
+
* not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing,
|
|
19
|
+
* software distributed under the License is distributed on an
|
|
20
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
21
|
+
* KIND, either express or implied. See the License for the
|
|
22
|
+
* specific language governing permissions and limitations
|
|
23
|
+
* under the License.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
function getAttrValue(context, attributes, attrName) {
|
|
27
|
+
const attr = attributes.find(attr => attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier' && attr.name.name === attrName);
|
|
28
|
+
if (!attr?.value) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
if (attr.value.type === 'Literal') {
|
|
32
|
+
return String(attr.value.value);
|
|
33
|
+
}
|
|
34
|
+
if (attr.value.type === 'JSXExpressionContainer') {
|
|
35
|
+
const expression = attr.value.expression;
|
|
36
|
+
if (expression.type === 'Literal') {
|
|
37
|
+
return String(expression.value);
|
|
38
|
+
}
|
|
39
|
+
return context.sourceCode.getText(expression);
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
package/lib/esm/index.js
CHANGED
|
@@ -21,11 +21,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
21
21
|
const href_or_on_click_1 = require("./rules/href_or_on_click");
|
|
22
22
|
const no_restricted_eui_imports_1 = require("./rules/no_restricted_eui_imports");
|
|
23
23
|
const no_css_color_1 = require("./rules/no_css_color");
|
|
24
|
+
const require_aria_label_for_modals_1 = require("./rules/a11y/require_aria_label_for_modals");
|
|
25
|
+
const consistent_is_invalid_props_1 = require("./rules/a11y/consistent_is_invalid_props");
|
|
26
|
+
const sr_output_disabled_tooltip_1 = require("./rules/a11y/sr_output_disabled_tooltip");
|
|
27
|
+
const prefer_eui_icon_tip_1 = require("./rules/a11y/prefer_eui_icon_tip");
|
|
24
28
|
const config = {
|
|
25
29
|
rules: {
|
|
26
30
|
'href-or-on-click': href_or_on_click_1.HrefOnClick,
|
|
27
31
|
'no-restricted-eui-imports': no_restricted_eui_imports_1.NoRestrictedEuiImports,
|
|
28
32
|
'no-css-color': no_css_color_1.NoCssColor,
|
|
33
|
+
'require-aria-label-for-modals': require_aria_label_for_modals_1.RequireAriaLabelForModals,
|
|
34
|
+
'consistent-is-invalid-props': consistent_is_invalid_props_1.ConsistentIsInvalidProps,
|
|
35
|
+
'sr-output-disabled-tooltip': sr_output_disabled_tooltip_1.ScreenReaderOutputDisabledTooltip,
|
|
36
|
+
'prefer-eui-icon-tip': prefer_eui_icon_tip_1.PreferEuiIconTip,
|
|
29
37
|
},
|
|
30
38
|
configs: {
|
|
31
39
|
recommended: {
|
|
@@ -34,6 +42,11 @@ const config = {
|
|
|
34
42
|
'@elastic/eui/href-or-on-click': 'warn',
|
|
35
43
|
'@elastic/eui/no-restricted-eui-imports': 'warn',
|
|
36
44
|
'@elastic/eui/no-css-color': 'warn',
|
|
45
|
+
'@elastic/eui/require-aria-label-for-modals': 'warn',
|
|
46
|
+
'@elastic/eui/consistent-is-invalid-props': 'warn',
|
|
47
|
+
'@elastic/eui/sr-output-disabled-tooltip': 'warn',
|
|
48
|
+
'@elastic/eui/no-css_color': 'warn',
|
|
49
|
+
'@elastic/eui/prefer-eui-icon-tip': 'warn',
|
|
37
50
|
},
|
|
38
51
|
},
|
|
39
52
|
},
|
package/lib/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;AAEH,+DAAuD;AACvD,iFAA2E;AAC3E,uDAAkD;AAElD,MAAM,MAAM,GAAG;IACb,KAAK,EAAE;QACL,kBAAkB,EAAE,8BAAW;QAC/B,2BAA2B,EAAE,kDAAsB;QACnD,cAAc,EAAE,yBAAU;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;AAEH,+DAAuD;AACvD,iFAA2E;AAC3E,uDAAkD;AAElD,8FAAuF;AACvF,0FAAoF;AACpF,wFAA4F;AAC5F,0EAAoE;AAEpE,MAAM,MAAM,GAAG;IACb,KAAK,EAAE;QACL,kBAAkB,EAAE,8BAAW;QAC/B,2BAA2B,EAAE,kDAAsB;QACnD,cAAc,EAAE,yBAAU;QAC1B,+BAA+B,EAAE,yDAAyB;QAC1D,6BAA6B,EAAE,sDAAwB;QACvD,4BAA4B,EAAE,8DAAiC;QAC/D,qBAAqB,EAAE,sCAAgB;KACxC;IACD,OAAO,EAAE;QACP,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,4BAA4B,CAAC;YACvC,KAAK,EAAE;gBACL,+BAA+B,EAAE,MAAM;gBACvC,wCAAwC,EAAE,MAAM;gBAChD,2BAA2B,EAAE,MAAM;gBACnC,4CAA4C,EAAE,MAAM;gBACpD,0CAA0C,EAAE,MAAM;gBAClD,yCAAyC,EAAE,MAAM;gBACjD,2BAA2B,EAAE,MAAM;gBACnC,kCAAkC,EAAE,MAAM;aAC3C;SACF;KACF;CACF,CAAC;AAEF,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
4
|
+
* license agreements. See the NOTICE file distributed with
|
|
5
|
+
* this work for additional information regarding copyright
|
|
6
|
+
* ownership. Elasticsearch B.V. licenses this file to you under
|
|
7
|
+
* the Apache License, Version 2.0 (the "License"); you may
|
|
8
|
+
* not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing,
|
|
14
|
+
* software distributed under the License is distributed on an
|
|
15
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
* KIND, either express or implied. See the License for the
|
|
17
|
+
* specific language governing permissions and limitations
|
|
18
|
+
* under the License.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.ConsistentIsInvalidProps = void 0;
|
|
22
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
23
|
+
const get_attr_value_1 = require("../../utils/get_attr_value");
|
|
24
|
+
const formControlComponent = 'EuiFormRow';
|
|
25
|
+
const formControlChildComponents = [
|
|
26
|
+
'EuiFieldNumber',
|
|
27
|
+
'EuiFilePicker',
|
|
28
|
+
'EuiFieldText',
|
|
29
|
+
'EuiComboBox',
|
|
30
|
+
'EuiTextArea',
|
|
31
|
+
'EuiFormControlLayoutDelimited',
|
|
32
|
+
'SingleFieldSelect',
|
|
33
|
+
'EuiSelect',
|
|
34
|
+
];
|
|
35
|
+
exports.ConsistentIsInvalidProps = utils_1.ESLintUtils.RuleCreator.withoutDocs({
|
|
36
|
+
create(context) {
|
|
37
|
+
return {
|
|
38
|
+
JSXElement(node) {
|
|
39
|
+
const openingElement = node.openingElement;
|
|
40
|
+
if (openingElement?.name.type !== 'JSXIdentifier' ||
|
|
41
|
+
openingElement.name.name !== formControlComponent ||
|
|
42
|
+
openingElement.attributes.length === 0) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const formRowIsInvalid = (0, get_attr_value_1.getAttrValue)(context, openingElement.attributes, 'isInvalid');
|
|
46
|
+
if (formRowIsInvalid === undefined) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const childElement = node.children.find((child) => child.type === 'JSXElement' &&
|
|
50
|
+
child.openingElement?.name.type === 'JSXIdentifier' &&
|
|
51
|
+
formControlChildComponents.includes(child.openingElement.name.name));
|
|
52
|
+
if (!childElement) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const childIsInvalid = (0, get_attr_value_1.getAttrValue)(context, childElement.openingElement.attributes, 'isInvalid');
|
|
56
|
+
if (childIsInvalid !== formRowIsInvalid) {
|
|
57
|
+
const componentName = childElement.openingElement.name.name;
|
|
58
|
+
context.report({
|
|
59
|
+
node: childElement,
|
|
60
|
+
messageId: 'inconsistentIsInvalid',
|
|
61
|
+
fix: (fixer) => {
|
|
62
|
+
const childIsInvalidAttr = childElement.openingElement.attributes.find((attr) => attr.type === 'JSXAttribute' &&
|
|
63
|
+
attr.name.type === 'JSXIdentifier' &&
|
|
64
|
+
attr.name.name === 'isInvalid');
|
|
65
|
+
if (childIsInvalidAttr) {
|
|
66
|
+
return fixer.replaceText(childIsInvalidAttr, `isInvalid={${formRowIsInvalid}}`);
|
|
67
|
+
}
|
|
68
|
+
const insertPosition = childElement.openingElement.name.range[1];
|
|
69
|
+
return fixer.insertTextAfterRange([insertPosition, insertPosition], ` isInvalid={${formRowIsInvalid}}`);
|
|
70
|
+
},
|
|
71
|
+
data: {
|
|
72
|
+
formControlComponent: formControlComponent,
|
|
73
|
+
component: componentName,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
meta: {
|
|
81
|
+
type: 'problem',
|
|
82
|
+
docs: {
|
|
83
|
+
description: `Ensure {{component}} inherit "isInvalid" prop from parent {{formControlComponent}}`,
|
|
84
|
+
},
|
|
85
|
+
fixable: 'code',
|
|
86
|
+
schema: [],
|
|
87
|
+
messages: {
|
|
88
|
+
inconsistentIsInvalid: `{{component}} should have the same "isInvalid" prop value as its parent {{formControlComponent}}.`,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
defaultOptions: [],
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=consistent_is_invalid_props.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consistent_is_invalid_props.js","sourceRoot":"","sources":["../../../../src/rules/a11y/consistent_is_invalid_props.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAEH,oDAAsE;AACtE,+DAA0D;AAC1D,MAAM,oBAAoB,GAAG,YAAY,CAAC;AAE1C,MAAM,0BAA0B,GAAG;IACjC,gBAAgB;IAChB,eAAe;IACf,cAAc;IACd,aAAa;IACb,aAAa;IACb,+BAA+B;IAC/B,mBAAmB;IACnB,WAAW;CACZ,CAAC;AAEW,QAAA,wBAAwB,GAAG,mBAAW,CAAC,WAAW,CAAC,WAAW,CAAC;IAC1E,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,UAAU,CAAC,IAAI;gBACb,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC;gBAE3C,IACE,cAAc,EAAE,IAAI,CAAC,IAAI,KAAK,eAAe;oBAC7C,cAAc,CAAC,IAAI,CAAC,IAAI,KAAK,oBAAoB;oBACjD,cAAc,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EACtC,CAAC;oBACD,OAAO;gBACT,CAAC;gBAED,MAAM,gBAAgB,GAAG,IAAA,6BAAY,EACnC,OAAO,EACP,cAAc,CAAC,UAAU,EACzB,WAAW,CACZ,CAAC;gBAEF,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;oBACnC,OAAO;gBACT,CAAC;gBAED,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CACrC,CAAC,KAAK,EAAgC,EAAE,CACtC,KAAK,CAAC,IAAI,KAAK,YAAY;oBAC3B,KAAK,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,KAAK,eAAe;oBACnD,0BAA0B,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CACtE,CAAC;gBAEF,IAAI,CAAC,YAAY,EAAE,CAAC;oBAClB,OAAO;gBACT,CAAC;gBAED,MAAM,cAAc,GAAG,IAAA,6BAAY,EACjC,OAAO,EACP,YAAY,CAAC,cAAc,CAAC,UAAU,EACtC,WAAW,CACZ,CAAC;gBAEF,IAAI,cAAc,KAAK,gBAAgB,EAAE,CAAC;oBACxC,MAAM,aAAa,GACjB,YAAY,CAAC,cAAc,CAAC,IAC7B,CAAC,IAAI,CAAC;oBAEP,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI,EAAE,YAAY;wBAClB,SAAS,EAAE,uBAAuB;wBAClC,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE;4BACb,MAAM,kBAAkB,GACtB,YAAY,CAAC,cAAc,CAAC,UAAU,CAAC,IAAI,CACzC,CAAC,IAAI,EAAiC,EAAE,CACtC,IAAI,CAAC,IAAI,KAAK,cAAc;gCAC5B,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,eAAe;gCAClC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,WAAW,CACjC,CAAC;4BAEJ,IAAI,kBAAkB,EAAE,CAAC;gCACvB,OAAO,KAAK,CAAC,WAAW,CACtB,kBAAkB,EAClB,cAAc,gBAAgB,GAAG,CAClC,CAAC;4BACJ,CAAC;4BAED,MAAM,cAAc,GAAG,YAAY,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;4BAEjE,OAAO,KAAK,CAAC,oBAAoB,CAC/B,CAAC,cAAc,EAAE,cAAc,CAAC,EAChC,eAAe,gBAAgB,GAAG,CACnC,CAAC;wBACJ,CAAC;wBACD,IAAI,EAAE;4BACJ,oBAAoB,EAAE,oBAAoB;4BAC1C,SAAS,EAAE,aAAa;yBACzB;qBACF,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,oFAAoF;SAClG;QACD,OAAO,EAAE,MAAM;QACf,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE;YACR,qBAAqB,EAAE,mGAAmG;SAC3H;KACF;IACD,cAAc,EAAE,EAAE;CACnB,CAAC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PreferEuiIconTip = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const TOOLTIP_COMPONENT = 'EuiToolTip';
|
|
6
|
+
const ICON_COMPONENT = 'EuiIcon';
|
|
7
|
+
const ICON_TIP_COMPONENT = 'EuiIconTip';
|
|
8
|
+
exports.PreferEuiIconTip = utils_1.ESLintUtils.RuleCreator.withoutDocs({
|
|
9
|
+
create(context) {
|
|
10
|
+
return {
|
|
11
|
+
JSXElement(node) {
|
|
12
|
+
const openingElement = node.openingElement;
|
|
13
|
+
if (openingElement.name.type !== 'JSXIdentifier' ||
|
|
14
|
+
openingElement.name.name !== TOOLTIP_COMPONENT) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// Find first JSX child
|
|
18
|
+
const firstChild = node.children.find((child) => child.type === 'JSXElement');
|
|
19
|
+
if (firstChild &&
|
|
20
|
+
firstChild.openingElement.name.type === 'JSXIdentifier' &&
|
|
21
|
+
firstChild.openingElement.name.name === ICON_COMPONENT) {
|
|
22
|
+
context.report({
|
|
23
|
+
node: firstChild,
|
|
24
|
+
messageId: 'preferEuiIconTip'
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
meta: {
|
|
31
|
+
type: 'suggestion',
|
|
32
|
+
docs: {
|
|
33
|
+
description: `Prefer using ${ICON_TIP_COMPONENT} over <${TOOLTIP_COMPONENT}><${ICON_COMPONENT}/></${TOOLTIP_COMPONENT}>, as it delivers better accessibility and improved support for assistive technologies.`
|
|
34
|
+
},
|
|
35
|
+
schema: [],
|
|
36
|
+
messages: {
|
|
37
|
+
preferEuiIconTip: `Ensure ${ICON_TIP_COMPONENT} is used rather than <${TOOLTIP_COMPONENT}><${ICON_COMPONENT}/></${TOOLTIP_COMPONENT}>, as it delivers better accessibility and improved support for assistive technologies.`,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
defaultOptions: [],
|
|
41
|
+
});
|
|
42
|
+
//# sourceMappingURL=prefer_eui_icon_tip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer_eui_icon_tip.js","sourceRoot":"","sources":["../../../../src/rules/a11y/prefer_eui_icon_tip.ts"],"names":[],"mappings":";;;AAAA,oDAAsE;AAEtE,MAAM,iBAAiB,GAAG,YAAY,CAAC;AACvC,MAAM,cAAc,GAAG,SAAS,CAAC;AACjC,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAE3B,QAAA,gBAAgB,GAAG,mBAAW,CAAC,WAAW,CAAC,WAAW,CAAC;IAClE,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,UAAU,CAAC,IAAI;gBACb,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC;gBAC3C,IACE,cAAc,CAAC,IAAI,CAAC,IAAI,KAAK,eAAe;oBAC5C,cAAc,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB,EAC9C,CAAC;oBACD,OAAO;gBACT,CAAC;gBAED,uBAAuB;gBACvB,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CACnC,CAAC,KAAK,EAAgC,EAAE,CACtC,KAAK,CAAC,IAAI,KAAK,YAAY,CAC9B,CAAC;gBAEF,IACE,UAAU;oBACV,UAAU,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,KAAK,eAAe;oBACvD,UAAU,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,KAAK,cAAc,EACtD,CAAC;oBACD,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI,EAAE,UAAU;wBAChB,SAAS,EAAE,kBAAkB;qBAC9B,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EAAE,gBAAgB,kBAAkB,UAAU,iBAAiB,KAAK,cAAc,OAAO,iBAAiB,yFAAyF;SAC/M;QACD,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE;YACR,gBAAgB,EAAE,UAAU,kBAAkB,yBAAyB,iBAAiB,KAAK,cAAc,OAAO,iBAAiB,yFAAyF;SAC7N;KACF;IACD,cAAc,EAAE,EAAE;CACnB,CAAC,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
4
|
+
* license agreements. See the NOTICE file distributed with
|
|
5
|
+
* this work for additional information regarding copyright
|
|
6
|
+
* ownership. Elasticsearch B.V. licenses this file to you under
|
|
7
|
+
* the Apache License, Version 2.0 (the "License"); you may
|
|
8
|
+
* not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing,
|
|
14
|
+
* software distributed under the License is distributed on an
|
|
15
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
* KIND, either express or implied. See the License for the
|
|
17
|
+
* specific language governing permissions and limitations
|
|
18
|
+
* under the License.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.RequireAriaLabelForModals = void 0;
|
|
22
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
23
|
+
const modalComponents = ['EuiModal', 'EuiFlyout'];
|
|
24
|
+
const confirmModalComponents = ['EuiConfirmModal'];
|
|
25
|
+
exports.RequireAriaLabelForModals = utils_1.ESLintUtils.RuleCreator.withoutDocs({
|
|
26
|
+
create(context) {
|
|
27
|
+
function checkAttributes(node, componentName, messageId) {
|
|
28
|
+
const hasAriaLabel = node.attributes.some((attr) => attr.type === 'JSXAttribute' &&
|
|
29
|
+
typeof attr.name.name === 'string' &&
|
|
30
|
+
['aria-label', 'aria-labelledby'].includes(attr.name.name));
|
|
31
|
+
if (!hasAriaLabel) {
|
|
32
|
+
context.report({
|
|
33
|
+
node,
|
|
34
|
+
messageId: messageId,
|
|
35
|
+
data: { component: componentName },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
JSXOpeningElement(node) {
|
|
41
|
+
if (node.name.type === 'JSXIdentifier') {
|
|
42
|
+
if (modalComponents.includes(node.name.name)) {
|
|
43
|
+
checkAttributes(node, node.name.name, 'modalAriaMissing');
|
|
44
|
+
}
|
|
45
|
+
if (confirmModalComponents.includes(node.name.name)) {
|
|
46
|
+
checkAttributes(node, node.name.name, 'confirmModalAriaMissing');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
meta: {
|
|
54
|
+
type: 'problem',
|
|
55
|
+
docs: {
|
|
56
|
+
description: 'Ensure modals have \'aria-label\' or \'aria-labelledby\'',
|
|
57
|
+
},
|
|
58
|
+
schema: [],
|
|
59
|
+
messages: {
|
|
60
|
+
modalAriaMissing: [
|
|
61
|
+
'{{ component }} must have either \'aria-label\' or \'aria-labelledby\' prop for accessibility.',
|
|
62
|
+
'\n',
|
|
63
|
+
'Option 1: Using \'aria-labelledby\' (preferred):',
|
|
64
|
+
'1. Import \'useGeneratedHtmlId\':',
|
|
65
|
+
' import { useGeneratedHtmlId } from \'@elastic/eui\';',
|
|
66
|
+
'2. Update your component:',
|
|
67
|
+
' const modalTitleId = useGeneratedHtmlId();',
|
|
68
|
+
' ...',
|
|
69
|
+
' <{{ component }}',
|
|
70
|
+
' aria-labelledby={modalTitleId}',
|
|
71
|
+
' {...props} ',
|
|
72
|
+
' />',
|
|
73
|
+
' <{{ component }}Header>',
|
|
74
|
+
' <EuiTitle id={modalTitleId}>',
|
|
75
|
+
' {\'Descriptive title for the {{ component }}\'}',
|
|
76
|
+
' </EuiTitle>',
|
|
77
|
+
' </{ component }}Header>',
|
|
78
|
+
' ...',
|
|
79
|
+
' </{{ component }}>',
|
|
80
|
+
'\n',
|
|
81
|
+
'Option 2: Using \'aria-label\':',
|
|
82
|
+
' <{{ component }} aria-label="Descriptive title for the {{ component }}" {...props} />',
|
|
83
|
+
].join('\n'),
|
|
84
|
+
confirmModalAriaMissing: [
|
|
85
|
+
'{{ component }} must have either \'aria-label\' or \'aria-labelledby\' prop for accessibility.',
|
|
86
|
+
'\n',
|
|
87
|
+
'Option 1: Using \'aria-labelledby\' (preferred):',
|
|
88
|
+
'1. Import \'useGeneratedHtmlId\':',
|
|
89
|
+
' import { useGeneratedHtmlId } from \'@elastic/eui\';',
|
|
90
|
+
'2. Update your component:',
|
|
91
|
+
' const modalTitleId = useGeneratedHtmlId();',
|
|
92
|
+
' ...',
|
|
93
|
+
' <{{ component }}',
|
|
94
|
+
' title="Descriptive title for the {{ component }}"',
|
|
95
|
+
' aria-labelledby={modalTitleId}',
|
|
96
|
+
' titleProps={({id: modalTitleId })}',
|
|
97
|
+
' {...props} ',
|
|
98
|
+
' />',
|
|
99
|
+
'\n',
|
|
100
|
+
'Option 2: Using \'aria-label\':',
|
|
101
|
+
' <{{ component }} aria-label="Descriptive title for the {{ component }}" {...props} />',
|
|
102
|
+
].join('\n')
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
defaultOptions: [],
|
|
106
|
+
});
|
|
107
|
+
//# sourceMappingURL=require_aria_label_for_modals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require_aria_label_for_modals.js","sourceRoot":"","sources":["../../../../src/rules/a11y/require_aria_label_for_modals.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAEH,oDAAiE;AAEjE,MAAM,eAAe,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;AAClD,MAAM,sBAAsB,GAAG,CAAC,iBAAiB,CAAC,CAAC;AAEtC,QAAA,yBAAyB,GAAG,mBAAW,CAAC,WAAW,CAAC,WAAW,CAAC;IAC3E,MAAM,CAAC,OAAO;QACZ,SAAS,eAAe,CAAC,IAAgC,EAAE,aAAqB,EAAE,SAAyD;YACzI,MAAM,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CACvC,CAAC,IAAI,EAAE,EAAE,CACP,IAAI,CAAC,IAAI,KAAK,cAAc;gBAC5B,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ;gBAClC,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAC7D,CAAC;YAEF,IAAI,CAAC,YAAY,EAAE,CAAC;gBAClB,OAAO,CAAC,MAAM,CAAC;oBACb,IAAI;oBACJ,SAAS,EAAE,SAAS;oBACpB,IAAI,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE;iBACnC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO;YACL,iBAAiB,CAAC,IAAI;gBACpB,IACE,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,eAAe,EAClC,CAAC;oBACD,IAAI,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;wBAC7C,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAA;oBAC3D,CAAC;oBAED,IAAI,sBAAsB,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;wBACpD,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,yBAAyB,CAAC,CAAA;oBAClE,CAAC;gBACH,CAAC;gBACD,OAAM;YACR,CAAC;SACF,CAAC;IACJ,CAAC;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,0DAA0D;SACxE;QACD,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE;YACR,gBAAgB,EAAE;gBAChB,gGAAgG;gBAChG,IAAI;gBACJ,kDAAkD;gBAClD,mCAAmC;gBACnC,yDAAyD;gBACzD,2BAA2B;gBAC3B,+CAA+C;gBAC/C,QAAQ;gBACR,qBAAqB;gBACrB,oCAAoC;gBACpC,iBAAiB;gBACjB,OAAO;gBACP,8BAA8B;gBAC9B,qCAAqC;gBACrC,0DAA0D;gBAC1D,oBAAoB;gBACpB,8BAA8B;gBAC9B,UAAU;gBACV,uBAAuB;gBACvB,IAAI;gBACJ,iCAAiC;gBACjC,0FAA0F;aAC3F,CAAC,IAAI,CAAC,IAAI,CAAC;YAEZ,uBAAuB,EAAE;gBACvB,gGAAgG;gBAChG,IAAI;gBACJ,kDAAkD;gBAClD,mCAAmC;gBACnC,yDAAyD;gBACzD,2BAA2B;gBAC3B,+CAA+C;gBAC/C,QAAQ;gBACR,qBAAqB;gBACrB,wDAAwD;gBACxD,qCAAqC;gBACrC,yCAAyC;gBACzC,kBAAkB;gBAClB,OAAO;gBACP,IAAI;gBACJ,iCAAiC;gBACjC,0FAA0F;aAC7F,CAAC,IAAI,CAAC,IAAI,CAAC;SACX;KACF;IACD,cAAc,EAAE,EAAE;CACnB,CAAC,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
4
|
+
* or more contributor license agreements. Licensed under the Elastic License
|
|
5
|
+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
6
|
+
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
7
|
+
* Side Public License, v 1.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.ScreenReaderOutputDisabledTooltip = void 0;
|
|
11
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
12
|
+
const get_attr_value_1 = require("../../utils/get_attr_value");
|
|
13
|
+
const tooltipComponent = 'EuiToolTip';
|
|
14
|
+
const disabledTooltipComponentProp = 'disableScreenReaderOutput';
|
|
15
|
+
const buttonComponents = ['EuiButtonIcon'];
|
|
16
|
+
const normalizeAttrString = (str) => str?.trim().replace(/\s+/g, ' ');
|
|
17
|
+
exports.ScreenReaderOutputDisabledTooltip = utils_1.ESLintUtils.RuleCreator.withoutDocs({
|
|
18
|
+
create(context) {
|
|
19
|
+
return {
|
|
20
|
+
JSXElement(node) {
|
|
21
|
+
const openingElement = node.openingElement;
|
|
22
|
+
if (openingElement?.name.type !== 'JSXIdentifier' ||
|
|
23
|
+
openingElement.name.name !== tooltipComponent) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const tooltipContent = (0, get_attr_value_1.getAttrValue)(context, openingElement.attributes, 'content');
|
|
27
|
+
const hasDisableScreenReader = openingElement.attributes.some((attr) => attr.type === 'JSXAttribute' &&
|
|
28
|
+
attr.name.type === 'JSXIdentifier' &&
|
|
29
|
+
attr.name.name === disabledTooltipComponentProp);
|
|
30
|
+
if (hasDisableScreenReader) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const buttonElement = node.children.find((child) => child.type === 'JSXElement' &&
|
|
34
|
+
child.openingElement?.name.type === 'JSXIdentifier' &&
|
|
35
|
+
buttonComponents.includes(child.openingElement.name.name));
|
|
36
|
+
if (!buttonElement) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const ariaLabel = (0, get_attr_value_1.getAttrValue)(context, buttonElement.openingElement.attributes, 'aria-label');
|
|
40
|
+
if (tooltipContent &&
|
|
41
|
+
ariaLabel &&
|
|
42
|
+
normalizeAttrString(tooltipContent) === normalizeAttrString(ariaLabel)) {
|
|
43
|
+
const buttonElementName = buttonElement.openingElement.name.name;
|
|
44
|
+
context.report({
|
|
45
|
+
node: openingElement,
|
|
46
|
+
messageId: 'requireDisableScreenReader',
|
|
47
|
+
fix: (fixer) => {
|
|
48
|
+
const lastAttr = openingElement.attributes[openingElement.attributes.length - 1];
|
|
49
|
+
const insertPosition = lastAttr ? lastAttr.range[1] : openingElement.name.range[1];
|
|
50
|
+
return fixer.insertTextAfterRange([insertPosition, insertPosition], ` ${disabledTooltipComponentProp}`);
|
|
51
|
+
},
|
|
52
|
+
data: {
|
|
53
|
+
tooltipComponent,
|
|
54
|
+
disabledTooltipComponentProp,
|
|
55
|
+
buttonElementName,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
meta: {
|
|
63
|
+
type: 'problem',
|
|
64
|
+
docs: {
|
|
65
|
+
description: 'Ensure "{{disabledTooltipComponentProp}}" attribute is set when {{tooltipComponent}} content matches {{buttonElementName}} "aria-label"',
|
|
66
|
+
},
|
|
67
|
+
fixable: 'code',
|
|
68
|
+
schema: [],
|
|
69
|
+
messages: {
|
|
70
|
+
requireDisableScreenReader: '{{tooltipComponent}} should include "{{disabledTooltipComponentProp}}" attribute when its content matches the "aria-label" of {{buttonElementName}} to avoid redundant announcements.',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
defaultOptions: [],
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=sr_output_disabled_tooltip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sr_output_disabled_tooltip.js","sourceRoot":"","sources":["../../../../src/rules/a11y/sr_output_disabled_tooltip.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { type TSESTree, type TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
export declare function getAttrValue<TContext extends TSESLint.RuleContext<string, unknown[]>>(context: TContext, attributes: TSESTree.JSXOpeningElement['attributes'], attrName: string): string | undefined;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
4
|
+
* license agreements. See the NOTICE file distributed with
|
|
5
|
+
* this work for additional information regarding copyright
|
|
6
|
+
* ownership. Elasticsearch B.V. licenses this file to you under
|
|
7
|
+
* the Apache License, Version 2.0 (the "License"); you may
|
|
8
|
+
* not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing,
|
|
14
|
+
* software distributed under the License is distributed on an
|
|
15
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
* KIND, either express or implied. See the License for the
|
|
17
|
+
* specific language governing permissions and limitations
|
|
18
|
+
* under the License.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.getAttrValue = getAttrValue;
|
|
22
|
+
function getAttrValue(context, attributes, attrName) {
|
|
23
|
+
const attr = attributes.find((attr) => attr.type === 'JSXAttribute' &&
|
|
24
|
+
attr.name.type === 'JSXIdentifier' &&
|
|
25
|
+
attr.name.name === attrName);
|
|
26
|
+
if (!attr?.value) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
if (attr.value.type === 'Literal') {
|
|
30
|
+
return String(attr.value.value);
|
|
31
|
+
}
|
|
32
|
+
if (attr.value.type === 'JSXExpressionContainer') {
|
|
33
|
+
const expression = attr.value.expression;
|
|
34
|
+
if (expression.type === 'Literal') {
|
|
35
|
+
return String(expression.value);
|
|
36
|
+
}
|
|
37
|
+
return context.sourceCode.getText(expression);
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=get_attr_value.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get_attr_value.js","sourceRoot":"","sources":["../../../src/utils/get_attr_value.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;AAIH,oCAiCC;AAjCD,SAAgB,YAAY,CAG1B,OAAiB,EACjB,UAAoD,EACpD,QAAgB;IAEhB,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAC1B,CAAC,IAAI,EAAiC,EAAE,CACtC,IAAI,CAAC,IAAI,KAAK,cAAc;QAC5B,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,eAAe;QAClC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAC9B,CAAC;IAEF,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;QACjB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAClC,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAED,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,wBAAwB,EAAE,CAAC;QACjD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;QAEzC,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAClC,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;QAED,OAAO,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|