@elastic/eslint-plugin-eui 2.3.0 → 2.5.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.
Files changed (104) hide show
  1. package/README.md +18 -3
  2. package/lib/cjs/index.js +19 -18
  3. package/lib/cjs/rules/a11y/accessible_interactive_element.d.ts +3 -0
  4. package/lib/cjs/rules/a11y/accessible_interactive_element.d.ts.map +1 -0
  5. package/lib/cjs/rules/a11y/accessible_interactive_element.js +51 -0
  6. package/lib/cjs/rules/a11y/callout_announce_on_mount.d.ts +3 -0
  7. package/lib/cjs/rules/a11y/callout_announce_on_mount.d.ts.map +1 -0
  8. package/lib/cjs/rules/a11y/callout_announce_on_mount.js +56 -0
  9. package/lib/cjs/rules/a11y/consistent_is_invalid_props.d.ts.map +1 -1
  10. package/lib/cjs/rules/a11y/consistent_is_invalid_props.js +7 -18
  11. package/lib/cjs/rules/a11y/no_unnamed_interactive_element.d.ts +3 -0
  12. package/lib/cjs/rules/a11y/no_unnamed_interactive_element.d.ts.map +1 -0
  13. package/lib/cjs/rules/a11y/no_unnamed_interactive_element.js +78 -0
  14. package/lib/cjs/rules/a11y/no_unnamed_radio_group.d.ts.map +1 -1
  15. package/lib/cjs/rules/a11y/no_unnamed_radio_group.js +5 -16
  16. package/lib/cjs/rules/a11y/prefer_eui_icon_tip.d.ts.map +1 -1
  17. package/lib/cjs/rules/a11y/prefer_eui_icon_tip.js +8 -0
  18. package/lib/cjs/rules/a11y/require_aria_label_for_modals.d.ts.map +1 -1
  19. package/lib/cjs/rules/a11y/require_aria_label_for_modals.js +5 -16
  20. package/lib/cjs/rules/a11y/sr_output_disabled_tooltip.js +2 -2
  21. package/lib/cjs/rules/a11y/tooltip_focusable_anchor.d.ts +3 -0
  22. package/lib/cjs/rules/a11y/tooltip_focusable_anchor.d.ts.map +1 -0
  23. package/lib/cjs/rules/a11y/tooltip_focusable_anchor.js +68 -0
  24. package/lib/cjs/rules/href_or_on_click.d.ts.map +1 -1
  25. package/lib/cjs/rules/href_or_on_click.js +5 -16
  26. package/lib/cjs/rules/no_css_color.d.ts.map +1 -1
  27. package/lib/cjs/rules/no_css_color.js +14 -7
  28. package/lib/cjs/rules/no_restricted_eui_imports.d.ts.map +1 -1
  29. package/lib/cjs/rules/no_restricted_eui_imports.js +5 -16
  30. package/lib/cjs/utils/are_attrs_equal.d.ts.map +1 -1
  31. package/lib/cjs/utils/are_attrs_equal.js +5 -16
  32. package/lib/cjs/utils/constants.d.ts +27 -0
  33. package/lib/cjs/utils/constants.d.ts.map +1 -0
  34. package/lib/cjs/utils/constants.js +41 -0
  35. package/lib/cjs/utils/get_allowed_a11y_prop_names_for_component.d.ts +24 -0
  36. package/lib/cjs/utils/get_allowed_a11y_prop_names_for_component.d.ts.map +1 -0
  37. package/lib/cjs/utils/get_allowed_a11y_prop_names_for_component.js +39 -0
  38. package/lib/cjs/utils/get_attr_value.d.ts +2 -1
  39. package/lib/cjs/utils/get_attr_value.d.ts.map +1 -1
  40. package/lib/cjs/utils/get_attr_value.js +11 -18
  41. package/lib/cjs/utils/has_a11y_prop_for_component.d.ts +20 -0
  42. package/lib/cjs/utils/has_a11y_prop_for_component.d.ts.map +1 -0
  43. package/lib/cjs/utils/has_a11y_prop_for_component.js +36 -0
  44. package/lib/cjs/utils/has_spread.d.ts +12 -0
  45. package/lib/cjs/utils/has_spread.d.ts.map +1 -0
  46. package/lib/cjs/utils/has_spread.js +27 -0
  47. package/lib/cjs/utils/is_in_conditional_rendering.d.ts +3 -0
  48. package/lib/cjs/utils/is_in_conditional_rendering.d.ts.map +1 -0
  49. package/lib/cjs/utils/is_in_conditional_rendering.js +24 -0
  50. package/lib/cjs/utils/resolve_member_expression_root.d.ts.map +1 -1
  51. package/lib/cjs/utils/resolve_member_expression_root.js +8 -0
  52. package/lib/esm/index.js +17 -16
  53. package/lib/esm/index.js.map +1 -1
  54. package/lib/esm/rules/a11y/accessible_interactive_element.d.ts +2 -0
  55. package/lib/esm/rules/a11y/accessible_interactive_element.js +50 -0
  56. package/lib/esm/rules/a11y/accessible_interactive_element.js.map +1 -0
  57. package/lib/esm/rules/a11y/callout_announce_on_mount.d.ts +2 -0
  58. package/lib/esm/rules/a11y/callout_announce_on_mount.js +66 -0
  59. package/lib/esm/rules/a11y/callout_announce_on_mount.js.map +1 -0
  60. package/lib/esm/rules/a11y/consistent_is_invalid_props.js +7 -18
  61. package/lib/esm/rules/a11y/consistent_is_invalid_props.js.map +1 -1
  62. package/lib/esm/rules/a11y/no_unnamed_interactive_element.d.ts +2 -0
  63. package/lib/esm/rules/a11y/no_unnamed_interactive_element.js +94 -0
  64. package/lib/esm/rules/a11y/no_unnamed_interactive_element.js.map +1 -0
  65. package/lib/esm/rules/a11y/no_unnamed_radio_group.js +5 -16
  66. package/lib/esm/rules/a11y/no_unnamed_radio_group.js.map +1 -1
  67. package/lib/esm/rules/a11y/prefer_eui_icon_tip.js +7 -0
  68. package/lib/esm/rules/a11y/prefer_eui_icon_tip.js.map +1 -1
  69. package/lib/esm/rules/a11y/require_aria_label_for_modals.js +5 -16
  70. package/lib/esm/rules/a11y/require_aria_label_for_modals.js.map +1 -1
  71. package/lib/esm/rules/a11y/sr_output_disabled_tooltip.js +2 -2
  72. package/lib/esm/rules/a11y/sr_output_disabled_tooltip.js.map +1 -1
  73. package/lib/esm/rules/a11y/tooltip_focusable_anchor.d.ts +2 -0
  74. package/lib/esm/rules/a11y/tooltip_focusable_anchor.js +84 -0
  75. package/lib/esm/rules/a11y/tooltip_focusable_anchor.js.map +1 -0
  76. package/lib/esm/rules/href_or_on_click.js +5 -16
  77. package/lib/esm/rules/href_or_on_click.js.map +1 -1
  78. package/lib/esm/rules/no_css_color.js +22 -7
  79. package/lib/esm/rules/no_css_color.js.map +1 -1
  80. package/lib/esm/rules/no_restricted_eui_imports.js +5 -16
  81. package/lib/esm/rules/no_restricted_eui_imports.js.map +1 -1
  82. package/lib/esm/utils/are_attrs_equal.js +5 -16
  83. package/lib/esm/utils/are_attrs_equal.js.map +1 -1
  84. package/lib/esm/utils/constants.d.ts +26 -0
  85. package/lib/esm/utils/constants.js +119 -0
  86. package/lib/esm/utils/constants.js.map +1 -0
  87. package/lib/esm/utils/get_allowed_a11y_prop_names_for_component.d.ts +23 -0
  88. package/lib/esm/utils/get_allowed_a11y_prop_names_for_component.js +34 -0
  89. package/lib/esm/utils/get_allowed_a11y_prop_names_for_component.js.map +1 -0
  90. package/lib/esm/utils/get_attr_value.d.ts +2 -1
  91. package/lib/esm/utils/get_attr_value.js +11 -18
  92. package/lib/esm/utils/get_attr_value.js.map +1 -1
  93. package/lib/esm/utils/has_a11y_prop_for_component.d.ts +19 -0
  94. package/lib/esm/utils/has_a11y_prop_for_component.js +34 -0
  95. package/lib/esm/utils/has_a11y_prop_for_component.js.map +1 -0
  96. package/lib/esm/utils/has_spread.d.ts +11 -0
  97. package/lib/esm/utils/has_spread.js +23 -0
  98. package/lib/esm/utils/has_spread.js.map +1 -0
  99. package/lib/esm/utils/is_in_conditional_rendering.d.ts +2 -0
  100. package/lib/esm/utils/is_in_conditional_rendering.js +23 -0
  101. package/lib/esm/utils/is_in_conditional_rendering.js.map +1 -0
  102. package/lib/esm/utils/resolve_member_expression_root.js +7 -0
  103. package/lib/esm/utils/resolve_member_expression_root.js.map +1 -1
  104. package/package.json +1 -1
