react-native-element-inspector 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +134 -0
- package/lib/ElementCycler.d.ts +14 -0
- package/lib/ElementCycler.d.ts.map +1 -0
- package/lib/ElementCycler.js +33 -0
- package/lib/ElementCycler.js.map +1 -0
- package/lib/ElementHighlighter.d.ts +21 -0
- package/lib/ElementHighlighter.d.ts.map +1 -0
- package/lib/ElementHighlighter.js +89 -0
- package/lib/ElementHighlighter.js.map +1 -0
- package/lib/ElementInspector.d.ts +12 -0
- package/lib/ElementInspector.d.ts.map +1 -0
- package/lib/ElementInspector.js +74 -0
- package/lib/ElementInspector.js.map +1 -0
- package/lib/components/Checkbox.d.ts +7 -0
- package/lib/components/Checkbox.d.ts.map +1 -0
- package/lib/components/Checkbox.js +30 -0
- package/lib/components/Checkbox.js.map +1 -0
- package/lib/components/index.d.ts +2 -0
- package/lib/components/index.d.ts.map +1 -0
- package/lib/components/index.js +2 -0
- package/lib/components/index.js.map +1 -0
- package/lib/constants/colors.d.ts +24 -0
- package/lib/constants/colors.d.ts.map +1 -0
- package/lib/constants/colors.js +18 -0
- package/lib/constants/colors.js.map +1 -0
- package/lib/constants/index.d.ts +3 -0
- package/lib/constants/index.d.ts.map +1 -0
- package/lib/constants/index.js +3 -0
- package/lib/constants/index.js.map +1 -0
- package/lib/constants/ui.d.ts +56 -0
- package/lib/constants/ui.d.ts.map +1 -0
- package/lib/constants/ui.js +46 -0
- package/lib/constants/ui.js.map +1 -0
- package/lib/fiber/FiberAdapter.d.ts +52 -0
- package/lib/fiber/FiberAdapter.d.ts.map +1 -0
- package/lib/fiber/FiberAdapter.js +112 -0
- package/lib/fiber/FiberAdapter.js.map +1 -0
- package/lib/fiber/detectVersion.d.ts +9 -0
- package/lib/fiber/detectVersion.d.ts.map +1 -0
- package/lib/fiber/detectVersion.js +17 -0
- package/lib/fiber/detectVersion.js.map +1 -0
- package/lib/fiber/index.d.ts +5 -0
- package/lib/fiber/index.d.ts.map +1 -0
- package/lib/fiber/index.js +4 -0
- package/lib/fiber/index.js.map +1 -0
- package/lib/fiber/types.d.ts +35 -0
- package/lib/fiber/types.d.ts.map +1 -0
- package/lib/fiber/types.js +3 -0
- package/lib/fiber/types.js.map +1 -0
- package/lib/floatingPanel/AddPropertyRow.d.ts +13 -0
- package/lib/floatingPanel/AddPropertyRow.d.ts.map +1 -0
- package/lib/floatingPanel/AddPropertyRow.js +61 -0
- package/lib/floatingPanel/AddPropertyRow.js.map +1 -0
- package/lib/floatingPanel/CloseButton.d.ts +7 -0
- package/lib/floatingPanel/CloseButton.d.ts.map +1 -0
- package/lib/floatingPanel/CloseButton.js +21 -0
- package/lib/floatingPanel/CloseButton.js.map +1 -0
- package/lib/floatingPanel/EditableValue.d.ts +12 -0
- package/lib/floatingPanel/EditableValue.d.ts.map +1 -0
- package/lib/floatingPanel/EditableValue.js +67 -0
- package/lib/floatingPanel/EditableValue.js.map +1 -0
- package/lib/floatingPanel/FloatingPanel.d.ts +7 -0
- package/lib/floatingPanel/FloatingPanel.d.ts.map +1 -0
- package/lib/floatingPanel/FloatingPanel.js +70 -0
- package/lib/floatingPanel/FloatingPanel.js.map +1 -0
- package/lib/floatingPanel/HandleContent.d.ts +7 -0
- package/lib/floatingPanel/HandleContent.d.ts.map +1 -0
- package/lib/floatingPanel/HandleContent.js +35 -0
- package/lib/floatingPanel/HandleContent.js.map +1 -0
- package/lib/floatingPanel/InspectorBubble.d.ts +6 -0
- package/lib/floatingPanel/InspectorBubble.d.ts.map +1 -0
- package/lib/floatingPanel/InspectorBubble.js +91 -0
- package/lib/floatingPanel/InspectorBubble.js.map +1 -0
- package/lib/floatingPanel/PanelBody.d.ts +8 -0
- package/lib/floatingPanel/PanelBody.d.ts.map +1 -0
- package/lib/floatingPanel/PanelBody.js +72 -0
- package/lib/floatingPanel/PanelBody.js.map +1 -0
- package/lib/floatingPanel/PanelFooter.d.ts +9 -0
- package/lib/floatingPanel/PanelFooter.d.ts.map +1 -0
- package/lib/floatingPanel/PanelFooter.js +21 -0
- package/lib/floatingPanel/PanelFooter.js.map +1 -0
- package/lib/floatingPanel/PanelHeader.d.ts +13 -0
- package/lib/floatingPanel/PanelHeader.d.ts.map +1 -0
- package/lib/floatingPanel/PanelHeader.js +56 -0
- package/lib/floatingPanel/PanelHeader.js.map +1 -0
- package/lib/floatingPanel/index.d.ts +12 -0
- package/lib/floatingPanel/index.d.ts.map +1 -0
- package/lib/floatingPanel/index.js +11 -0
- package/lib/floatingPanel/index.js.map +1 -0
- package/lib/floatingPanel/panelUtils.d.ts +3 -0
- package/lib/floatingPanel/panelUtils.d.ts.map +1 -0
- package/lib/floatingPanel/panelUtils.js +12 -0
- package/lib/floatingPanel/panelUtils.js.map +1 -0
- package/lib/floatingPanel/types.d.ts +23 -0
- package/lib/floatingPanel/types.d.ts.map +1 -0
- package/lib/floatingPanel/types.js +2 -0
- package/lib/floatingPanel/types.js.map +1 -0
- package/lib/hooks/index.d.ts +8 -0
- package/lib/hooks/index.d.ts.map +1 -0
- package/lib/hooks/index.js +8 -0
- package/lib/hooks/index.js.map +1 -0
- package/lib/hooks/useDebouncedCallback.d.ts +2 -0
- package/lib/hooks/useDebouncedCallback.d.ts.map +1 -0
- package/lib/hooks/useDebouncedCallback.js +12 -0
- package/lib/hooks/useDebouncedCallback.js.map +1 -0
- package/lib/hooks/useDebouncedValue.d.ts +2 -0
- package/lib/hooks/useDebouncedValue.d.ts.map +1 -0
- package/lib/hooks/useDebouncedValue.js +10 -0
- package/lib/hooks/useDebouncedValue.js.map +1 -0
- package/lib/hooks/useFloatingPanel.d.ts +14 -0
- package/lib/hooks/useFloatingPanel.d.ts.map +1 -0
- package/lib/hooks/useFloatingPanel.js +132 -0
- package/lib/hooks/useFloatingPanel.js.map +1 -0
- package/lib/hooks/useLayoutSnapshot.d.ts +14 -0
- package/lib/hooks/useLayoutSnapshot.d.ts.map +1 -0
- package/lib/hooks/useLayoutSnapshot.js +39 -0
- package/lib/hooks/useLayoutSnapshot.js.map +1 -0
- package/lib/hooks/useStyleMutation.d.ts +14 -0
- package/lib/hooks/useStyleMutation.d.ts.map +1 -0
- package/lib/hooks/useStyleMutation.js +22 -0
- package/lib/hooks/useStyleMutation.js.map +1 -0
- package/lib/hooks/useStyleOverrides.d.ts +24 -0
- package/lib/hooks/useStyleOverrides.d.ts.map +1 -0
- package/lib/hooks/useStyleOverrides.js +165 -0
- package/lib/hooks/useStyleOverrides.js.map +1 -0
- package/lib/hooks/useTapToSelect.d.ts +20 -0
- package/lib/hooks/useTapToSelect.d.ts.map +1 -0
- package/lib/hooks/useTapToSelect.js +58 -0
- package/lib/hooks/useTapToSelect.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -0
- package/lib/utils/clamp.d.ts +2 -0
- package/lib/utils/clamp.d.ts.map +1 -0
- package/lib/utils/clamp.js +2 -0
- package/lib/utils/clamp.js.map +1 -0
- package/lib/utils/flattenStyles.d.ts +6 -0
- package/lib/utils/flattenStyles.d.ts.map +1 -0
- package/lib/utils/flattenStyles.js +11 -0
- package/lib/utils/flattenStyles.js.map +1 -0
- package/lib/utils/hitTest.d.ts +7 -0
- package/lib/utils/hitTest.d.ts.map +1 -0
- package/lib/utils/hitTest.js +20 -0
- package/lib/utils/hitTest.js.map +1 -0
- package/lib/utils/index.d.ts +12 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/lib/utils/index.js +9 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/layoutSnapshot.d.ts +7 -0
- package/lib/utils/layoutSnapshot.d.ts.map +1 -0
- package/lib/utils/layoutSnapshot.js +42 -0
- package/lib/utils/layoutSnapshot.js.map +1 -0
- package/lib/utils/sourceMapping.d.ts +26 -0
- package/lib/utils/sourceMapping.d.ts.map +1 -0
- package/lib/utils/sourceMapping.js +53 -0
- package/lib/utils/sourceMapping.js.map +1 -0
- package/lib/utils/styleFormatting.d.ts +5 -0
- package/lib/utils/styleFormatting.d.ts.map +1 -0
- package/lib/utils/styleFormatting.js +38 -0
- package/lib/utils/styleFormatting.js.map +1 -0
- package/lib/utils/styleInputParsing.d.ts +5 -0
- package/lib/utils/styleInputParsing.d.ts.map +1 -0
- package/lib/utils/styleInputParsing.js +33 -0
- package/lib/utils/styleInputParsing.js.map +1 -0
- package/lib/utils/yogaLayout.d.ts +33 -0
- package/lib/utils/yogaLayout.d.ts.map +1 -0
- package/lib/utils/yogaLayout.js +33 -0
- package/lib/utils/yogaLayout.js.map +1 -0
- package/package.json +74 -0
- package/src/ElementCycler.tsx +64 -0
- package/src/ElementHighlighter.tsx +122 -0
- package/src/ElementInspector.tsx +119 -0
- package/src/components/Checkbox.tsx +41 -0
- package/src/components/index.ts +1 -0
- package/src/constants/colors.ts +18 -0
- package/src/constants/index.ts +9 -0
- package/src/constants/ui.ts +51 -0
- package/src/fiber/FiberAdapter.ts +153 -0
- package/src/fiber/detectVersion.ts +19 -0
- package/src/fiber/index.ts +4 -0
- package/src/fiber/types.ts +36 -0
- package/src/floatingPanel/AddPropertyRow.tsx +102 -0
- package/src/floatingPanel/CloseButton.tsx +34 -0
- package/src/floatingPanel/EditableValue.tsx +109 -0
- package/src/floatingPanel/FloatingPanel.tsx +114 -0
- package/src/floatingPanel/HandleContent.tsx +45 -0
- package/src/floatingPanel/InspectorBubble.tsx +121 -0
- package/src/floatingPanel/PanelBody.tsx +162 -0
- package/src/floatingPanel/PanelFooter.tsx +36 -0
- package/src/floatingPanel/PanelHeader.tsx +111 -0
- package/src/floatingPanel/index.ts +11 -0
- package/src/floatingPanel/panelUtils.ts +13 -0
- package/src/floatingPanel/types.ts +26 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/useDebouncedCallback.ts +18 -0
- package/src/hooks/useDebouncedValue.ts +12 -0
- package/src/hooks/useFloatingPanel.ts +191 -0
- package/src/hooks/useLayoutSnapshot.ts +42 -0
- package/src/hooks/useStyleMutation.ts +31 -0
- package/src/hooks/useStyleOverrides.ts +176 -0
- package/src/hooks/useTapToSelect.ts +76 -0
- package/src/index.ts +2 -0
- package/src/utils/clamp.ts +2 -0
- package/src/utils/flattenStyles.ts +12 -0
- package/src/utils/hitTest.ts +29 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/layoutSnapshot.ts +48 -0
- package/src/utils/sourceMapping.ts +67 -0
- package/src/utils/styleFormatting.ts +34 -0
- package/src/utils/styleInputParsing.ts +33 -0
- package/src/utils/yogaLayout.ts +49 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
import { Pressable, StyleSheet, Text, TextInput } from 'react-native';
|
|
3
|
+
import { EDITABLE_VALUE, EDITABLE_VALUE_COLORS, MONOSPACE_FONT } from '../constants';
|
|
4
|
+
import { parseInput, toEditableString } from '../utils';
|
|
5
|
+
|
|
6
|
+
interface EditableValueProps {
|
|
7
|
+
value: unknown;
|
|
8
|
+
displayValue: string;
|
|
9
|
+
onSubmit: (parsed: unknown) => void;
|
|
10
|
+
variant: 'key' | 'value';
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
initialEditing?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Tappable style value that becomes an inline TextInput on press. */
|
|
16
|
+
export const EditableValue = ({
|
|
17
|
+
value,
|
|
18
|
+
displayValue,
|
|
19
|
+
onSubmit,
|
|
20
|
+
variant,
|
|
21
|
+
disabled,
|
|
22
|
+
initialEditing = false,
|
|
23
|
+
}: EditableValueProps) => {
|
|
24
|
+
const colors = EDITABLE_VALUE_COLORS[variant];
|
|
25
|
+
const [editing, setEditing] = useState(initialEditing);
|
|
26
|
+
const [draft, setDraft] = useState('');
|
|
27
|
+
const [invalid, setInvalid] = useState(false);
|
|
28
|
+
const committedRef = useRef(false);
|
|
29
|
+
|
|
30
|
+
const startEditing = () => {
|
|
31
|
+
if (disabled) return;
|
|
32
|
+
committedRef.current = false;
|
|
33
|
+
setDraft(toEditableString(value));
|
|
34
|
+
setEditing(true);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const commitEdit = () => {
|
|
38
|
+
// Guard against double-fire from onSubmitEditing + onBlur
|
|
39
|
+
if (committedRef.current) return;
|
|
40
|
+
committedRef.current = true;
|
|
41
|
+
|
|
42
|
+
const trimmed = draft.trim();
|
|
43
|
+
const changed = trimmed !== '' && trimmed !== toEditableString(value);
|
|
44
|
+
|
|
45
|
+
if (changed) {
|
|
46
|
+
const isInvalidKey = variant === 'key' && !EDITABLE_VALUE.VALID_STYLE_KEY.test(trimmed);
|
|
47
|
+
const isTooLong = trimmed.length > EDITABLE_VALUE.MAX_VALUE_LENGTH;
|
|
48
|
+
|
|
49
|
+
if (isInvalidKey || isTooLong) {
|
|
50
|
+
setInvalid(true);
|
|
51
|
+
setTimeout(() => setEditing(false), 300);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const parsed = parseInput(trimmed, value);
|
|
56
|
+
onSubmit(parsed);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setEditing(false);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (editing) {
|
|
63
|
+
return (
|
|
64
|
+
<TextInput
|
|
65
|
+
style={[
|
|
66
|
+
styles.base,
|
|
67
|
+
styles.editing,
|
|
68
|
+
{ color: colors.text, borderColor: invalid ? '#f44' : colors.underline },
|
|
69
|
+
]}
|
|
70
|
+
value={draft}
|
|
71
|
+
onChangeText={setDraft}
|
|
72
|
+
onSubmitEditing={commitEdit}
|
|
73
|
+
onBlur={commitEdit}
|
|
74
|
+
autoCapitalize='none'
|
|
75
|
+
autoCorrect={false}
|
|
76
|
+
autoFocus
|
|
77
|
+
selectTextOnFocus
|
|
78
|
+
returnKeyType='done'
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<Pressable onPress={startEditing} hitSlop={4}>
|
|
85
|
+
<Text style={[styles.base, { color: colors.text }, disabled && styles.strikethrough]}>
|
|
86
|
+
{displayValue}
|
|
87
|
+
</Text>
|
|
88
|
+
</Pressable>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const styles = StyleSheet.create({
|
|
93
|
+
base: {
|
|
94
|
+
fontSize: 13,
|
|
95
|
+
fontFamily: MONOSPACE_FONT,
|
|
96
|
+
padding: 0,
|
|
97
|
+
margin: 0,
|
|
98
|
+
minWidth: 20,
|
|
99
|
+
},
|
|
100
|
+
editing: {
|
|
101
|
+
borderWidth: 1,
|
|
102
|
+
borderRadius: 3,
|
|
103
|
+
paddingHorizontal: 4,
|
|
104
|
+
paddingVertical: 2,
|
|
105
|
+
},
|
|
106
|
+
strikethrough: {
|
|
107
|
+
textDecorationLine: 'line-through',
|
|
108
|
+
},
|
|
109
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Animated, StyleSheet, useWindowDimensions, View } from 'react-native';
|
|
2
|
+
import { FLOATING_PANEL, Z_INDEX } from '../constants';
|
|
3
|
+
import { useFloatingPanel } from '../hooks';
|
|
4
|
+
import { HandleContent } from './HandleContent';
|
|
5
|
+
import { InspectorBubble } from './InspectorBubble';
|
|
6
|
+
import { PanelBody } from './PanelBody';
|
|
7
|
+
import { PanelHeader } from './PanelHeader';
|
|
8
|
+
import type { FloatingPanelProps } from './types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Unified floating panel that renders as a bubble, handle, or expanded panel.
|
|
12
|
+
* Draggable via PanResponder. Bubble/handle snap to edge; expanded floats freely.
|
|
13
|
+
*/
|
|
14
|
+
export const FloatingPanel = ({
|
|
15
|
+
panelState,
|
|
16
|
+
selected,
|
|
17
|
+
matches,
|
|
18
|
+
selectedIndex,
|
|
19
|
+
onToggleInspect,
|
|
20
|
+
onCycleNext,
|
|
21
|
+
onCyclePrevious,
|
|
22
|
+
onClose,
|
|
23
|
+
}: FloatingPanelProps) => {
|
|
24
|
+
const { width: screenWidth, height: screenHeight } = useWindowDimensions();
|
|
25
|
+
|
|
26
|
+
const { position, expandProgress, panHandlers, panelSize } = useFloatingPanel({
|
|
27
|
+
panelState,
|
|
28
|
+
screenWidth,
|
|
29
|
+
screenHeight,
|
|
30
|
+
onTap: onToggleInspect,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const bodyHeight = expandProgress.interpolate({
|
|
34
|
+
inputRange: [0, 1],
|
|
35
|
+
outputRange: [0, FLOATING_PANEL.PANEL_BODY_HEIGHT],
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const bodyOpacity = expandProgress.interpolate({
|
|
39
|
+
inputRange: [0, 0.6, 1],
|
|
40
|
+
outputRange: [0, 0, 1],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Animated.View
|
|
45
|
+
style={[
|
|
46
|
+
styles.container,
|
|
47
|
+
{
|
|
48
|
+
left: position.x,
|
|
49
|
+
top: position.y,
|
|
50
|
+
width: panelSize.width,
|
|
51
|
+
zIndex: Z_INDEX.FLOATING_PANEL,
|
|
52
|
+
},
|
|
53
|
+
panelState === 'bubble' && styles.bubbleContainer,
|
|
54
|
+
panelState === 'handle' && styles.handleContainer,
|
|
55
|
+
]}
|
|
56
|
+
>
|
|
57
|
+
{/* Drag surface — the touchable/draggable area */}
|
|
58
|
+
<View {...panHandlers}>
|
|
59
|
+
{panelState === 'bubble' && <InspectorBubble />}
|
|
60
|
+
{panelState === 'handle' && <HandleContent onClose={onClose} />}
|
|
61
|
+
{panelState === 'expanded' && selected && (
|
|
62
|
+
<PanelHeader
|
|
63
|
+
selected={selected}
|
|
64
|
+
matches={matches}
|
|
65
|
+
selectedIndex={selectedIndex}
|
|
66
|
+
onCycleNext={onCycleNext}
|
|
67
|
+
onCyclePrevious={onCyclePrevious}
|
|
68
|
+
onClose={onClose}
|
|
69
|
+
/>
|
|
70
|
+
)}
|
|
71
|
+
</View>
|
|
72
|
+
|
|
73
|
+
{/* Expandable body — always mounted when selected so added-property state survives collapse */}
|
|
74
|
+
{selected && (
|
|
75
|
+
<Animated.View
|
|
76
|
+
style={[styles.body, { height: bodyHeight, opacity: bodyOpacity }]}
|
|
77
|
+
pointerEvents={panelState === 'expanded' ? 'auto' : 'none'}
|
|
78
|
+
>
|
|
79
|
+
<PanelBody element={selected} />
|
|
80
|
+
</Animated.View>
|
|
81
|
+
)}
|
|
82
|
+
</Animated.View>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const styles = StyleSheet.create({
|
|
87
|
+
container: {
|
|
88
|
+
position: 'absolute',
|
|
89
|
+
backgroundColor: 'rgba(30, 30, 30, 0.97)',
|
|
90
|
+
borderRadius: 12,
|
|
91
|
+
borderWidth: 1,
|
|
92
|
+
borderColor: 'rgba(255, 255, 255, 0.08)',
|
|
93
|
+
shadowColor: '#000',
|
|
94
|
+
shadowOffset: { width: 0, height: 4 },
|
|
95
|
+
shadowOpacity: 0.4,
|
|
96
|
+
shadowRadius: 8,
|
|
97
|
+
elevation: 8,
|
|
98
|
+
overflow: 'hidden',
|
|
99
|
+
},
|
|
100
|
+
bubbleContainer: {
|
|
101
|
+
borderRadius: FLOATING_PANEL.BUBBLE_SIZE / 2,
|
|
102
|
+
backgroundColor: 'transparent',
|
|
103
|
+
borderWidth: 0,
|
|
104
|
+
overflow: 'visible',
|
|
105
|
+
shadowOpacity: 0,
|
|
106
|
+
elevation: 0,
|
|
107
|
+
},
|
|
108
|
+
handleContainer: {
|
|
109
|
+
borderColor: 'rgba(255, 59, 48, 0.2)',
|
|
110
|
+
},
|
|
111
|
+
body: {
|
|
112
|
+
overflow: 'hidden',
|
|
113
|
+
},
|
|
114
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { StyleSheet, Text, View } from 'react-native';
|
|
2
|
+
import { CloseButton } from './CloseButton';
|
|
3
|
+
|
|
4
|
+
interface HandleContentProps {
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Floating handle shown in inspect mode before an element is selected. */
|
|
9
|
+
export const HandleContent = ({ onClose }: HandleContentProps) => (
|
|
10
|
+
<View style={styles.handle}>
|
|
11
|
+
<View style={styles.dot} />
|
|
12
|
+
<Text style={styles.handleText}>Tap an element</Text>
|
|
13
|
+
<CloseButton onPress={onClose} />
|
|
14
|
+
</View>
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const styles = StyleSheet.create({
|
|
18
|
+
handle: {
|
|
19
|
+
flexDirection: 'row',
|
|
20
|
+
alignItems: 'center',
|
|
21
|
+
paddingHorizontal: 14,
|
|
22
|
+
paddingVertical: 10,
|
|
23
|
+
backgroundColor: '#0F3460',
|
|
24
|
+
borderRadius: 22,
|
|
25
|
+
borderWidth: 1,
|
|
26
|
+
borderColor: 'rgba(79, 195, 247, 0.3)',
|
|
27
|
+
gap: 8,
|
|
28
|
+
},
|
|
29
|
+
dot: {
|
|
30
|
+
width: 8,
|
|
31
|
+
height: 8,
|
|
32
|
+
borderRadius: 4,
|
|
33
|
+
backgroundColor: '#E94560',
|
|
34
|
+
shadowColor: '#E94560',
|
|
35
|
+
shadowOffset: { width: 0, height: 0 },
|
|
36
|
+
shadowOpacity: 0.6,
|
|
37
|
+
shadowRadius: 3,
|
|
38
|
+
},
|
|
39
|
+
handleText: {
|
|
40
|
+
color: '#FFFFFF',
|
|
41
|
+
fontSize: 13,
|
|
42
|
+
fontWeight: '500',
|
|
43
|
+
flex: 1,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { Animated, Easing, StyleSheet, View } from 'react-native';
|
|
3
|
+
import { INSPECTOR_BUBBLE } from '../constants';
|
|
4
|
+
|
|
5
|
+
const { SIZE: BUBBLE_SIZE, SWEEP_WIDTH, CENTER_DOT_SIZE, TRAIL_ARMS } = INSPECTOR_BUBBLE;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Inspector bubble with a radar-sweep animation.
|
|
9
|
+
* A bright orange line rotates around a dark orange circle with a fading trail.
|
|
10
|
+
*/
|
|
11
|
+
export const InspectorBubble = () => {
|
|
12
|
+
const sweepAnim = useRef(new Animated.Value(0)).current;
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const loop = Animated.loop(
|
|
16
|
+
Animated.timing(sweepAnim, {
|
|
17
|
+
toValue: 1,
|
|
18
|
+
duration: 3000,
|
|
19
|
+
easing: Easing.linear,
|
|
20
|
+
useNativeDriver: true,
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
loop.start();
|
|
24
|
+
return () => loop.stop();
|
|
25
|
+
}, [sweepAnim]);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<View style={styles.container}>
|
|
29
|
+
{/* Clipping circle — contains all radar elements */}
|
|
30
|
+
<View style={styles.clipCircle}>
|
|
31
|
+
{/* Dark orange base fill */}
|
|
32
|
+
<View style={styles.baseFill} />
|
|
33
|
+
|
|
34
|
+
{/* Subtle grid ring at ~60% radius for radar feel */}
|
|
35
|
+
<View style={styles.gridRing} />
|
|
36
|
+
|
|
37
|
+
{/* Sweep line + fading trail arms */}
|
|
38
|
+
{TRAIL_ARMS.map((arm) => {
|
|
39
|
+
const rotation = sweepAnim.interpolate({
|
|
40
|
+
inputRange: [0, 1],
|
|
41
|
+
outputRange: [`${arm.offsetDeg}deg`, `${360 + arm.offsetDeg}deg`],
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Animated.View
|
|
46
|
+
key={arm.key}
|
|
47
|
+
style={[
|
|
48
|
+
styles.armWrapper,
|
|
49
|
+
{
|
|
50
|
+
opacity: arm.opacity,
|
|
51
|
+
transform: [{ rotate: rotation }],
|
|
52
|
+
},
|
|
53
|
+
]}
|
|
54
|
+
>
|
|
55
|
+
<View style={styles.sweepLine} />
|
|
56
|
+
</Animated.View>
|
|
57
|
+
);
|
|
58
|
+
})}
|
|
59
|
+
|
|
60
|
+
{/* Center dot */}
|
|
61
|
+
<View style={styles.centerDot} />
|
|
62
|
+
</View>
|
|
63
|
+
</View>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const styles = StyleSheet.create({
|
|
68
|
+
container: {
|
|
69
|
+
width: BUBBLE_SIZE,
|
|
70
|
+
height: BUBBLE_SIZE,
|
|
71
|
+
justifyContent: 'center',
|
|
72
|
+
alignItems: 'center',
|
|
73
|
+
shadowColor: '#f97316',
|
|
74
|
+
shadowOffset: { width: 0, height: 0 },
|
|
75
|
+
shadowOpacity: 0.5,
|
|
76
|
+
shadowRadius: 10,
|
|
77
|
+
elevation: 8,
|
|
78
|
+
},
|
|
79
|
+
clipCircle: {
|
|
80
|
+
width: BUBBLE_SIZE,
|
|
81
|
+
height: BUBBLE_SIZE,
|
|
82
|
+
borderRadius: BUBBLE_SIZE / 2,
|
|
83
|
+
borderWidth: 2,
|
|
84
|
+
borderColor: '#ea580c',
|
|
85
|
+
overflow: 'hidden',
|
|
86
|
+
justifyContent: 'center',
|
|
87
|
+
alignItems: 'center',
|
|
88
|
+
},
|
|
89
|
+
baseFill: {
|
|
90
|
+
...StyleSheet.absoluteFillObject,
|
|
91
|
+
backgroundColor: '#7c2d12',
|
|
92
|
+
},
|
|
93
|
+
gridRing: {
|
|
94
|
+
position: 'absolute',
|
|
95
|
+
width: BUBBLE_SIZE * 0.55,
|
|
96
|
+
height: BUBBLE_SIZE * 0.55,
|
|
97
|
+
borderRadius: (BUBBLE_SIZE * 0.55) / 2,
|
|
98
|
+
borderWidth: 1,
|
|
99
|
+
borderColor: 'rgba(234, 88, 12, 0.25)',
|
|
100
|
+
},
|
|
101
|
+
armWrapper: {
|
|
102
|
+
position: 'absolute',
|
|
103
|
+
width: BUBBLE_SIZE,
|
|
104
|
+
height: BUBBLE_SIZE,
|
|
105
|
+
alignItems: 'center',
|
|
106
|
+
},
|
|
107
|
+
sweepLine: {
|
|
108
|
+
width: SWEEP_WIDTH,
|
|
109
|
+
height: BUBBLE_SIZE / 2 - CENTER_DOT_SIZE / 2 - 2,
|
|
110
|
+
backgroundColor: '#fb923c',
|
|
111
|
+
borderRadius: SWEEP_WIDTH / 2,
|
|
112
|
+
marginTop: 3,
|
|
113
|
+
},
|
|
114
|
+
centerDot: {
|
|
115
|
+
position: 'absolute',
|
|
116
|
+
width: CENTER_DOT_SIZE,
|
|
117
|
+
height: CENTER_DOT_SIZE,
|
|
118
|
+
borderRadius: CENTER_DOT_SIZE / 2,
|
|
119
|
+
backgroundColor: '#f97316',
|
|
120
|
+
},
|
|
121
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { ScrollView, StyleSheet, View } from 'react-native';
|
|
3
|
+
import { Checkbox } from '../components';
|
|
4
|
+
import type { MeasuredElement } from '../fiber';
|
|
5
|
+
import { useStyleOverrides } from '../hooks';
|
|
6
|
+
import { formatValue, isColorProp } from '../utils';
|
|
7
|
+
import { AddPropertyButton, AddPropertyRow } from './AddPropertyRow';
|
|
8
|
+
import { EditableValue } from './EditableValue';
|
|
9
|
+
import { PanelFooter } from './PanelFooter';
|
|
10
|
+
|
|
11
|
+
interface PanelBodyProps {
|
|
12
|
+
element: MeasuredElement;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Scrollable style property list with footer — the main content of the expanded panel. */
|
|
16
|
+
export const PanelBody = ({ element }: PanelBodyProps) => {
|
|
17
|
+
const {
|
|
18
|
+
entries,
|
|
19
|
+
addedEntries,
|
|
20
|
+
resolveEntry,
|
|
21
|
+
handleToggle,
|
|
22
|
+
handleValueChange,
|
|
23
|
+
handleKeyChange,
|
|
24
|
+
handleAddProperty,
|
|
25
|
+
handleAddedValueChange,
|
|
26
|
+
handleAddedKeyChange,
|
|
27
|
+
handleRemoveProperty,
|
|
28
|
+
} = useStyleOverrides(element);
|
|
29
|
+
|
|
30
|
+
const [pendingAdd, setPendingAdd] = useState(false);
|
|
31
|
+
|
|
32
|
+
const totalCount = entries.length + addedEntries.length;
|
|
33
|
+
if (entries.length === 0 && addedEntries.length === 0 && !pendingAdd) return null;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<View style={styles.container}>
|
|
37
|
+
<ScrollView
|
|
38
|
+
style={styles.list}
|
|
39
|
+
contentContainerStyle={styles.listContent}
|
|
40
|
+
nestedScrollEnabled
|
|
41
|
+
keyboardShouldPersistTaps='handled'
|
|
42
|
+
>
|
|
43
|
+
{entries.map(([originalKey, originalValue], index) => {
|
|
44
|
+
const { activeKey, displayValue, disabled } = resolveEntry(originalKey, originalValue);
|
|
45
|
+
return (
|
|
46
|
+
<View key={originalKey} style={[styles.row, index % 2 === 0 && styles.rowAlt]}>
|
|
47
|
+
<Checkbox checked={!disabled} onToggle={() => handleToggle(originalKey)} />
|
|
48
|
+
<View style={[styles.rowContent, disabled && styles.rowDisabled]}>
|
|
49
|
+
<EditableValue
|
|
50
|
+
value={activeKey}
|
|
51
|
+
displayValue={activeKey}
|
|
52
|
+
onSubmit={(newKey) => handleKeyChange(originalKey, String(newKey))}
|
|
53
|
+
variant='key'
|
|
54
|
+
disabled={disabled}
|
|
55
|
+
/>
|
|
56
|
+
<View style={styles.valueContainer}>
|
|
57
|
+
{isColorProp(activeKey) && (
|
|
58
|
+
<View style={[styles.colorSwatch, { backgroundColor: String(displayValue) }]} />
|
|
59
|
+
)}
|
|
60
|
+
<EditableValue
|
|
61
|
+
value={displayValue}
|
|
62
|
+
displayValue={formatValue(displayValue)}
|
|
63
|
+
onSubmit={(newValue) => handleValueChange(originalKey, newValue)}
|
|
64
|
+
variant='value'
|
|
65
|
+
disabled={disabled}
|
|
66
|
+
/>
|
|
67
|
+
</View>
|
|
68
|
+
</View>
|
|
69
|
+
</View>
|
|
70
|
+
);
|
|
71
|
+
})}
|
|
72
|
+
|
|
73
|
+
{addedEntries.map(([key, value], index) => {
|
|
74
|
+
const rowIndex = entries.length + index;
|
|
75
|
+
return (
|
|
76
|
+
<View key={`added-${key}`} style={[styles.row, rowIndex % 2 === 0 && styles.rowAlt]}>
|
|
77
|
+
<Checkbox checked onToggle={() => handleRemoveProperty(key)} />
|
|
78
|
+
<View style={styles.rowContent}>
|
|
79
|
+
<EditableValue
|
|
80
|
+
value={key}
|
|
81
|
+
displayValue={key}
|
|
82
|
+
onSubmit={(newKey) => handleAddedKeyChange(key, String(newKey))}
|
|
83
|
+
variant='key'
|
|
84
|
+
/>
|
|
85
|
+
<View style={styles.valueContainer}>
|
|
86
|
+
{isColorProp(key) && (
|
|
87
|
+
<View style={[styles.colorSwatch, { backgroundColor: String(value) }]} />
|
|
88
|
+
)}
|
|
89
|
+
<EditableValue
|
|
90
|
+
value={value}
|
|
91
|
+
displayValue={formatValue(value)}
|
|
92
|
+
onSubmit={(newValue) => handleAddedValueChange(key, newValue)}
|
|
93
|
+
variant='value'
|
|
94
|
+
/>
|
|
95
|
+
</View>
|
|
96
|
+
</View>
|
|
97
|
+
</View>
|
|
98
|
+
);
|
|
99
|
+
})}
|
|
100
|
+
|
|
101
|
+
{pendingAdd ? (
|
|
102
|
+
<AddPropertyRow
|
|
103
|
+
onAdd={(key, value) => {
|
|
104
|
+
handleAddProperty(key, value);
|
|
105
|
+
setPendingAdd(false);
|
|
106
|
+
}}
|
|
107
|
+
onCancel={() => setPendingAdd(false)}
|
|
108
|
+
/>
|
|
109
|
+
) : (
|
|
110
|
+
<AddPropertyButton onPress={() => setPendingAdd(true)} />
|
|
111
|
+
)}
|
|
112
|
+
</ScrollView>
|
|
113
|
+
|
|
114
|
+
<PanelFooter propertyCount={totalCount} width={element.width} height={element.height} />
|
|
115
|
+
</View>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const styles = StyleSheet.create({
|
|
120
|
+
container: {
|
|
121
|
+
flex: 1,
|
|
122
|
+
},
|
|
123
|
+
list: {
|
|
124
|
+
flex: 1,
|
|
125
|
+
},
|
|
126
|
+
listContent: {
|
|
127
|
+
paddingHorizontal: 12,
|
|
128
|
+
paddingVertical: 4,
|
|
129
|
+
},
|
|
130
|
+
row: {
|
|
131
|
+
flexDirection: 'row',
|
|
132
|
+
alignItems: 'center',
|
|
133
|
+
paddingVertical: 5,
|
|
134
|
+
paddingHorizontal: 8,
|
|
135
|
+
borderRadius: 4,
|
|
136
|
+
},
|
|
137
|
+
rowContent: {
|
|
138
|
+
flex: 1,
|
|
139
|
+
flexDirection: 'row',
|
|
140
|
+
justifyContent: 'space-between',
|
|
141
|
+
alignItems: 'center',
|
|
142
|
+
},
|
|
143
|
+
rowAlt: {
|
|
144
|
+
backgroundColor: 'rgba(255, 255, 255, 0.03)',
|
|
145
|
+
},
|
|
146
|
+
rowDisabled: {
|
|
147
|
+
opacity: 0.35,
|
|
148
|
+
},
|
|
149
|
+
valueContainer: {
|
|
150
|
+
flexDirection: 'row',
|
|
151
|
+
alignItems: 'center',
|
|
152
|
+
flexShrink: 1,
|
|
153
|
+
},
|
|
154
|
+
colorSwatch: {
|
|
155
|
+
width: 14,
|
|
156
|
+
height: 14,
|
|
157
|
+
borderRadius: 3,
|
|
158
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
159
|
+
borderColor: '#666',
|
|
160
|
+
marginRight: 6,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { StyleSheet, Text, View } from 'react-native';
|
|
2
|
+
import { MONOSPACE_FONT } from '../constants';
|
|
3
|
+
|
|
4
|
+
interface PanelFooterProps {
|
|
5
|
+
propertyCount: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Expanded panel footer — property count, element dimensions, with top divider. */
|
|
11
|
+
export const PanelFooter = ({ propertyCount, width, height }: PanelFooterProps) => (
|
|
12
|
+
<View style={styles.footer}>
|
|
13
|
+
<Text style={styles.footerText}>
|
|
14
|
+
{propertyCount} {propertyCount === 1 ? 'property' : 'properties'}
|
|
15
|
+
</Text>
|
|
16
|
+
<Text style={styles.footerText}>
|
|
17
|
+
{Math.round(width)} × {Math.round(height)}
|
|
18
|
+
</Text>
|
|
19
|
+
</View>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const styles = StyleSheet.create({
|
|
23
|
+
footer: {
|
|
24
|
+
flexDirection: 'row',
|
|
25
|
+
justifyContent: 'space-between',
|
|
26
|
+
paddingHorizontal: 12,
|
|
27
|
+
paddingVertical: 6,
|
|
28
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
29
|
+
borderTopColor: '#444',
|
|
30
|
+
},
|
|
31
|
+
footerText: {
|
|
32
|
+
color: '#666',
|
|
33
|
+
fontSize: 11,
|
|
34
|
+
fontFamily: MONOSPACE_FONT,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { StyleSheet, Text, View } from 'react-native';
|
|
2
|
+
import { MONOSPACE_FONT } from '../constants';
|
|
3
|
+
import { ElementCycler } from '../ElementCycler';
|
|
4
|
+
import type { MeasuredElement } from '../fiber';
|
|
5
|
+
import { formatSourceLocation, getOwnerName, getSourceLocation } from '../utils';
|
|
6
|
+
import { CloseButton } from './CloseButton';
|
|
7
|
+
|
|
8
|
+
interface PanelHeaderProps {
|
|
9
|
+
selected: MeasuredElement;
|
|
10
|
+
matches: MeasuredElement[];
|
|
11
|
+
selectedIndex: number;
|
|
12
|
+
onCycleNext: () => void;
|
|
13
|
+
onCyclePrevious: () => void;
|
|
14
|
+
onClose: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Expanded panel header — grab bar, component info, cycling controls, close button. */
|
|
18
|
+
export const PanelHeader = ({
|
|
19
|
+
selected,
|
|
20
|
+
matches,
|
|
21
|
+
selectedIndex,
|
|
22
|
+
onCycleNext,
|
|
23
|
+
onCyclePrevious,
|
|
24
|
+
onClose,
|
|
25
|
+
}: PanelHeaderProps) => {
|
|
26
|
+
const source = getSourceLocation(selected.fiber);
|
|
27
|
+
const ownerName = getOwnerName(selected.fiber);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<View style={styles.header}>
|
|
31
|
+
{/* Grab bar — visual drag indicator */}
|
|
32
|
+
<View style={styles.grabBarContainer}>
|
|
33
|
+
<View style={styles.grabBar} />
|
|
34
|
+
</View>
|
|
35
|
+
|
|
36
|
+
{/* Component info row */}
|
|
37
|
+
<View style={styles.infoRow}>
|
|
38
|
+
<Text style={styles.componentName} numberOfLines={1}>
|
|
39
|
+
<{selected.componentName}>
|
|
40
|
+
</Text>
|
|
41
|
+
|
|
42
|
+
<View style={styles.controls}>
|
|
43
|
+
<ElementCycler
|
|
44
|
+
total={matches.length}
|
|
45
|
+
currentIndex={selectedIndex}
|
|
46
|
+
componentName={selected.componentName}
|
|
47
|
+
onPrevious={onCyclePrevious}
|
|
48
|
+
onNext={onCycleNext}
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
<CloseButton onPress={onClose} />
|
|
52
|
+
</View>
|
|
53
|
+
</View>
|
|
54
|
+
|
|
55
|
+
{/* Source location — on its own line so it's not truncated */}
|
|
56
|
+
{source ? (
|
|
57
|
+
<Text style={styles.sourceText} numberOfLines={1}>
|
|
58
|
+
{formatSourceLocation(source)}
|
|
59
|
+
</Text>
|
|
60
|
+
) : ownerName ? (
|
|
61
|
+
<Text style={styles.sourceText} numberOfLines={1}>
|
|
62
|
+
{ownerName}
|
|
63
|
+
</Text>
|
|
64
|
+
) : null}
|
|
65
|
+
</View>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const styles = StyleSheet.create({
|
|
70
|
+
header: {
|
|
71
|
+
paddingTop: 6,
|
|
72
|
+
paddingHorizontal: 12,
|
|
73
|
+
paddingBottom: 8,
|
|
74
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
75
|
+
borderBottomColor: '#444',
|
|
76
|
+
},
|
|
77
|
+
grabBarContainer: {
|
|
78
|
+
alignItems: 'center',
|
|
79
|
+
paddingBottom: 6,
|
|
80
|
+
},
|
|
81
|
+
grabBar: {
|
|
82
|
+
width: 40,
|
|
83
|
+
height: 4,
|
|
84
|
+
borderRadius: 2,
|
|
85
|
+
backgroundColor: '#666',
|
|
86
|
+
},
|
|
87
|
+
infoRow: {
|
|
88
|
+
flexDirection: 'row',
|
|
89
|
+
alignItems: 'center',
|
|
90
|
+
justifyContent: 'space-between',
|
|
91
|
+
},
|
|
92
|
+
componentName: {
|
|
93
|
+
color: '#E06C75',
|
|
94
|
+
fontSize: 14,
|
|
95
|
+
fontWeight: '600',
|
|
96
|
+
fontFamily: MONOSPACE_FONT,
|
|
97
|
+
flexShrink: 1,
|
|
98
|
+
},
|
|
99
|
+
controls: {
|
|
100
|
+
flexDirection: 'row',
|
|
101
|
+
alignItems: 'center',
|
|
102
|
+
gap: 6,
|
|
103
|
+
flexShrink: 0,
|
|
104
|
+
},
|
|
105
|
+
sourceText: {
|
|
106
|
+
color: '#888',
|
|
107
|
+
fontSize: 11,
|
|
108
|
+
marginTop: 4,
|
|
109
|
+
fontFamily: MONOSPACE_FONT,
|
|
110
|
+
},
|
|
111
|
+
});
|