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,11 @@
|
|
|
1
|
+
export { AddPropertyButton, AddPropertyRow } from './AddPropertyRow';
|
|
2
|
+
export { CloseButton } from './CloseButton';
|
|
3
|
+
export { EditableValue } from './EditableValue';
|
|
4
|
+
export { FloatingPanel } from './FloatingPanel';
|
|
5
|
+
export { HandleContent } from './HandleContent';
|
|
6
|
+
export { InspectorBubble } from './InspectorBubble';
|
|
7
|
+
export { PanelBody } from './PanelBody';
|
|
8
|
+
export { PanelFooter } from './PanelFooter';
|
|
9
|
+
export { PanelHeader } from './PanelHeader';
|
|
10
|
+
export { getPanelSize } from './panelUtils';
|
|
11
|
+
export type { FloatingPanelConfig, FloatingPanelProps, PanelSize, PanelState } from './types';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FLOATING_PANEL } from '../constants/ui';
|
|
2
|
+
import type { PanelSize, PanelState } from './types';
|
|
3
|
+
|
|
4
|
+
export const getPanelSize = (panelState: PanelState): PanelSize => {
|
|
5
|
+
switch (panelState) {
|
|
6
|
+
case 'bubble':
|
|
7
|
+
return { width: FLOATING_PANEL.BUBBLE_SIZE, height: FLOATING_PANEL.BUBBLE_SIZE };
|
|
8
|
+
case 'handle':
|
|
9
|
+
return { width: FLOATING_PANEL.HANDLE_WIDTH, height: FLOATING_PANEL.HANDLE_HEIGHT };
|
|
10
|
+
case 'expanded':
|
|
11
|
+
return { width: FLOATING_PANEL.PANEL_WIDTH, height: FLOATING_PANEL.PANEL_HEIGHT };
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { MeasuredElement } from '../fiber';
|
|
2
|
+
|
|
3
|
+
export type PanelState = 'bubble' | 'handle' | 'expanded';
|
|
4
|
+
|
|
5
|
+
export interface FloatingPanelConfig {
|
|
6
|
+
panelState: PanelState;
|
|
7
|
+
screenWidth: number;
|
|
8
|
+
screenHeight: number;
|
|
9
|
+
onTap: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PanelSize {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface FloatingPanelProps {
|
|
18
|
+
panelState: PanelState;
|
|
19
|
+
selected: MeasuredElement | null;
|
|
20
|
+
matches: MeasuredElement[];
|
|
21
|
+
selectedIndex: number;
|
|
22
|
+
onToggleInspect: () => void;
|
|
23
|
+
onCycleNext: () => void;
|
|
24
|
+
onCyclePrevious: () => void;
|
|
25
|
+
onClose: () => void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { useDebouncedCallback } from './useDebouncedCallback';
|
|
2
|
+
export { useDebouncedValue } from './useDebouncedValue';
|
|
3
|
+
export { useFloatingPanel } from './useFloatingPanel';
|
|
4
|
+
export { useLayoutSnapshot } from './useLayoutSnapshot';
|
|
5
|
+
export { useStyleMutation } from './useStyleMutation';
|
|
6
|
+
export { useStyleOverrides } from './useStyleOverrides';
|
|
7
|
+
export { useTapToSelect } from './useTapToSelect';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
export const useDebouncedCallback = <Args extends unknown[]>(
|
|
4
|
+
callback: (...args: Args) => void,
|
|
5
|
+
delay: number,
|
|
6
|
+
): ((...args: Args) => void) => {
|
|
7
|
+
const timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
8
|
+
const callbackRef = useRef(callback);
|
|
9
|
+
callbackRef.current = callback;
|
|
10
|
+
|
|
11
|
+
return useCallback(
|
|
12
|
+
(...args: Args) => {
|
|
13
|
+
if (timeout.current) clearTimeout(timeout.current);
|
|
14
|
+
timeout.current = setTimeout(() => callbackRef.current(...args), delay);
|
|
15
|
+
},
|
|
16
|
+
[delay],
|
|
17
|
+
);
|
|
18
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export const useDebouncedValue = <T>(value: T, delay: number): T => {
|
|
4
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const timer = setTimeout(() => setDebouncedValue(value), delay);
|
|
8
|
+
return () => clearTimeout(timer);
|
|
9
|
+
}, [value, delay]);
|
|
10
|
+
|
|
11
|
+
return debouncedValue;
|
|
12
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { Animated, PanResponder } from 'react-native';
|
|
3
|
+
import { FLOATING_PANEL } from '../constants';
|
|
4
|
+
import { getPanelSize } from '../floatingPanel/panelUtils';
|
|
5
|
+
import type { FloatingPanelConfig } from '../floatingPanel/types';
|
|
6
|
+
import { clamp } from '../utils';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Core animation/gesture hook for the floating inspector panel.
|
|
10
|
+
* Owns Animated.ValueXY for position, PanResponder for drag,
|
|
11
|
+
* and Animated.Value for expand/collapse transitions.
|
|
12
|
+
*/
|
|
13
|
+
export const useFloatingPanel = ({
|
|
14
|
+
panelState,
|
|
15
|
+
screenWidth,
|
|
16
|
+
screenHeight,
|
|
17
|
+
onTap,
|
|
18
|
+
}: FloatingPanelConfig) => {
|
|
19
|
+
// Mutable refs so PanResponder callbacks never capture stale values
|
|
20
|
+
const panelStateRef = useRef(panelState);
|
|
21
|
+
panelStateRef.current = panelState;
|
|
22
|
+
|
|
23
|
+
const onTapRef = useRef(onTap);
|
|
24
|
+
onTapRef.current = onTap;
|
|
25
|
+
|
|
26
|
+
const screenRef = useRef({ width: screenWidth, height: screenHeight });
|
|
27
|
+
screenRef.current = { width: screenWidth, height: screenHeight };
|
|
28
|
+
|
|
29
|
+
// Initial position: middle-left edge
|
|
30
|
+
const initialX = FLOATING_PANEL.EDGE_MARGIN;
|
|
31
|
+
const initialY = (screenHeight - FLOATING_PANEL.BUBBLE_SIZE) / 2;
|
|
32
|
+
|
|
33
|
+
const position = useRef(new Animated.ValueXY({ x: initialX, y: initialY })).current;
|
|
34
|
+
const expandProgress = useRef(new Animated.Value(0)).current;
|
|
35
|
+
|
|
36
|
+
// Track raw position for synchronous reads (Animated.Value doesn't expose current value)
|
|
37
|
+
const currentPosition = useRef<{ x: number; y: number }>({ x: initialX, y: initialY });
|
|
38
|
+
const isDragging = useRef(false);
|
|
39
|
+
const dragStart = useRef({ x: 0, y: 0 });
|
|
40
|
+
|
|
41
|
+
const panelSize = useMemo(() => getPanelSize(panelState), [panelState]);
|
|
42
|
+
const panelSizeRef = useRef(panelSize);
|
|
43
|
+
panelSizeRef.current = panelSize;
|
|
44
|
+
|
|
45
|
+
// Snap to nearest left/right edge with spring animation
|
|
46
|
+
const snapToEdge = useCallback(() => {
|
|
47
|
+
const { width } = screenRef.current;
|
|
48
|
+
const size = panelSizeRef.current;
|
|
49
|
+
const midpoint = width / 2;
|
|
50
|
+
const centerX = currentPosition.current.x + size.width / 2;
|
|
51
|
+
|
|
52
|
+
const targetX =
|
|
53
|
+
centerX < midpoint
|
|
54
|
+
? FLOATING_PANEL.EDGE_MARGIN
|
|
55
|
+
: width - size.width - FLOATING_PANEL.EDGE_MARGIN;
|
|
56
|
+
|
|
57
|
+
currentPosition.current.x = targetX;
|
|
58
|
+
|
|
59
|
+
Animated.spring(position.x, {
|
|
60
|
+
toValue: targetX,
|
|
61
|
+
friction: FLOATING_PANEL.SNAP_FRICTION,
|
|
62
|
+
tension: FLOATING_PANEL.SNAP_TENSION,
|
|
63
|
+
useNativeDriver: false,
|
|
64
|
+
}).start();
|
|
65
|
+
}, [position.x]);
|
|
66
|
+
|
|
67
|
+
// PanResponder — recreated when screen dimensions change
|
|
68
|
+
const panResponder = useMemo(
|
|
69
|
+
() =>
|
|
70
|
+
PanResponder.create({
|
|
71
|
+
onStartShouldSetPanResponder: () => true,
|
|
72
|
+
onMoveShouldSetPanResponder: (_, gesture) =>
|
|
73
|
+
Math.abs(gesture.dx) > FLOATING_PANEL.TAP_THRESHOLD ||
|
|
74
|
+
Math.abs(gesture.dy) > FLOATING_PANEL.TAP_THRESHOLD,
|
|
75
|
+
|
|
76
|
+
onPanResponderGrant: () => {
|
|
77
|
+
isDragging.current = false;
|
|
78
|
+
dragStart.current = { ...currentPosition.current };
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
onPanResponderMove: (_, gesture) => {
|
|
82
|
+
const movedEnough =
|
|
83
|
+
Math.abs(gesture.dx) > FLOATING_PANEL.TAP_THRESHOLD ||
|
|
84
|
+
Math.abs(gesture.dy) > FLOATING_PANEL.TAP_THRESHOLD;
|
|
85
|
+
|
|
86
|
+
if (movedEnough) {
|
|
87
|
+
isDragging.current = true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!isDragging.current) return;
|
|
91
|
+
|
|
92
|
+
const { width: screenW, height: screenH } = screenRef.current;
|
|
93
|
+
const size = panelSizeRef.current;
|
|
94
|
+
|
|
95
|
+
const newX = clamp(
|
|
96
|
+
dragStart.current.x + gesture.dx,
|
|
97
|
+
FLOATING_PANEL.EDGE_MARGIN,
|
|
98
|
+
screenW - size.width - FLOATING_PANEL.EDGE_MARGIN,
|
|
99
|
+
);
|
|
100
|
+
const newY = clamp(
|
|
101
|
+
dragStart.current.y + gesture.dy,
|
|
102
|
+
FLOATING_PANEL.TOP_SAFE_AREA,
|
|
103
|
+
screenH - size.height - FLOATING_PANEL.BOTTOM_SAFE_AREA,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
position.setValue({ x: newX, y: newY });
|
|
107
|
+
currentPosition.current = { x: newX, y: newY };
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
onPanResponderRelease: () => {
|
|
111
|
+
if (!isDragging.current) {
|
|
112
|
+
onTapRef.current();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Bubble and handle snap to edge; expanded stays where dropped
|
|
117
|
+
if (panelStateRef.current !== 'expanded') {
|
|
118
|
+
snapToEdge();
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
}),
|
|
122
|
+
[position, snapToEdge],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Animate expand/collapse when panelState changes
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
Animated.timing(expandProgress, {
|
|
128
|
+
toValue: panelState === 'expanded' ? 1 : 0,
|
|
129
|
+
duration: FLOATING_PANEL.EXPAND_DURATION,
|
|
130
|
+
useNativeDriver: false,
|
|
131
|
+
}).start();
|
|
132
|
+
}, [panelState, expandProgress]);
|
|
133
|
+
|
|
134
|
+
// Reposition panel on state change so the new size stays on-screen.
|
|
135
|
+
// Bubble/handle: instantly snap to nearest edge.
|
|
136
|
+
// Expanded: spring-animate into screen bounds.
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
const { x, y } = currentPosition.current;
|
|
139
|
+
const size = getPanelSize(panelState);
|
|
140
|
+
|
|
141
|
+
if (panelState === 'expanded') {
|
|
142
|
+
const targetX = clamp(
|
|
143
|
+
x,
|
|
144
|
+
FLOATING_PANEL.EDGE_MARGIN,
|
|
145
|
+
screenWidth - size.width - FLOATING_PANEL.EDGE_MARGIN,
|
|
146
|
+
);
|
|
147
|
+
const targetY = clamp(
|
|
148
|
+
y,
|
|
149
|
+
FLOATING_PANEL.TOP_SAFE_AREA,
|
|
150
|
+
screenHeight - size.height - FLOATING_PANEL.BOTTOM_SAFE_AREA,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (targetX !== x || targetY !== y) {
|
|
154
|
+
currentPosition.current = { x: targetX, y: targetY };
|
|
155
|
+
Animated.spring(position, {
|
|
156
|
+
toValue: { x: targetX, y: targetY },
|
|
157
|
+
friction: FLOATING_PANEL.SNAP_FRICTION,
|
|
158
|
+
tension: FLOATING_PANEL.SNAP_TENSION,
|
|
159
|
+
useNativeDriver: false,
|
|
160
|
+
}).start();
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
// Bubble / handle — snap instantly to nearest edge (no animation needed,
|
|
164
|
+
// the panel content swap already provides the visual transition)
|
|
165
|
+
const midpoint = screenWidth / 2;
|
|
166
|
+
const centerX = x + size.width / 2;
|
|
167
|
+
|
|
168
|
+
const targetX =
|
|
169
|
+
centerX < midpoint
|
|
170
|
+
? FLOATING_PANEL.EDGE_MARGIN
|
|
171
|
+
: screenWidth - size.width - FLOATING_PANEL.EDGE_MARGIN;
|
|
172
|
+
const targetY = clamp(
|
|
173
|
+
y,
|
|
174
|
+
FLOATING_PANEL.TOP_SAFE_AREA,
|
|
175
|
+
screenHeight - size.height - FLOATING_PANEL.BOTTOM_SAFE_AREA,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (targetX !== x || targetY !== y) {
|
|
179
|
+
currentPosition.current = { x: targetX, y: targetY };
|
|
180
|
+
position.setValue({ x: targetX, y: targetY });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}, [panelState, screenWidth, screenHeight, position]);
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
position,
|
|
187
|
+
expandProgress,
|
|
188
|
+
panHandlers: panResponder.panHandlers,
|
|
189
|
+
panelSize,
|
|
190
|
+
};
|
|
191
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from 'react';
|
|
2
|
+
import { FiberAdapter, type MeasuredElement } from '../fiber';
|
|
3
|
+
import { buildLayoutSnapshot } from '../utils';
|
|
4
|
+
import { useDebouncedCallback } from './useDebouncedCallback';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Builds and caches the layout snapshot when inspect mode is activated.
|
|
8
|
+
* Call `buildSnapshot` on inspect-mode entry. Call `invalidate` after
|
|
9
|
+
* style mutations — it debounces and rebuilds automatically.
|
|
10
|
+
*/
|
|
11
|
+
export const useLayoutSnapshot = () => {
|
|
12
|
+
const [snapshot, setSnapshot] = useState<MeasuredElement[]>([]);
|
|
13
|
+
const [isBuilding, setIsBuilding] = useState(false);
|
|
14
|
+
const snapshotRef = useRef<MeasuredElement[]>([]);
|
|
15
|
+
|
|
16
|
+
const buildSnapshot = useCallback(async (): Promise<number> => {
|
|
17
|
+
const root = FiberAdapter.getFiberRoot();
|
|
18
|
+
if (!root) return 0;
|
|
19
|
+
|
|
20
|
+
setIsBuilding(true);
|
|
21
|
+
try {
|
|
22
|
+
const elements = await buildLayoutSnapshot(root);
|
|
23
|
+
snapshotRef.current = elements;
|
|
24
|
+
setSnapshot(elements);
|
|
25
|
+
return elements.length;
|
|
26
|
+
} finally {
|
|
27
|
+
setIsBuilding(false);
|
|
28
|
+
}
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
/** Mark stale — debounced 300ms so rapid style edits don't rebuild every time. */
|
|
32
|
+
const invalidate = useDebouncedCallback(buildSnapshot, 300);
|
|
33
|
+
|
|
34
|
+
/** Rebuild only if the snapshot is empty (first time). */
|
|
35
|
+
const ensureFresh = useCallback(async () => {
|
|
36
|
+
if (snapshotRef.current.length === 0) {
|
|
37
|
+
await buildSnapshot();
|
|
38
|
+
}
|
|
39
|
+
}, [buildSnapshot]);
|
|
40
|
+
|
|
41
|
+
return { snapshot, isBuilding, buildSnapshot, invalidate, ensureFresh };
|
|
42
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
import { FiberAdapter, type MeasuredElement } from '../fiber';
|
|
3
|
+
import { flattenStyles } from '../utils';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Captures the original flattened style for an element and provides
|
|
7
|
+
* a function to replace the element's entire style via overrideProps.
|
|
8
|
+
*
|
|
9
|
+
* All override/disabled tracking belongs in the consuming component —
|
|
10
|
+
* this hook only handles fiber interaction.
|
|
11
|
+
*/
|
|
12
|
+
export const useStyleMutation = (element: MeasuredElement) => {
|
|
13
|
+
const originalStyle = useMemo(() => {
|
|
14
|
+
const flat = flattenStyles(element.fiber.memoizedProps?.style);
|
|
15
|
+
return flat ? structuredClone(flat) : {};
|
|
16
|
+
}, [element]);
|
|
17
|
+
|
|
18
|
+
/** Replace the element's entire style with a flat object. */
|
|
19
|
+
const applyStyle = useCallback(
|
|
20
|
+
(style: Record<string, unknown>) => FiberAdapter.setStyle(element.fiber, style),
|
|
21
|
+
[element],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
/** Restore the element's original style. */
|
|
25
|
+
const resetStyle = useCallback(
|
|
26
|
+
() => FiberAdapter.setStyle(element.fiber, originalStyle),
|
|
27
|
+
[element, originalStyle],
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
return { originalStyle, applyStyle, resetStyle };
|
|
31
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
import { EDITABLE_VALUE } from '../constants';
|
|
3
|
+
import type { MeasuredElement } from '../fiber';
|
|
4
|
+
import { useStyleMutation } from './useStyleMutation';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Manages style overrides, key renames, property toggling, and added properties
|
|
8
|
+
* for a selected element. Builds the final style object and applies it to the fiber
|
|
9
|
+
* on every change.
|
|
10
|
+
*/
|
|
11
|
+
export const useStyleOverrides = (element: MeasuredElement) => {
|
|
12
|
+
const { originalStyle, applyStyle } = useStyleMutation(element);
|
|
13
|
+
const [overrides, setOverrides] = useState<Record<string, unknown>>({});
|
|
14
|
+
const [disabledKeys, setDisabledKeys] = useState<Set<string>>(new Set());
|
|
15
|
+
const [keyRenames, setKeyRenames] = useState<Record<string, string>>({});
|
|
16
|
+
const [addedProperties, setAddedProperties] = useState<Record<string, unknown>>({});
|
|
17
|
+
|
|
18
|
+
// Refs mirror state so handlers always read the latest values (no stale closures)
|
|
19
|
+
const overridesRef = useRef(overrides);
|
|
20
|
+
const disabledRef = useRef(disabledKeys);
|
|
21
|
+
const keyRenamesRef = useRef(keyRenames);
|
|
22
|
+
const addedRef = useRef(addedProperties);
|
|
23
|
+
const prevElementRef = useRef(element);
|
|
24
|
+
|
|
25
|
+
// Reset when element changes (render-phase setState — React's recommended
|
|
26
|
+
// pattern for adjusting state based on props, avoids stale flash from useEffect)
|
|
27
|
+
if (prevElementRef.current !== element) {
|
|
28
|
+
prevElementRef.current = element;
|
|
29
|
+
overridesRef.current = {};
|
|
30
|
+
disabledRef.current = new Set();
|
|
31
|
+
keyRenamesRef.current = {};
|
|
32
|
+
addedRef.current = {};
|
|
33
|
+
setOverrides({});
|
|
34
|
+
setDisabledKeys(new Set());
|
|
35
|
+
setKeyRenames({});
|
|
36
|
+
setAddedProperties({});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Check if a key collides with any active key (original, renamed, or added). */
|
|
40
|
+
const isKeyTaken = (key: string, excludeOriginalKey?: string): boolean => {
|
|
41
|
+
for (const originalKey of Object.keys(originalStyle)) {
|
|
42
|
+
if (originalKey === excludeOriginalKey) continue;
|
|
43
|
+
const activeKey = keyRenamesRef.current[originalKey] ?? originalKey;
|
|
44
|
+
if (activeKey === key) return true;
|
|
45
|
+
}
|
|
46
|
+
if (key in addedRef.current) return true;
|
|
47
|
+
return false;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Build a flat style from original + overrides − disabled + renames + added, then apply
|
|
51
|
+
const buildAndApply = (
|
|
52
|
+
nextOverrides: Record<string, unknown>,
|
|
53
|
+
nextDisabled: Set<string>,
|
|
54
|
+
nextRenames: Record<string, string>,
|
|
55
|
+
nextAdded: Record<string, unknown>,
|
|
56
|
+
) => {
|
|
57
|
+
const style: Record<string, unknown> = {};
|
|
58
|
+
for (const [originalKey, originalValue] of Object.entries(originalStyle)) {
|
|
59
|
+
if (nextDisabled.has(originalKey)) continue;
|
|
60
|
+
const activeKey = nextRenames[originalKey] ?? originalKey;
|
|
61
|
+
const value = activeKey in nextOverrides ? nextOverrides[activeKey] : originalValue;
|
|
62
|
+
style[activeKey] = value;
|
|
63
|
+
}
|
|
64
|
+
for (const [key, value] of Object.entries(nextAdded)) {
|
|
65
|
+
style[key] = value;
|
|
66
|
+
}
|
|
67
|
+
applyStyle(style);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleToggle = (key: string) => {
|
|
71
|
+
const nextDisabled = new Set(disabledRef.current);
|
|
72
|
+
if (nextDisabled.has(key)) {
|
|
73
|
+
nextDisabled.delete(key);
|
|
74
|
+
} else {
|
|
75
|
+
nextDisabled.add(key);
|
|
76
|
+
}
|
|
77
|
+
disabledRef.current = nextDisabled;
|
|
78
|
+
buildAndApply(overridesRef.current, nextDisabled, keyRenamesRef.current, addedRef.current);
|
|
79
|
+
setDisabledKeys(nextDisabled);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const handleValueChange = (originalKey: string, newValue: unknown) => {
|
|
83
|
+
const activeKey = keyRenamesRef.current[originalKey] ?? originalKey;
|
|
84
|
+
const nextOverrides = { ...overridesRef.current, [activeKey]: newValue };
|
|
85
|
+
overridesRef.current = nextOverrides;
|
|
86
|
+
buildAndApply(nextOverrides, disabledRef.current, keyRenamesRef.current, addedRef.current);
|
|
87
|
+
setOverrides(nextOverrides);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const handleKeyChange = (originalKey: string, newKey: string) => {
|
|
91
|
+
const currentRenamedKey = keyRenamesRef.current[originalKey] ?? originalKey;
|
|
92
|
+
if (newKey === currentRenamedKey) return;
|
|
93
|
+
if (isKeyTaken(newKey, originalKey)) return;
|
|
94
|
+
|
|
95
|
+
const nextRenames = { ...keyRenamesRef.current };
|
|
96
|
+
const nextOverrides = { ...overridesRef.current };
|
|
97
|
+
if (currentRenamedKey in nextOverrides) {
|
|
98
|
+
nextOverrides[newKey] = nextOverrides[currentRenamedKey];
|
|
99
|
+
delete nextOverrides[currentRenamedKey];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (newKey === originalKey) {
|
|
103
|
+
delete nextRenames[originalKey];
|
|
104
|
+
} else {
|
|
105
|
+
nextRenames[originalKey] = newKey;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
overridesRef.current = nextOverrides;
|
|
109
|
+
keyRenamesRef.current = nextRenames;
|
|
110
|
+
buildAndApply(nextOverrides, disabledRef.current, nextRenames, addedRef.current);
|
|
111
|
+
setOverrides(nextOverrides);
|
|
112
|
+
setKeyRenames(nextRenames);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const handleAddProperty = (key: string, value: unknown) => {
|
|
116
|
+
const trimmed = key.trim();
|
|
117
|
+
if (!EDITABLE_VALUE.VALID_STYLE_KEY.test(trimmed)) return;
|
|
118
|
+
if (isKeyTaken(trimmed)) return;
|
|
119
|
+
|
|
120
|
+
const nextAdded = { ...addedRef.current, [trimmed]: value };
|
|
121
|
+
addedRef.current = nextAdded;
|
|
122
|
+
buildAndApply(overridesRef.current, disabledRef.current, keyRenamesRef.current, nextAdded);
|
|
123
|
+
setAddedProperties(nextAdded);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const handleAddedValueChange = (key: string, newValue: unknown) => {
|
|
127
|
+
const nextAdded = { ...addedRef.current, [key]: newValue };
|
|
128
|
+
addedRef.current = nextAdded;
|
|
129
|
+
buildAndApply(overridesRef.current, disabledRef.current, keyRenamesRef.current, nextAdded);
|
|
130
|
+
setAddedProperties(nextAdded);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const handleAddedKeyChange = (oldKey: string, newKey: string) => {
|
|
134
|
+
const trimmed = newKey.trim();
|
|
135
|
+
if (!trimmed || trimmed === oldKey) return;
|
|
136
|
+
if (!EDITABLE_VALUE.VALID_STYLE_KEY.test(trimmed)) return;
|
|
137
|
+
if (isKeyTaken(trimmed)) return;
|
|
138
|
+
|
|
139
|
+
const nextAdded = { ...addedRef.current };
|
|
140
|
+
nextAdded[trimmed] = nextAdded[oldKey];
|
|
141
|
+
delete nextAdded[oldKey];
|
|
142
|
+
addedRef.current = nextAdded;
|
|
143
|
+
buildAndApply(overridesRef.current, disabledRef.current, keyRenamesRef.current, nextAdded);
|
|
144
|
+
setAddedProperties(nextAdded);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const handleRemoveProperty = (key: string) => {
|
|
148
|
+
const nextAdded = { ...addedRef.current };
|
|
149
|
+
delete nextAdded[key];
|
|
150
|
+
addedRef.current = nextAdded;
|
|
151
|
+
buildAndApply(overridesRef.current, disabledRef.current, keyRenamesRef.current, nextAdded);
|
|
152
|
+
setAddedProperties(nextAdded);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/** Resolve the active key and display value for an original style entry. */
|
|
156
|
+
const resolveEntry = (originalKey: string, originalValue: unknown) => {
|
|
157
|
+
const activeKey = keyRenames[originalKey] ?? originalKey;
|
|
158
|
+
const displayValue = activeKey in overrides ? overrides[activeKey] : originalValue;
|
|
159
|
+
const disabled = disabledKeys.has(originalKey);
|
|
160
|
+
return { activeKey, displayValue, disabled };
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
originalStyle,
|
|
165
|
+
entries: Object.entries(originalStyle),
|
|
166
|
+
addedEntries: Object.entries(addedProperties),
|
|
167
|
+
resolveEntry,
|
|
168
|
+
handleToggle,
|
|
169
|
+
handleValueChange,
|
|
170
|
+
handleKeyChange,
|
|
171
|
+
handleAddProperty,
|
|
172
|
+
handleAddedValueChange,
|
|
173
|
+
handleAddedKeyChange,
|
|
174
|
+
handleRemoveProperty,
|
|
175
|
+
};
|
|
176
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import type { GestureResponderEvent } from 'react-native';
|
|
3
|
+
import type { MeasuredElement } from '../fiber';
|
|
4
|
+
import { hitTest } from '../utils';
|
|
5
|
+
|
|
6
|
+
interface TapToSelectState {
|
|
7
|
+
/** All overlapping elements at the tap point, sorted by area (smallest first) */
|
|
8
|
+
matches: MeasuredElement[];
|
|
9
|
+
/** Currently selected index in the matches array */
|
|
10
|
+
selectedIndex: number;
|
|
11
|
+
/** The currently selected element */
|
|
12
|
+
selected: MeasuredElement | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Handles tap-to-select interaction.
|
|
17
|
+
* On tap, hit-tests the snapshot and selects the most specific element.
|
|
18
|
+
* Supports cycling through overlapping elements.
|
|
19
|
+
*/
|
|
20
|
+
export const useTapToSelect = (snapshot: MeasuredElement[]) => {
|
|
21
|
+
const [state, setState] = useState<TapToSelectState>({
|
|
22
|
+
matches: [],
|
|
23
|
+
selectedIndex: 0,
|
|
24
|
+
selected: null,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const handleTap = useCallback(
|
|
28
|
+
(event: GestureResponderEvent) => {
|
|
29
|
+
const { pageX, pageY } = event.nativeEvent;
|
|
30
|
+
const matches = hitTest(snapshot, pageX, pageY);
|
|
31
|
+
|
|
32
|
+
setState({
|
|
33
|
+
matches,
|
|
34
|
+
selectedIndex: 0,
|
|
35
|
+
selected: matches[0] ?? null,
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
[snapshot],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const cycleNext = useCallback(() => {
|
|
42
|
+
setState((prev) => {
|
|
43
|
+
if (prev.matches.length === 0) return prev;
|
|
44
|
+
const nextIndex = (prev.selectedIndex + 1) % prev.matches.length;
|
|
45
|
+
return {
|
|
46
|
+
...prev,
|
|
47
|
+
selectedIndex: nextIndex,
|
|
48
|
+
selected: prev.matches[nextIndex] ?? null,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const cyclePrevious = useCallback(() => {
|
|
54
|
+
setState((prev) => {
|
|
55
|
+
if (prev.matches.length === 0) return prev;
|
|
56
|
+
const prevIndex = (prev.selectedIndex - 1 + prev.matches.length) % prev.matches.length;
|
|
57
|
+
return {
|
|
58
|
+
...prev,
|
|
59
|
+
selectedIndex: prevIndex,
|
|
60
|
+
selected: prev.matches[prevIndex] ?? null,
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
const clearSelection = useCallback(() => {
|
|
66
|
+
setState({ matches: [], selectedIndex: 0, selected: null });
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
...state,
|
|
71
|
+
handleTap,
|
|
72
|
+
cycleNext,
|
|
73
|
+
cyclePrevious,
|
|
74
|
+
clearSelection,
|
|
75
|
+
};
|
|
76
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type StyleProp, StyleSheet, type ViewStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export type StyleObject = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve any style value (StyleSheet ID, array, or plain object) into a flat object.
|
|
7
|
+
*/
|
|
8
|
+
export const flattenStyles = (style: unknown): StyleObject | null => {
|
|
9
|
+
if (!style) return null;
|
|
10
|
+
const flat = StyleSheet.flatten(style as StyleProp<ViewStyle>);
|
|
11
|
+
return (flat ?? null) as StyleObject | null;
|
|
12
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { MeasuredElement } from '../fiber';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Given a tap point and a layout snapshot, return all elements
|
|
5
|
+
* whose bounds contain the point, sorted by area (smallest first).
|
|
6
|
+
*/
|
|
7
|
+
export const hitTest = (
|
|
8
|
+
snapshot: MeasuredElement[],
|
|
9
|
+
tapX: number,
|
|
10
|
+
tapY: number,
|
|
11
|
+
): MeasuredElement[] => {
|
|
12
|
+
const matches: MeasuredElement[] = [];
|
|
13
|
+
|
|
14
|
+
for (const element of snapshot) {
|
|
15
|
+
const { x, y, width, height } = element;
|
|
16
|
+
|
|
17
|
+
// Skip zero-size elements
|
|
18
|
+
if (width === 0 || height === 0) continue;
|
|
19
|
+
|
|
20
|
+
if (tapX >= x && tapX <= x + width && tapY >= y && tapY <= y + height) {
|
|
21
|
+
matches.push(element);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Sort by area ascending — smallest (most specific) element first
|
|
26
|
+
matches.sort((a, b) => a.width * a.height - b.width * b.height);
|
|
27
|
+
|
|
28
|
+
return matches;
|
|
29
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { clamp } from './clamp';
|
|
2
|
+
export type { StyleObject } from './flattenStyles';
|
|
3
|
+
export { flattenStyles } from './flattenStyles';
|
|
4
|
+
export { hitTest } from './hitTest';
|
|
5
|
+
export { buildLayoutSnapshot } from './layoutSnapshot';
|
|
6
|
+
export type { SourceLocation } from './sourceMapping';
|
|
7
|
+
export { formatSourceLocation, getOwnerName, getSourceLocation } from './sourceMapping';
|
|
8
|
+
export { formatValue, isColorProp } from './styleFormatting';
|
|
9
|
+
export { parseInput, toEditableString } from './styleInputParsing';
|
|
10
|
+
export type { BoxModel } from './yogaLayout';
|
|
11
|
+
export { extractBoxModel } from './yogaLayout';
|