@appilots/sdk 0.11.0 → 0.11.1
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/dist/.build-meta.json +3 -3
- package/dist/{chunk-IZEWWAA7.js → chunk-DRXUBLGY.js} +8 -8
- package/dist/{chunk-NA5H4IHH.mjs → chunk-GSHE45YX.mjs} +263 -81
- package/dist/{chunk-55Y2AQE7.js → chunk-KZ2BLMS4.js} +3 -1
- package/dist/{chunk-3K7ORC4X.mjs → chunk-LLH7QCSX.mjs} +1 -1
- package/dist/{chunk-G6KI7KVH.mjs → chunk-PQZ64CCE.mjs} +3 -1
- package/dist/{chunk-2YPEQTL2.js → chunk-X4MPSZL3.js} +537 -355
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.js +11 -11
- package/dist/hooks/index.mjs +2 -2
- package/dist/{index-BPZ0j71S.d.mts → index-C01Spw6F.d.mts} +1 -1
- package/dist/{index-BPZ0j71S.d.ts → index-C01Spw6F.d.ts} +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +72 -72
- package/dist/index.mjs +5 -5
- package/dist/navigation/index.js +13 -13
- package/dist/navigation/index.mjs +2 -2
- package/package.json +1 -1
package/dist/.build-meta.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sourceHash": "
|
|
3
|
-
"gitSha": "
|
|
4
|
-
"builtAt": "2026-08-
|
|
2
|
+
"sourceHash": "6574aab0f4f8f43f",
|
|
3
|
+
"gitSha": "cdbfa225c28ac0cf0bf495bb8a8ec0c55d4dc1d1",
|
|
4
|
+
"builtAt": "2026-08-26T01:42:57.571Z"
|
|
5
5
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkKZ2BLMS4_js = require('./chunk-KZ2BLMS4.js');
|
|
4
4
|
var React = require('react');
|
|
5
5
|
|
|
6
6
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -15,17 +15,17 @@ function getActiveRouteName(state) {
|
|
|
15
15
|
return route.name;
|
|
16
16
|
}
|
|
17
17
|
function AppilotsNavigationContainer({ children }) {
|
|
18
|
-
const { emit } =
|
|
18
|
+
const { emit } = chunkKZ2BLMS4_js.useAppilotsContext();
|
|
19
19
|
const internalRef = React.useRef(null);
|
|
20
20
|
const handleRef = React.useCallback(
|
|
21
21
|
(instance) => {
|
|
22
22
|
internalRef.current = instance;
|
|
23
|
-
|
|
23
|
+
chunkKZ2BLMS4_js.setNavigationRef(instance);
|
|
24
24
|
const childRef = children.ref;
|
|
25
25
|
if (typeof childRef === "function") childRef(instance);
|
|
26
26
|
else if (childRef && typeof childRef === "object") childRef.current = instance;
|
|
27
27
|
if (instance) {
|
|
28
|
-
|
|
28
|
+
chunkKZ2BLMS4_js.appilotsDebugLog("NavigationContainer ref captured");
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
[children]
|
|
@@ -34,8 +34,8 @@ function AppilotsNavigationContainer({ children }) {
|
|
|
34
34
|
(state) => {
|
|
35
35
|
const routeName = getActiveRouteName(state);
|
|
36
36
|
if (routeName) {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
chunkKZ2BLMS4_js.appilotsDebugLog(`Screen changed \u2192 "${routeName}"`);
|
|
38
|
+
chunkKZ2BLMS4_js.setCurrentScreen(routeName);
|
|
39
39
|
emit({
|
|
40
40
|
type: "navigation:change",
|
|
41
41
|
timestamp: Date.now(),
|
|
@@ -50,8 +50,8 @@ function AppilotsNavigationContainer({ children }) {
|
|
|
50
50
|
const timer = setTimeout(() => {
|
|
51
51
|
const route = internalRef.current?.getCurrentRoute?.();
|
|
52
52
|
if (route?.name) {
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
chunkKZ2BLMS4_js.appilotsDebugLog(`Initial screen \u2192 "${route.name}"`);
|
|
54
|
+
chunkKZ2BLMS4_js.setCurrentScreen(route.name);
|
|
55
55
|
emit({
|
|
56
56
|
type: "navigation:change",
|
|
57
57
|
timestamp: Date.now(),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getFiberRoot, appilotsDebugWarn, recordIntrospectionFailure, getCurrentScreen, walkFiber, getActiveRouteNames, clampSnapshotToWireLimits, deriveInteractionElements, rectForNode, toWireElement, attachElementIdsToChoiceGroups, elementRegistry, appilotsDebugLog, getLastWalkDiagnostics, SDK_VERSION, geometryStats, geometryAvailability, getNameManglingProbe, emptyDetectorCounts, getAutoTrackingState, getIntrospectionDiagnostics, getDefaultRegistry, useAppilotsContext, ChatSessionMachine, getCurrentScreenSignature, ActionQueueMachine, getScreenMetadata, setCurrentScreen, useResolvedRegistry, totalDetectorHits, listRegistry, getOpenNativeDialog, componentRegistry, getLastAnsweredNativeDialog, probeLoadingState, isListItemPressTarget, waitForLoadingSettle, describeValue, waitForScreenSettle, screenDepartedBaseline, routesBelongToSameFeature, actionPressTargetId, getNavigationStateSnapshot, getNavigationRef, isOptionalStepAutomationFailure, OPTIONAL_STEP_AUTOMATION_HINT, normalize3, matchScore, FORM_VALIDATION_TEXT_RE, idLooselyMatches, parseStableElementId, shouldSkipSubtree, isAlwaysListContainer, isHeuristicListContainer, findListItems, getTypeName, foldForCompare, isModalFiber, getAllScreens, snapshotShowsOpenCreateForm,
|
|
1
|
+
import { getFiberRoot, appilotsDebugWarn, recordIntrospectionFailure, getCurrentScreen, walkFiber, getActiveRouteNames, clampSnapshotToWireLimits, deriveInteractionElements, rectForNode, toWireElement, attachElementIdsToChoiceGroups, elementRegistry, appilotsDebugLog, getLastWalkDiagnostics, SDK_VERSION, geometryStats, geometryAvailability, getNameManglingProbe, emptyDetectorCounts, getAutoTrackingState, getIntrospectionDiagnostics, getDefaultRegistry, useAppilotsContext, ChatSessionMachine, getCurrentScreenSignature, ActionQueueMachine, getScreenMetadata, setCurrentScreen, useResolvedRegistry, totalDetectorHits, listRegistry, getOpenNativeDialog, componentRegistry, getLastAnsweredNativeDialog, probeLoadingState, isListItemPressTarget, waitForLoadingSettle, describeValue, waitForScreenSettle, screenDepartedBaseline, routesBelongToSameFeature, actionPressTargetId, getNavigationStateSnapshot, getNavigationRef, isOptionalStepAutomationFailure, OPTIONAL_STEP_AUTOMATION_HINT, isSwitchLike, normalize3, matchScore, FORM_VALIDATION_TEXT_RE, idLooselyMatches, parseStableElementId, shouldSkipSubtree, isAlwaysListContainer, isHeuristicListContainer, findListItems, getTypeName, foldForCompare, isModalFiber, switchValue, getAllScreens, snapshotShowsOpenCreateForm, isReferenceable, looksDestructiveActionLabel, isGenericSelectValue, FORM_WRITE_SUCCESS_TEXT_RE, TOUCHABLE_NAMES } from './chunk-PQZ64CCE.mjs';
|
|
2
2
|
import React, { useMemo, useRef, useSyncExternalStore, useEffect, useCallback, useState } from 'react';
|
|
3
3
|
import { Alert } from 'react-native';
|
|
4
4
|
|
|
@@ -270,6 +270,39 @@ function appilotsSelfCheck() {
|
|
|
270
270
|
};
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
// src/introspection/switchFlip.ts
|
|
274
|
+
function declaredState(props) {
|
|
275
|
+
if (typeof props?.value === "boolean") return props.value;
|
|
276
|
+
const aria = props?.["aria-checked"];
|
|
277
|
+
if (typeof aria === "boolean") return aria;
|
|
278
|
+
const checked = props?.accessibilityState?.checked;
|
|
279
|
+
return typeof checked === "boolean" ? checked : void 0;
|
|
280
|
+
}
|
|
281
|
+
function isReactNativeSwitch(fiber) {
|
|
282
|
+
return getTypeName(fiber?.type) === "Switch" || getTypeName(fiber?.elementType) === "Switch";
|
|
283
|
+
}
|
|
284
|
+
var SWITCH_FLIP_HANDLERS = ["onValueChange", "onChange", "onPress"];
|
|
285
|
+
function flipSwitchFiber(fiber, next) {
|
|
286
|
+
const props = fiber?.memoizedProps ?? {};
|
|
287
|
+
const onValueChange = props.onValueChange;
|
|
288
|
+
const onChange = props.onChange;
|
|
289
|
+
const onPress = props.onPress;
|
|
290
|
+
const handler = typeof onValueChange === "function" ? { fn: onValueChange, via: "onValueChange" } : typeof onChange === "function" ? { fn: onChange, via: "onChange" } : typeof onPress === "function" ? { fn: onPress, via: "onPress" } : null;
|
|
291
|
+
if (!handler) return { ok: false, reason: "no-handler" };
|
|
292
|
+
try {
|
|
293
|
+
if (handler.via === "onPress") {
|
|
294
|
+
if (declaredState(props) === next) return { ok: true, via: "onPress", noop: true };
|
|
295
|
+
handler.fn();
|
|
296
|
+
} else if (handler.via === "onChange") {
|
|
297
|
+
if (isReactNativeSwitch(fiber)) handler.fn({ nativeEvent: { value: next }, value: next });
|
|
298
|
+
else handler.fn(next);
|
|
299
|
+
} else handler.fn(next);
|
|
300
|
+
} catch (err) {
|
|
301
|
+
return { ok: false, reason: "threw", error: err?.message ?? "switch handler threw" };
|
|
302
|
+
}
|
|
303
|
+
return { ok: true, via: handler.via };
|
|
304
|
+
}
|
|
305
|
+
|
|
273
306
|
// src/introspection/findInteractive.ts
|
|
274
307
|
function getTypeName2(type) {
|
|
275
308
|
if (!type) return null;
|
|
@@ -355,7 +388,7 @@ function shouldSkipSubtree2(fiber) {
|
|
|
355
388
|
return false;
|
|
356
389
|
}
|
|
357
390
|
var normalize = foldForCompare;
|
|
358
|
-
function
|
|
391
|
+
function scoreMatchDetail(fiber, queryNorm, queryRaw) {
|
|
359
392
|
const props = fiber.memoizedProps ?? {};
|
|
360
393
|
const candidates = [];
|
|
361
394
|
if (typeof props.testID === "string") candidates.push({ val: props.testID, weight: 100 });
|
|
@@ -378,13 +411,16 @@ function scoreMatch(fiber, queryNorm, queryRaw) {
|
|
|
378
411
|
else if (queryNorm && (vNorm.includes(queryNorm) || queryNorm.includes(vNorm)))
|
|
379
412
|
best = Math.max(best, weight - 20);
|
|
380
413
|
}
|
|
381
|
-
|
|
414
|
+
const named = best >= IDENTITY_MATCH_SCORE;
|
|
415
|
+
const disabledProp = props.disabled === true;
|
|
416
|
+
const disabledA11y = typeof props.accessibilityState === "object" && props.accessibilityState !== null && props.accessibilityState.disabled === true;
|
|
417
|
+
if (best > 0 && disabledProp) {
|
|
382
418
|
best = Math.max(1, best - 60);
|
|
383
419
|
}
|
|
384
|
-
if (best > 0 &&
|
|
420
|
+
if (best > 0 && disabledA11y) {
|
|
385
421
|
best = Math.max(1, best - 60);
|
|
386
422
|
}
|
|
387
|
-
return best;
|
|
423
|
+
return { score: best, named, disabled: disabledProp || disabledA11y };
|
|
388
424
|
}
|
|
389
425
|
function wrapTextInput(fiber) {
|
|
390
426
|
const getProps = () => fiber.memoizedProps ?? {};
|
|
@@ -421,13 +457,31 @@ function wrapSwitch(fiber) {
|
|
|
421
457
|
return {
|
|
422
458
|
kind: "toggle",
|
|
423
459
|
label: getProps().accessibilityLabel ?? getProps().testID,
|
|
424
|
-
|
|
460
|
+
// `switchValue` reads `accessibilityState.checked` when there is no
|
|
461
|
+
// `value` prop. A CUSTOM switch (a Pressable with
|
|
462
|
+
// `accessibilityRole="switch"`) has only the former, and reporting
|
|
463
|
+
// `false` for a control that is on is worse than reporting nothing:
|
|
464
|
+
// the caller inverts this to decide where to move.
|
|
465
|
+
getValue: () => switchValue(getProps()),
|
|
425
466
|
setValue: (value) => {
|
|
426
|
-
const { onValueChange, onChange } = getProps();
|
|
427
467
|
const boolVal = typeof value === "boolean" ? value : value === "true" || value === true;
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
468
|
+
const flipped = flipSwitchFiber(fiber, boolVal);
|
|
469
|
+
if (flipped.ok) return;
|
|
470
|
+
if (flipped.reason === "no-handler") {
|
|
471
|
+
throw new Error(`Switch exposes none of: ${SWITCH_FLIP_HANDLERS.join(", ")}`);
|
|
472
|
+
}
|
|
473
|
+
throw new Error(flipped.error);
|
|
474
|
+
},
|
|
475
|
+
// A custom switch is a `Pressable` underneath, so it may well declare
|
|
476
|
+
// its own long press (hold to configure, hold for a shortcut menu).
|
|
477
|
+
// Classifying it as a toggle took it out of the `kind: 'target'`
|
|
478
|
+
// lookup, and without this the long press would fall through to the
|
|
479
|
+
// best remaining candidate — the card that WRAPS it.
|
|
480
|
+
longPress: () => {
|
|
481
|
+
const { onLongPress } = getProps();
|
|
482
|
+
if (typeof onLongPress !== "function") return false;
|
|
483
|
+
onLongPress();
|
|
484
|
+
return true;
|
|
431
485
|
}
|
|
432
486
|
};
|
|
433
487
|
}
|
|
@@ -449,6 +503,19 @@ function wrapTouchable(fiber, label) {
|
|
|
449
503
|
}
|
|
450
504
|
};
|
|
451
505
|
}
|
|
506
|
+
var IDENTITY_MATCH_SCORE = 100;
|
|
507
|
+
function describeCandidate(fiber, kind) {
|
|
508
|
+
const props = fiber?.memoizedProps ?? {};
|
|
509
|
+
return props.testID ?? props.accessibilityLabel ?? props.placeholder ?? (kind === "target" ? findChildText(fiber) : void 0);
|
|
510
|
+
}
|
|
511
|
+
function isDescendantOf(fiber, ancestor) {
|
|
512
|
+
if (!fiber || !ancestor) return false;
|
|
513
|
+
let node = fiber.return;
|
|
514
|
+
for (let depth = 0; node && depth < 200; depth++, node = node.return) {
|
|
515
|
+
if (node === ancestor) return true;
|
|
516
|
+
}
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
452
519
|
function findInteractiveByIdentifier(query, options = {}) {
|
|
453
520
|
const root = getFiberRoot();
|
|
454
521
|
if (!root) {
|
|
@@ -460,6 +527,10 @@ function findInteractiveByIdentifier(query, options = {}) {
|
|
|
460
527
|
let bestScore = 0;
|
|
461
528
|
let bestHandle = null;
|
|
462
529
|
let bestId;
|
|
530
|
+
let bestFiber = null;
|
|
531
|
+
let bestNamed = false;
|
|
532
|
+
let bestDisabled = false;
|
|
533
|
+
let tied = [];
|
|
463
534
|
const visited = /* @__PURE__ */ new WeakSet();
|
|
464
535
|
const stack = [];
|
|
465
536
|
if (root.child) stack.push(root.child);
|
|
@@ -469,26 +540,52 @@ function findInteractiveByIdentifier(query, options = {}) {
|
|
|
469
540
|
visited.add(fiber);
|
|
470
541
|
if (fiber.sibling) stack.push(fiber.sibling);
|
|
471
542
|
if (shouldSkipSubtree2(fiber)) continue;
|
|
543
|
+
const props = fiber.memoizedProps ?? {};
|
|
472
544
|
let kind = null;
|
|
473
545
|
if (matchesName(fiber, "TextInput")) kind = "field";
|
|
474
|
-
else if (
|
|
546
|
+
else if (isSwitchLike(fiber, props)) kind = "toggle";
|
|
475
547
|
else if (isPressableTarget(fiber)) kind = "target";
|
|
476
548
|
if (kind && (!options.kind || options.kind === kind)) {
|
|
477
|
-
const
|
|
478
|
-
|
|
549
|
+
const detail = scoreMatchDetail(fiber, queryNorm, queryRaw);
|
|
550
|
+
const score = detail.score;
|
|
551
|
+
if (score > 0) {
|
|
552
|
+
if (score > bestScore) tied = [{ fiber, kind }];
|
|
553
|
+
else if (score === bestScore) tied.push({ fiber, kind });
|
|
554
|
+
}
|
|
555
|
+
const beatsBest = score > bestScore || score > 0 && score === bestScore && isDescendantOf(fiber, bestFiber);
|
|
556
|
+
if (beatsBest) {
|
|
479
557
|
bestScore = score;
|
|
480
|
-
|
|
481
|
-
|
|
558
|
+
bestFiber = fiber;
|
|
559
|
+
bestNamed = detail.named;
|
|
560
|
+
bestDisabled = detail.disabled;
|
|
561
|
+
bestId = describeCandidate(fiber, kind);
|
|
482
562
|
if (kind === "field") bestHandle = wrapTextInput(fiber);
|
|
483
563
|
else if (kind === "toggle") bestHandle = wrapSwitch(fiber);
|
|
484
564
|
else bestHandle = wrapTouchable(fiber, findChildText(fiber));
|
|
485
565
|
}
|
|
486
566
|
}
|
|
567
|
+
if (kind === "toggle") continue;
|
|
487
568
|
if (fiber.child) stack.push(fiber.child);
|
|
488
569
|
}
|
|
570
|
+
if (bestHandle && bestScore < (options.minScore ?? 0)) {
|
|
571
|
+
appilotsDebugLog(
|
|
572
|
+
`findInteractiveByIdentifier: query="${query}" \u2192 best match scored ${bestScore}, below the requested floor of ${options.minScore} \u2014 refusing it`
|
|
573
|
+
);
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
489
576
|
if (bestHandle) {
|
|
577
|
+
const independent = tied.filter(
|
|
578
|
+
(candidate) => !tied.some(
|
|
579
|
+
(other) => other.fiber !== candidate.fiber && isDescendantOf(other.fiber, candidate.fiber)
|
|
580
|
+
)
|
|
581
|
+
);
|
|
582
|
+
bestHandle.matchCount = independent.length;
|
|
583
|
+
bestHandle.named = bestNamed;
|
|
584
|
+
bestHandle.disabled = bestDisabled;
|
|
585
|
+
const candidates = independent.map(({ fiber, kind }) => describeCandidate(fiber, kind)).filter((id) => typeof id === "string" && id.length > 0).slice(0, 8);
|
|
586
|
+
if (candidates.length > 0) bestHandle.matchCandidates = candidates;
|
|
490
587
|
appilotsDebugLog(
|
|
491
|
-
`findInteractiveByIdentifier: query="${query}" \u2192 kind=${bestHandle.kind} label="${bestHandle.label ?? bestId}" score=${bestScore}`
|
|
588
|
+
`findInteractiveByIdentifier: query="${query}" \u2192 kind=${bestHandle.kind} label="${bestHandle.label ?? bestId}" score=${bestScore} named=${bestNamed} disabled=${bestDisabled} tied=${independent.length}`
|
|
492
589
|
);
|
|
493
590
|
} else {
|
|
494
591
|
appilotsDebugLog(`findInteractiveByIdentifier: query="${query}" \u2192 no match`);
|
|
@@ -898,11 +995,27 @@ function pressResolvedListItem(list, itemFiber, listIndex, itemIndex) {
|
|
|
898
995
|
const safeIdentifiableTouchables = identifiableTouchables.filter(
|
|
899
996
|
(candidate) => !touchableLooksDestructive(candidate)
|
|
900
997
|
);
|
|
998
|
+
const rowSwitches = findSwitchFibers(itemFiber);
|
|
999
|
+
if (rowSwitches.length > 0) {
|
|
1000
|
+
const switchCandidates = rowSwitches.map(
|
|
1001
|
+
(fiber, index) => describeTouchable(fiber) ?? `switch ${index + 1} of "list-${listIndex}-item-${itemIndex}" (role=toggle element)`
|
|
1002
|
+
);
|
|
1003
|
+
const pressCandidates = identifiableTouchables.map(describeTouchable).filter((d) => !!d);
|
|
1004
|
+
return {
|
|
1005
|
+
ok: false,
|
|
1006
|
+
reason: "ambiguous-touchables",
|
|
1007
|
+
refusal: "row-has-switch",
|
|
1008
|
+
error: `Item "list-${listIndex}-item-${itemIndex}" contains a switch, so pressing the row would activate the row itself and leave the switch untouched. To flip the switch, emit ui_interaction action="toggle" on the row's role=toggle element id from VISIBLE STATE` + (pressCandidates.length > 0 ? "; to activate the row instead, use a specific pressable id from the candidates." : "."),
|
|
1009
|
+
containerType: list.containerType,
|
|
1010
|
+
candidates: [...switchCandidates, ...pressCandidates].slice(0, 8)
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
901
1013
|
if (identifiableTouchables.length > 0 && safeIdentifiableTouchables.length === 0) {
|
|
902
1014
|
const candidates = identifiableTouchables.map(describeTouchable).filter((d) => !!d).slice(0, 8);
|
|
903
1015
|
return {
|
|
904
1016
|
ok: false,
|
|
905
1017
|
reason: "ambiguous-touchables",
|
|
1018
|
+
refusal: "destructive-only",
|
|
906
1019
|
error: `Item "list-${listIndex}-item-${itemIndex}" only exposes destructive controls. Use the specific button id from the row (for example the cancel button id) instead of pressing the list ordinal.`,
|
|
907
1020
|
containerType: list.containerType,
|
|
908
1021
|
candidates
|
|
@@ -1126,24 +1239,22 @@ function toggleListItemByIdentity(locator, options) {
|
|
|
1126
1239
|
const props = target?.memoizedProps ?? {};
|
|
1127
1240
|
const current = switchValue(props);
|
|
1128
1241
|
if (options?.expected !== void 0 && current === options.expected) {
|
|
1129
|
-
return { ok: true, containerType: list.containerType, value: current };
|
|
1130
|
-
}
|
|
1131
|
-
const onValueChange = props.onValueChange ?? props.onChange;
|
|
1132
|
-
if (typeof onValueChange !== "function") {
|
|
1133
|
-
return {
|
|
1134
|
-
ok: false,
|
|
1135
|
-
reason: "no-touchable",
|
|
1136
|
-
error: "The row has a switch but it exposes no onValueChange handler.",
|
|
1137
|
-
containerType: list.containerType
|
|
1138
|
-
};
|
|
1242
|
+
return { ok: true, containerType: list.containerType, value: current, noop: true };
|
|
1139
1243
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1244
|
+
const flipped = flipSwitchFiber(target, !current);
|
|
1245
|
+
if (!flipped.ok) {
|
|
1246
|
+
if (flipped.reason === "no-handler") {
|
|
1247
|
+
return {
|
|
1248
|
+
ok: false,
|
|
1249
|
+
reason: "no-touchable",
|
|
1250
|
+
error: `The row has a switch but it exposes no handler that could flip it (looked for ${SWITCH_FLIP_HANDLERS.join(", ")}).`,
|
|
1251
|
+
containerType: list.containerType
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1143
1254
|
return {
|
|
1144
1255
|
ok: false,
|
|
1145
1256
|
reason: "press-threw",
|
|
1146
|
-
error:
|
|
1257
|
+
error: flipped.error,
|
|
1147
1258
|
containerType: list.containerType
|
|
1148
1259
|
};
|
|
1149
1260
|
}
|
|
@@ -1171,6 +1282,9 @@ function screenDiagnose(targetId, category = "component-not-found") {
|
|
|
1171
1282
|
function isPressLike(action) {
|
|
1172
1283
|
return action === "press" || action === "tap" || action === "click" || action === "longPress";
|
|
1173
1284
|
}
|
|
1285
|
+
function isPlainPress(action) {
|
|
1286
|
+
return action === "press" || action === "tap" || action === "click";
|
|
1287
|
+
}
|
|
1174
1288
|
function isElementIdentifier(value) {
|
|
1175
1289
|
return value.trim().toLowerCase().startsWith("el:");
|
|
1176
1290
|
}
|
|
@@ -1281,6 +1395,54 @@ function dispatchRegisteredTarget(targetId, registry, action) {
|
|
|
1281
1395
|
diagnose: screenDiagnose(targetId)
|
|
1282
1396
|
};
|
|
1283
1397
|
}
|
|
1398
|
+
function dispatchIdentifiedToggle(targetId, action) {
|
|
1399
|
+
if (!isPressLike(action)) return null;
|
|
1400
|
+
const winner = findInteractiveByIdentifier(targetId, { minScore: IDENTITY_MATCH_SCORE });
|
|
1401
|
+
if (winner) {
|
|
1402
|
+
if (winner.kind !== "toggle" || !winner.setValue) return null;
|
|
1403
|
+
if ((winner.matchCount ?? 1) > 1) {
|
|
1404
|
+
return {
|
|
1405
|
+
success: false,
|
|
1406
|
+
error: `"${targetId}" names ${winner.matchCount} controls on this screen \u2014 aim at one of the candidates instead`,
|
|
1407
|
+
diagnose: {
|
|
1408
|
+
...screenDiagnose(targetId, "ambiguous-target"),
|
|
1409
|
+
recoverable: true,
|
|
1410
|
+
...winner.matchCandidates && winner.matchCandidates.length > 0 ? { candidates: winner.matchCandidates } : {}
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
if (!isPlainPress(action)) {
|
|
1415
|
+
if (winner.longPress?.()) return { success: true };
|
|
1416
|
+
return {
|
|
1417
|
+
success: false,
|
|
1418
|
+
error: `"${targetId}" names a switch and it declares no long-press handler. Use action="toggle" to flip it.`,
|
|
1419
|
+
diagnose: { ...screenDiagnose(targetId, "unknown"), recoverable: true }
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
appilotsDebugLog(
|
|
1423
|
+
`elementDispatch: "${action}" on "${targetId}" names a switch \u2014 flipping it instead of pressing, so the verb cannot land on an ancestor`
|
|
1424
|
+
);
|
|
1425
|
+
try {
|
|
1426
|
+
winner.setValue(!winner.getValue?.());
|
|
1427
|
+
return { success: true, effect: "changed" };
|
|
1428
|
+
} catch (err) {
|
|
1429
|
+
return {
|
|
1430
|
+
success: false,
|
|
1431
|
+
error: err?.message ?? `Could not flip the switch named "${targetId}"`,
|
|
1432
|
+
diagnose: screenDiagnose(targetId, "unknown")
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
const disabledToggle = findInteractiveByIdentifier(targetId, { kind: "toggle" });
|
|
1437
|
+
if (disabledToggle?.named && disabledToggle.disabled) {
|
|
1438
|
+
return {
|
|
1439
|
+
success: false,
|
|
1440
|
+
error: `The switch named "${targetId}" is currently disabled`,
|
|
1441
|
+
diagnose: screenDiagnose(targetId, "disabled")
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
return null;
|
|
1445
|
+
}
|
|
1284
1446
|
function dispatchFiberTarget(targetId, action) {
|
|
1285
1447
|
const kind = action === "toggle" ? "toggle" : action === "focus" ? "field" : "target";
|
|
1286
1448
|
const handle = findInteractiveByIdentifier(targetId, { kind });
|
|
@@ -1330,6 +1492,8 @@ function dispatchLegacyTarget(targetId, registry, action) {
|
|
|
1330
1492
|
}
|
|
1331
1493
|
const registered = dispatchRegisteredTarget(targetId, registry, action);
|
|
1332
1494
|
if (registered.success) return registered;
|
|
1495
|
+
const named = dispatchIdentifiedToggle(targetId, action);
|
|
1496
|
+
if (named) return named;
|
|
1333
1497
|
const fiber = dispatchFiberTarget(targetId, action);
|
|
1334
1498
|
if (fiber.success) return fiber;
|
|
1335
1499
|
return {
|
|
@@ -1346,6 +1510,9 @@ function dispatchElement(element, registry, action) {
|
|
|
1346
1510
|
diagnose: screenDiagnose(element.id, "disabled")
|
|
1347
1511
|
};
|
|
1348
1512
|
}
|
|
1513
|
+
if (element.role === "toggle" && (action === "toggle" || isPressLike(action))) {
|
|
1514
|
+
return dispatchToggleElement(element, registry);
|
|
1515
|
+
}
|
|
1349
1516
|
if (element.listContext && isPressLike(action)) {
|
|
1350
1517
|
const pressed = pressListItemByIdentity({
|
|
1351
1518
|
...element.listContext,
|
|
@@ -1354,6 +1521,18 @@ function dispatchElement(element, registry, action) {
|
|
|
1354
1521
|
texts: element.texts
|
|
1355
1522
|
});
|
|
1356
1523
|
if (pressed.ok) return { success: true };
|
|
1524
|
+
if (pressed.refusal) {
|
|
1525
|
+
return {
|
|
1526
|
+
success: false,
|
|
1527
|
+
error: pressed.error ?? `Could not press element "${element.id}" (${pressed.reason})`,
|
|
1528
|
+
diagnose: {
|
|
1529
|
+
...screenDiagnose(element.id),
|
|
1530
|
+
category: "ambiguous-target",
|
|
1531
|
+
recoverable: true,
|
|
1532
|
+
...pressed.candidates && pressed.candidates.length > 0 ? { candidates: pressed.candidates } : {}
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1357
1536
|
if (element.targetId && element.targetId !== element.id) {
|
|
1358
1537
|
const fallback = dispatchLegacyTarget(element.targetId, registry, action);
|
|
1359
1538
|
if (fallback.success) return fallback;
|
|
@@ -1385,50 +1564,6 @@ function dispatchElement(element, registry, action) {
|
|
|
1385
1564
|
diagnose: { ...screenDiagnose(element.id, "unknown"), recoverable: true }
|
|
1386
1565
|
};
|
|
1387
1566
|
}
|
|
1388
|
-
if (element.role === "toggle" && (action === "toggle" || isPressLike(action))) {
|
|
1389
|
-
if (element.listContext) {
|
|
1390
|
-
const toggled = toggleListItemByIdentity(
|
|
1391
|
-
{
|
|
1392
|
-
listIndex: element.listContext.listIndex,
|
|
1393
|
-
listId: element.listContext.listId,
|
|
1394
|
-
listLabel: element.listContext.listLabel,
|
|
1395
|
-
itemIndex: element.listContext.itemIndex,
|
|
1396
|
-
itemKey: element.listContext.itemKey,
|
|
1397
|
-
reactKey: element.listContext.reactKey,
|
|
1398
|
-
syntheticId: element.listContext.syntheticId,
|
|
1399
|
-
label: element.label,
|
|
1400
|
-
texts: element.texts
|
|
1401
|
-
},
|
|
1402
|
-
// The observation's `selected` is the state BEFORE this action.
|
|
1403
|
-
// Passing the desired end state makes the flip idempotent: a
|
|
1404
|
-
// retry or a recovery hop must not undo the work and call it
|
|
1405
|
-
// success. A toggle only knows how to invert, so without this the
|
|
1406
|
-
// second attempt silently returns the row to where it started.
|
|
1407
|
-
typeof element.selected === "boolean" ? { expected: !element.selected } : void 0
|
|
1408
|
-
);
|
|
1409
|
-
if (toggled.ok) {
|
|
1410
|
-
return {
|
|
1411
|
-
success: true,
|
|
1412
|
-
// Grounded: read back off the fiber after the handler ran, not
|
|
1413
|
-
// inferred from "the call did not throw".
|
|
1414
|
-
effect: "changed"
|
|
1415
|
-
};
|
|
1416
|
-
}
|
|
1417
|
-
return {
|
|
1418
|
-
success: false,
|
|
1419
|
-
error: toggled.error ?? `Could not flip the switch in row "${element.label ?? element.id}" (${toggled.reason})`,
|
|
1420
|
-
diagnose: {
|
|
1421
|
-
...screenDiagnose(
|
|
1422
|
-
element.id,
|
|
1423
|
-
toggled.reason === "ambiguous-touchables" || toggled.reason === "ambiguous-items" ? "ambiguous-target" : "component-not-found"
|
|
1424
|
-
),
|
|
1425
|
-
...toggled.candidates && toggled.candidates.length > 0 ? { candidates: toggled.candidates } : {}
|
|
1426
|
-
}
|
|
1427
|
-
};
|
|
1428
|
-
}
|
|
1429
|
-
const targetId = element.targetId ?? element.label ?? element.id;
|
|
1430
|
-
return dispatchLegacyTarget(targetId, registry, "toggle");
|
|
1431
|
-
}
|
|
1432
1567
|
if (element.role === "input" && (action === "focus" || isPressLike(action))) {
|
|
1433
1568
|
const targetId = element.targetId ?? element.label ?? element.id;
|
|
1434
1569
|
return dispatchLegacyTarget(targetId, registry, "focus");
|
|
@@ -1443,6 +1578,54 @@ function dispatchElement(element, registry, action) {
|
|
|
1443
1578
|
diagnose: screenDiagnose(element.id, "unknown")
|
|
1444
1579
|
};
|
|
1445
1580
|
}
|
|
1581
|
+
function dispatchToggleElement(element, registry) {
|
|
1582
|
+
if (element.listContext) {
|
|
1583
|
+
const toggled = toggleListItemByIdentity(
|
|
1584
|
+
{
|
|
1585
|
+
listIndex: element.listContext.listIndex,
|
|
1586
|
+
listId: element.listContext.listId,
|
|
1587
|
+
listLabel: element.listContext.listLabel,
|
|
1588
|
+
itemIndex: element.listContext.itemIndex,
|
|
1589
|
+
itemKey: element.listContext.itemKey,
|
|
1590
|
+
reactKey: element.listContext.reactKey,
|
|
1591
|
+
syntheticId: element.listContext.syntheticId,
|
|
1592
|
+
label: element.label,
|
|
1593
|
+
texts: element.texts
|
|
1594
|
+
},
|
|
1595
|
+
// The observation's `selected` is the state BEFORE this action.
|
|
1596
|
+
// Passing the desired end state makes the flip idempotent: a
|
|
1597
|
+
// retry or a recovery hop must not undo the work and call it
|
|
1598
|
+
// success. A toggle only knows how to invert, so without this the
|
|
1599
|
+
// second attempt silently returns the row to where it started.
|
|
1600
|
+
typeof element.selected === "boolean" ? { expected: !element.selected } : void 0
|
|
1601
|
+
);
|
|
1602
|
+
if (toggled.ok) {
|
|
1603
|
+
return {
|
|
1604
|
+
success: true,
|
|
1605
|
+
// Honest split (#504): a real flip is 'changed'; the idempotent
|
|
1606
|
+
// no-op — the switch already held the requested state, nothing
|
|
1607
|
+
// was touched — is 'completed' (goal state verified), NEVER
|
|
1608
|
+
// 'changed' (nothing changed this hop) and NEVER 'none' (which
|
|
1609
|
+
// would send recovery re-flipping a switch that is already
|
|
1610
|
+
// right).
|
|
1611
|
+
effect: toggled.noop ? "completed" : "changed"
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
return {
|
|
1615
|
+
success: false,
|
|
1616
|
+
error: toggled.error ?? `Could not flip the switch in row "${element.label ?? element.id}" (${toggled.reason})`,
|
|
1617
|
+
diagnose: {
|
|
1618
|
+
...screenDiagnose(
|
|
1619
|
+
element.id,
|
|
1620
|
+
toggled.reason === "ambiguous-touchables" || toggled.reason === "ambiguous-items" ? "ambiguous-target" : "component-not-found"
|
|
1621
|
+
),
|
|
1622
|
+
...toggled.candidates && toggled.candidates.length > 0 ? { candidates: toggled.candidates } : {}
|
|
1623
|
+
}
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
const targetId = element.targetId ?? element.label ?? element.id;
|
|
1627
|
+
return dispatchLegacyTarget(targetId, registry, "toggle");
|
|
1628
|
+
}
|
|
1446
1629
|
function dispatchStructuredElementId(identifier, action) {
|
|
1447
1630
|
if (!isElementIdentifier(identifier) || !isPressLike(action)) return null;
|
|
1448
1631
|
const parsed = parseStableElementId(identifier);
|
|
@@ -1469,6 +1652,8 @@ function dispatchElementOrTarget(identifier, registry, action = "press") {
|
|
|
1469
1652
|
if (result.success) return result;
|
|
1470
1653
|
if (result.diagnose?.category === "disabled") return result;
|
|
1471
1654
|
if (element.role === "slider") return result;
|
|
1655
|
+
if (element.role === "toggle") return result;
|
|
1656
|
+
if (result.diagnose?.category === "ambiguous-target") return result;
|
|
1472
1657
|
}
|
|
1473
1658
|
const structured = dispatchStructuredElementId(identifier, action);
|
|
1474
1659
|
if (structured?.success) return structured;
|
|
@@ -2207,7 +2392,6 @@ function uiInteractionHandler(payload, context) {
|
|
|
2207
2392
|
appilotsDebugLog(
|
|
2208
2393
|
`uiInteractionHandler: interaction element dispatch succeeded for "${rawComponentId}"`
|
|
2209
2394
|
);
|
|
2210
|
-
return { success: true };
|
|
2211
2395
|
}
|
|
2212
2396
|
return result;
|
|
2213
2397
|
}
|
|
@@ -2239,12 +2423,12 @@ function uiInteractionHandler(payload, context) {
|
|
|
2239
2423
|
};
|
|
2240
2424
|
}
|
|
2241
2425
|
if (result.reason === "ambiguous-touchables") {
|
|
2242
|
-
const
|
|
2426
|
+
const namesConcreteChoice = result.refusal !== void 0 || /only exposes destructive controls|contains a switch/i.test(result.error ?? "");
|
|
2243
2427
|
return {
|
|
2244
2428
|
success: false,
|
|
2245
2429
|
error: result.error ?? `Synthetic list ordinal press failed (${result.reason ?? "unknown"})`,
|
|
2246
2430
|
diagnose: {
|
|
2247
|
-
category:
|
|
2431
|
+
category: namesConcreteChoice ? "ambiguous-target" : "unknown",
|
|
2248
2432
|
targetId: rawComponentId,
|
|
2249
2433
|
screen,
|
|
2250
2434
|
recoverable: true,
|
|
@@ -2320,6 +2504,8 @@ function uiInteractionHandler(payload, context) {
|
|
|
2320
2504
|
appilotsDebugLog(
|
|
2321
2505
|
`uiInteractionHandler: registry miss for "${componentId}", falling back to fiber tree`
|
|
2322
2506
|
);
|
|
2507
|
+
const namedToggle = dispatchIdentifiedToggle(componentId, actionStr);
|
|
2508
|
+
if (namedToggle) return namedToggle;
|
|
2323
2509
|
const handle = findInteractiveByIdentifier(componentId, {
|
|
2324
2510
|
kind: actionStr === "toggle" ? "toggle" : actionStr === "focus" ? "field" : "target"
|
|
2325
2511
|
});
|
|
@@ -2332,10 +2518,6 @@ function uiInteractionHandler(payload, context) {
|
|
|
2332
2518
|
);
|
|
2333
2519
|
return { success: true };
|
|
2334
2520
|
}
|
|
2335
|
-
if (actionStr === "press" && handle.kind === "toggle" && handle.setValue) {
|
|
2336
|
-
handle.setValue(!handle.getValue?.());
|
|
2337
|
-
return { success: true };
|
|
2338
|
-
}
|
|
2339
2521
|
if (actionStr === "toggle" && handle.kind === "toggle" && handle.setValue) {
|
|
2340
2522
|
handle.setValue(!handle.getValue?.());
|
|
2341
2523
|
return { success: true };
|
|
@@ -4594,6 +4594,8 @@ function isSwitchLike(fiber, props) {
|
|
|
4594
4594
|
}
|
|
4595
4595
|
function switchValue(props) {
|
|
4596
4596
|
if (typeof props?.value === "boolean") return props.value;
|
|
4597
|
+
const aria = props?.["aria-checked"];
|
|
4598
|
+
if (typeof aria === "boolean") return aria;
|
|
4597
4599
|
const checked = props?.accessibilityState?.checked;
|
|
4598
4600
|
return checked === true;
|
|
4599
4601
|
}
|
|
@@ -5837,7 +5839,7 @@ async function waitForLoadingSettle(opts = {}) {
|
|
|
5837
5839
|
}
|
|
5838
5840
|
|
|
5839
5841
|
// src/version.ts
|
|
5840
|
-
var SDK_VERSION2 = "0.11.
|
|
5842
|
+
var SDK_VERSION2 = "0.11.1";
|
|
5841
5843
|
var FiberSentinel = class extends React2__default.default.Component {
|
|
5842
5844
|
componentDidMount() {
|
|
5843
5845
|
const fiber = this._reactInternals ?? this._reactInternalFiber ?? null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useAppilotsContext, setNavigationRef, appilotsDebugLog, setCurrentScreen } from './chunk-
|
|
1
|
+
import { useAppilotsContext, setNavigationRef, appilotsDebugLog, setCurrentScreen } from './chunk-PQZ64CCE.mjs';
|
|
2
2
|
import React, { useRef, useCallback, useEffect } from 'react';
|
|
3
3
|
|
|
4
4
|
function getActiveRouteName(state) {
|
|
@@ -4588,6 +4588,8 @@ function isSwitchLike(fiber, props) {
|
|
|
4588
4588
|
}
|
|
4589
4589
|
function switchValue(props) {
|
|
4590
4590
|
if (typeof props?.value === "boolean") return props.value;
|
|
4591
|
+
const aria = props?.["aria-checked"];
|
|
4592
|
+
if (typeof aria === "boolean") return aria;
|
|
4591
4593
|
const checked = props?.accessibilityState?.checked;
|
|
4592
4594
|
return checked === true;
|
|
4593
4595
|
}
|
|
@@ -5831,7 +5833,7 @@ async function waitForLoadingSettle(opts = {}) {
|
|
|
5831
5833
|
}
|
|
5832
5834
|
|
|
5833
5835
|
// src/version.ts
|
|
5834
|
-
var SDK_VERSION2 = "0.11.
|
|
5836
|
+
var SDK_VERSION2 = "0.11.1";
|
|
5835
5837
|
var FiberSentinel = class extends React2.Component {
|
|
5836
5838
|
componentDidMount() {
|
|
5837
5839
|
const fiber = this._reactInternals ?? this._reactInternalFiber ?? null;
|