@buoy-gg/shared-ui 7.0.30 → 7.0.31
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/lib/commonjs/JsModal.js +18 -2
- package/lib/commonjs/JsModal.web.js +7 -4
- package/lib/commonjs/dataViewer/DataTreeActionDock.js +4 -2
- package/lib/commonjs/dataViewer/LiveExplorer.js +930 -0
- package/lib/commonjs/dataViewer/ValueEditModal.js +385 -0
- package/lib/commonjs/dataViewer/index.js +47 -0
- package/lib/commonjs/dataViewer/nestedPath.js +115 -0
- package/lib/commonjs/dataViewer/treeActions.js +3 -2
- package/lib/commonjs/dataViewer/useTreeDraft.js +125 -0
- package/lib/commonjs/dataViewer/useTreeEditor.js +105 -0
- package/lib/commonjs/env/EnvironmentIndicator.js +7 -1
- package/lib/commonjs/hooks/safe-area-impl.js +1 -1
- package/lib/commonjs/index.js +24 -0
- package/lib/commonjs/license/tierLimits.js +14 -0
- package/lib/commonjs/storage/devToolsStorageKeys.js +36 -0
- package/lib/commonjs/sync/toolActionRegistry.js +166 -0
- package/lib/commonjs/sync/toolUiBridge.js +74 -0
- package/lib/commonjs/ui/components/CompactRow.js +4 -0
- package/lib/commonjs/ui/components/EventHistoryViewer/DiffModeTabs.js +9 -1
- package/lib/commonjs/ui/components/ExpandedInfoRow.js +3 -1
- package/lib/commonjs/ui/components/ModalHeader.js +7 -4
- package/lib/commonjs/ui/components/SearchBar.js +5 -109
- package/lib/commonjs/ui/components/ToolButton.js +5 -2
- package/lib/module/JsModal.js +18 -2
- package/lib/module/JsModal.web.js +7 -4
- package/lib/module/dataViewer/DataTreeActionDock.js +4 -2
- package/lib/module/dataViewer/LiveExplorer.js +925 -0
- package/lib/module/dataViewer/ValueEditModal.js +381 -0
- package/lib/module/dataViewer/index.js +10 -1
- package/lib/module/dataViewer/nestedPath.js +109 -0
- package/lib/module/dataViewer/treeActions.js +3 -2
- package/lib/module/dataViewer/useTreeDraft.js +121 -0
- package/lib/module/dataViewer/useTreeEditor.js +101 -0
- package/lib/module/env/EnvironmentIndicator.js +7 -1
- package/lib/module/hooks/safe-area-impl.js +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/license/tierLimits.js +14 -0
- package/lib/module/storage/devToolsStorageKeys.js +36 -0
- package/lib/module/sync/toolActionRegistry.js +154 -0
- package/lib/module/sync/toolUiBridge.js +68 -0
- package/lib/module/ui/components/CompactRow.js +4 -0
- package/lib/module/ui/components/EventHistoryViewer/DiffModeTabs.js +9 -1
- package/lib/module/ui/components/ExpandedInfoRow.js +3 -1
- package/lib/module/ui/components/ModalHeader.js +7 -4
- package/lib/module/ui/components/SearchBar.js +8 -112
- package/lib/module/ui/components/ToolButton.js +5 -2
- package/lib/typescript/commonjs/JsModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/JsModal.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dataViewer/DataTreeActionDock.d.ts +6 -1
- package/lib/typescript/commonjs/dataViewer/DataTreeActionDock.d.ts.map +1 -1
- package/lib/typescript/commonjs/dataViewer/LiveExplorer.d.ts +73 -0
- package/lib/typescript/commonjs/dataViewer/LiveExplorer.d.ts.map +1 -0
- package/lib/typescript/commonjs/dataViewer/ValueEditModal.d.ts +40 -0
- package/lib/typescript/commonjs/dataViewer/ValueEditModal.d.ts.map +1 -0
- package/lib/typescript/commonjs/dataViewer/index.d.ts +5 -0
- package/lib/typescript/commonjs/dataViewer/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/dataViewer/nestedPath.d.ts +20 -0
- package/lib/typescript/commonjs/dataViewer/nestedPath.d.ts.map +1 -0
- package/lib/typescript/commonjs/dataViewer/treeActions.d.ts +10 -0
- package/lib/typescript/commonjs/dataViewer/treeActions.d.ts.map +1 -1
- package/lib/typescript/commonjs/dataViewer/useTreeDraft.d.ts +81 -0
- package/lib/typescript/commonjs/dataViewer/useTreeDraft.d.ts.map +1 -0
- package/lib/typescript/commonjs/dataViewer/useTreeEditor.d.ts +52 -0
- package/lib/typescript/commonjs/dataViewer/useTreeEditor.d.ts.map +1 -0
- package/lib/typescript/commonjs/env/EnvironmentIndicator.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts +2 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/license/tierLimits.d.ts +10 -0
- package/lib/typescript/commonjs/license/tierLimits.d.ts.map +1 -1
- package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts +36 -0
- package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts.map +1 -1
- package/lib/typescript/commonjs/sync/toolActionRegistry.d.ts +85 -0
- package/lib/typescript/commonjs/sync/toolActionRegistry.d.ts.map +1 -0
- package/lib/typescript/commonjs/sync/toolUiBridge.d.ts +39 -0
- package/lib/typescript/commonjs/sync/toolUiBridge.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/CompactRow.d.ts +5 -1
- package/lib/typescript/commonjs/ui/components/CompactRow.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/EventHistoryViewer/DiffModeTabs.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/EventHistoryViewer/types.d.ts +6 -0
- package/lib/typescript/commonjs/ui/components/EventHistoryViewer/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ExpandedInfoRow.d.ts +7 -1
- package/lib/typescript/commonjs/ui/components/ExpandedInfoRow.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/SearchBar.d.ts +1 -3
- package/lib/typescript/commonjs/ui/components/SearchBar.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ToolButton.d.ts +5 -1
- package/lib/typescript/commonjs/ui/components/ToolButton.d.ts.map +1 -1
- package/lib/typescript/module/JsModal.d.ts.map +1 -1
- package/lib/typescript/module/JsModal.web.d.ts.map +1 -1
- package/lib/typescript/module/dataViewer/DataTreeActionDock.d.ts +6 -1
- package/lib/typescript/module/dataViewer/DataTreeActionDock.d.ts.map +1 -1
- package/lib/typescript/module/dataViewer/LiveExplorer.d.ts +73 -0
- package/lib/typescript/module/dataViewer/LiveExplorer.d.ts.map +1 -0
- package/lib/typescript/module/dataViewer/ValueEditModal.d.ts +40 -0
- package/lib/typescript/module/dataViewer/ValueEditModal.d.ts.map +1 -0
- package/lib/typescript/module/dataViewer/index.d.ts +5 -0
- package/lib/typescript/module/dataViewer/index.d.ts.map +1 -1
- package/lib/typescript/module/dataViewer/nestedPath.d.ts +20 -0
- package/lib/typescript/module/dataViewer/nestedPath.d.ts.map +1 -0
- package/lib/typescript/module/dataViewer/treeActions.d.ts +10 -0
- package/lib/typescript/module/dataViewer/treeActions.d.ts.map +1 -1
- package/lib/typescript/module/dataViewer/useTreeDraft.d.ts +81 -0
- package/lib/typescript/module/dataViewer/useTreeDraft.d.ts.map +1 -0
- package/lib/typescript/module/dataViewer/useTreeEditor.d.ts +52 -0
- package/lib/typescript/module/dataViewer/useTreeEditor.d.ts.map +1 -0
- package/lib/typescript/module/env/EnvironmentIndicator.d.ts.map +1 -1
- package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/module/index.d.ts +2 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/license/tierLimits.d.ts +10 -0
- package/lib/typescript/module/license/tierLimits.d.ts.map +1 -1
- package/lib/typescript/module/storage/devToolsStorageKeys.d.ts +36 -0
- package/lib/typescript/module/storage/devToolsStorageKeys.d.ts.map +1 -1
- package/lib/typescript/module/sync/toolActionRegistry.d.ts +85 -0
- package/lib/typescript/module/sync/toolActionRegistry.d.ts.map +1 -0
- package/lib/typescript/module/sync/toolUiBridge.d.ts +39 -0
- package/lib/typescript/module/sync/toolUiBridge.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/CompactRow.d.ts +5 -1
- package/lib/typescript/module/ui/components/CompactRow.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/EventHistoryViewer/DiffModeTabs.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/EventHistoryViewer/types.d.ts +6 -0
- package/lib/typescript/module/ui/components/EventHistoryViewer/types.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ExpandedInfoRow.d.ts +7 -1
- package/lib/typescript/module/ui/components/ExpandedInfoRow.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/SearchBar.d.ts +1 -3
- package/lib/typescript/module/ui/components/SearchBar.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ToolButton.d.ts +5 -1
- package/lib/typescript/module/ui/components/ToolButton.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useTreeDraft = useTreeDraft;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _jsonOps = require("./jsonOps.js");
|
|
9
|
+
/**
|
|
10
|
+
* A local draft of a tree value, edited in place and written ONCE.
|
|
11
|
+
*
|
|
12
|
+
* Applying each tree op straight to the owning document was the obvious first
|
|
13
|
+
* cut and it behaves badly for two compounding reasons:
|
|
14
|
+
*
|
|
15
|
+
* 1. **Every op is a round trip.** The write lands, the owner emits an event,
|
|
16
|
+
* a snapshot comes back, and the browser re-reads everything — so a six-tap
|
|
17
|
+
* reorder is six writes and six full refreshes. The tree rebuilds under the
|
|
18
|
+
* user mid-gesture.
|
|
19
|
+
* 2. **The highlight and the value fall out of step.** Selection is local and
|
|
20
|
+
* moves instantly; the value it points at only catches up when the round
|
|
21
|
+
* trip completes, so for a beat the highlight sits on the old contents.
|
|
22
|
+
*
|
|
23
|
+
* Optimistic updates — the React Query pattern of rendering the expected result
|
|
24
|
+
* and reconciling later — fix (2) but not (1): they still write per change.
|
|
25
|
+
* Buffering fixes both, and it's what every editor that owns a document does
|
|
26
|
+
* (a form's dirty state, an IDE's unsaved buffer, JSON Editor's in-memory doc
|
|
27
|
+
* with an explicit save).
|
|
28
|
+
*
|
|
29
|
+
* The trade is that the buffer can go stale if the owner writes the same
|
|
30
|
+
* document while you're editing. That's the same last-write-wins a text editor
|
|
31
|
+
* has, and `isDirty` at least makes it visible that you're holding unsaved
|
|
32
|
+
* changes.
|
|
33
|
+
*
|
|
34
|
+
* Grew up as the Storage tool's key editor; hoisted here so the Zustand store
|
|
35
|
+
* editor is the same editor rather than a drifting copy. The save contract
|
|
36
|
+
* takes the VALUE — a storage host stringifies it, a zustand host diffs it
|
|
37
|
+
* into a merge partial.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param liveValue the value as its owner currently holds it (already parsed)
|
|
42
|
+
* @param save persists the draft VALUE; rejecting keeps edit mode open
|
|
43
|
+
*/
|
|
44
|
+
function useTreeDraft(liveValue, save) {
|
|
45
|
+
const [draft, setDraft] = (0, _react.useState)(undefined);
|
|
46
|
+
const [isEditing, setIsEditing] = (0, _react.useState)(false);
|
|
47
|
+
const [isSaving, setIsSaving] = (0, _react.useState)(false);
|
|
48
|
+
const [error, setError] = (0, _react.useState)(null);
|
|
49
|
+
const [selectPath, setSelectPath] = (0, _react.useState)();
|
|
50
|
+
/**
|
|
51
|
+
* The value as it was when editing began. Compared against for dirtiness
|
|
52
|
+
* rather than against `liveValue`, which can move underneath us if the owner
|
|
53
|
+
* writes the same document — that would otherwise flip a clean draft to
|
|
54
|
+
* dirty.
|
|
55
|
+
*/
|
|
56
|
+
const [baseline, setBaseline] = (0, _react.useState)("");
|
|
57
|
+
const value = isEditing && draft !== undefined ? draft : liveValue;
|
|
58
|
+
const isDirty = isEditing && JSON.stringify(draft) !== baseline;
|
|
59
|
+
const begin = (0, _react.useCallback)(() => {
|
|
60
|
+
setDraft(liveValue);
|
|
61
|
+
setBaseline(JSON.stringify(liveValue));
|
|
62
|
+
setSelectPath(undefined);
|
|
63
|
+
setError(null);
|
|
64
|
+
setIsEditing(true);
|
|
65
|
+
}, [liveValue]);
|
|
66
|
+
const apply = (0, _react.useCallback)(op => {
|
|
67
|
+
// Purely local — this is the whole point. No write, so no event, no
|
|
68
|
+
// snapshot, no refresh, and the tree re-renders from state synchronously.
|
|
69
|
+
setDraft(prev => (0, _jsonOps.applyJsonOp)(prev, op));
|
|
70
|
+
const next = (0, _jsonOps.selectionPathAfterOp)(op);
|
|
71
|
+
if (next !== undefined) setSelectPath(next);
|
|
72
|
+
}, []);
|
|
73
|
+
const selectAt = (0, _react.useCallback)(path => {
|
|
74
|
+
setSelectPath(path);
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
/** Leave edit mode entirely. Only correct on the way out (after a write). */
|
|
78
|
+
const reset = (0, _react.useCallback)(() => {
|
|
79
|
+
setIsEditing(false);
|
|
80
|
+
setDraft(undefined);
|
|
81
|
+
setSelectPath(undefined);
|
|
82
|
+
setError(null);
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
/** Re-seed the draft from what's stored, staying in edit mode. */
|
|
86
|
+
const discard = (0, _react.useCallback)(() => {
|
|
87
|
+
setDraft(liveValue);
|
|
88
|
+
setBaseline(JSON.stringify(liveValue));
|
|
89
|
+
setSelectPath(undefined);
|
|
90
|
+
setError(null);
|
|
91
|
+
setIsEditing(true);
|
|
92
|
+
}, [liveValue]);
|
|
93
|
+
const commit = (0, _react.useCallback)(async () => {
|
|
94
|
+
if (!isDirty || !save) {
|
|
95
|
+
reset();
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
setIsSaving(true);
|
|
99
|
+
setError(null);
|
|
100
|
+
try {
|
|
101
|
+
await save(draft);
|
|
102
|
+
reset();
|
|
103
|
+
return true;
|
|
104
|
+
} catch (e) {
|
|
105
|
+
// Stay in edit mode so the work isn't lost to a failed write.
|
|
106
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
107
|
+
return false;
|
|
108
|
+
} finally {
|
|
109
|
+
setIsSaving(false);
|
|
110
|
+
}
|
|
111
|
+
}, [draft, isDirty, reset, save]);
|
|
112
|
+
return {
|
|
113
|
+
isEditing,
|
|
114
|
+
value,
|
|
115
|
+
isDirty,
|
|
116
|
+
isSaving,
|
|
117
|
+
error,
|
|
118
|
+
selectPath,
|
|
119
|
+
begin,
|
|
120
|
+
apply,
|
|
121
|
+
selectAt,
|
|
122
|
+
commit,
|
|
123
|
+
discard
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useTreeEditor = useTreeEditor;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _jsonOps = require("./jsonOps.js");
|
|
9
|
+
var _useTreeDraft = require("./useTreeDraft.js");
|
|
10
|
+
/**
|
|
11
|
+
* Everything editing a tree document involves, minus how it looks.
|
|
12
|
+
*
|
|
13
|
+
* The phone and the dashboard run the same editor: one buffered draft, one
|
|
14
|
+
* write on save, a selection the structural actions operate on, a modal for
|
|
15
|
+
* typing a value, and a guard so closing can't silently drop unsaved work.
|
|
16
|
+
* What differs is presentation — thumb-sized tiles under a full screen versus
|
|
17
|
+
* chips docked in a panel — which is a rendering concern and nothing more.
|
|
18
|
+
*
|
|
19
|
+
* Keeping the rules here means the surfaces (and the tools — storage keys and
|
|
20
|
+
* zustand stores run this same hook) can't drift on the things that would
|
|
21
|
+
* actually hurt: whether a boolean opens an editor, whether a failed write
|
|
22
|
+
* closes the editor anyway, what "dirty" means.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
function useTreeEditor({
|
|
26
|
+
value,
|
|
27
|
+
save,
|
|
28
|
+
onClose,
|
|
29
|
+
confirmDiscard
|
|
30
|
+
}) {
|
|
31
|
+
const draft = (0, _useTreeDraft.useTreeDraft)(value, save);
|
|
32
|
+
const [selection, setSelection] = (0, _react.useState)(null);
|
|
33
|
+
const [valuePath, setValuePath] = (0, _react.useState)(null);
|
|
34
|
+
const {
|
|
35
|
+
begin
|
|
36
|
+
} = draft;
|
|
37
|
+
(0, _react.useEffect)(() => {
|
|
38
|
+
begin();
|
|
39
|
+
// Seed the draft ONCE, when the editor opens. `begin` closes over the live
|
|
40
|
+
// value, whose identity changes every time the list refreshes underneath
|
|
41
|
+
// us (a parsed JSON string is a new object each pass), so re-running would
|
|
42
|
+
// reset the draft mid-edit and discard the user's work.
|
|
43
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* "Change this node". For a boolean that's a flip rather than a modal — the
|
|
48
|
+
* same rule `treeActionsFor` applies to the action controls, so the gesture
|
|
49
|
+
* and the button can't mean different things.
|
|
50
|
+
*/
|
|
51
|
+
const openValueEditor = (0, _react.useCallback)(path => {
|
|
52
|
+
const current = (0, _jsonOps.getAtPath)(draft.value, path);
|
|
53
|
+
if (typeof current === "boolean") {
|
|
54
|
+
draft.apply({
|
|
55
|
+
kind: "replace",
|
|
56
|
+
path,
|
|
57
|
+
value: !current
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
setValuePath(path);
|
|
62
|
+
}, [draft]);
|
|
63
|
+
const applyValue = (0, _react.useCallback)(next => {
|
|
64
|
+
draft.apply({
|
|
65
|
+
kind: "replace",
|
|
66
|
+
path: valuePath ?? [],
|
|
67
|
+
value: next
|
|
68
|
+
});
|
|
69
|
+
setValuePath(null);
|
|
70
|
+
}, [draft, valuePath]);
|
|
71
|
+
|
|
72
|
+
// Only leave once the write actually landed — `commit` resolves false and
|
|
73
|
+
// keeps the draft on screen when the owner refuses it, and closing anyway
|
|
74
|
+
// would throw the work away while showing an error nobody ever sees.
|
|
75
|
+
const saveAndClose = (0, _react.useCallback)(() => {
|
|
76
|
+
void draft.commit().then(saved => saved && onClose());
|
|
77
|
+
}, [draft, onClose]);
|
|
78
|
+
const requestClose = (0, _react.useCallback)(() => {
|
|
79
|
+
if (!draft.isDirty) {
|
|
80
|
+
onClose();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// The draft is the only copy of this work, so closing over it silently is
|
|
84
|
+
// the one way this editor can lose something the user can't get back.
|
|
85
|
+
confirmDiscard({
|
|
86
|
+
save: saveAndClose,
|
|
87
|
+
discard: () => {
|
|
88
|
+
draft.discard();
|
|
89
|
+
onClose();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}, [confirmDiscard, draft, onClose, saveAndClose]);
|
|
93
|
+
return {
|
|
94
|
+
draft,
|
|
95
|
+
selection,
|
|
96
|
+
setSelection,
|
|
97
|
+
valuePath,
|
|
98
|
+
openValueEditor,
|
|
99
|
+
closeValueEditor: () => setValuePath(null),
|
|
100
|
+
applyValue,
|
|
101
|
+
openRawEditor: () => setValuePath([]),
|
|
102
|
+
requestClose,
|
|
103
|
+
saveAndClose
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -93,7 +93,13 @@ function EnvironmentIndicator({
|
|
|
93
93
|
style: {
|
|
94
94
|
fontSize: 11,
|
|
95
95
|
fontWeight: "600",
|
|
96
|
-
fontFamily:
|
|
96
|
+
// No fontFamily on purpose: the platform UI font (SF Pro / Roboto)
|
|
97
|
+
// is what this badge is meant to look like, and it is what the DOM
|
|
98
|
+
// twin in EnvironmentIndicator.web.tsx has always rendered. This
|
|
99
|
+
// used to name "Poppins-SemiBold" — a family no app, package or
|
|
100
|
+
// example in the repo has ever loaded, so it resolved nowhere:
|
|
101
|
+
// silently the system font on device, and the browser's default
|
|
102
|
+
// SERIF under react-native-web (the docs demo).
|
|
97
103
|
color: _gameUIColors.gameUIColors.primaryLight,
|
|
98
104
|
letterSpacing: 0.5
|
|
99
105
|
},
|
|
@@ -7,7 +7,7 @@ exports.useNativeSafeAreaInsets = exports.safeAreaType = exports.hasSafeAreaPack
|
|
|
7
7
|
/**
|
|
8
8
|
* Auto-generated safe area implementation
|
|
9
9
|
* Detected: none
|
|
10
|
-
* Generated at: 2026-08-
|
|
10
|
+
* Generated at: 2026-08-29T17:41:46.184Z
|
|
11
11
|
*
|
|
12
12
|
* DO NOT EDIT - This file is generated by scripts/detect-safe-area.js
|
|
13
13
|
*
|
package/lib/commonjs/index.js
CHANGED
|
@@ -720,6 +720,30 @@ Object.keys(_wireBudget).forEach(function (key) {
|
|
|
720
720
|
}
|
|
721
721
|
});
|
|
722
722
|
});
|
|
723
|
+
var _toolActionRegistry = require("./sync/toolActionRegistry.js");
|
|
724
|
+
Object.keys(_toolActionRegistry).forEach(function (key) {
|
|
725
|
+
if (key === "default" || key === "__esModule") return;
|
|
726
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
727
|
+
if (key in exports && exports[key] === _toolActionRegistry[key]) return;
|
|
728
|
+
Object.defineProperty(exports, key, {
|
|
729
|
+
enumerable: true,
|
|
730
|
+
get: function () {
|
|
731
|
+
return _toolActionRegistry[key];
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
});
|
|
735
|
+
var _toolUiBridge = require("./sync/toolUiBridge.js");
|
|
736
|
+
Object.keys(_toolUiBridge).forEach(function (key) {
|
|
737
|
+
if (key === "default" || key === "__esModule") return;
|
|
738
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
739
|
+
if (key in exports && exports[key] === _toolUiBridge[key]) return;
|
|
740
|
+
Object.defineProperty(exports, key, {
|
|
741
|
+
enumerable: true,
|
|
742
|
+
get: function () {
|
|
743
|
+
return _toolUiBridge[key];
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
});
|
|
723
747
|
var _index3 = require("./utils/index.js");
|
|
724
748
|
var _index4 = require("./utils/formatting/index.js");
|
|
725
749
|
Object.keys(_index4).forEach(function (key) {
|
|
@@ -107,6 +107,20 @@ const LIMITS = {
|
|
|
107
107
|
anonymous: 1,
|
|
108
108
|
free: 3,
|
|
109
109
|
pro: Infinity
|
|
110
|
+
},
|
|
111
|
+
// ── scenarios ─────────────────────────────────────────────────────────────
|
|
112
|
+
// The SCENARIO is the unit, not the rules it creates: a free user's
|
|
113
|
+
// three-endpoint scenario either runs whole or refuses up front, and is never
|
|
114
|
+
// silently trimmed to one rule mid-run. Enforcement is atomic in pre-flight.
|
|
115
|
+
"scenarios.saved": {
|
|
116
|
+
anonymous: 1,
|
|
117
|
+
free: 3,
|
|
118
|
+
pro: Infinity
|
|
119
|
+
},
|
|
120
|
+
"scenarios.active": {
|
|
121
|
+
anonymous: 1,
|
|
122
|
+
free: 1,
|
|
123
|
+
pro: 3
|
|
110
124
|
}
|
|
111
125
|
};
|
|
112
126
|
const TIER_LIMITS = exports.TIER_LIMITS = LIMITS;
|
|
@@ -135,6 +135,18 @@ const devToolsStorageKeys = exports.devToolsStorageKeys = {
|
|
|
135
135
|
root: () => `${devToolsStorageKeys.base}_time_machine`,
|
|
136
136
|
diffViewerMode: () => `${devToolsStorageKeys.timeMachine.root()}_diff_viewer_mode` // 'split' | 'tree'
|
|
137
137
|
},
|
|
138
|
+
/**
|
|
139
|
+
* Scenarios storage keys. The library (saved + draft scenarios) and the
|
|
140
|
+
* active-scenario record live in separate keys deliberately: the active
|
|
141
|
+
* record is written on every run and deactivation, while the library changes
|
|
142
|
+
* rarely, and a single blob would rewrite the whole library each run.
|
|
143
|
+
*/
|
|
144
|
+
scenarios: {
|
|
145
|
+
root: () => `${devToolsStorageKeys.base}_scenarios`,
|
|
146
|
+
modal: () => `${devToolsStorageKeys.scenarios.root()}_modal`,
|
|
147
|
+
library: () => `${devToolsStorageKeys.scenarios.root()}_library`,
|
|
148
|
+
active: () => `${devToolsStorageKeys.scenarios.root()}_active`
|
|
149
|
+
},
|
|
138
150
|
/**
|
|
139
151
|
* React Query-related storage keys
|
|
140
152
|
*/
|
|
@@ -209,16 +221,40 @@ const devToolsStorageKeys = exports.devToolsStorageKeys = {
|
|
|
209
221
|
settings: () => `${devToolsStorageKeys.benchmark.root()}_settings`,
|
|
210
222
|
selectedReports: () => `${devToolsStorageKeys.benchmark.root()}_selected_reports`
|
|
211
223
|
},
|
|
224
|
+
/**
|
|
225
|
+
* Debug Borders overlay keys
|
|
226
|
+
*/
|
|
227
|
+
debugBorders: {
|
|
228
|
+
root: () => `${devToolsStorageKeys.base}_debug_borders`,
|
|
229
|
+
/** JsModal `persistenceKey` for the overlay's info window. */
|
|
230
|
+
infoModal: () => `${devToolsStorageKeys.debugBorders.root()}_info_modal`
|
|
231
|
+
},
|
|
212
232
|
/**
|
|
213
233
|
* Unified events-related storage keys
|
|
214
234
|
*/
|
|
215
235
|
events: {
|
|
216
236
|
root: () => `${devToolsStorageKeys.base}_events`,
|
|
237
|
+
/**
|
|
238
|
+
* NOT what the Events tool uses. Its preset passes the literal
|
|
239
|
+
* `"buoy-events-modal"` to JsModal, and three things outside this repo's
|
|
240
|
+
* runtime now pin that string: the website's live tour, its QA script, and
|
|
241
|
+
* the e2e runner's reset. `isDevToolsStorageKey` treats the `buoy-` prefix
|
|
242
|
+
* as internal, so nothing leaks — this is a naming inconsistency, not a
|
|
243
|
+
* bug, and moving the key would cost a coordinated change across four
|
|
244
|
+
* surfaces to buy tidiness. Kept so the shape of this file stays uniform.
|
|
245
|
+
*/
|
|
217
246
|
modal: () => `${devToolsStorageKeys.events.root()}_modal`,
|
|
218
247
|
/** Selected badge/source filters */
|
|
219
248
|
enabledSources: () => `${devToolsStorageKeys.events.root()}_enabled_sources`,
|
|
220
249
|
/** Whether event capturing is active */
|
|
221
250
|
isCapturing: () => `${devToolsStorageKeys.events.root()}_is_capturing`,
|
|
251
|
+
/**
|
|
252
|
+
* When capture was last switched off (ms epoch). Persisted with the
|
|
253
|
+
* capturing flag so a remount/relaunch while paused keeps hiding events
|
|
254
|
+
* captured into the shared store after the ORIGINAL pause moment (a
|
|
255
|
+
* watching dashboard keeps sources alive through the device's pause).
|
|
256
|
+
*/
|
|
257
|
+
pausedAt: () => `${devToolsStorageKeys.events.root()}_paused_at`,
|
|
222
258
|
/** Copy/export settings */
|
|
223
259
|
copySettings: () => `${devToolsStorageKeys.events.root()}_copy_settings`
|
|
224
260
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.dispatchToolAction = dispatchToolAction;
|
|
7
|
+
exports.getToolAdapters = getToolAdapters;
|
|
8
|
+
exports.isToolRegistryReady = isToolRegistryReady;
|
|
9
|
+
exports.listSnapshotToolIds = listSnapshotToolIds;
|
|
10
|
+
exports.listToolActions = listToolActions;
|
|
11
|
+
exports.listToolIds = listToolIds;
|
|
12
|
+
exports.readToolSnapshot = readToolSnapshot;
|
|
13
|
+
exports.registerToolAdapters = registerToolAdapters;
|
|
14
|
+
exports.waitForToolRegistry = waitForToolRegistry;
|
|
15
|
+
/**
|
|
16
|
+
* toolActionRegistry — the on-device twin of the broker's action dispatch.
|
|
17
|
+
*
|
|
18
|
+
* Buoy already has exactly one place where `{toolId, action, params}` becomes a
|
|
19
|
+
* real call into a tool: `handleAction` in external-sync, fed by the adapter
|
|
20
|
+
* map that `autoExternalSync` assembles. That map was private to the React
|
|
21
|
+
* tree, so anything running ON the device (the Scenarios runner) had no way to
|
|
22
|
+
* reach it and would have had to duplicate the map — two dispatch paths that
|
|
23
|
+
* drift the first time an adapter changes.
|
|
24
|
+
*
|
|
25
|
+
* This module publishes the SAME map through a global seam, so a scenario step,
|
|
26
|
+
* a desktop click and an MCP call are one code path with two entry points.
|
|
27
|
+
*
|
|
28
|
+
* WHY A GLOBAL, NOT AN IMPORT: the map is built inside a component (the
|
|
29
|
+
* react-query adapter is a hook, the env adapter is a factory closed over a
|
|
30
|
+
* prop, and custom tools arrive as props), so it cannot be a module constant.
|
|
31
|
+
* Meanwhile the consumer must not import `@buoy-gg/core` — that package imports
|
|
32
|
+
* the tools, and a tool importing it back is the require cycle that puts a
|
|
33
|
+
* LogBox on every reload. Same reasoning, and the same shape, as
|
|
34
|
+
* `crashFlush.ts` in external-sync.
|
|
35
|
+
*
|
|
36
|
+
* WE STORE A GETTER, NOT A SNAPSHOT, for the reason `attachDeviceSync` does:
|
|
37
|
+
* the adapter map is re-created on renders that change its inputs, and a stale
|
|
38
|
+
* captured copy would keep dispatching into adapters the tree has replaced.
|
|
39
|
+
*
|
|
40
|
+
* SAFETY: this registry is populated only while `AutoExternalSync` is mounted,
|
|
41
|
+
* which is itself gated (dev, or release + an explicit opt-in + a real Pro
|
|
42
|
+
* license). It is deliberately NOT a substitute for a caller's own production
|
|
43
|
+
* gate — anything that bends app state through it must check `__DEV__` itself,
|
|
44
|
+
* because the actions reachable here (network overrides, storage writes,
|
|
45
|
+
* impersonation) have no business running in a shipped app.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
const REGISTRY_KEY = "__buoyToolActionRegistry";
|
|
49
|
+
|
|
50
|
+
/** The duck-typed adapter shape, matching external-sync's `ToolSyncAdapter`. */
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A SET of getters, not one slot — React can briefly mount two providers, or
|
|
54
|
+
* re-run an effect's setup before the previous cleanup. A last-writer-wins slot
|
|
55
|
+
* would let the second registration silently blank the first.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
function sources() {
|
|
59
|
+
const g = globalThis;
|
|
60
|
+
if (!g[REGISTRY_KEY]) g[REGISTRY_KEY] = new Set();
|
|
61
|
+
return g[REGISTRY_KEY];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Publish the live adapter map. Called by `autoExternalSync` with a getter that
|
|
66
|
+
* reads its current `useMemo` value. Returns the unregister function.
|
|
67
|
+
*/
|
|
68
|
+
function registerToolAdapters(getTools) {
|
|
69
|
+
const set = sources();
|
|
70
|
+
set.add(getTools);
|
|
71
|
+
return () => {
|
|
72
|
+
set.delete(getTools);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The merged adapter map across every live source. */
|
|
77
|
+
function getToolAdapters() {
|
|
78
|
+
const merged = {};
|
|
79
|
+
for (const get of sources()) {
|
|
80
|
+
try {
|
|
81
|
+
Object.assign(merged, get());
|
|
82
|
+
} catch {
|
|
83
|
+
// A source that throws must not take the whole registry down.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return merged;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Tool ids currently dispatchable. */
|
|
90
|
+
function listToolIds() {
|
|
91
|
+
return Object.keys(getToolAdapters());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** `toolId -> action names`, for pre-flight validation and capability display. */
|
|
95
|
+
function listToolActions() {
|
|
96
|
+
const out = {};
|
|
97
|
+
for (const [id, adapter] of Object.entries(getToolAdapters())) {
|
|
98
|
+
out[id] = Object.keys(adapter.actions ?? {});
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Tool ids whose adapter publishes a `getSnapshot`.
|
|
105
|
+
*
|
|
106
|
+
* Buoy's read surface is split in two: some tools answer through `actions`
|
|
107
|
+
* (`listQueries`, `async.getAllKeys`), and some answer only through the
|
|
108
|
+
* snapshot the sync layer polls — console entries, network events, the route
|
|
109
|
+
* sitemap, the env table. Anything driving tools ON the device sees only the
|
|
110
|
+
* first half unless it asks for the second, which is why the console and
|
|
111
|
+
* network panels look empty to an in-app agent that has an action for
|
|
112
|
+
* everything except reading them.
|
|
113
|
+
*/
|
|
114
|
+
function listSnapshotToolIds() {
|
|
115
|
+
return Object.entries(getToolAdapters()).filter(([, adapter]) => typeof adapter.getSnapshot === "function").map(([id]) => id);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Read one tool's snapshot — the same value `attachDeviceSync` puts on the wire
|
|
120
|
+
* for MCP's `get_snapshot`, without a socket.
|
|
121
|
+
*
|
|
122
|
+
* Async to match {@link dispatchToolAction}: every caller already awaits, and
|
|
123
|
+
* an adapter is free to make this a promise later without breaking them.
|
|
124
|
+
*/
|
|
125
|
+
async function readToolSnapshot(toolId) {
|
|
126
|
+
const tools = getToolAdapters();
|
|
127
|
+
const adapter = tools[toolId];
|
|
128
|
+
if (!adapter) throw new Error(`Unknown tool "${toolId}"`);
|
|
129
|
+
if (typeof adapter.getSnapshot !== "function") {
|
|
130
|
+
throw new Error(`Tool "${toolId}" has no snapshot to read`);
|
|
131
|
+
}
|
|
132
|
+
return await adapter.getSnapshot();
|
|
133
|
+
}
|
|
134
|
+
function isToolRegistryReady() {
|
|
135
|
+
return sources().size > 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Dispatch one action, shaping failures exactly as `handleAction` does: an
|
|
140
|
+
* unknown tool and an unknown action are distinguishable, and adapter errors
|
|
141
|
+
* surface as plain messages rather than objects nobody can read on a phone.
|
|
142
|
+
*/
|
|
143
|
+
async function dispatchToolAction(toolId, action, params) {
|
|
144
|
+
const tools = getToolAdapters();
|
|
145
|
+
const tool = tools[toolId];
|
|
146
|
+
const fn = tool?.actions?.[action];
|
|
147
|
+
if (!fn) {
|
|
148
|
+
throw new Error(tool ? `Unknown action "${action}" on tool "${toolId}"` : `Unknown tool "${toolId}"`);
|
|
149
|
+
}
|
|
150
|
+
return await fn(params);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Wait for the registry to be populated, for the narrow window between first
|
|
155
|
+
* frame and `AutoExternalSync` mounting. Resolves false on timeout so the
|
|
156
|
+
* caller can say "Buoy tools are still starting up" instead of "unknown tool".
|
|
157
|
+
*/
|
|
158
|
+
async function waitForToolRegistry(timeoutMs = 2000) {
|
|
159
|
+
if (isToolRegistryReady()) return true;
|
|
160
|
+
const deadline = Date.now() + timeoutMs;
|
|
161
|
+
while (Date.now() < deadline) {
|
|
162
|
+
await new Promise(r => setTimeout(r, 50));
|
|
163
|
+
if (isToolRegistryReady()) return true;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.canOpenBuoyTools = canOpenBuoyTools;
|
|
7
|
+
exports.openBuoyTool = openBuoyTool;
|
|
8
|
+
exports.registerToolOpener = registerToolOpener;
|
|
9
|
+
/**
|
|
10
|
+
* toolUiBridge — let a tool ask the host to bring another tool back on screen.
|
|
11
|
+
*
|
|
12
|
+
* WHY THIS EXISTS. Buoy's tool overlays (the SIMULATED banner, the recording
|
|
13
|
+
* banner, the perf HUD) render as siblings of `AppHostProvider`, not inside it,
|
|
14
|
+
* so none of them can reach the host that knows how to open and restore tools.
|
|
15
|
+
* That is usually fine — an overlay's job is to be seen, not to navigate. It
|
|
16
|
+
* stops being fine the moment an overlay is the ONLY thing on screen and the
|
|
17
|
+
* next step lives inside a tool: the macro recorder minimizes Scenarios so a
|
|
18
|
+
* tester can drive the app, and when they stop, the review screen they need is
|
|
19
|
+
* behind a tool icon they have to know to hunt for.
|
|
20
|
+
*
|
|
21
|
+
* The alternative was moving every overlay inside the provider, which would
|
|
22
|
+
* re-order the stacking of six unrelated tools to fix one banner's button. This
|
|
23
|
+
* is the same global-seam shape `toolActionRegistry` already uses for the same
|
|
24
|
+
* reason: the capability lives inside a component tree, the caller must not
|
|
25
|
+
* import `@buoy-gg/core` (that package imports the tools, and a tool importing
|
|
26
|
+
* it back is a require cycle), so it is published through a global.
|
|
27
|
+
*
|
|
28
|
+
* A SET of openers, not one slot, for the reason the action registry keeps one:
|
|
29
|
+
* React can briefly mount two providers, or re-run an effect's setup before the
|
|
30
|
+
* previous cleanup, and a last-writer-wins slot lets the second registration
|
|
31
|
+
* silently blank the first.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const BRIDGE_KEY = "__buoyToolUiBridge";
|
|
35
|
+
|
|
36
|
+
/** Returns true when the tool was actually brought back on screen. */
|
|
37
|
+
|
|
38
|
+
function openers() {
|
|
39
|
+
const g = globalThis;
|
|
40
|
+
if (!g[BRIDGE_KEY]) g[BRIDGE_KEY] = new Set();
|
|
41
|
+
return g[BRIDGE_KEY];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Published by the host, inside the provider that owns open tools. */
|
|
45
|
+
function registerToolOpener(opener) {
|
|
46
|
+
const set = openers();
|
|
47
|
+
set.add(opener);
|
|
48
|
+
return () => {
|
|
49
|
+
set.delete(opener);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Ask the host to show a tool.
|
|
55
|
+
*
|
|
56
|
+
* Returns false when nothing could do it — no host mounted, or the tool isn't
|
|
57
|
+
* open for it to restore. Callers must treat that as a real answer and say so,
|
|
58
|
+
* rather than rendering a button that silently does nothing.
|
|
59
|
+
*/
|
|
60
|
+
function openBuoyTool(toolId) {
|
|
61
|
+
for (const opener of openers()) {
|
|
62
|
+
try {
|
|
63
|
+
if (opener(toolId)) return true;
|
|
64
|
+
} catch {
|
|
65
|
+
// A host that throws must not stop another from answering.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** True when some host is listening — for deciding whether to offer the button. */
|
|
72
|
+
function canOpenBuoyTools() {
|
|
73
|
+
return openers().size > 0;
|
|
74
|
+
}
|
|
@@ -29,6 +29,8 @@ function CompactRow({
|
|
|
29
29
|
testID,
|
|
30
30
|
statusTestID,
|
|
31
31
|
statusSublabelTestID,
|
|
32
|
+
primaryTestID,
|
|
33
|
+
secondaryTestID,
|
|
32
34
|
accessibilityLabel
|
|
33
35
|
}) {
|
|
34
36
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
@@ -82,12 +84,14 @@ function CompactRow({
|
|
|
82
84
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
83
85
|
style: styles.queryHash,
|
|
84
86
|
numberOfLines: isExpanded ? undefined : 2,
|
|
87
|
+
testID: primaryTestID,
|
|
85
88
|
children: primaryText
|
|
86
89
|
}), !isExpanded && (secondaryText || secondaryAccessory) ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
87
90
|
style: styles.secondaryRow,
|
|
88
91
|
children: [secondaryText ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
89
92
|
style: styles.secondaryText,
|
|
90
93
|
numberOfLines: 1,
|
|
94
|
+
testID: secondaryTestID,
|
|
91
95
|
children: secondaryText
|
|
92
96
|
}) : null, secondaryAccessory]
|
|
93
97
|
}) : null]
|
|
@@ -24,7 +24,8 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
24
24
|
const DiffModeTabs = exports.DiffModeTabs = /*#__PURE__*/(0, _react.memo)(function DiffModeTabs({
|
|
25
25
|
tabs,
|
|
26
26
|
activeTab,
|
|
27
|
-
onTabChange
|
|
27
|
+
onTabChange,
|
|
28
|
+
testIDPrefix
|
|
28
29
|
}) {
|
|
29
30
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
30
31
|
style: styles.container,
|
|
@@ -36,6 +37,13 @@ const DiffModeTabs = exports.DiffModeTabs = /*#__PURE__*/(0, _react.memo)(functi
|
|
|
36
37
|
onPress: () => !isDisabled && onTabChange(tab.key),
|
|
37
38
|
activeOpacity: isDisabled ? 1 : 0.7,
|
|
38
39
|
disabled: isDisabled,
|
|
40
|
+
testID: testIDPrefix ? `${testIDPrefix}-${tab.key}` : undefined,
|
|
41
|
+
accessibilityRole: "button",
|
|
42
|
+
accessibilityLabel: tab.label,
|
|
43
|
+
accessibilityState: {
|
|
44
|
+
selected: isActive,
|
|
45
|
+
disabled: isDisabled
|
|
46
|
+
},
|
|
39
47
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
40
48
|
style: [styles.tabText, isActive && styles.tabTextActive, isDisabled && styles.tabTextDisabled],
|
|
41
49
|
children: tab.label
|
|
@@ -45,7 +45,8 @@ function PillBadge({
|
|
|
45
45
|
color,
|
|
46
46
|
children,
|
|
47
47
|
icon,
|
|
48
|
-
size = "md"
|
|
48
|
+
size = "md",
|
|
49
|
+
testID
|
|
49
50
|
}) {
|
|
50
51
|
const isSm = size === "sm";
|
|
51
52
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
@@ -60,6 +61,7 @@ function PillBadge({
|
|
|
60
61
|
style: [styles.pillText, isSm && styles.pillTextSm, {
|
|
61
62
|
color
|
|
62
63
|
}],
|
|
64
|
+
testID: testID,
|
|
63
65
|
children: children
|
|
64
66
|
})]
|
|
65
67
|
});
|