elit 3.5.6 → 3.5.7
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/Cargo.toml +1 -1
- package/README.md +1 -1
- package/desktop/build.rs +83 -0
- package/desktop/icon.rs +106 -0
- package/desktop/lib.rs +2 -0
- package/desktop/main.rs +235 -0
- package/desktop/native_main.rs +128 -0
- package/desktop/native_renderer/action_widgets.rs +184 -0
- package/desktop/native_renderer/app_models.rs +171 -0
- package/desktop/native_renderer/app_runtime.rs +140 -0
- package/desktop/native_renderer/container_rendering.rs +610 -0
- package/desktop/native_renderer/content_widgets.rs +634 -0
- package/desktop/native_renderer/css_models.rs +371 -0
- package/desktop/native_renderer/embedded_surfaces.rs +414 -0
- package/desktop/native_renderer/form_controls.rs +516 -0
- package/desktop/native_renderer/interaction_dispatch.rs +89 -0
- package/desktop/native_renderer/runtime_support.rs +135 -0
- package/desktop/native_renderer/utilities.rs +495 -0
- package/desktop/native_renderer/vector_drawing.rs +491 -0
- package/desktop/native_renderer.rs +4122 -0
- package/desktop/runtime/external.rs +422 -0
- package/desktop/runtime/mod.rs +67 -0
- package/desktop/runtime/quickjs.rs +106 -0
- package/desktop/window.rs +383 -0
- package/package.json +6 -3
- package/dist/build.d.mts +0 -20
- package/dist/chokidar.d.mts +0 -134
- package/dist/cli.d.mts +0 -81
- package/dist/config.d.mts +0 -254
- package/dist/coverage.d.mts +0 -85
- package/dist/database.d.mts +0 -52
- package/dist/desktop.d.mts +0 -68
- package/dist/dom.d.mts +0 -87
- package/dist/el.d.mts +0 -208
- package/dist/fs.d.mts +0 -255
- package/dist/hmr.d.mts +0 -38
- package/dist/http.d.mts +0 -169
- package/dist/https.d.mts +0 -108
- package/dist/index.d.mts +0 -13
- package/dist/mime-types.d.mts +0 -48
- package/dist/native.d.mts +0 -136
- package/dist/path.d.mts +0 -163
- package/dist/router.d.mts +0 -49
- package/dist/runtime.d.mts +0 -97
- package/dist/server-D0Dp4R5z.d.mts +0 -449
- package/dist/server.d.mts +0 -7
- package/dist/state.d.mts +0 -117
- package/dist/style.d.mts +0 -232
- package/dist/test-reporter.d.mts +0 -77
- package/dist/test-runtime.d.mts +0 -122
- package/dist/test.d.mts +0 -39
- package/dist/types.d.mts +0 -586
- package/dist/universal.d.mts +0 -21
- package/dist/ws.d.mts +0 -200
- package/dist/wss.d.mts +0 -108
- package/src/build.ts +0 -362
- package/src/chokidar.ts +0 -427
- package/src/cli.ts +0 -1162
- package/src/config.ts +0 -509
- package/src/coverage.ts +0 -1479
- package/src/database.ts +0 -1410
- package/src/desktop-auto-render.ts +0 -317
- package/src/desktop-cli.ts +0 -1533
- package/src/desktop.ts +0 -99
- package/src/dev-build.ts +0 -340
- package/src/dom.ts +0 -901
- package/src/el.ts +0 -183
- package/src/fs.ts +0 -609
- package/src/hmr.ts +0 -149
- package/src/http.ts +0 -856
- package/src/https.ts +0 -411
- package/src/index.ts +0 -16
- package/src/mime-types.ts +0 -222
- package/src/mobile-cli.ts +0 -2313
- package/src/native-background.ts +0 -444
- package/src/native-border.ts +0 -343
- package/src/native-canvas.ts +0 -260
- package/src/native-cli.ts +0 -414
- package/src/native-color.ts +0 -904
- package/src/native-estimation.ts +0 -194
- package/src/native-grid.ts +0 -590
- package/src/native-interaction.ts +0 -1289
- package/src/native-layout.ts +0 -568
- package/src/native-link.ts +0 -76
- package/src/native-render-support.ts +0 -361
- package/src/native-spacing.ts +0 -231
- package/src/native-state.ts +0 -318
- package/src/native-strings.ts +0 -46
- package/src/native-transform.ts +0 -120
- package/src/native-types.ts +0 -439
- package/src/native-typography.ts +0 -254
- package/src/native-units.ts +0 -441
- package/src/native-vector.ts +0 -910
- package/src/native.ts +0 -5606
- package/src/path.ts +0 -493
- package/src/pm-cli.ts +0 -2498
- package/src/preview-build.ts +0 -294
- package/src/render-context.ts +0 -138
- package/src/router.ts +0 -260
- package/src/runtime.ts +0 -97
- package/src/server.ts +0 -2294
- package/src/state.ts +0 -556
- package/src/style.ts +0 -1790
- package/src/test-globals.d.ts +0 -184
- package/src/test-reporter.ts +0 -609
- package/src/test-runtime.ts +0 -1359
- package/src/test.ts +0 -368
- package/src/types.ts +0 -381
- package/src/universal.ts +0 -81
- package/src/wapk-cli.ts +0 -3213
- package/src/workspace-package.ts +0 -102
- package/src/ws.ts +0 -648
- package/src/wss.ts +0 -241
|
@@ -1,1289 +0,0 @@
|
|
|
1
|
-
import { type NativeStyleResolveOptions } from './style';
|
|
2
|
-
import { getNativeStyleResolveOptions, parsePlainNumericValue } from './native-units';
|
|
3
|
-
import { parseCssColor, toComposeColorLiteral } from './native-color';
|
|
4
|
-
import { isExternalDestination, resolveNativeLinkHint } from './native-link';
|
|
5
|
-
import { flattenTextContent, quoteKotlinString, quoteSwiftString } from './native-strings';
|
|
6
|
-
import { prependComposeModifierCall } from './native-canvas';
|
|
7
|
-
import { buildComposeTextStyleLiteralFromStyle } from './native-typography';
|
|
8
|
-
import type {
|
|
9
|
-
NativeBindingReference,
|
|
10
|
-
NativeControlEventExpressionOptions,
|
|
11
|
-
NativeElementNode,
|
|
12
|
-
NativeNode,
|
|
13
|
-
NativePickerOption,
|
|
14
|
-
NativePropValue,
|
|
15
|
-
} from './native-types';
|
|
16
|
-
|
|
17
|
-
const NATIVE_PATTERN_MAX_LENGTH = 500;
|
|
18
|
-
const REDOS_NESTED_QUANTIFIER = /\([^()]*[+*][^()]*\)[+*]/;
|
|
19
|
-
const IMAGE_FALLBACK_STOP_WORDS = new Set([
|
|
20
|
-
'image',
|
|
21
|
-
'icon',
|
|
22
|
-
'public',
|
|
23
|
-
'assets',
|
|
24
|
-
'asset',
|
|
25
|
-
'favicon',
|
|
26
|
-
'svg',
|
|
27
|
-
'png',
|
|
28
|
-
'jpg',
|
|
29
|
-
'jpeg',
|
|
30
|
-
'webp',
|
|
31
|
-
]);
|
|
32
|
-
|
|
33
|
-
function resolveNativeInputTypeValue(sourceTag: string, props: Record<string, NativePropValue>): string | undefined {
|
|
34
|
-
if (sourceTag === 'textarea') {
|
|
35
|
-
return 'textarea';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (sourceTag !== 'input') {
|
|
39
|
-
return undefined;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return typeof props.type === 'string' && props.type.trim()
|
|
43
|
-
? props.type.trim().toLowerCase()
|
|
44
|
-
: 'text';
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function isCheckboxInput(sourceTag: string, props: Record<string, NativePropValue>): boolean {
|
|
48
|
-
return resolveNativeInputTypeValue(sourceTag, props) === 'checkbox';
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function isRangeInput(sourceTag: string, props: Record<string, NativePropValue>): boolean {
|
|
52
|
-
return resolveNativeInputTypeValue(sourceTag, props) === 'range';
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function toNativeBoolean(value: NativePropValue | undefined): boolean {
|
|
56
|
-
if (typeof value === 'boolean') return value;
|
|
57
|
-
if (typeof value === 'number') return value !== 0;
|
|
58
|
-
if (typeof value === 'string') {
|
|
59
|
-
const normalized = value.trim().toLowerCase();
|
|
60
|
-
return normalized === 'true' || normalized === '1' || normalized === 'on' || normalized === 'yes';
|
|
61
|
-
}
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function buildComposeButtonModifier(
|
|
66
|
-
modifier: string,
|
|
67
|
-
onClickExpression?: string,
|
|
68
|
-
enabled = true,
|
|
69
|
-
interactionSourceName?: string,
|
|
70
|
-
): string {
|
|
71
|
-
if (!onClickExpression || !enabled) {
|
|
72
|
-
return modifier;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return prependComposeModifierCall(
|
|
76
|
-
modifier,
|
|
77
|
-
interactionSourceName
|
|
78
|
-
? `clickable(interactionSource = ${interactionSourceName}, indication = LocalIndication.current) { ${onClickExpression} }`
|
|
79
|
-
: `clickable { ${onClickExpression} }`,
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function buildComposeTextInputArgsFromStyle(
|
|
84
|
-
node: NativeElementNode,
|
|
85
|
-
style: Record<string, NativePropValue> | undefined,
|
|
86
|
-
submitActionExpression?: string,
|
|
87
|
-
styleResolveOptions: NativeStyleResolveOptions = getNativeStyleResolveOptions('generic'),
|
|
88
|
-
): string[] {
|
|
89
|
-
const args: string[] = [];
|
|
90
|
-
if (style) {
|
|
91
|
-
const textStyle = buildComposeTextStyleLiteralFromStyle(style, styleResolveOptions);
|
|
92
|
-
if (textStyle) {
|
|
93
|
-
args.push(`textStyle = ${textStyle}`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const color = parseCssColor(style.color);
|
|
97
|
-
if (color) {
|
|
98
|
-
args.push(`cursorBrush = SolidColor(${toComposeColorLiteral(color)})`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (isNativeDisabled(node)) {
|
|
103
|
-
args.push('enabled = false');
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (isNativeReadOnly(node)) {
|
|
107
|
-
args.push('readOnly = true');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const keyboardType = resolveComposeKeyboardType(node);
|
|
111
|
-
if (keyboardType || submitActionExpression) {
|
|
112
|
-
const keyboardArgs: string[] = [];
|
|
113
|
-
if (keyboardType) {
|
|
114
|
-
keyboardArgs.push(`keyboardType = ${keyboardType}`);
|
|
115
|
-
}
|
|
116
|
-
if (submitActionExpression) {
|
|
117
|
-
keyboardArgs.push('imeAction = androidx.compose.ui.text.input.ImeAction.Done');
|
|
118
|
-
}
|
|
119
|
-
args.push(`keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(${keyboardArgs.join(', ')})`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (submitActionExpression) {
|
|
123
|
-
args.push(`keyboardActions = androidx.compose.foundation.text.KeyboardActions(onDone = { ${submitActionExpression} })`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (resolveNativeTextInputType(node) === 'password') {
|
|
127
|
-
args.push('visualTransformation = androidx.compose.ui.text.input.PasswordVisualTransformation()');
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
args.push(`singleLine = ${node.sourceTag === 'textarea' ? 'false' : 'true'}`);
|
|
131
|
-
if (node.sourceTag === 'textarea') {
|
|
132
|
-
args.push('minLines = 4');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return args;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function buildSwiftUIButtonModifiersFromStyle(
|
|
139
|
-
node: NativeElementNode,
|
|
140
|
-
modifiers: string[],
|
|
141
|
-
style: Record<string, NativePropValue> | undefined,
|
|
142
|
-
): string[] {
|
|
143
|
-
const interactiveModifiers = [
|
|
144
|
-
...(node.sourceTag === 'button' && isNativeDisabled(node) ? ['.disabled(true)'] : []),
|
|
145
|
-
...modifiers,
|
|
146
|
-
];
|
|
147
|
-
return style ? ['.buttonStyle(.plain)', ...interactiveModifiers] : interactiveModifiers;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function isNativeDisabled(node: NativeElementNode): boolean {
|
|
151
|
-
return toNativeBoolean(node.props.disabled) || toNativeBoolean(node.props['aria-disabled']);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function isNativeFormControl(node: NativeElementNode): boolean {
|
|
155
|
-
return node.component === 'TextInput' || node.component === 'Toggle' || node.component === 'Picker' || node.component === 'Slider';
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export function isNativeEnabled(node: NativeElementNode): boolean {
|
|
159
|
-
return (node.component === 'Button' || isNativeFormControl(node)) && !isNativeDisabled(node);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function isNativeChecked(node: NativeElementNode): boolean {
|
|
163
|
-
return toNativeBoolean(node.props.checked) || toNativeBoolean(node.props['aria-checked']);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function isNativeSelected(node: NativeElementNode): boolean {
|
|
167
|
-
return toNativeBoolean(node.props.selected)
|
|
168
|
-
|| toNativeBoolean(node.props['aria-selected'])
|
|
169
|
-
|| typeof node.props['aria-current'] === 'string';
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export function hasNativePressedAccessibilityState(node: NativeElementNode): boolean {
|
|
173
|
-
return node.props['aria-pressed'] !== undefined;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export function isNativePressed(node: NativeElementNode): boolean {
|
|
177
|
-
return toNativeBoolean(node.props['aria-pressed'])
|
|
178
|
-
|| toNativeBoolean(node.props.pressed)
|
|
179
|
-
|| toNativeBoolean(node.props.active);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function isNativeActive(node: NativeElementNode): boolean {
|
|
183
|
-
return !isNativeDisabled(node) && isNativePressed(node);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export function isNativeRequired(node: NativeElementNode): boolean {
|
|
187
|
-
return toNativeBoolean(node.props.required) || toNativeBoolean(node.props['aria-required']);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export function isNativeMultiple(node: NativeElementNode): boolean {
|
|
191
|
-
return node.component === 'Picker' && toNativeBoolean(node.props.multiple);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function canNativeParticipateInValidation(node: NativeElementNode): boolean {
|
|
195
|
-
return isNativeFormControl(node) && !isNativeDisabled(node);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function resolveNativeTextInputValue(node: NativeElementNode): string {
|
|
199
|
-
return typeof node.props.value === 'string' || typeof node.props.value === 'number'
|
|
200
|
-
? String(node.props.value)
|
|
201
|
-
: '';
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function parseNativeNonNegativeIntegerConstraint(value: NativePropValue | undefined): number | undefined {
|
|
205
|
-
const parsed = parsePlainNumericValue(value);
|
|
206
|
-
return parsed !== undefined && Number.isInteger(parsed) && parsed >= 0
|
|
207
|
-
? parsed
|
|
208
|
-
: undefined;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function resolveNativeTextInputMinLength(node: NativeElementNode): number | undefined {
|
|
212
|
-
return parseNativeNonNegativeIntegerConstraint(node.props.minLength ?? node.props.minlength);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function resolveNativeTextInputMaxLength(node: NativeElementNode): number | undefined {
|
|
216
|
-
return parseNativeNonNegativeIntegerConstraint(node.props.maxLength ?? node.props.maxlength);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function resolveNativePatternExpression(node: NativeElementNode): RegExp | undefined {
|
|
220
|
-
if (node.component !== 'TextInput' || typeof node.props.pattern !== 'string' || !node.props.pattern.trim()) {
|
|
221
|
-
return undefined;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const pattern = node.props.pattern.trim();
|
|
225
|
-
if (pattern.length > NATIVE_PATTERN_MAX_LENGTH || REDOS_NESTED_QUANTIFIER.test(pattern)) {
|
|
226
|
-
return undefined;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
try {
|
|
230
|
-
return new RegExp(`^(?:${pattern})$`);
|
|
231
|
-
} catch {
|
|
232
|
-
return undefined;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function resolveNativeNumericConstraint(value: NativePropValue | undefined): number | undefined {
|
|
237
|
-
return parsePlainNumericValue(value);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export function resolveNativeStepConstraint(node: NativeElementNode): number | undefined {
|
|
241
|
-
if (node.props.step === undefined) {
|
|
242
|
-
return undefined;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (typeof node.props.step === 'string' && node.props.step.trim().toLowerCase() === 'any') {
|
|
246
|
-
return undefined;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const parsed = resolveNativeNumericConstraint(node.props.step);
|
|
250
|
-
return parsed !== undefined && parsed > 0 ? parsed : undefined;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export function resolveNativeTextInputType(node: NativeElementNode): 'text' | 'password' | 'email' | 'number' | 'tel' | 'url' | 'search' | 'textarea' {
|
|
254
|
-
const inputType = resolveNativeInputTypeValue(node.sourceTag, node.props);
|
|
255
|
-
|
|
256
|
-
switch (inputType) {
|
|
257
|
-
case 'password':
|
|
258
|
-
case 'email':
|
|
259
|
-
case 'number':
|
|
260
|
-
case 'tel':
|
|
261
|
-
case 'url':
|
|
262
|
-
case 'search':
|
|
263
|
-
return inputType;
|
|
264
|
-
default:
|
|
265
|
-
return 'text';
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function supportsNativePatternValidation(node: NativeElementNode): boolean {
|
|
270
|
-
switch (resolveNativeTextInputType(node)) {
|
|
271
|
-
case 'text':
|
|
272
|
-
case 'password':
|
|
273
|
-
case 'email':
|
|
274
|
-
case 'tel':
|
|
275
|
-
case 'url':
|
|
276
|
-
case 'search':
|
|
277
|
-
return true;
|
|
278
|
-
default:
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function isNativeEmailValue(value: string): boolean {
|
|
284
|
-
return /^[^\s@]+@[^\s@.]+(?:\.[^\s@.]+)+$/.test(value);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function isNativeUrlValue(value: string): boolean {
|
|
288
|
-
try {
|
|
289
|
-
const parsed = new URL(value);
|
|
290
|
-
return Boolean(parsed.protocol && parsed.hostname);
|
|
291
|
-
} catch {
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export function hasNativeValidationConstraint(node: NativeElementNode): boolean {
|
|
297
|
-
if (node.props['aria-invalid'] !== undefined) {
|
|
298
|
-
return true;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (node.component === 'TextInput') {
|
|
302
|
-
return isNativeRequired(node)
|
|
303
|
-
|| resolveNativeTextInputMinLength(node) !== undefined
|
|
304
|
-
|| resolveNativeTextInputMaxLength(node) !== undefined
|
|
305
|
-
|| resolveNativePatternExpression(node) !== undefined
|
|
306
|
-
|| resolveNativeNumericConstraint(node.props.min) !== undefined
|
|
307
|
-
|| resolveNativeNumericConstraint(node.props.max) !== undefined
|
|
308
|
-
|| resolveNativeStepConstraint(node) !== undefined
|
|
309
|
-
|| resolveNativeTextInputType(node) === 'email'
|
|
310
|
-
|| resolveNativeTextInputType(node) === 'url'
|
|
311
|
-
|| resolveNativeTextInputType(node) === 'number';
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return isNativeRequired(node);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function isNativeTextInputConstraintInvalid(node: NativeElementNode): boolean {
|
|
318
|
-
const value = resolveNativeTextInputValue(node);
|
|
319
|
-
const trimmedValue = value.trim();
|
|
320
|
-
|
|
321
|
-
if (isNativeRequired(node) && trimmedValue.length === 0) {
|
|
322
|
-
return true;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (trimmedValue.length === 0) {
|
|
326
|
-
return false;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const inputType = resolveNativeTextInputType(node);
|
|
330
|
-
if (inputType === 'email' && !isNativeEmailValue(trimmedValue)) {
|
|
331
|
-
return true;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (inputType === 'url' && !isNativeUrlValue(trimmedValue)) {
|
|
335
|
-
return true;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const minLength = resolveNativeTextInputMinLength(node);
|
|
339
|
-
if (minLength !== undefined && value.length < minLength) {
|
|
340
|
-
return true;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const maxLength = resolveNativeTextInputMaxLength(node);
|
|
344
|
-
if (maxLength !== undefined && value.length > maxLength) {
|
|
345
|
-
return true;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const patternExpression = supportsNativePatternValidation(node)
|
|
349
|
-
? resolveNativePatternExpression(node)
|
|
350
|
-
: undefined;
|
|
351
|
-
if (patternExpression && !patternExpression.test(value)) {
|
|
352
|
-
return true;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (inputType === 'number') {
|
|
356
|
-
const numericValue = Number(trimmedValue);
|
|
357
|
-
if (!Number.isFinite(numericValue)) {
|
|
358
|
-
return true;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const min = resolveNativeNumericConstraint(node.props.min);
|
|
362
|
-
if (min !== undefined && numericValue < min) {
|
|
363
|
-
return true;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const max = resolveNativeNumericConstraint(node.props.max);
|
|
367
|
-
if (max !== undefined && numericValue > max) {
|
|
368
|
-
return true;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const step = resolveNativeStepConstraint(node);
|
|
372
|
-
if (step !== undefined) {
|
|
373
|
-
const stepBase = resolveNativeNumericConstraint(node.props.min) ?? 0;
|
|
374
|
-
const steps = (numericValue - stepBase) / step;
|
|
375
|
-
if (Math.abs(steps - Math.round(steps)) > 1e-7) {
|
|
376
|
-
return true;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
export function isNativePlaceholderShown(node: NativeElementNode): boolean {
|
|
385
|
-
return node.component === 'TextInput'
|
|
386
|
-
&& typeof node.props.placeholder === 'string'
|
|
387
|
-
&& node.props.placeholder.length > 0
|
|
388
|
-
&& resolveNativeTextInputValue(node).length === 0;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
export function isNativeReadOnlyState(node: NativeElementNode): boolean {
|
|
392
|
-
return node.component === 'TextInput' && (isNativeReadOnly(node) || isNativeDisabled(node));
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
export function isNativeReadWrite(node: NativeElementNode): boolean {
|
|
396
|
-
return node.component === 'TextInput' && !isNativeReadOnlyState(node);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
export function isNativeElementEmpty(node: NativeElementNode): boolean {
|
|
400
|
-
return node.children.every((child) => child.kind === 'text' && child.value.length === 0);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
export function isNativeFocusWithin(node: NativeElementNode): boolean {
|
|
404
|
-
if (isNativePseudoFocused(node)) {
|
|
405
|
-
return true;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return node.children.some((child) => child.kind === 'element' && isNativeFocusWithin(child));
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function isNativeAriaInvalid(node: NativeElementNode): boolean {
|
|
412
|
-
const value = node.props['aria-invalid'];
|
|
413
|
-
if (value === undefined || value === null || value === false) {
|
|
414
|
-
return false;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (typeof value === 'string') {
|
|
418
|
-
const normalized = value.trim().toLowerCase();
|
|
419
|
-
return normalized.length > 0 && normalized !== 'false';
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
return toNativeBoolean(value);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
export function isNativeInvalid(node: NativeElementNode): boolean {
|
|
426
|
-
if (isNativeAriaInvalid(node)) {
|
|
427
|
-
return true;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if (!isNativeFormControl(node)) {
|
|
431
|
-
return false;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (!canNativeParticipateInValidation(node)) {
|
|
435
|
-
return false;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (node.component === 'TextInput') {
|
|
439
|
-
return isNativeTextInputConstraintInvalid(node);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (!isNativeRequired(node)) {
|
|
443
|
-
return false;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if (node.component === 'Toggle') {
|
|
447
|
-
return !isNativeChecked(node);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (node.component === 'Picker') {
|
|
451
|
-
const options = resolveNativePickerOptions(node);
|
|
452
|
-
return isNativeMultiple(node)
|
|
453
|
-
? resolveNativePickerInitialSelections(node, options).length === 0
|
|
454
|
-
: resolveNativePickerInitialSelection(node, options).trim().length === 0;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
return false;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
export function isNativeValid(node: NativeElementNode): boolean {
|
|
461
|
-
return canNativeParticipateInValidation(node) && !isNativeInvalid(node);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
export function isNativeOptional(node: NativeElementNode): boolean {
|
|
465
|
-
return isNativeFormControl(node) && !isNativeRequired(node);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
export function isNativeReadOnly(node: NativeElementNode): boolean {
|
|
469
|
-
return toNativeBoolean(node.props.readOnly) || toNativeBoolean(node.props.readonly);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
function parseNativeTabIndex(node: NativeElementNode): number | undefined {
|
|
473
|
-
const rawValue = node.props.tabIndex ?? node.props.tabindex;
|
|
474
|
-
if (typeof rawValue === 'number' && Number.isInteger(rawValue)) {
|
|
475
|
-
return rawValue;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
if (typeof rawValue === 'string' && /^-?\d+$/.test(rawValue.trim())) {
|
|
479
|
-
return Number(rawValue.trim());
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
return undefined;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
function hasNativeExplicitFocusSignal(node: NativeElementNode): boolean {
|
|
486
|
-
return toNativeBoolean(node.props.autoFocus)
|
|
487
|
-
|| toNativeBoolean(node.props.autofocus)
|
|
488
|
-
|| toNativeBoolean(node.props.focused)
|
|
489
|
-
|| toNativeBoolean(node.props['aria-focused']);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
function isNativeFocusableRole(node: NativeElementNode): boolean {
|
|
493
|
-
const role = typeof node.props.role === 'string'
|
|
494
|
-
? node.props.role.trim().toLowerCase()
|
|
495
|
-
: undefined;
|
|
496
|
-
|
|
497
|
-
return role === 'button'
|
|
498
|
-
|| role === 'link'
|
|
499
|
-
|| role === 'checkbox'
|
|
500
|
-
|| role === 'switch'
|
|
501
|
-
|| role === 'tab'
|
|
502
|
-
|| role === 'textbox'
|
|
503
|
-
|| role === 'combobox';
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function isNativeFocusableElement(node: NativeElementNode): boolean {
|
|
507
|
-
if (isNativeDisabled(node)) {
|
|
508
|
-
return false;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
const tabIndex = parseNativeTabIndex(node);
|
|
512
|
-
if (tabIndex !== undefined) {
|
|
513
|
-
return tabIndex >= 0;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
if (toNativeBoolean(node.props.contentEditable) || toNativeBoolean(node.props.contenteditable)) {
|
|
517
|
-
return true;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
if (node.component === 'TextInput' || node.component === 'Button' || node.component === 'Link' || node.component === 'Toggle' || node.component === 'Picker' || node.component === 'Slider') {
|
|
521
|
-
return true;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
return isNativeFocusableRole(node);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
export function isNativePseudoFocused(node: NativeElementNode): boolean {
|
|
528
|
-
return hasNativeExplicitFocusSignal(node) && isNativeFocusableElement(node);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
export function shouldNativeAutoFocus(node: NativeElementNode): boolean {
|
|
532
|
-
return node.component === 'TextInput' && hasNativeExplicitFocusSignal(node) && !isNativeDisabled(node);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
export function isNativeMuted(node: NativeElementNode): boolean {
|
|
536
|
-
return toNativeBoolean(node.props.muted);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
export function shouldNativeShowVideoControls(node: NativeElementNode): boolean {
|
|
540
|
-
return node.sourceTag === 'video' && toNativeBoolean(node.props.controls);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
export function resolveNativeVideoPoster(node: NativeElementNode): string | undefined {
|
|
544
|
-
return node.sourceTag === 'video' && typeof node.props.poster === 'string' && node.props.poster.trim()
|
|
545
|
-
? node.props.poster.trim()
|
|
546
|
-
: undefined;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
export function shouldNativePlayInline(node: NativeElementNode): boolean {
|
|
550
|
-
return node.sourceTag === 'video' && (
|
|
551
|
-
toNativeBoolean(node.props.playsInline)
|
|
552
|
-
|| toNativeBoolean(node.props.playsinline)
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
export function resolveNativeExplicitAccessibilityLabel(node: NativeElementNode): string | undefined {
|
|
557
|
-
const explicitLabel = typeof node.props['aria-label'] === 'string' && node.props['aria-label'].trim()
|
|
558
|
-
? node.props['aria-label'].trim()
|
|
559
|
-
: typeof node.props.title === 'string' && node.props.title.trim()
|
|
560
|
-
? node.props.title.trim()
|
|
561
|
-
: undefined;
|
|
562
|
-
|
|
563
|
-
if (explicitLabel) {
|
|
564
|
-
return explicitLabel;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
if (typeof node.props.alt === 'string' && node.props.alt.trim()) {
|
|
568
|
-
return node.props.alt.trim();
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
return undefined;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
export function resolveNativeMediaLabel(node: NativeElementNode): string {
|
|
575
|
-
const explicitLabel = typeof node.props['aria-label'] === 'string' && node.props['aria-label'].trim()
|
|
576
|
-
? node.props['aria-label'].trim()
|
|
577
|
-
: typeof node.props.title === 'string' && node.props.title.trim()
|
|
578
|
-
? node.props.title.trim()
|
|
579
|
-
: undefined;
|
|
580
|
-
|
|
581
|
-
if (explicitLabel) {
|
|
582
|
-
return explicitLabel;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
const textContent = flattenTextContent(node.children).trim();
|
|
586
|
-
if (textContent) {
|
|
587
|
-
return textContent;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
return node.sourceTag === 'audio' ? 'Audio' : 'Video';
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
function tokenizeImageFallbackWords(value: string): string[] {
|
|
594
|
-
return value
|
|
595
|
-
.split(/[^a-zA-Z0-9]+/)
|
|
596
|
-
.map((token) => token.trim())
|
|
597
|
-
.filter((token) => token.length > 0)
|
|
598
|
-
.filter((token) => !/^\d+$/.test(token))
|
|
599
|
-
.filter((token) => !IMAGE_FALLBACK_STOP_WORDS.has(token.toLowerCase()));
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
export function resolveImageFallbackLabel(source: string, alt?: string): string {
|
|
603
|
-
const altTokens = alt ? tokenizeImageFallbackWords(alt) : [];
|
|
604
|
-
const sourceTokens = tokenizeImageFallbackWords(source.replace(/\.[a-z0-9]+$/i, ''));
|
|
605
|
-
const tokens = altTokens.length > 0 ? altTokens : sourceTokens;
|
|
606
|
-
|
|
607
|
-
if (tokens.length === 0) {
|
|
608
|
-
return 'IMG';
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
if (tokens.length === 1) {
|
|
612
|
-
return tokens[0]!.slice(0, 2).toUpperCase();
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
const initials = tokens
|
|
616
|
-
.slice(0, 2)
|
|
617
|
-
.map((token) => token[0]!.toUpperCase())
|
|
618
|
-
.join('');
|
|
619
|
-
|
|
620
|
-
return initials || 'IMG';
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
export function resolveNativeAccessibilityLabel(node: NativeElementNode): string | undefined {
|
|
624
|
-
const explicitLabel = resolveNativeExplicitAccessibilityLabel(node);
|
|
625
|
-
|
|
626
|
-
if (explicitLabel) {
|
|
627
|
-
return explicitLabel;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
if (node.component === 'Picker') {
|
|
631
|
-
if (typeof node.props.placeholder === 'string' && node.props.placeholder.trim()) {
|
|
632
|
-
return node.props.placeholder.trim();
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return isNativeMultiple(node) ? 'Selection list' : 'Select';
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
const textContent = flattenTextContent(node.children).trim();
|
|
639
|
-
if (textContent) {
|
|
640
|
-
return textContent;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
if (typeof node.props.placeholder === 'string' && node.props.placeholder.trim()) {
|
|
644
|
-
return node.props.placeholder.trim();
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
if (node.component === 'Media') {
|
|
648
|
-
return resolveNativeMediaLabel(node);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
if (node.component === 'WebView') {
|
|
652
|
-
return 'Web content';
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
return undefined;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
export function resolveNativeAccessibilityHint(node: NativeElementNode): string | undefined {
|
|
659
|
-
const parts: string[] = [];
|
|
660
|
-
|
|
661
|
-
if (typeof node.props['aria-description'] === 'string' && node.props['aria-description'].trim()) {
|
|
662
|
-
parts.push(node.props['aria-description'].trim());
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
const linkHint = resolveNativeLinkHint(node);
|
|
666
|
-
if (linkHint) {
|
|
667
|
-
parts.push(linkHint);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
return parts.length > 0 ? parts.join(', ') : undefined;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
export function resolveNativeAccessibilityRole(node: NativeElementNode): 'button' | 'link' | 'checkbox' | 'switch' | 'tab' | 'image' | 'heading' | undefined {
|
|
674
|
-
const explicitRole = typeof node.props.role === 'string'
|
|
675
|
-
? node.props.role.trim().toLowerCase()
|
|
676
|
-
: undefined;
|
|
677
|
-
|
|
678
|
-
switch (explicitRole) {
|
|
679
|
-
case 'button':
|
|
680
|
-
case 'link':
|
|
681
|
-
case 'checkbox':
|
|
682
|
-
case 'switch':
|
|
683
|
-
case 'tab':
|
|
684
|
-
case 'image':
|
|
685
|
-
case 'heading':
|
|
686
|
-
return explicitRole;
|
|
687
|
-
case 'img':
|
|
688
|
-
return 'image';
|
|
689
|
-
default:
|
|
690
|
-
break;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
return undefined;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
export function hasExplicitNativeAccessibilitySignal(node: NativeElementNode): boolean {
|
|
697
|
-
return Boolean(
|
|
698
|
-
resolveNativeExplicitAccessibilityLabel(node)
|
|
699
|
-
|| resolveNativeAccessibilityHint(node)
|
|
700
|
-
|| resolveNativeLinkHint(node)
|
|
701
|
-
|| (typeof node.props.role === 'string' && node.props.role.trim())
|
|
702
|
-
|| node.props['aria-selected'] !== undefined
|
|
703
|
-
|| node.props['aria-checked'] !== undefined
|
|
704
|
-
|| node.props['aria-pressed'] !== undefined
|
|
705
|
-
|| node.props['aria-disabled'] !== undefined
|
|
706
|
-
|| node.props['aria-expanded'] !== undefined
|
|
707
|
-
|| node.props['aria-invalid'] !== undefined
|
|
708
|
-
|| node.props['aria-current'] !== undefined
|
|
709
|
-
|| node.props['aria-valuetext'] !== undefined
|
|
710
|
-
|| node.props['aria-required'] !== undefined
|
|
711
|
-
|| toNativeBoolean(node.props.required)
|
|
712
|
-
|| isNativeMultiple(node)
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
export function shouldEmitNativeAccessibilityLabel(node: NativeElementNode): boolean {
|
|
717
|
-
return hasExplicitNativeAccessibilitySignal(node);
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
export function resolveNativeAccessibilityStateParts(node: NativeElementNode): string[] {
|
|
721
|
-
const parts: string[] = [];
|
|
722
|
-
const role = resolveNativeAccessibilityRole(node);
|
|
723
|
-
const hasSelectedState = node.props['aria-selected'] !== undefined || typeof node.props['aria-current'] === 'string' || role === 'tab';
|
|
724
|
-
const hasCheckedState = node.component === 'Toggle' || node.props['aria-checked'] !== undefined || role === 'checkbox' || role === 'switch';
|
|
725
|
-
const hasPressedState = hasNativePressedAccessibilityState(node);
|
|
726
|
-
|
|
727
|
-
if (isNativeRequired(node)) {
|
|
728
|
-
parts.push('Required');
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
if (isNativeInvalid(node)) {
|
|
732
|
-
parts.push('Invalid');
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
if (!isNativeInvalid(node) && hasNativeValidationConstraint(node) && isNativeValid(node)) {
|
|
736
|
-
parts.push('Valid');
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
if (isNativeDisabled(node)) {
|
|
740
|
-
parts.push('Disabled');
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
if (hasSelectedState) {
|
|
744
|
-
parts.push(isNativeSelected(node) ? 'Selected' : 'Not selected');
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
if (hasCheckedState) {
|
|
748
|
-
parts.push(isNativeChecked(node) ? 'Checked' : 'Unchecked');
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
if (hasPressedState) {
|
|
752
|
-
parts.push(isNativePressed(node) ? 'Pressed' : 'Not pressed');
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
if (node.props['aria-expanded'] !== undefined) {
|
|
756
|
-
parts.push(toNativeBoolean(node.props['aria-expanded']) ? 'Expanded' : 'Collapsed');
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
if (typeof node.props['aria-valuetext'] === 'string' && node.props['aria-valuetext'].trim()) {
|
|
760
|
-
parts.push(node.props['aria-valuetext'].trim());
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
return [...new Set(parts)];
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
function resolveComposeAccessibilityRoleExpression(node: NativeElementNode): string | undefined {
|
|
767
|
-
switch (resolveNativeAccessibilityRole(node)) {
|
|
768
|
-
case 'button':
|
|
769
|
-
case 'link':
|
|
770
|
-
return 'Role.Button';
|
|
771
|
-
case 'checkbox':
|
|
772
|
-
return 'Role.Checkbox';
|
|
773
|
-
case 'switch':
|
|
774
|
-
return 'Role.Switch';
|
|
775
|
-
case 'tab':
|
|
776
|
-
return 'Role.Tab';
|
|
777
|
-
case 'image':
|
|
778
|
-
return 'Role.Image';
|
|
779
|
-
default:
|
|
780
|
-
return undefined;
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
export function buildComposeAccessibilityModifier(node: NativeElementNode): string | undefined {
|
|
785
|
-
if (!hasExplicitNativeAccessibilitySignal(node)) {
|
|
786
|
-
return undefined;
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
const statements: string[] = [];
|
|
790
|
-
const label = shouldEmitNativeAccessibilityLabel(node) ? resolveNativeAccessibilityLabel(node) : undefined;
|
|
791
|
-
const hint = resolveNativeAccessibilityHint(node);
|
|
792
|
-
const stateParts = resolveNativeAccessibilityStateParts(node);
|
|
793
|
-
const stateDescription = [hint, ...stateParts].filter((value): value is string => Boolean(value)).join(', ');
|
|
794
|
-
const roleExpression = resolveComposeAccessibilityRoleExpression(node);
|
|
795
|
-
const role = resolveNativeAccessibilityRole(node);
|
|
796
|
-
|
|
797
|
-
if (label) {
|
|
798
|
-
statements.push(`contentDescription = ${quoteKotlinString(label)}`);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
if (roleExpression) {
|
|
802
|
-
statements.push(`role = ${roleExpression}`);
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
if ((node.props['aria-selected'] !== undefined || typeof node.props['aria-current'] === 'string' || role === 'tab') && isNativeSelected(node)) {
|
|
806
|
-
statements.push('selected = true');
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
if (stateDescription) {
|
|
810
|
-
statements.push(`stateDescription = ${quoteKotlinString(stateDescription)}`);
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
if (role === 'heading') {
|
|
814
|
-
statements.push('heading()');
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
if (node.props['aria-disabled'] !== undefined) {
|
|
818
|
-
statements.push('disabled()');
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
return statements.length > 0
|
|
822
|
-
? `semantics(mergeDescendants = true) { ${statements.join('; ')} }`
|
|
823
|
-
: undefined;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
export function buildSwiftAccessibilityModifiers(node: NativeElementNode): string[] {
|
|
827
|
-
if (!hasExplicitNativeAccessibilitySignal(node)) {
|
|
828
|
-
return [];
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
const modifiers: string[] = [];
|
|
832
|
-
const label = shouldEmitNativeAccessibilityLabel(node) ? resolveNativeAccessibilityLabel(node) : undefined;
|
|
833
|
-
const hint = resolveNativeAccessibilityHint(node);
|
|
834
|
-
const value = resolveNativeAccessibilityStateParts(node).join(', ');
|
|
835
|
-
const role = resolveNativeAccessibilityRole(node);
|
|
836
|
-
|
|
837
|
-
if (label) {
|
|
838
|
-
modifiers.push(`.accessibilityLabel(${quoteSwiftString(label)})`);
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
if (hint) {
|
|
842
|
-
modifiers.push(`.accessibilityHint(${quoteSwiftString(hint)})`);
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
if (value) {
|
|
846
|
-
modifiers.push(`.accessibilityValue(${quoteSwiftString(value)})`);
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
if (role === 'button') {
|
|
850
|
-
modifiers.push('.accessibilityAddTraits(.isButton)');
|
|
851
|
-
} else if (role === 'link') {
|
|
852
|
-
modifiers.push('.accessibilityAddTraits(.isLink)');
|
|
853
|
-
} else if (role === 'image') {
|
|
854
|
-
modifiers.push('.accessibilityAddTraits(.isImage)');
|
|
855
|
-
} else if (role === 'heading') {
|
|
856
|
-
modifiers.push('.accessibilityAddTraits(.isHeader)');
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
if ((node.props['aria-selected'] !== undefined || typeof node.props['aria-current'] === 'string' || role === 'tab') && isNativeSelected(node)) {
|
|
860
|
-
modifiers.push('.accessibilityAddTraits(.isSelected)');
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
return modifiers;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
export function resolveNativeRangeMin(node: NativeElementNode): number {
|
|
867
|
-
return resolveNativeNumericConstraint(node.props.min) ?? 0;
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
export function resolveNativeRangeMax(node: NativeElementNode): number {
|
|
871
|
-
const min = resolveNativeRangeMin(node);
|
|
872
|
-
const max = resolveNativeNumericConstraint(node.props.max);
|
|
873
|
-
return max !== undefined && max > min ? max : min + 100;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
export function resolveNativeRangeInitialValue(node: NativeElementNode): number {
|
|
877
|
-
const min = resolveNativeRangeMin(node);
|
|
878
|
-
const max = resolveNativeRangeMax(node);
|
|
879
|
-
const value = resolveNativeNumericConstraint(node.props.value);
|
|
880
|
-
const candidate = value !== undefined ? value : min;
|
|
881
|
-
return Math.min(max, Math.max(min, candidate));
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
export function resolveComposeSliderSteps(node: NativeElementNode): number | undefined {
|
|
885
|
-
const step = resolveNativeStepConstraint(node);
|
|
886
|
-
if (step === undefined) {
|
|
887
|
-
return undefined;
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
const intervals = (resolveNativeRangeMax(node) - resolveNativeRangeMin(node)) / step;
|
|
891
|
-
if (!Number.isFinite(intervals)) {
|
|
892
|
-
return undefined;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
const roundedIntervals = Math.round(intervals);
|
|
896
|
-
if (roundedIntervals < 1 || Math.abs(intervals - roundedIntervals) > 1e-7) {
|
|
897
|
-
return undefined;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
return Math.max(0, roundedIntervals - 1);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
export function resolveComposeKeyboardType(node: NativeElementNode): string | undefined {
|
|
904
|
-
switch (resolveNativeTextInputType(node)) {
|
|
905
|
-
case 'email':
|
|
906
|
-
return 'androidx.compose.ui.text.input.KeyboardType.Email';
|
|
907
|
-
case 'number':
|
|
908
|
-
return 'androidx.compose.ui.text.input.KeyboardType.Decimal';
|
|
909
|
-
case 'password':
|
|
910
|
-
return 'androidx.compose.ui.text.input.KeyboardType.Password';
|
|
911
|
-
case 'tel':
|
|
912
|
-
return 'androidx.compose.ui.text.input.KeyboardType.Phone';
|
|
913
|
-
case 'url':
|
|
914
|
-
return 'androidx.compose.ui.text.input.KeyboardType.Uri';
|
|
915
|
-
case 'search':
|
|
916
|
-
return 'androidx.compose.ui.text.input.KeyboardType.Text';
|
|
917
|
-
default:
|
|
918
|
-
return undefined;
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
export function resolveSwiftKeyboardTypeModifier(node: NativeElementNode): string | undefined {
|
|
923
|
-
switch (resolveNativeTextInputType(node)) {
|
|
924
|
-
case 'email':
|
|
925
|
-
return '.keyboardType(.emailAddress)';
|
|
926
|
-
case 'number':
|
|
927
|
-
return '.keyboardType(.decimalPad)';
|
|
928
|
-
case 'tel':
|
|
929
|
-
return '.keyboardType(.phonePad)';
|
|
930
|
-
case 'url':
|
|
931
|
-
return '.keyboardType(.URL)';
|
|
932
|
-
case 'search':
|
|
933
|
-
return '.keyboardType(.webSearch)';
|
|
934
|
-
default:
|
|
935
|
-
return undefined;
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
export function shouldDisableNativeTextCapitalization(node: NativeElementNode): boolean {
|
|
940
|
-
const inputType = resolveNativeTextInputType(node);
|
|
941
|
-
return inputType === 'email' || inputType === 'password' || inputType === 'url';
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
export function serializeNativePayload(value: NativePropValue | undefined): string | undefined {
|
|
945
|
-
if (value === undefined) return undefined;
|
|
946
|
-
if (typeof value === 'string') return value;
|
|
947
|
-
return JSON.stringify(value);
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
export function resolveNativeAction(node: NativeElementNode): string | undefined {
|
|
951
|
-
return typeof node.props.nativeAction === 'string' && node.props.nativeAction.trim()
|
|
952
|
-
? node.props.nativeAction
|
|
953
|
-
: undefined;
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
export function resolveNativeRoute(node: NativeElementNode): string | undefined {
|
|
957
|
-
if (typeof node.props.nativeRoute === 'string' && node.props.nativeRoute.trim()) {
|
|
958
|
-
return node.props.nativeRoute;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
const destination = typeof node.props.destination === 'string' ? node.props.destination : undefined;
|
|
962
|
-
if (destination && !isExternalDestination(destination)) {
|
|
963
|
-
return destination;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
return undefined;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
export function buildComposeBridgeInvocation(action?: string, route?: string, payloadJson?: string): string | undefined {
|
|
970
|
-
const args: string[] = [];
|
|
971
|
-
|
|
972
|
-
if (action) args.push(`action = ${quoteKotlinString(action)}`);
|
|
973
|
-
if (route) args.push(`route = ${quoteKotlinString(route)}`);
|
|
974
|
-
if (payloadJson) args.push(`payloadJson = ${quoteKotlinString(payloadJson)}`);
|
|
975
|
-
|
|
976
|
-
return args.length > 0 ? `ElitNativeBridge.dispatch(${args.join(', ')})` : undefined;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
export function buildSwiftBridgeInvocation(action?: string, route?: string, payloadJson?: string): string | undefined {
|
|
980
|
-
const args: string[] = [];
|
|
981
|
-
|
|
982
|
-
if (action) args.push(`action: ${quoteSwiftString(action)}`);
|
|
983
|
-
if (route) args.push(`route: ${quoteSwiftString(route)}`);
|
|
984
|
-
if (payloadJson) args.push(`payloadJson: ${quoteSwiftString(payloadJson)}`);
|
|
985
|
-
|
|
986
|
-
return args.length > 0 ? `ElitNativeBridge.dispatch(${args.join(', ')})` : undefined;
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
function resolveNativeControlEventInputType(node: NativeElementNode): string | undefined {
|
|
990
|
-
if (node.component === 'Picker') {
|
|
991
|
-
return isNativeMultiple(node) ? 'select-multiple' : 'select-one';
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
if (node.component === 'Toggle') {
|
|
995
|
-
return typeof node.props.type === 'string' && node.props.type.trim()
|
|
996
|
-
? node.props.type.trim().toLowerCase()
|
|
997
|
-
: 'checkbox';
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
if (node.component === 'Slider') {
|
|
1001
|
-
return 'range';
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
return resolveNativeInputTypeValue(node.sourceTag, node.props);
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
function shouldDispatchNativeControlEvent(node: NativeElementNode, eventName: 'input' | 'change' | 'submit'): boolean {
|
|
1008
|
-
if (!node.events.includes(eventName)) {
|
|
1009
|
-
return false;
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
return !(eventName === 'input' && getNativeBindingReference(node) && node.events.every((candidate) => candidate === 'input'));
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
function resolveNativeControlEventAction(node: NativeElementNode, eventName: 'input' | 'change' | 'submit'): string {
|
|
1016
|
-
return resolveNativeAction(node) ?? `elit.event.${eventName}`;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
function buildComposeControlEventPayloadInvocation(
|
|
1020
|
-
node: NativeElementNode,
|
|
1021
|
-
eventName: 'input' | 'change' | 'submit',
|
|
1022
|
-
options: NativeControlEventExpressionOptions = {},
|
|
1023
|
-
): string {
|
|
1024
|
-
const args = [
|
|
1025
|
-
`event = ${quoteKotlinString(eventName)}`,
|
|
1026
|
-
`sourceTag = ${quoteKotlinString(node.sourceTag)}`,
|
|
1027
|
-
];
|
|
1028
|
-
const inputType = resolveNativeControlEventInputType(node);
|
|
1029
|
-
const detailJson = serializeNativePayload(node.props.nativePayload);
|
|
1030
|
-
|
|
1031
|
-
if (inputType) {
|
|
1032
|
-
args.push(`inputType = ${quoteKotlinString(inputType)}`);
|
|
1033
|
-
}
|
|
1034
|
-
if (options.valueExpression) {
|
|
1035
|
-
args.push(`value = ${options.valueExpression}`);
|
|
1036
|
-
}
|
|
1037
|
-
if (options.valuesExpression) {
|
|
1038
|
-
args.push(`values = ${options.valuesExpression}`);
|
|
1039
|
-
}
|
|
1040
|
-
if (options.checkedExpression) {
|
|
1041
|
-
args.push(`checked = ${options.checkedExpression}`);
|
|
1042
|
-
}
|
|
1043
|
-
if (detailJson) {
|
|
1044
|
-
args.push(`detailJson = ${quoteKotlinString(detailJson)}`);
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
return `ElitNativeBridge.controlEventPayload(${args.join(', ')})`;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
export function buildComposeControlEventDispatchInvocation(
|
|
1051
|
-
node: NativeElementNode,
|
|
1052
|
-
eventName: 'input' | 'change' | 'submit',
|
|
1053
|
-
options: NativeControlEventExpressionOptions = {},
|
|
1054
|
-
): string {
|
|
1055
|
-
return `ElitNativeBridge.dispatch(action = ${quoteKotlinString(resolveNativeControlEventAction(node, eventName))}, payloadJson = ${buildComposeControlEventPayloadInvocation(node, eventName, options)})`;
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
export function buildComposeControlEventDispatchStatements(
|
|
1059
|
-
node: NativeElementNode,
|
|
1060
|
-
options: NativeControlEventExpressionOptions = {},
|
|
1061
|
-
): string[] {
|
|
1062
|
-
const statements: string[] = [];
|
|
1063
|
-
if (shouldDispatchNativeControlEvent(node, 'input')) {
|
|
1064
|
-
statements.push(buildComposeControlEventDispatchInvocation(node, 'input', options));
|
|
1065
|
-
}
|
|
1066
|
-
if (shouldDispatchNativeControlEvent(node, 'change')) {
|
|
1067
|
-
statements.push(buildComposeControlEventDispatchInvocation(node, 'change', options));
|
|
1068
|
-
}
|
|
1069
|
-
return statements;
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
function buildSwiftControlEventPayloadInvocation(
|
|
1073
|
-
node: NativeElementNode,
|
|
1074
|
-
eventName: 'input' | 'change' | 'submit',
|
|
1075
|
-
options: NativeControlEventExpressionOptions = {},
|
|
1076
|
-
): string {
|
|
1077
|
-
const args = [
|
|
1078
|
-
`event: ${quoteSwiftString(eventName)}`,
|
|
1079
|
-
`sourceTag: ${quoteSwiftString(node.sourceTag)}`,
|
|
1080
|
-
];
|
|
1081
|
-
const inputType = resolveNativeControlEventInputType(node);
|
|
1082
|
-
const detailJson = serializeNativePayload(node.props.nativePayload);
|
|
1083
|
-
|
|
1084
|
-
if (inputType) {
|
|
1085
|
-
args.push(`inputType: ${quoteSwiftString(inputType)}`);
|
|
1086
|
-
}
|
|
1087
|
-
if (options.valueExpression) {
|
|
1088
|
-
args.push(`value: ${options.valueExpression}`);
|
|
1089
|
-
}
|
|
1090
|
-
if (options.valuesExpression) {
|
|
1091
|
-
args.push(`values: ${options.valuesExpression}`);
|
|
1092
|
-
}
|
|
1093
|
-
if (options.checkedExpression) {
|
|
1094
|
-
args.push(`checked: ${options.checkedExpression}`);
|
|
1095
|
-
}
|
|
1096
|
-
if (detailJson) {
|
|
1097
|
-
args.push(`detailJson: ${quoteSwiftString(detailJson)}`);
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
return `ElitNativeBridge.controlEventPayload(${args.join(', ')})`;
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
export function buildSwiftControlEventDispatchInvocation(
|
|
1104
|
-
node: NativeElementNode,
|
|
1105
|
-
eventName: 'input' | 'change' | 'submit',
|
|
1106
|
-
options: NativeControlEventExpressionOptions = {},
|
|
1107
|
-
): string {
|
|
1108
|
-
return `ElitNativeBridge.dispatch(action: ${quoteSwiftString(resolveNativeControlEventAction(node, eventName))}, payloadJson: ${buildSwiftControlEventPayloadInvocation(node, eventName, options)})`;
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
export function buildSwiftControlEventDispatchStatements(
|
|
1112
|
-
node: NativeElementNode,
|
|
1113
|
-
options: NativeControlEventExpressionOptions = {},
|
|
1114
|
-
): string[] {
|
|
1115
|
-
const statements: string[] = [];
|
|
1116
|
-
if (shouldDispatchNativeControlEvent(node, 'input')) {
|
|
1117
|
-
statements.push(buildSwiftControlEventDispatchInvocation(node, 'input', options));
|
|
1118
|
-
}
|
|
1119
|
-
if (shouldDispatchNativeControlEvent(node, 'change')) {
|
|
1120
|
-
statements.push(buildSwiftControlEventDispatchInvocation(node, 'change', options));
|
|
1121
|
-
}
|
|
1122
|
-
return statements;
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
export function getNativeBindingReference(node: NativeElementNode): NativeBindingReference | undefined {
|
|
1126
|
-
const binding = node.props.nativeBinding;
|
|
1127
|
-
if (!binding || typeof binding !== 'object' || Array.isArray(binding)) {
|
|
1128
|
-
return undefined;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
const id = typeof binding.id === 'string' ? binding.id : undefined;
|
|
1132
|
-
const kind = binding.kind === 'value' || binding.kind === 'checked' ? binding.kind : undefined;
|
|
1133
|
-
const valueType = binding.valueType === 'boolean' || binding.valueType === 'number' || binding.valueType === 'string' || binding.valueType === 'string-array'
|
|
1134
|
-
? binding.valueType
|
|
1135
|
-
: undefined;
|
|
1136
|
-
|
|
1137
|
-
if (!id || !kind || !valueType) {
|
|
1138
|
-
return undefined;
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
return { id, kind, valueType };
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
function collectNativePickerOptionNodes(nodes: NativeNode[]): NativeElementNode[] {
|
|
1145
|
-
const options: NativeElementNode[] = [];
|
|
1146
|
-
|
|
1147
|
-
for (const node of nodes) {
|
|
1148
|
-
if (node.kind !== 'element') {
|
|
1149
|
-
continue;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
if (node.component === 'Option') {
|
|
1153
|
-
options.push(node);
|
|
1154
|
-
continue;
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
if (node.sourceTag === 'optgroup') {
|
|
1158
|
-
options.push(...collectNativePickerOptionNodes(node.children));
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
return options;
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
export function resolveNativePickerOptionLabel(node: NativeElementNode): string {
|
|
1166
|
-
if (typeof node.props.label === 'string' && node.props.label.trim()) {
|
|
1167
|
-
return node.props.label;
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
const textContent = flattenTextContent(node.children).trim();
|
|
1171
|
-
if (textContent) {
|
|
1172
|
-
return textContent;
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
if (typeof node.props.value === 'string' || typeof node.props.value === 'number' || typeof node.props.value === 'boolean') {
|
|
1176
|
-
return String(node.props.value);
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
return 'Option';
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
function resolveNativePickerOptionValue(node: NativeElementNode): string {
|
|
1183
|
-
if (typeof node.props.value === 'string' || typeof node.props.value === 'number' || typeof node.props.value === 'boolean') {
|
|
1184
|
-
return String(node.props.value);
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
return resolveNativePickerOptionLabel(node);
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
export function resolveNativePickerOptions(node: NativeElementNode): NativePickerOption[] {
|
|
1191
|
-
return collectNativePickerOptionNodes(node.children).map((optionNode) => ({
|
|
1192
|
-
label: resolveNativePickerOptionLabel(optionNode),
|
|
1193
|
-
value: resolveNativePickerOptionValue(optionNode),
|
|
1194
|
-
selected: isNativeSelected(optionNode),
|
|
1195
|
-
disabled: isNativeDisabled(optionNode),
|
|
1196
|
-
}));
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
export function resolveNativePickerInitialSelection(node: NativeElementNode, options: NativePickerOption[]): string {
|
|
1200
|
-
if (isNativeMultiple(node)) {
|
|
1201
|
-
return resolveNativePickerInitialSelections(node, options)[0] ?? '';
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
const explicitValue = typeof node.props.value === 'string' || typeof node.props.value === 'number' || typeof node.props.value === 'boolean'
|
|
1205
|
-
? String(node.props.value)
|
|
1206
|
-
: undefined;
|
|
1207
|
-
|
|
1208
|
-
if (explicitValue && options.some((option) => option.value === explicitValue)) {
|
|
1209
|
-
return explicitValue;
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
const selectedOption = options.find((option) => option.selected);
|
|
1213
|
-
|
|
1214
|
-
if (selectedOption) {
|
|
1215
|
-
return selectedOption.value;
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
if (isNativeRequired(node)) {
|
|
1219
|
-
return '';
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
return options[0]?.value ?? '';
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
export function resolveNativePickerInitialSelections(node: NativeElementNode, options: NativePickerOption[]): string[] {
|
|
1226
|
-
if (Array.isArray(node.props.value)) {
|
|
1227
|
-
const explicitValues = node.props.value
|
|
1228
|
-
.filter((value): value is string | number | boolean => typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')
|
|
1229
|
-
.map((value) => String(value));
|
|
1230
|
-
|
|
1231
|
-
return explicitValues.filter((value, index) => explicitValues.indexOf(value) === index && options.some((option) => option.value === value));
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
const explicitValue = typeof node.props.value === 'string' || typeof node.props.value === 'number' || typeof node.props.value === 'boolean'
|
|
1235
|
-
? String(node.props.value)
|
|
1236
|
-
: undefined;
|
|
1237
|
-
|
|
1238
|
-
if (explicitValue && options.some((option) => option.value === explicitValue)) {
|
|
1239
|
-
return [explicitValue];
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
return options
|
|
1243
|
-
.filter((option) => option.selected)
|
|
1244
|
-
.map((option) => option.value);
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
export function resolveNativePickerDisplayLabel(value: string, options: NativePickerOption[]): string {
|
|
1248
|
-
return options.find((option) => option.value === value)?.label ?? value;
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
export function buildComposePickerLabelExpression(selectionExpression: string, options: NativePickerOption[], placeholder?: string): string {
|
|
1252
|
-
const fallbackLabel = placeholder ? quoteKotlinString(placeholder) : undefined;
|
|
1253
|
-
|
|
1254
|
-
if (options.length === 0 || options.every((option) => option.value === option.label)) {
|
|
1255
|
-
return fallbackLabel
|
|
1256
|
-
? `if (${selectionExpression}.isEmpty()) ${fallbackLabel} else ${selectionExpression}`
|
|
1257
|
-
: selectionExpression;
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
const branches = options.map((option) => `${quoteKotlinString(option.value)} -> ${quoteKotlinString(option.label)}`).join('; ');
|
|
1261
|
-
return fallbackLabel
|
|
1262
|
-
? `when (${selectionExpression}) { "" -> ${fallbackLabel}; ${branches}; else -> ${selectionExpression} }`
|
|
1263
|
-
: `when (${selectionExpression}) { ${branches}; else -> ${selectionExpression} }`;
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
export function resolveNativeProgressFraction(props: Record<string, NativePropValue>): number | undefined {
|
|
1267
|
-
const value = parsePlainNumericValue(props.value);
|
|
1268
|
-
if (value === undefined) {
|
|
1269
|
-
return undefined;
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
const max = parsePlainNumericValue(props.max);
|
|
1273
|
-
const denominator = max !== undefined && max > 0 ? max : 1;
|
|
1274
|
-
return Math.max(0, Math.min(1, value / denominator));
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
export function resolveNativeSurfaceSource(node: NativeElementNode): string | undefined {
|
|
1278
|
-
const source = typeof node.props.source === 'string' && node.props.source.trim()
|
|
1279
|
-
? node.props.source.trim()
|
|
1280
|
-
: typeof node.props.src === 'string' && node.props.src.trim()
|
|
1281
|
-
? node.props.src.trim()
|
|
1282
|
-
: typeof node.props.data === 'string' && node.props.data.trim()
|
|
1283
|
-
? node.props.data.trim()
|
|
1284
|
-
: typeof node.props.destination === 'string' && node.props.destination.trim()
|
|
1285
|
-
? node.props.destination.trim()
|
|
1286
|
-
: undefined;
|
|
1287
|
-
|
|
1288
|
-
return source && source.length > 0 ? source : undefined;
|
|
1289
|
-
}
|