package/README.md CHANGED
@@ -129,15 +129,15 @@ It's worth pointing out that although the examples provided are specific to EUI
129
129
 
130
130
  ### `@elastic/eui/require-aria-label-for-modals`
131
131
 
132
- Ensures that EUI modal components (`EuiModal`, `EuiFlyout`, `EuiFlyoutResizable` ,`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.
132
+ Ensure that EUI modal components (`EuiModal`, `EuiFlyout`, `EuiFlyoutResizable` ,`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
133
 
134
134
  ### `@elastic/eui/consistent-is-invalid-props`
135
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.
136
+ Ensure 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
137
 
138
138
  ### `@elastic/eui/sr-output-disabled-tooltip`
139
139
 
140
- Ensures `disableScreenReaderOutput` is set when `EuiToolTip` content matches `EuiButtonIcon` "aria-label".
140
+ Ensure `disableScreenReaderOutput` is set when `EuiToolTip` content matches `EuiButtonIcon` "aria-label".
141
141
 
142
142
  ### `@elastic/eui/prefer-eui-icon-tip`
143
143
 
@@ -147,6 +147,21 @@ Ensure `EuiIconTip` is used rather than `<EuiToolTip><EuiIcon/></EuiToolTip>`, a
147
147
 
148
148
  Ensure that all radio input components (`EuiRadio`, `EuiRadioGroup`) have a `name` attribute. The `name` attribute is required for radio inputs to be grouped correctly, allowing users to select only one option from a set. Without a `name`, radios may not behave as expected and can cause accessibility issues for assistive technologies.
149
149
 
150
+ ### `@elastic/eui/callout-announce-on-mount`
151
+
152
+ Ensure that `EuiCallOut` components rendered conditionally have the `announceOnMount` prop for better accessibility. When callouts appear dynamically (e.g., after user interactions, form validation errors, or status changes), screen readers may not announce their content to users. The `announceOnMount` prop ensures these messages are properly announced to users with assistive technologies.
153
+
154
+ ### `@elastic/eui/no-unnamed-interactive-element`
155
+
156
+ Ensure that appropriate aria-attributes are set for `EuiBetaBadge`, `EuiButtonIcon`, `EuiComboBox`, `EuiSelect`, `EuiSelectWithWidth`,`EuiSuperSelect`,`EuiPagination`, `EuiTreeView`, `EuiBreadcrumbs`. Without this rule, screen reader users lose context, keyboard navigation can be confusing.
157
+
158
+ ### `@elastic/eui/tooltip-focusable-anchor`
159
+
160
+ Ensure `EuiTooltip` components are anchored to elements that can receive keyboard focus, making them accessible to all users. When using non-interactive elements (like `span`or `EuiText`) as tooltip anchors, they must include `tabIndex={0}` to be keyboard-focusable. For better accessibility, prefer using semantic interactive components (like `EuiButton` or `EuiLink`) which are focusable by default.
161
+
162
+ ### `@elastic/eui/accessible-interactive-element`
163
+ Ensure interactive EUI components (like e.g. `EuiLink`, `EuiButton`, `EuiRadio`) remain accessible by prohibiting `tabIndex={-1}`, which removes them from keyboard navigation.
164
+
150
165
  ## Testing
151
166
 
152
167
  ### Running unit tests
package/lib/cjs/index.js CHANGED
@@ -8,23 +8,16 @@ var _consistent_is_invalid_props = require("./rules/a11y/consistent_is_invalid_p
8
8
  var _sr_output_disabled_tooltip = require("./rules/a11y/sr_output_disabled_tooltip");
9
9
  var _prefer_eui_icon_tip = require("./rules/a11y/prefer_eui_icon_tip");
10
10
  var _no_unnamed_radio_group = require("./rules/a11y/no_unnamed_radio_group");
11
+ var _no_unnamed_interactive_element = require("./rules/a11y/no_unnamed_interactive_element");
12
+ var _tooltip_focusable_anchor = require("./rules/a11y/tooltip_focusable_anchor");
13
+ var _callout_announce_on_mount = require("./rules/a11y/callout_announce_on_mount");
14
+ var _accessible_interactive_element = require("./rules/a11y/accessible_interactive_element");
11
15
  /*
12
- * Licensed to Elasticsearch B.V. under one or more contributor
13
- * license agreements. See the NOTICE file distributed with
14
- * this work for additional information regarding copyright
15
- * ownership. Elasticsearch B.V. licenses this file to you under
16
- * the Apache License, Version 2.0 (the "License"); you may
17
- * not use this file except in compliance with the License.
18
- * You may obtain a copy of the License at
19
- *
20
- * http://www.apache.org/licenses/LICENSE-2.0
21
- *
22
- * Unless required by applicable law or agreed to in writing,
23
- * software distributed under the License is distributed on an
24
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
25
- * KIND, either express or implied. See the License for the
26
- * specific language governing permissions and limitations
27
- * under the License.
16
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
17
+ * or more contributor license agreements. Licensed under the Elastic License
18
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
19
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
20
+ * Side Public License, v 1.
28
21
  */
29
22
 
30
23
  const config = {
@@ -36,7 +29,11 @@ const config = {
36
29
  'consistent-is-invalid-props': _consistent_is_invalid_props.ConsistentIsInvalidProps,
37
30
  'sr-output-disabled-tooltip': _sr_output_disabled_tooltip.ScreenReaderOutputDisabledTooltip,
38
31
  'prefer-eui-icon-tip': _prefer_eui_icon_tip.PreferEuiIconTip,
39
- 'no-unnamed-radio-group': _no_unnamed_radio_group.NoUnnamedRadioGroup
32
+ 'no-unnamed-radio-group': _no_unnamed_radio_group.NoUnnamedRadioGroup,
33
+ 'callout-announce-on-mount': _callout_announce_on_mount.CallOutAnnounceOnMount,
34
+ 'no-unnamed-interactive-element': _no_unnamed_interactive_element.NoUnnamedInteractiveElement,
35
+ 'tooltip-focusable-anchor': _tooltip_focusable_anchor.TooltipFocusableAnchor,
36
+ 'accessible-interactive-element': _accessible_interactive_element.AccessibleInteractiveElements
40
37
  },
41
38
  configs: {
42
39
  recommended: {
@@ -49,7 +46,11 @@ const config = {
49
46
  '@elastic/eui/consistent-is-invalid-props': 'warn',
50
47
  '@elastic/eui/sr-output-disabled-tooltip': 'warn',
51
48
  '@elastic/eui/prefer-eui-icon-tip': 'warn',
52
- '@elastic/eui/no-unnamed-radio-group': 'warn'
49
+ '@elastic/eui/no-unnamed-radio-group': 'warn',
50
+ '@elastic/eui/callout-announce-on-mount': 'warn',
51
+ '@elastic/eui/no-unnamed-interactive-element': 'warn',
52
+ '@elastic/eui/tooltip-focusable-anchor': 'warn',
53
+ '@elastic/eui/accessible-interactive-element': 'warn'
53
54
  }
54
55
  }
55
56
  }
@@ -0,0 +1,3 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const AccessibleInteractiveElements: ESLintUtils.RuleModule<"disallowTabIndex", [], unknown, ESLintUtils.RuleListener>;
3
+ //# sourceMappingURL=accessible_interactive_element.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessible_interactive_element.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/accessible_interactive_element.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAItE,eAAO,MAAM,6BAA6B,mFAuCxC,CAAC"}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.AccessibleInteractiveElements = void 0;
7
+ var _utils = require("@typescript-eslint/utils");
8
+ var _constants = require("../../utils/constants");
9
+ var _get_attr_value = require("../../utils/get_attr_value");
10
+ /*
11
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
12
+ * or more contributor license agreements. Licensed under the Elastic License
13
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
14
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
15
+ * Side Public License, v 1.
16
+ */
17
+
18
+ const AccessibleInteractiveElements = exports.AccessibleInteractiveElements = _utils.ESLintUtils.RuleCreator.withoutDocs({
19
+ create(context) {
20
+ return {
21
+ JSXOpeningElement(node) {
22
+ if (node.name.type !== 'JSXIdentifier') return;
23
+ const componentName = node.name.name;
24
+ if (!_constants.INTERACTIVE_EUI_COMPONENTS.includes(componentName)) return;
25
+ const tabIndexAttribute = node.attributes.find(attr => attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier' && attr.name.name === 'tabIndex');
26
+ if (tabIndexAttribute && (Number((0, _get_attr_value.extractAttrValue)(context, tabIndexAttribute)) || 0) === -1) {
27
+ context.report({
28
+ node: node,
29
+ messageId: 'disallowTabIndex',
30
+ data: {
31
+ component: componentName
32
+ },
33
+ fix: fixer => fixer.remove(tabIndexAttribute)
34
+ });
35
+ }
36
+ }
37
+ };
38
+ },
39
+ meta: {
40
+ type: 'problem',
41
+ docs: {
42
+ description: 'Ensure interactive EUI components remain accessible by prohibiting tabIndex={-1}, which removes them from keyboard navigation.'
43
+ },
44
+ fixable: 'code',
45
+ schema: [],
46
+ messages: {
47
+ disallowTabIndex: '{{component}} is an interactive EUI component and must not use tabIndex={-1}, as this removes it from keyboard navigation and impairs accessibility.'
48
+ }
49
+ },
50
+ defaultOptions: []
51
+ });
@@ -0,0 +1,3 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const CallOutAnnounceOnMount: ESLintUtils.RuleModule<"missingAnnounceOnMount", [], unknown, ESLintUtils.RuleListener>;
3
+ //# sourceMappingURL=callout_announce_on_mount.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callout_announce_on_mount.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/callout_announce_on_mount.ts"],"names":[],"mappings":"AAQA,OAAO,EAAG,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAMxD,eAAO,MAAM,sBAAsB,yFAuDjC,CAAC"}
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.CallOutAnnounceOnMount = void 0;
7
+ var _utils = require("@typescript-eslint/utils");
8
+ var _is_in_conditional_rendering = require("../../utils/is_in_conditional_rendering");
9
+ var _has_spread = require("../../utils/has_spread");
10
+ /*
11
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
12
+ * or more contributor license agreements. Licensed under the Elastic License
13
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
14
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
15
+ * Side Public License, v 1.
16
+ */
17
+
18
+ const CALLOUT_COMPONENT = 'EuiCallOut';
19
+ const CallOutAnnounceOnMount = exports.CallOutAnnounceOnMount = _utils.ESLintUtils.RuleCreator.withoutDocs({
20
+ create(context) {
21
+ return {
22
+ JSXElement(node) {
23
+ const {
24
+ openingElement
25
+ } = node;
26
+ if (openingElement.name.type !== 'JSXIdentifier' || openingElement.name.name !== CALLOUT_COMPONENT) {
27
+ return;
28
+ }
29
+ if (openingElement.attributes.some(attr => attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier' && attr.name.name === 'announceOnMount')) {
30
+ return;
31
+ }
32
+ if ((0, _is_in_conditional_rendering.isInConditionalRendering)(node)) {
33
+ context.report({
34
+ node: openingElement,
35
+ messageId: 'missingAnnounceOnMount',
36
+ fix: (0, _has_spread.hasSpread)(openingElement.attributes) ? undefined : fixer => {
37
+ return fixer.insertTextAfterRange([openingElement.name.range[1], openingElement.name.range[1]], ' announceOnMount');
38
+ }
39
+ });
40
+ }
41
+ }
42
+ };
43
+ },
44
+ meta: {
45
+ type: 'problem',
46
+ docs: {
47
+ description: `Ensure ${CALLOUT_COMPONENT} components that are conditionally rendered have announceOnMount prop for better accessibility`
48
+ },
49
+ fixable: 'code',
50
+ schema: [],
51
+ messages: {
52
+ missingAnnounceOnMount: [`${CALLOUT_COMPONENT} should have \`announceOnMount\` prop when conditionally rendered for better accessibility.`, '\n', `When ${CALLOUT_COMPONENT} appears dynamically (e.g., after user interaction, form validation, etc.),`, 'screen readers may not announce its content. Adding `announceOnMount` ensures the callout', 'is properly announced to users with assistive technologies.', '\n', `Note: If ${CALLOUT_COMPONENT} is inside a condition and not an action, explicitly set \`announceOnMount={false}\``, 'Example:', ` <${CALLOUT_COMPONENT} announceOnMount title="Error" color="danger">`, ' This message will be announced when it appears', ` </${CALLOUT_COMPONENT}>`].join('\n')
53
+ }
54
+ },
55
+ defaultOptions: []
56
+ });
@@ -1 +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;AAiBtE,eAAO,MAAM,wBAAwB,wFA6FnC,CAAC"}
1
+ {"version":3,"file":"consistent_is_invalid_props.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/consistent_is_invalid_props.ts"],"names":[],"mappings":"AAQA,OAAO,EAAiB,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAiBtE,eAAO,MAAM,wBAAwB,wFA6FnC,CAAC"}
@@ -8,22 +8,11 @@ var _utils = require("@typescript-eslint/utils");
8
8
  var _get_attr_value = require("../../utils/get_attr_value");
9
9
  var _are_attrs_equal = require("../../utils/are_attrs_equal");
10
10
  /*
11
- * Licensed to Elasticsearch B.V. under one or more contributor
12
- * license agreements. See the NOTICE file distributed with
13
- * this work for additional information regarding copyright
14
- * ownership. Elasticsearch B.V. licenses this file to you under
15
- * the Apache License, Version 2.0 (the "License"); you may
16
- * not use this file except in compliance with the License.
17
- * You may obtain a copy of the License at
18
- *
19
- * http://www.apache.org/licenses/LICENSE-2.0
20
- *
21
- * Unless required by applicable law or agreed to in writing,
22
- * software distributed under the License is distributed on an
23
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
24
- * KIND, either express or implied. See the License for the
25
- * specific language governing permissions and limitations
26
- * under the License.
11
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
12
+ * or more contributor license agreements. Licensed under the Elastic License
13
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
14
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
15
+ * Side Public License, v 1.
27
16
  */
28
17
 
29
18
  const formControlComponent = 'EuiFormRow';
@@ -36,7 +25,7 @@ const ConsistentIsInvalidProps = exports.ConsistentIsInvalidProps = _utils.ESLin
36
25
  if (openingElement?.name.type !== 'JSXIdentifier' || openingElement.name.name !== formControlComponent || openingElement.attributes.length === 0) {
37
26
  return;
38
27
  }
39
- const formRowIsInvalid = (0, _get_attr_value.getAttrValue)(context, openingElement.attributes, 'isInvalid');
28
+ const formRowIsInvalid = (0, _get_attr_value.findAttrValue)(context, openingElement.attributes, 'isInvalid');
40
29
  if (formRowIsInvalid === undefined) {
41
30
  return;
42
31
  }
@@ -44,7 +33,7 @@ const ConsistentIsInvalidProps = exports.ConsistentIsInvalidProps = _utils.ESLin
44
33
  if (!childElement) {
45
34
  return;
46
35
  }
47
- const childIsInvalid = (0, _get_attr_value.getAttrValue)(context, childElement.openingElement.attributes, 'isInvalid');
36
+ const childIsInvalid = (0, _get_attr_value.findAttrValue)(context, childElement.openingElement.attributes, 'isInvalid');
48
37
  if (!(0, _are_attrs_equal.areAttrsEqual)(childIsInvalid, formRowIsInvalid)) {
49
38
  const componentName = childElement.openingElement.name.name;
50
39
  context.report({
@@ -0,0 +1,3 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const NoUnnamedInteractiveElement: ESLintUtils.RuleModule<"missingA11y", [], unknown, ESLintUtils.RuleListener>;
3
+ //# sourceMappingURL=no_unnamed_interactive_element.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no_unnamed_interactive_element.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/no_unnamed_interactive_element.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AA+BtE,eAAO,MAAM,2BAA2B,8EAyEtC,CAAC"}
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NoUnnamedInteractiveElement = void 0;
7
+ var _utils = require("@typescript-eslint/utils");
8
+ var _has_spread = require("../../utils/has_spread");
9
+ var _get_allowed_a11y_prop_names_for_component = require("../../utils/get_allowed_a11y_prop_names_for_component");
10
+ var _has_a11y_prop_for_component = require("../../utils/has_a11y_prop_for_component");
11
+ /*
12
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
13
+ * or more contributor license agreements. Licensed under the Elastic License
14
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
15
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
16
+ * Side Public License, v 1.
17
+ */
18
+
19
+ const interactiveComponents = ['EuiBetaBadge', 'EuiButtonIcon', 'EuiComboBox', 'EuiSelect', 'EuiSelectWithWidth', 'EuiSuperSelect', 'EuiPagination', 'EuiTreeView', 'EuiBreadcrumbs'];
20
+ const wrappingComponents = ['EuiFormRow'];
21
+ const interactiveComponentsWithLabel = ['EuiBetaBadge'];
22
+ const baseA11yProps = ['aria-label', 'aria-labelledby'];
23
+
24
+ // Single source of truth for the utils (keeps them reusable)
25
+ const a11yConfig = {
26
+ interactiveComponentsWithLabel: [...interactiveComponentsWithLabel],
27
+ wrappingComponents: [...wrappingComponents],
28
+ baseA11yProps: [...baseA11yProps]
29
+ };
30
+ const NoUnnamedInteractiveElement = exports.NoUnnamedInteractiveElement = _utils.ESLintUtils.RuleCreator.withoutDocs({
31
+ meta: {
32
+ type: 'problem',
33
+ hasSuggestions: false,
34
+ schema: [],
35
+ messages: {
36
+ missingA11y: '{{component}} must include an accessible label. Use one of: {{a11yProps}}'
37
+ }
38
+ },
39
+ defaultOptions: [],
40
+ create(context) {
41
+ const sourceCode = context.sourceCode;
42
+ function report(opening) {
43
+ if (opening.name.type !== 'JSXIdentifier') return;
44
+ const component = opening.name.name;
45
+ const allowed = (0, _get_allowed_a11y_prop_names_for_component.getAllowedA11yPropNamesForComponent)(component, a11yConfig).join(', ');
46
+ context.report({
47
+ node: opening,
48
+ messageId: 'missingA11y',
49
+ data: {
50
+ component,
51
+ a11yProps: allowed
52
+ }
53
+ });
54
+ }
55
+ return {
56
+ JSXOpeningElement(node) {
57
+ if (node.name.type !== 'JSXIdentifier') return;
58
+ const componentName = node.name.name;
59
+ const isInteractive = interactiveComponents.includes(componentName);
60
+ if (!isInteractive) return;
61
+ if ((0, _has_spread.hasSpread)(node.attributes) || (0, _has_a11y_prop_for_component.hasA11yPropForComponent)(componentName, node.attributes, a11yConfig)) {
62
+ return;
63
+ }
64
+ const ancestors = sourceCode.getAncestors(node);
65
+ const wrapper = [...ancestors].reverse().find(a => a.type === 'JSXElement' && a.openingElement.name.type === 'JSXIdentifier' && wrappingComponents.includes(a.openingElement.name.name));
66
+ if (wrapper) {
67
+ const open = wrapper.openingElement;
68
+ const wrapperName = open.name.type === 'JSXIdentifier' ? open.name.name : '';
69
+ if (!(0, _has_spread.hasSpread)(open.attributes) && !(0, _has_a11y_prop_for_component.hasA11yPropForComponent)(wrapperName, open.attributes, a11yConfig)) {
70
+ report(open);
71
+ }
72
+ } else {
73
+ report(node);
74
+ }
75
+ }
76
+ };
77
+ }
78
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"no_unnamed_radio_group.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/no_unnamed_radio_group.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIvD,eAAO,MAAM,mBAAmB,mFAuC9B,CAAC"}
1
+ {"version":3,"file":"no_unnamed_radio_group.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/no_unnamed_radio_group.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIvD,eAAO,MAAM,mBAAmB,mFAuC9B,CAAC"}
@@ -6,22 +6,11 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.NoUnnamedRadioGroup = void 0;
7
7
  var _utils = require("@typescript-eslint/utils");
8
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.
9
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
10
+ * or more contributor license agreements. Licensed under the Elastic License
11
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
12
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
13
+ * Side Public License, v 1.
25
14
  */
26
15
 
27
16
  const radioComponents = ['EuiRadio', 'EuiRadioGroup'];
@@ -1 +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"}
1
+ {"version":3,"file":"prefer_eui_icon_tip.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/prefer_eui_icon_tip.ts"],"names":[],"mappings":"AAQA,OAAO,EAAiB,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAMtE,eAAO,MAAM,gBAAgB,mFA0C3B,CAAC"}
@@ -5,6 +5,14 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.PreferEuiIconTip = void 0;
7
7
  var _utils = require("@typescript-eslint/utils");
8
+ /*
9
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
10
+ * or more contributor license agreements. Licensed under the Elastic License
11
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
12
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
13
+ * Side Public License, v 1.
14
+ */
15
+
8
16
  const TOOLTIP_COMPONENT = 'EuiToolTip';
9
17
  const ICON_COMPONENT = 'EuiIcon';
10
18
  const ICON_TIP_COMPONENT = 'EuiIconTip';
@@ -1 +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"}
1
+ {"version":3,"file":"require_aria_label_for_modals.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/require_aria_label_for_modals.ts"],"names":[],"mappings":"AAQA,OAAO,EAAY,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAKjE,eAAO,MAAM,yBAAyB,+GA0FpC,CAAC"}
@@ -6,22 +6,11 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.RequireAriaLabelForModals = void 0;
7
7
  var _utils = require("@typescript-eslint/utils");
8
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.
9
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
10
+ * or more contributor license agreements. Licensed under the Elastic License
11
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
12
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
13
+ * Side Public License, v 1.
25
14
  */
26
15
 
27
16
  const modalComponents = ['EuiModal', 'EuiFlyout', 'EuiFlyoutResizable'];
@@ -26,7 +26,7 @@ const ScreenReaderOutputDisabledTooltip = exports.ScreenReaderOutputDisabledTool
26
26
  if (openingElement?.name.type !== 'JSXIdentifier' || openingElement.name.name !== tooltipComponent) {
27
27
  return;
28
28
  }
29
- const tooltipContent = (0, _get_attr_value.getAttrValue)(context, openingElement.attributes, 'content');
29
+ const tooltipContent = (0, _get_attr_value.findAttrValue)(context, openingElement.attributes, 'content');
30
30
  const hasDisableScreenReader = openingElement.attributes.some(attr => attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier' && attr.name.name === disabledTooltipComponentProp);
31
31
  if (hasDisableScreenReader) {
32
32
  return;
@@ -35,7 +35,7 @@ const ScreenReaderOutputDisabledTooltip = exports.ScreenReaderOutputDisabledTool
35
35
  if (!buttonElement) {
36
36
  return;
37
37
  }
38
- const ariaLabel = (0, _get_attr_value.getAttrValue)(context, buttonElement.openingElement.attributes, 'aria-label');
38
+ const ariaLabel = (0, _get_attr_value.findAttrValue)(context, buttonElement.openingElement.attributes, 'aria-label');
39
39
  if (tooltipContent && ariaLabel && (0, _are_attrs_equal.areAttrsEqual)(tooltipContent, ariaLabel)) {
40
40
  const buttonElementName = buttonElement.openingElement.name.name;
41
41
  context.report({
@@ -0,0 +1,3 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const TooltipFocusableAnchor: ESLintUtils.RuleModule<"requiresFocusableAnchor", [], unknown, ESLintUtils.RuleListener>;
3
+ //# sourceMappingURL=tooltip_focusable_anchor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip_focusable_anchor.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/tooltip_focusable_anchor.ts"],"names":[],"mappings":"AAQA,OAAO,EAAY,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAoBjE,eAAO,MAAM,sBAAsB,0FAyEjC,CAAC"}
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.TooltipFocusableAnchor = void 0;
7
+ var _utils = require("@typescript-eslint/utils");
8
+ var _constants = require("../../utils/constants");
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 TOOL_TIP_COMPONENT = 'EuiToolTip';
18
+ const NON_INTERACTIVE_ELEMENTS = [..._constants.NON_INTERACTIVE_HTML_TAGS,
19
+ // note: not a complete list, based on the most common violations
20
+ 'EuiText', 'EuiImage', 'EuiBadge', 'EuiBetaBadge'];
21
+ const INTERACTIVE_ATTRS = ['tabIndex', 'href', 'onClick'];
22
+ const TooltipFocusableAnchor = exports.TooltipFocusableAnchor = _utils.ESLintUtils.RuleCreator.withoutDocs({
23
+ create(context) {
24
+ const isTooltipComponent = node => node.name.type === 'JSXIdentifier' && node.name.name === TOOL_TIP_COMPONENT;
25
+ const hasInteractiveAttribute = node => node.attributes.some(attr => attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier' && INTERACTIVE_ATTRS.includes(attr.name.name));
26
+ const isNonInteractiveElement = elementName => NON_INTERACTIVE_ELEMENTS.includes(elementName);
27
+ return {
28
+ JSXElement(node) {
29
+ const {
30
+ openingElement
31
+ } = node;
32
+ if (!isTooltipComponent(openingElement)) {
33
+ return;
34
+ }
35
+ const children = node.children.filter(child => child.type === 'JSXElement');
36
+ if (children.length === 0) {
37
+ return;
38
+ }
39
+ const firstChild = children[0].openingElement;
40
+ if (firstChild.name.type !== 'JSXIdentifier') {
41
+ return;
42
+ }
43
+ const childName = firstChild.name.name;
44
+ if (isNonInteractiveElement(childName) && !hasInteractiveAttribute(firstChild)) {
45
+ context.report({
46
+ node: firstChild,
47
+ messageId: 'requiresFocusableAnchor',
48
+ data: {
49
+ element: childName,
50
+ toolTipComponent: TOOL_TIP_COMPONENT
51
+ }
52
+ });
53
+ }
54
+ }
55
+ };
56
+ },
57
+ meta: {
58
+ type: 'problem',
59
+ docs: {
60
+ description: 'Ensure {{ toolTipComponent }} components are anchored to focusable elements'
61
+ },
62
+ schema: [],
63
+ messages: {
64
+ requiresFocusableAnchor: ['{{ element }} inside {{ toolTipComponent }} must be focusable for accessibility.', 'Option 1: Add tabIndex attribute to make the element keyboard focusable:', ' <{{ toolTipComponent }} content=\'...\'>', ' <{{ element }} tabIndex={0}>...</{{ element }}>', ' </{{ toolTipComponent }}>', 'Option 2 (Preferred): Use an interactive component instead:', ' <{{ toolTipComponent }} content=\'...\'>', ' <EuiButton>...</EuiButton>', ' </{{ toolTipComponent }}>'].join('\n')
65
+ }
66
+ },
67
+ defaultOptions: []
68
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"href_or_on_click.d.ts","sourceRoot":"","sources":["../../../src/rules/href_or_on_click.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAY,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIjE,eAAO,MAAM,WAAW,gFA8CtB,CAAC"}
1
+ {"version":3,"file":"href_or_on_click.d.ts","sourceRoot":"","sources":["../../../src/rules/href_or_on_click.ts"],"names":[],"mappings":"AAQA,OAAO,EAAY,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIjE,eAAO,MAAM,WAAW,gFA8CtB,CAAC"}
@@ -6,22 +6,11 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.HrefOnClick = void 0;
7
7
  var _utils = require("@typescript-eslint/utils");
8
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.
9
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
10
+ * or more contributor license agreements. Licensed under the Elastic License
11
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
12
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
13
+ * Side Public License, v 1.
25
14
  */
26
15
 
27
16
  const componentNames = ['EuiButton', 'EuiButtonEmpty', 'EuiLink', 'EuiBadge'];
@@ -1 +1 @@
1
- {"version":3,"file":"no_css_color.d.ts","sourceRoot":"","sources":["../../../src/rules/no_css_color.ts"],"names":[],"mappings":"AAUA,OAAO,EAAY,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAyNjE,eAAO,MAAM,UAAU,2IAsRrB,CAAC"}
1
+ {"version":3,"file":"no_css_color.d.ts","sourceRoot":"","sources":["../../../src/rules/no_css_color.ts"],"names":[],"mappings":"AASA,OAAO,EAAY,WAAW,EAAE,MAAM,0BAA0B,CAAC;AA4OjE,eAAO,MAAM,UAAU,2IAsRrB,CAAC"}
@@ -9,11 +9,10 @@ var _utils = require("@typescript-eslint/utils");
9
9
  var _resolve_member_expression_root = require("../utils/resolve_member_expression_root");
10
10
  /*
11
11
  * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
12
- * or more contributor license agreements. Licensed under the "Elastic License
13
- * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
14
- * Public License v 1"; you may not use this file except in compliance with, at
15
- * your election, the "Elastic License 2.0", the "GNU Affero General Public
16
- * License v3.0 only", or the "Server Side Public License, v 1".
12
+ * or more contributor license agreements. Licensed under the Elastic License
13
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
14
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
15
+ * Side Public License, v 1.
17
16
  */
18
17
 
19
18
  /**
@@ -39,9 +38,17 @@ const checkPropertySpecifiesInvalidCSSColor = ([property, value]) => {
39
38
  // build the resolved color property to check if the value is a string after parsing the style declaration
40
39
  const resolvedColorProperty = anchor === 'color' ? 'color' : anchor + 'Color';
41
40
 
42
- // in trying to keep this rule simple, it's enough if we get a value back, because if it was an identifier we would have been able to set a value within this invocation
43
41
  // @ts-expect-error the types for this packages specifics an index signature of number, alongside other valid CSS properties
44
- return Boolean(style[resolvedColorProperty]);
42
+ const colorValue = style[resolvedColorProperty];
43
+ if (!colorValue) return false;
44
+
45
+ // Allow CSS keywords that are valid and should not trigger warnings
46
+ const allowedCssKeywords = ['currentcolor', 'transparent', 'inherit', 'initial', 'unset', 'revert', 'revert-layer'];
47
+ const normalizedColorValue = colorValue.toLowerCase().trim();
48
+ if (allowedCssKeywords.includes(normalizedColorValue)) {
49
+ return false;
50
+ }
51
+ return true;
45
52
  };
46
53
 
47
54
  /**