react-native-boost 0.4.1 → 0.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.
- package/README.md +3 -5
- package/dist/esm/index.mjs +39 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -1
- package/dist/plugin/esm/index.mjs +168 -44
- package/dist/plugin/esm/index.mjs.map +1 -1
- package/dist/plugin/index.js +168 -44
- package/dist/plugin/index.js.map +1 -1
- package/package.json +5 -5
- package/src/plugin/optimizers/text/index.ts +99 -46
- package/src/plugin/optimizers/view/index.ts +9 -11
- package/src/plugin/types/index.ts +23 -0
- package/src/plugin/utils/common.ts +154 -1
- package/src/runtime/index.ts +56 -0
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import { NodePath, types as t } from '@babel/core';
|
|
2
|
-
import { addNamed } from '@babel/helper-module-imports';
|
|
3
2
|
import { HubFile, Optimizer } from '../../types';
|
|
4
3
|
import PluginError from '../../utils/plugin-error';
|
|
5
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
addFileImportHint,
|
|
6
|
+
buildPropertiesFromAttributes,
|
|
7
|
+
hasAccessibilityProperty,
|
|
8
|
+
hasBlacklistedProperty,
|
|
9
|
+
shouldIgnoreOptimization,
|
|
10
|
+
} from '../../utils/common';
|
|
6
11
|
|
|
7
12
|
export const textBlacklistedProperties = new Set([
|
|
8
|
-
'accessible',
|
|
9
|
-
'accessibilityLabel',
|
|
10
|
-
'accessibilityState',
|
|
11
13
|
'allowFontScaling',
|
|
12
|
-
'aria-busy',
|
|
13
|
-
'aria-checked',
|
|
14
|
-
'aria-disabled',
|
|
15
|
-
'aria-expanded',
|
|
16
|
-
'aria-label',
|
|
17
|
-
'aria-selected',
|
|
18
14
|
'ellipsizeMode',
|
|
19
15
|
'id',
|
|
20
16
|
'nativeID',
|
|
@@ -78,16 +74,93 @@ export const textOptimizer: Optimizer = (path, log = () => {}) => {
|
|
|
78
74
|
|
|
79
75
|
// Optimize props
|
|
80
76
|
fixNegativeNumberOfLines({ path, log });
|
|
81
|
-
optimizeStyleTag({ path, file });
|
|
82
77
|
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
// Process style and accessibility props
|
|
79
|
+
const originalAttributes = [...path.node.attributes];
|
|
80
|
+
let styleAttribute, styleExpr;
|
|
81
|
+
for (const attribute of originalAttributes) {
|
|
82
|
+
if (t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name, { name: 'style' })) {
|
|
83
|
+
styleAttribute = attribute;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
86
|
}
|
|
87
|
-
if (
|
|
88
|
-
|
|
87
|
+
if (
|
|
88
|
+
styleAttribute &&
|
|
89
|
+
styleAttribute.value &&
|
|
90
|
+
t.isJSXExpressionContainer(styleAttribute.value) &&
|
|
91
|
+
!t.isJSXEmptyExpression(styleAttribute.value.expression)
|
|
92
|
+
) {
|
|
93
|
+
styleExpr = styleAttribute.value.expression;
|
|
89
94
|
}
|
|
90
|
-
const
|
|
95
|
+
const hasA11y = hasAccessibilityProperty(path, originalAttributes);
|
|
96
|
+
|
|
97
|
+
if (styleExpr && hasA11y) {
|
|
98
|
+
// When both style and accessibility properties exist, we split them into two separate spread attributes
|
|
99
|
+
|
|
100
|
+
// Filter out the style attribute for accessibility props
|
|
101
|
+
const accessibilityAttributes = originalAttributes.filter((attribute) => {
|
|
102
|
+
if (t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name, { name: 'style' })) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Set up the accessibility import if needed
|
|
109
|
+
const normalizeIdentifier = addFileImportHint({
|
|
110
|
+
file,
|
|
111
|
+
nameHint: 'normalizeAccessibilityProps',
|
|
112
|
+
path,
|
|
113
|
+
importName: 'normalizeAccessibilityProps',
|
|
114
|
+
moduleName: 'react-native-boost',
|
|
115
|
+
});
|
|
116
|
+
const accessibilityObject = buildPropertiesFromAttributes(accessibilityAttributes);
|
|
117
|
+
const accessibilityExpr = t.callExpression(t.identifier(normalizeIdentifier.name), [accessibilityObject]);
|
|
118
|
+
|
|
119
|
+
// Set up the style import if needed.
|
|
120
|
+
const flattenIdentifier = addFileImportHint({
|
|
121
|
+
file,
|
|
122
|
+
nameHint: 'flattenTextStyle',
|
|
123
|
+
path,
|
|
124
|
+
importName: 'flattenTextStyle',
|
|
125
|
+
moduleName: 'react-native-boost',
|
|
126
|
+
});
|
|
127
|
+
const flattenedStyleExpr = t.callExpression(t.identifier(flattenIdentifier.name), [styleExpr]);
|
|
128
|
+
|
|
129
|
+
// Use two separate JSX spread attributes so that accessibility and style props remain distinct.
|
|
130
|
+
path.node.attributes = [t.jsxSpreadAttribute(accessibilityExpr), t.jsxSpreadAttribute(flattenedStyleExpr)];
|
|
131
|
+
} else if (styleExpr) {
|
|
132
|
+
// Only style attribute is present.
|
|
133
|
+
const flattenIdentifier = addFileImportHint({
|
|
134
|
+
file,
|
|
135
|
+
nameHint: 'flattenTextStyle',
|
|
136
|
+
path,
|
|
137
|
+
importName: 'flattenTextStyle',
|
|
138
|
+
moduleName: 'react-native-boost',
|
|
139
|
+
});
|
|
140
|
+
const flattened = t.callExpression(t.identifier(flattenIdentifier.name), [styleExpr]);
|
|
141
|
+
path.node.attributes = [t.jsxSpreadAttribute(flattened)];
|
|
142
|
+
} else if (hasA11y) {
|
|
143
|
+
// Only accessibility properties are present.
|
|
144
|
+
const normalizeIdentifier = addFileImportHint({
|
|
145
|
+
file,
|
|
146
|
+
nameHint: 'normalizeAccessibilityProps',
|
|
147
|
+
path,
|
|
148
|
+
importName: 'normalizeAccessibilityProps',
|
|
149
|
+
moduleName: 'react-native-boost',
|
|
150
|
+
});
|
|
151
|
+
const propsObject = buildPropertiesFromAttributes(originalAttributes);
|
|
152
|
+
const normalized = t.callExpression(t.identifier(normalizeIdentifier.name), [propsObject]);
|
|
153
|
+
path.node.attributes = [t.jsxSpreadAttribute(normalized)];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Add TextNativeComponent import (cached on file) so we only add it once per file.
|
|
157
|
+
const nativeTextIdentifier = addFileImportHint({
|
|
158
|
+
file,
|
|
159
|
+
nameHint: 'NativeText',
|
|
160
|
+
path,
|
|
161
|
+
importName: 'NativeText',
|
|
162
|
+
moduleName: 'react-native/Libraries/Text/TextNativeComponent',
|
|
163
|
+
});
|
|
91
164
|
path.node.name.name = nativeTextIdentifier.name;
|
|
92
165
|
|
|
93
166
|
// If the element is not self-closing, update the closing element as well
|
|
@@ -106,17 +179,16 @@ function hasOnlyStringChildren(path: NodePath<t.JSXOpeningElement>, node: t.JSXE
|
|
|
106
179
|
}
|
|
107
180
|
|
|
108
181
|
function isStringNode(path: NodePath<t.JSXOpeningElement>, child: t.Node): boolean {
|
|
109
|
-
if (t.isJSXText(child)) return true;
|
|
182
|
+
if (t.isJSXText(child) || t.isStringLiteral(child)) return true;
|
|
110
183
|
|
|
111
184
|
// Check for JSX expressions
|
|
112
185
|
if (t.isJSXExpressionContainer(child)) {
|
|
113
186
|
const expression = child.expression;
|
|
114
|
-
|
|
115
|
-
// If the expression is an identifier, look it up in the current scope
|
|
116
187
|
if (t.isIdentifier(expression)) {
|
|
117
188
|
const binding = path.scope.getBinding(expression.name);
|
|
118
189
|
return binding ? t.isStringLiteral(binding.path.node) : false;
|
|
119
190
|
}
|
|
191
|
+
if (t.isStringLiteral(expression)) return true;
|
|
120
192
|
}
|
|
121
193
|
return false;
|
|
122
194
|
}
|
|
@@ -155,38 +227,19 @@ function fixNegativeNumberOfLines({
|
|
|
155
227
|
}
|
|
156
228
|
}
|
|
157
229
|
|
|
158
|
-
function optimizeStyleTag({ path, file }: { path: NodePath<t.JSXOpeningElement>; file: HubFile }) {
|
|
159
|
-
let shouldImportFlattenTextStyle = false;
|
|
160
|
-
const nameHint = '_flattenTextStyle';
|
|
161
|
-
|
|
162
|
-
for (const [index, attribute] of path.node.attributes.entries()) {
|
|
163
|
-
if (t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name, { name: 'style' })) {
|
|
164
|
-
shouldImportFlattenTextStyle = true;
|
|
165
|
-
|
|
166
|
-
if (t.isJSXExpressionContainer(attribute.value) && !t.isJSXEmptyExpression(attribute.value.expression)) {
|
|
167
|
-
path.node.attributes[index] = t.jsxSpreadAttribute(
|
|
168
|
-
t.callExpression(t.identifier(nameHint), [attribute.value.expression])
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (shouldImportFlattenTextStyle && !file.__hasImports?.flattenTextStyle) {
|
|
175
|
-
if (!file.__hasImports) file.__hasImports = {};
|
|
176
|
-
file.__hasImports.flattenTextStyle = addNamed(path, 'flattenTextStyle', 'react-native-boost', { nameHint });
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
230
|
function hasInvalidChildren(path: NodePath<t.JSXOpeningElement>): boolean {
|
|
181
231
|
for (const attribute of path.node.attributes) {
|
|
182
|
-
if (t.isJSXSpreadAttribute(attribute))
|
|
232
|
+
if (t.isJSXSpreadAttribute(attribute)) continue; // Spread attributes are handled in hasBlacklistedProperty
|
|
183
233
|
|
|
184
234
|
if (t.isJSXIdentifier(attribute.name) && attribute.value) {
|
|
185
235
|
// For a "children" attribute, optimization is allowed only if it is a string
|
|
186
236
|
if (attribute.name.name === 'children') {
|
|
187
|
-
|
|
237
|
+
if (!isStringNode(path, attribute.value)) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
} else if (textBlacklistedProperties.has(attribute.name.name)) {
|
|
241
|
+
return true;
|
|
188
242
|
}
|
|
189
|
-
return textBlacklistedProperties.has(attribute.name.name);
|
|
190
243
|
}
|
|
191
244
|
}
|
|
192
245
|
return false;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { NodePath, types as t } from '@babel/core';
|
|
2
|
-
import { addDefault } from '@babel/helper-module-imports';
|
|
3
2
|
import { HubFile, Optimizer } from '../../types';
|
|
4
3
|
import PluginError from '../../utils/plugin-error';
|
|
5
|
-
import { hasBlacklistedProperty, shouldIgnoreOptimization } from '../../utils/common';
|
|
4
|
+
import { addFileImportHint, hasBlacklistedProperty, shouldIgnoreOptimization } from '../../utils/common';
|
|
6
5
|
|
|
7
6
|
export const viewBlacklistedProperties = new Set([
|
|
8
7
|
'accessible',
|
|
@@ -79,15 +78,14 @@ export const viewOptimizer: Optimizer = (path, log = () => {}) => {
|
|
|
79
78
|
log(`Optimizing View component in ${filename}:${lineNumber}`);
|
|
80
79
|
|
|
81
80
|
// Add ViewNativeComponent import (cached on the file) to prevent duplicate imports.
|
|
82
|
-
|
|
83
|
-
file
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
const viewNativeIdentifier = file.__hasImports.NativeView;
|
|
81
|
+
const viewNativeIdentifier = addFileImportHint({
|
|
82
|
+
file,
|
|
83
|
+
path,
|
|
84
|
+
importName: 'ViewNativeComponent',
|
|
85
|
+
moduleName: 'react-native/Libraries/Components/View/ViewNativeComponent',
|
|
86
|
+
importType: 'default',
|
|
87
|
+
nameHint: 'NativeView',
|
|
88
|
+
});
|
|
91
89
|
|
|
92
90
|
// Replace the component with its native counterpart.
|
|
93
91
|
path.node.name.name = viewNativeIdentifier.name;
|
|
@@ -36,3 +36,26 @@ export type HubFile = t.File & {
|
|
|
36
36
|
__hasImports?: Record<string, t.Identifier>;
|
|
37
37
|
__optimized?: boolean;
|
|
38
38
|
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Options for adding a file import hint.
|
|
42
|
+
*/
|
|
43
|
+
export interface FileImportOptions {
|
|
44
|
+
file: HubFile;
|
|
45
|
+
/** The name hint which also acts as the cache key to ensure the import is only added once (e.g. 'normalizeAccessibilityProps') */
|
|
46
|
+
nameHint: string;
|
|
47
|
+
/** The current Babel NodePath */
|
|
48
|
+
path: NodePath;
|
|
49
|
+
/**
|
|
50
|
+
* The named import string (e.g. 'normalizeAccessibilityProps'). Ignored if importType is "default".
|
|
51
|
+
*/
|
|
52
|
+
importName: string;
|
|
53
|
+
/** The module to import from (e.g. 'react-native-boost') */
|
|
54
|
+
moduleName: string;
|
|
55
|
+
/**
|
|
56
|
+
* Determines which helper to use:
|
|
57
|
+
* - "named" (default) uses addNamed (requires importName)
|
|
58
|
+
* - "default" uses addDefault
|
|
59
|
+
*/
|
|
60
|
+
importType?: 'named' | 'default';
|
|
61
|
+
}
|
|
@@ -1,10 +1,41 @@
|
|
|
1
1
|
import { NodePath, types as t } from '@babel/core';
|
|
2
2
|
import { ensureArray } from './helpers';
|
|
3
|
-
import { HubFile } from '../types';
|
|
3
|
+
import { FileImportOptions, HubFile } from '../types';
|
|
4
4
|
import { minimatch } from 'minimatch';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import PluginError from './plugin-error';
|
|
7
|
+
import { addDefault, addNamed } from '@babel/helper-module-imports';
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Adds a hint to the file object to ensure that a specific import is added only once and cached on the file object.
|
|
11
|
+
*
|
|
12
|
+
* @param opts - Object containing the function arguments:
|
|
13
|
+
* - file: The Babel file object (e.g. HubFile)
|
|
14
|
+
* - nameHint: The name hint which also acts as the cache key to ensure the import is only added once (e.g. 'normalizeAccessibilityProps')
|
|
15
|
+
* - path: The current Babel NodePath
|
|
16
|
+
* - importName: The named import string (e.g. 'normalizeAccessibilityProps'), used when importType is 'named'
|
|
17
|
+
* - moduleName: The module to import from (e.g. 'react-native-boost')
|
|
18
|
+
* - importType: Either 'named' (default) or 'default' to determine the type of import to use.
|
|
19
|
+
*
|
|
20
|
+
* @returns The identifier returned by addNamed or addDefault.
|
|
21
|
+
*/
|
|
22
|
+
export function addFileImportHint({
|
|
23
|
+
file,
|
|
24
|
+
nameHint,
|
|
25
|
+
path,
|
|
26
|
+
importName,
|
|
27
|
+
moduleName,
|
|
28
|
+
importType = 'named',
|
|
29
|
+
}: FileImportOptions): t.Identifier {
|
|
30
|
+
if (!file.__hasImports?.[nameHint]) {
|
|
31
|
+
file.__hasImports = file.__hasImports || {};
|
|
32
|
+
file.__hasImports[nameHint] =
|
|
33
|
+
importType === 'default'
|
|
34
|
+
? addDefault(path, moduleName, { nameHint })
|
|
35
|
+
: addNamed(path, importName, moduleName, { nameHint });
|
|
36
|
+
}
|
|
37
|
+
return file.__hasImports[nameHint];
|
|
38
|
+
}
|
|
8
39
|
/**
|
|
9
40
|
* Checks if the file is in the list of ignored files.
|
|
10
41
|
*
|
|
@@ -44,6 +75,9 @@ export const isIgnoredFile = (p: NodePath<t.JSXOpeningElement>, ignores: string[
|
|
|
44
75
|
*
|
|
45
76
|
* The function looks up the JSXOpeningElement's own leading comments as well as
|
|
46
77
|
* the parent element's comments before falling back to inspect siblings.
|
|
78
|
+
*
|
|
79
|
+
* @param path - The path to the JSXOpeningElement.
|
|
80
|
+
* @returns true if the JSX element should be ignored.
|
|
47
81
|
*/
|
|
48
82
|
export const shouldIgnoreOptimization = (path: NodePath<t.JSXOpeningElement>): boolean => {
|
|
49
83
|
// Check for @boost-ignore in the leading comments on the JSX opening element.
|
|
@@ -109,6 +143,13 @@ export const shouldIgnoreOptimization = (path: NodePath<t.JSXOpeningElement>): b
|
|
|
109
143
|
return false;
|
|
110
144
|
};
|
|
111
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Checks if the JSX element has a blacklisted property.
|
|
148
|
+
*
|
|
149
|
+
* @param path - The path to the JSXOpeningElement.
|
|
150
|
+
* @param blacklist - The set of blacklisted properties.
|
|
151
|
+
* @returns true if the JSX element has a blacklisted property.
|
|
152
|
+
*/
|
|
112
153
|
export const hasBlacklistedProperty = (path: NodePath<t.JSXOpeningElement>, blacklist: Set<string>): boolean => {
|
|
113
154
|
return path.node.attributes.some((attribute) => {
|
|
114
155
|
// Check if we can resolve the spread attribute
|
|
@@ -141,3 +182,115 @@ export const hasBlacklistedProperty = (path: NodePath<t.JSXOpeningElement>, blac
|
|
|
141
182
|
return false;
|
|
142
183
|
});
|
|
143
184
|
};
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Helper that builds an Object.assign expression out of the existing JSX attributes.
|
|
188
|
+
* It handles both plain JSXAttributes and spread attributes.
|
|
189
|
+
*
|
|
190
|
+
* @param attributes - The attributes to build the expression from.
|
|
191
|
+
* @returns The Object.assign expression.
|
|
192
|
+
*/
|
|
193
|
+
export const buildPropertiesFromAttributes = (attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[]): t.Expression => {
|
|
194
|
+
const arguments_: t.Expression[] = [];
|
|
195
|
+
for (const attribute of attributes) {
|
|
196
|
+
if (t.isJSXSpreadAttribute(attribute)) {
|
|
197
|
+
arguments_.push(attribute.argument);
|
|
198
|
+
} else if (t.isJSXAttribute(attribute)) {
|
|
199
|
+
const key = attribute.name.name;
|
|
200
|
+
let value: t.Expression;
|
|
201
|
+
if (!attribute.value) {
|
|
202
|
+
value = t.booleanLiteral(true);
|
|
203
|
+
} else if (t.isStringLiteral(attribute.value)) {
|
|
204
|
+
value = attribute.value;
|
|
205
|
+
} else if (t.isJSXExpressionContainer(attribute.value)) {
|
|
206
|
+
value = t.isJSXEmptyExpression(attribute.value.expression)
|
|
207
|
+
? t.booleanLiteral(true)
|
|
208
|
+
: attribute.value.expression;
|
|
209
|
+
} else {
|
|
210
|
+
value = t.nullLiteral();
|
|
211
|
+
}
|
|
212
|
+
// If the key is not a valid JavaScript identifier (e.g. "aria-label"), use a string literal.
|
|
213
|
+
const validIdentifierRegex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
214
|
+
const keyNode =
|
|
215
|
+
typeof key === 'string' && validIdentifierRegex.test(key) ? t.identifier(key) : t.stringLiteral(key.toString());
|
|
216
|
+
|
|
217
|
+
arguments_.push(t.objectExpression([t.objectProperty(keyNode, value)]));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (arguments_.length === 0) {
|
|
221
|
+
return t.objectExpression([]);
|
|
222
|
+
}
|
|
223
|
+
return t.callExpression(t.memberExpression(t.identifier('Object'), t.identifier('assign')), [
|
|
224
|
+
t.objectExpression([]),
|
|
225
|
+
...arguments_,
|
|
226
|
+
]);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* The set of accessibility properties that need to be normalized.
|
|
231
|
+
*/
|
|
232
|
+
export const accessibilityProperties = new Set([
|
|
233
|
+
'accessibilityLabel',
|
|
234
|
+
'aria-label',
|
|
235
|
+
'accessibilityState',
|
|
236
|
+
'aria-busy',
|
|
237
|
+
'aria-checked',
|
|
238
|
+
'aria-disabled',
|
|
239
|
+
'aria-expanded',
|
|
240
|
+
'aria-selected',
|
|
241
|
+
'accessible',
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Checks if the JSX element has an accessibility property.
|
|
246
|
+
*
|
|
247
|
+
* @param path - The NodePath for the JSXOpeningElement, used for scope lookup.
|
|
248
|
+
* @param attributes - The attributes to check.
|
|
249
|
+
* @returns true if the JSX element has an accessibility property.
|
|
250
|
+
*/
|
|
251
|
+
export const hasAccessibilityProperty = (
|
|
252
|
+
path: NodePath<t.JSXOpeningElement>,
|
|
253
|
+
attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[]
|
|
254
|
+
): boolean => {
|
|
255
|
+
for (const attribute of attributes) {
|
|
256
|
+
if (t.isJSXAttribute(attribute)) {
|
|
257
|
+
const key = attribute.name.name;
|
|
258
|
+
if (typeof key === 'string' && accessibilityProperties.has(key)) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
} else if (t.isJSXSpreadAttribute(attribute)) {
|
|
262
|
+
if (t.isObjectExpression(attribute.argument)) {
|
|
263
|
+
for (const property of attribute.argument.properties) {
|
|
264
|
+
if (
|
|
265
|
+
t.isObjectProperty(property) &&
|
|
266
|
+
t.isIdentifier(property.key) &&
|
|
267
|
+
accessibilityProperties.has(property.key.name)
|
|
268
|
+
) {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} else if (t.isIdentifier(attribute.argument)) {
|
|
273
|
+
const binding = path.scope.getBinding(attribute.argument.name);
|
|
274
|
+
if (binding && t.isVariableDeclarator(binding.path.node)) {
|
|
275
|
+
const declarator = binding.path.node as t.VariableDeclarator;
|
|
276
|
+
if (declarator.init && t.isObjectExpression(declarator.init)) {
|
|
277
|
+
for (const property of declarator.init.properties) {
|
|
278
|
+
if (
|
|
279
|
+
t.isObjectProperty(property) &&
|
|
280
|
+
t.isIdentifier(property.key) &&
|
|
281
|
+
accessibilityProperties.has(property.key.name)
|
|
282
|
+
) {
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return true;
|
|
290
|
+
} else {
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
};
|
package/src/runtime/index.ts
CHANGED
|
@@ -55,4 +55,60 @@ export const verticalAlignToTextAlignVerticalMap = {
|
|
|
55
55
|
middle: 'center',
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Normalizes accessibility props.
|
|
60
|
+
*
|
|
61
|
+
* @param props - The props to normalize.
|
|
62
|
+
* @returns The normalized props.
|
|
63
|
+
*/
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
65
|
+
export function normalizeAccessibilityProperties(props: Record<string, any>): Record<string, any> {
|
|
66
|
+
const {
|
|
67
|
+
accessibilityLabel,
|
|
68
|
+
['aria-label']: ariaLabel,
|
|
69
|
+
accessibilityState,
|
|
70
|
+
['aria-busy']: ariaBusy,
|
|
71
|
+
['aria-checked']: ariaChecked,
|
|
72
|
+
['aria-disabled']: ariaDisabled,
|
|
73
|
+
['aria-expanded']: ariaExpanded,
|
|
74
|
+
['aria-selected']: ariaSelected,
|
|
75
|
+
accessible,
|
|
76
|
+
...restProperties
|
|
77
|
+
} = props;
|
|
78
|
+
|
|
79
|
+
// Merge label props: prefer the aria-label if defined.
|
|
80
|
+
const normalizedLabel = ariaLabel ?? accessibilityLabel;
|
|
81
|
+
|
|
82
|
+
// Merge the accessibilityState with any provided ARIA properties.
|
|
83
|
+
let normalizedState = accessibilityState;
|
|
84
|
+
if (ariaBusy != null || ariaChecked != null || ariaDisabled != null || ariaExpanded != null || ariaSelected != null) {
|
|
85
|
+
normalizedState =
|
|
86
|
+
normalizedState == null
|
|
87
|
+
? {
|
|
88
|
+
busy: ariaBusy,
|
|
89
|
+
checked: ariaChecked,
|
|
90
|
+
disabled: ariaDisabled,
|
|
91
|
+
expanded: ariaExpanded,
|
|
92
|
+
selected: ariaSelected,
|
|
93
|
+
}
|
|
94
|
+
: {
|
|
95
|
+
busy: ariaBusy ?? normalizedState.busy,
|
|
96
|
+
checked: ariaChecked ?? normalizedState.checked,
|
|
97
|
+
disabled: ariaDisabled ?? normalizedState.disabled,
|
|
98
|
+
expanded: ariaExpanded ?? normalizedState.expanded,
|
|
99
|
+
selected: ariaSelected ?? normalizedState.selected,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// For the accessible prop, if not provided, default to `true`
|
|
104
|
+
const normalizedAccessible = accessible == null ? true : accessible;
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
...restProperties,
|
|
108
|
+
accessibilityLabel: normalizedLabel,
|
|
109
|
+
accessibilityState: normalizedState,
|
|
110
|
+
accessible: normalizedAccessible,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
58
114
|
export * from './types';
|