@elastic/eslint-plugin-eui 2.13.0 → 2.14.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 +5 -5
- package/lib/cjs/rules/a11y/tooltip_button_icon_wrap.d.ts.map +1 -1
- package/lib/cjs/rules/a11y/tooltip_button_icon_wrap.js +4 -21
- package/lib/esm/rules/a11y/tooltip_button_icon_wrap.js +2 -29
- package/lib/esm/rules/a11y/tooltip_button_icon_wrap.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -200,18 +200,18 @@ Browser-native tooltips (the `title` prop) are unstyled, have no delay control,
|
|
|
200
200
|
|
|
201
201
|
The rule reports two situations:
|
|
202
202
|
|
|
203
|
-
- **`title` prop is present** - remove it and use `<EuiToolTip content={…}>` instead.
|
|
204
|
-
- **No `EuiToolTip` wrapper** - the button icon has no visible tooltip.
|
|
203
|
+
- **`title` prop is present** - remove it and use `<EuiToolTip content={…}>` instead.
|
|
204
|
+
- **No `EuiToolTip` wrapper** - the button icon has no visible tooltip. Wrap it with `<EuiToolTip content={ariaLabel}>`.
|
|
205
205
|
|
|
206
206
|
Buttons with spread props (`{...props}`) are intentionally skipped when no `title` prop is explicitly present because their final prop set cannot be statically determined.
|
|
207
207
|
|
|
208
208
|
#### Examples
|
|
209
209
|
|
|
210
210
|
```tsx
|
|
211
|
-
// ✗ Bad - browser tooltip
|
|
211
|
+
// ✗ Bad - browser tooltip
|
|
212
212
|
<EuiButtonIcon title="Edit item" aria-label="Edit item" iconType="pencil" />
|
|
213
213
|
|
|
214
|
-
// ✓
|
|
214
|
+
// ✓ Good
|
|
215
215
|
<EuiToolTip content="Edit item">
|
|
216
216
|
<EuiButtonIcon aria-label="Edit item" iconType="pencil" />
|
|
217
217
|
</EuiToolTip>
|
|
@@ -221,7 +221,7 @@ Buttons with spread props (`{...props}`) are intentionally skipped when no `titl
|
|
|
221
221
|
// ✗ Bad - no tooltip for sighted users
|
|
222
222
|
<EuiButtonIcon aria-label="Delete" iconType="trash" />
|
|
223
223
|
|
|
224
|
-
// ✓
|
|
224
|
+
// ✓ Good
|
|
225
225
|
<EuiToolTip content="Delete">
|
|
226
226
|
<EuiButtonIcon aria-label="Delete" iconType="trash" />
|
|
227
227
|
</EuiToolTip>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip_button_icon_wrap.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/tooltip_button_icon_wrap.ts"],"names":[],"mappings":"AAQA,OAAO,EAAiB,WAAW,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"tooltip_button_icon_wrap.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/tooltip_button_icon_wrap.ts"],"names":[],"mappings":"AAQA,OAAO,EAAiB,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAoCtE,eAAO,MAAM,qBAAqB,qHAiEhC,CAAC"}
|
|
@@ -5,7 +5,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.TooltipButtonIconWrap = void 0;
|
|
7
7
|
var _utils = require("@typescript-eslint/utils");
|
|
8
|
-
var _remove_attr = require("../../utils/remove_attr");
|
|
9
8
|
var _has_spread = require("../../utils/has_spread");
|
|
10
9
|
/*
|
|
11
10
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
@@ -53,36 +52,21 @@ const TooltipButtonIconWrap = exports.TooltipButtonIconWrap = _utils.ESLintUtils
|
|
|
53
52
|
return;
|
|
54
53
|
}
|
|
55
54
|
let titleAttr;
|
|
56
|
-
let ariaLabelAttr;
|
|
57
55
|
for (const attr of openingElement.attributes) {
|
|
58
56
|
if (attr.type !== 'JSXAttribute' || attr.name.type !== 'JSXIdentifier') continue;
|
|
59
57
|
if (attr.name.name === 'title') titleAttr = attr;
|
|
60
|
-
if (attr.name.name === 'aria-label') ariaLabelAttr = attr;
|
|
61
58
|
}
|
|
62
59
|
if (titleAttr) {
|
|
63
|
-
const alreadyWrapped = isWrappedByTooltip(node);
|
|
64
60
|
context.report({
|
|
65
61
|
node: openingElement,
|
|
66
|
-
messageId: 'useEuiToolTipInsteadOfTitle'
|
|
67
|
-
fix: titleAttr.value ? fixer => {
|
|
68
|
-
const titleValue = context.sourceCode.getText(titleAttr.value);
|
|
69
|
-
const [removeStart, removeEnd] = (0, _remove_attr.removeAttribute)(context, titleAttr);
|
|
70
|
-
if (alreadyWrapped) {
|
|
71
|
-
return fixer.removeRange([removeStart, removeEnd]);
|
|
72
|
-
}
|
|
73
|
-
return [fixer.removeRange([removeStart, removeEnd]), fixer.insertTextBefore(node, `<${TOOLTIP} content=${titleValue}>\n `), fixer.insertTextAfter(node, `\n</${TOOLTIP}>`)];
|
|
74
|
-
} : undefined
|
|
62
|
+
messageId: 'useEuiToolTipInsteadOfTitle'
|
|
75
63
|
});
|
|
76
64
|
return;
|
|
77
65
|
}
|
|
78
66
|
if (!isWrappedByTooltip(node) && !(0, _has_spread.hasSpread)(openingElement.attributes)) {
|
|
79
67
|
context.report({
|
|
80
68
|
node: openingElement,
|
|
81
|
-
messageId: 'wrapWithEuiToolTip'
|
|
82
|
-
fix: ariaLabelAttr?.value ? fixer => {
|
|
83
|
-
const ariaLabelValue = context.sourceCode.getText(ariaLabelAttr.value);
|
|
84
|
-
return [fixer.insertTextBefore(node, `<${TOOLTIP} content=${ariaLabelValue}>\n `), fixer.insertTextAfter(node, `\n</${TOOLTIP}>`)];
|
|
85
|
-
} : undefined
|
|
69
|
+
messageId: 'wrapWithEuiToolTip'
|
|
86
70
|
});
|
|
87
71
|
}
|
|
88
72
|
}
|
|
@@ -93,11 +77,10 @@ const TooltipButtonIconWrap = exports.TooltipButtonIconWrap = _utils.ESLintUtils
|
|
|
93
77
|
docs: {
|
|
94
78
|
description: `Ensure ${BUTTON_ICON} is wrapped with ${TOOLTIP} for sighted users`
|
|
95
79
|
},
|
|
96
|
-
fixable: 'code',
|
|
97
80
|
schema: [],
|
|
98
81
|
messages: {
|
|
99
|
-
useEuiToolTipInsteadOfTitle: [`Remove the \`title\` prop from ${BUTTON_ICON} and use ${TOOLTIP} instead.`, 'Browser-native tooltips are unstyled, have no delay control and are not keyboard-accessible.', `Wrap with <${TOOLTIP} content={label}> and use \`aria-label\` for screen readers
|
|
100
|
-
wrapWithEuiToolTip: [`${BUTTON_ICON} has no visible tooltip for sighted users.`, `Wrap with <${TOOLTIP} content={ariaLabel}> using the button's \`aria-label\` as content
|
|
82
|
+
useEuiToolTipInsteadOfTitle: [`Remove the \`title\` prop from ${BUTTON_ICON} and use ${TOOLTIP} instead.`, 'Browser-native tooltips are unstyled, have no delay control and are not keyboard-accessible.', `Wrap with <${TOOLTIP} content={label}> and use \`aria-label\` for screen readers.`, 'If you want to skip the tooltip but are unsure, please ping or contact EUI team.'].join(' '),
|
|
83
|
+
wrapWithEuiToolTip: [`${BUTTON_ICON} has no visible tooltip for sighted users.`, `Wrap with <${TOOLTIP} content={ariaLabel}> using the button's \`aria-label\` as content.`, 'If you want to skip the tooltip but are unsure, please ping or contact EUI team.'].join(' ')
|
|
101
84
|
}
|
|
102
85
|
},
|
|
103
86
|
defaultOptions: []
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.TooltipButtonIconWrap = void 0;
|
|
11
11
|
const utils_1 = require("@typescript-eslint/utils");
|
|
12
|
-
const remove_attr_1 = require("../../utils/remove_attr");
|
|
13
12
|
const has_spread_1 = require("../../utils/has_spread");
|
|
14
13
|
const BUTTON_ICON = 'EuiButtonIcon';
|
|
15
14
|
const TOOLTIP = 'EuiToolTip';
|
|
@@ -48,35 +47,17 @@ exports.TooltipButtonIconWrap = utils_1.ESLintUtils.RuleCreator.withoutDocs({
|
|
|
48
47
|
return;
|
|
49
48
|
}
|
|
50
49
|
let titleAttr;
|
|
51
|
-
let ariaLabelAttr;
|
|
52
50
|
for (const attr of openingElement.attributes) {
|
|
53
51
|
if (attr.type !== 'JSXAttribute' ||
|
|
54
52
|
attr.name.type !== 'JSXIdentifier')
|
|
55
53
|
continue;
|
|
56
54
|
if (attr.name.name === 'title')
|
|
57
55
|
titleAttr = attr;
|
|
58
|
-
if (attr.name.name === 'aria-label')
|
|
59
|
-
ariaLabelAttr = attr;
|
|
60
56
|
}
|
|
61
57
|
if (titleAttr) {
|
|
62
|
-
const alreadyWrapped = isWrappedByTooltip(node);
|
|
63
58
|
context.report({
|
|
64
59
|
node: openingElement,
|
|
65
60
|
messageId: 'useEuiToolTipInsteadOfTitle',
|
|
66
|
-
fix: titleAttr.value
|
|
67
|
-
? (fixer) => {
|
|
68
|
-
const titleValue = context.sourceCode.getText(titleAttr.value);
|
|
69
|
-
const [removeStart, removeEnd] = (0, remove_attr_1.removeAttribute)(context, titleAttr);
|
|
70
|
-
if (alreadyWrapped) {
|
|
71
|
-
return fixer.removeRange([removeStart, removeEnd]);
|
|
72
|
-
}
|
|
73
|
-
return [
|
|
74
|
-
fixer.removeRange([removeStart, removeEnd]),
|
|
75
|
-
fixer.insertTextBefore(node, `<${TOOLTIP} content=${titleValue}>\n `),
|
|
76
|
-
fixer.insertTextAfter(node, `\n</${TOOLTIP}>`),
|
|
77
|
-
];
|
|
78
|
-
}
|
|
79
|
-
: undefined,
|
|
80
61
|
});
|
|
81
62
|
return;
|
|
82
63
|
}
|
|
@@ -85,15 +66,6 @@ exports.TooltipButtonIconWrap = utils_1.ESLintUtils.RuleCreator.withoutDocs({
|
|
|
85
66
|
context.report({
|
|
86
67
|
node: openingElement,
|
|
87
68
|
messageId: 'wrapWithEuiToolTip',
|
|
88
|
-
fix: ariaLabelAttr?.value
|
|
89
|
-
? (fixer) => {
|
|
90
|
-
const ariaLabelValue = context.sourceCode.getText(ariaLabelAttr.value);
|
|
91
|
-
return [
|
|
92
|
-
fixer.insertTextBefore(node, `<${TOOLTIP} content=${ariaLabelValue}>\n `),
|
|
93
|
-
fixer.insertTextAfter(node, `\n</${TOOLTIP}>`),
|
|
94
|
-
];
|
|
95
|
-
}
|
|
96
|
-
: undefined,
|
|
97
69
|
});
|
|
98
70
|
}
|
|
99
71
|
},
|
|
@@ -104,17 +76,18 @@ exports.TooltipButtonIconWrap = utils_1.ESLintUtils.RuleCreator.withoutDocs({
|
|
|
104
76
|
docs: {
|
|
105
77
|
description: `Ensure ${BUTTON_ICON} is wrapped with ${TOOLTIP} for sighted users`,
|
|
106
78
|
},
|
|
107
|
-
fixable: 'code',
|
|
108
79
|
schema: [],
|
|
109
80
|
messages: {
|
|
110
81
|
useEuiToolTipInsteadOfTitle: [
|
|
111
82
|
`Remove the \`title\` prop from ${BUTTON_ICON} and use ${TOOLTIP} instead.`,
|
|
112
83
|
'Browser-native tooltips are unstyled, have no delay control and are not keyboard-accessible.',
|
|
113
84
|
`Wrap with <${TOOLTIP} content={label}> and use \`aria-label\` for screen readers.`,
|
|
85
|
+
'If you want to skip the tooltip but are unsure, please ping or contact EUI team.',
|
|
114
86
|
].join(' '),
|
|
115
87
|
wrapWithEuiToolTip: [
|
|
116
88
|
`${BUTTON_ICON} has no visible tooltip for sighted users.`,
|
|
117
89
|
`Wrap with <${TOOLTIP} content={ariaLabel}> using the button's \`aria-label\` as content.`,
|
|
90
|
+
'If you want to skip the tooltip but are unsure, please ping or contact EUI team.',
|
|
118
91
|
].join(' '),
|
|
119
92
|
},
|
|
120
93
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip_button_icon_wrap.js","sourceRoot":"","sources":["../../../../src/rules/a11y/tooltip_button_icon_wrap.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAEH,oDAAsE;AACtE,
|
|
1
|
+
{"version":3,"file":"tooltip_button_icon_wrap.js","sourceRoot":"","sources":["../../../../src/rules/a11y/tooltip_button_icon_wrap.ts"],"names":[],"mappings":"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"}
|