react-native-drax 0.11.0-alpha.1 → 1.0.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.md +1 -1
- package/README.md +385 -227
- package/lib/module/DebugOverlay.js +121 -0
- package/lib/module/DebugOverlay.js.map +1 -0
- package/lib/module/Drax.js +36 -0
- package/lib/module/Drax.js.map +1 -0
- package/lib/module/DraxContext.js +6 -0
- package/lib/module/DraxContext.js.map +1 -0
- package/lib/module/DraxHandle.js +47 -0
- package/lib/module/DraxHandle.js.map +1 -0
- package/lib/module/DraxHandleContext.js +11 -0
- package/lib/module/DraxHandleContext.js.map +1 -0
- package/lib/module/DraxList.js +108 -0
- package/lib/module/DraxList.js.map +1 -0
- package/lib/module/DraxProvider.js +203 -0
- package/lib/module/DraxProvider.js.map +1 -0
- package/lib/module/DraxScrollView.js +167 -0
- package/lib/module/DraxScrollView.js.map +1 -0
- package/lib/module/DraxSubprovider.js +21 -0
- package/lib/module/DraxSubprovider.js.map +1 -0
- package/lib/module/DraxView.js +348 -0
- package/lib/module/DraxView.js.map +1 -0
- package/lib/module/HoverLayer.js +152 -0
- package/lib/module/HoverLayer.js.map +1 -0
- package/lib/module/SortableBoardContainer.js +386 -0
- package/lib/module/SortableBoardContainer.js.map +1 -0
- package/lib/module/SortableBoardContext.js +6 -0
- package/lib/module/SortableBoardContext.js.map +1 -0
- package/lib/module/SortableContainer.js +561 -0
- package/lib/module/SortableContainer.js.map +1 -0
- package/lib/module/SortableItem.js +226 -0
- package/lib/module/SortableItem.js.map +1 -0
- package/lib/module/SortableItemContext.js +38 -0
- package/lib/module/SortableItemContext.js.map +1 -0
- package/lib/module/compat/detectVersion.js +19 -0
- package/lib/module/compat/detectVersion.js.map +1 -0
- package/lib/module/compat/index.js +5 -0
- package/lib/module/compat/index.js.map +1 -0
- package/lib/module/compat/types.js +4 -0
- package/lib/module/compat/types.js.map +1 -0
- package/lib/module/compat/useDraxPanGesture.js +94 -0
- package/lib/module/compat/useDraxPanGesture.js.map +1 -0
- package/lib/module/hooks/index.js +5 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/hooks/useCallbackDispatch.js +681 -0
- package/lib/module/hooks/useCallbackDispatch.js.map +1 -0
- package/lib/module/hooks/useDragGesture.js +240 -0
- package/lib/module/hooks/useDragGesture.js.map +1 -0
- package/lib/module/hooks/useDraxContext.js +12 -0
- package/lib/module/hooks/useDraxContext.js.map +1 -0
- package/lib/module/hooks/useDraxId.js +13 -0
- package/lib/module/hooks/useDraxId.js.map +1 -0
- package/lib/module/hooks/useDraxMethods.js +73 -0
- package/lib/module/hooks/useDraxMethods.js.map +1 -0
- package/lib/module/hooks/useDraxScrollHandler.js +97 -0
- package/lib/module/hooks/useDraxScrollHandler.js.map +1 -0
- package/lib/module/hooks/useSortableBoard.js +37 -0
- package/lib/module/hooks/useSortableBoard.js.map +1 -0
- package/lib/module/hooks/useSortableList.js +824 -0
- package/lib/module/hooks/useSortableList.js.map +1 -0
- package/lib/module/hooks/useSpatialIndex.js +283 -0
- package/lib/module/hooks/useSpatialIndex.js.map +1 -0
- package/lib/module/hooks/useViewStyles.js +158 -0
- package/lib/module/hooks/useViewStyles.js.map +1 -0
- package/lib/module/hooks/useWebScrollFreeze.js +52 -0
- package/lib/module/hooks/useWebScrollFreeze.js.map +1 -0
- package/lib/module/index.js +37 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/math.js +222 -0
- package/lib/module/math.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/params.js +88 -0
- package/lib/module/params.js.map +1 -0
- package/lib/module/types.js +213 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/DebugOverlay.d.ts +17 -0
- package/lib/typescript/src/DebugOverlay.d.ts.map +1 -0
- package/lib/typescript/src/Drax.d.ts +28 -0
- package/lib/typescript/src/Drax.d.ts.map +1 -0
- package/lib/typescript/src/DraxContext.d.ts +3 -0
- package/lib/typescript/src/DraxContext.d.ts.map +1 -0
- package/lib/typescript/src/DraxHandle.d.ts +25 -0
- package/lib/typescript/src/DraxHandle.d.ts.map +1 -0
- package/lib/typescript/src/DraxHandleContext.d.ts +12 -0
- package/lib/typescript/src/DraxHandleContext.d.ts.map +1 -0
- package/lib/typescript/src/DraxList.d.ts +66 -0
- package/lib/typescript/src/DraxList.d.ts.map +1 -0
- package/lib/typescript/src/DraxProvider.d.ts +4 -0
- package/lib/typescript/src/DraxProvider.d.ts.map +1 -0
- package/lib/typescript/src/DraxScrollView.d.ts +7 -0
- package/lib/typescript/src/DraxScrollView.d.ts.map +1 -0
- package/lib/typescript/src/DraxSubprovider.d.ts +4 -0
- package/lib/typescript/src/DraxSubprovider.d.ts.map +1 -0
- package/lib/typescript/src/DraxView.d.ts +4 -0
- package/lib/typescript/src/DraxView.d.ts.map +1 -0
- package/lib/typescript/src/HoverLayer.d.ts +38 -0
- package/lib/typescript/src/HoverLayer.d.ts.map +1 -0
- package/lib/typescript/src/SortableBoardContainer.d.ts +11 -0
- package/lib/typescript/src/SortableBoardContainer.d.ts.map +1 -0
- package/lib/typescript/src/SortableBoardContext.d.ts +4 -0
- package/lib/typescript/src/SortableBoardContext.d.ts.map +1 -0
- package/lib/typescript/src/SortableContainer.d.ts +13 -0
- package/lib/typescript/src/SortableContainer.d.ts.map +1 -0
- package/lib/typescript/src/SortableItem.d.ts +14 -0
- package/lib/typescript/src/SortableItem.d.ts.map +1 -0
- package/lib/typescript/src/SortableItemContext.d.ts +37 -0
- package/lib/typescript/src/SortableItemContext.d.ts.map +1 -0
- package/lib/typescript/src/compat/detectVersion.d.ts +2 -0
- package/lib/typescript/src/compat/detectVersion.d.ts.map +1 -0
- package/lib/typescript/src/compat/index.d.ts +4 -0
- package/lib/typescript/src/compat/index.d.ts.map +1 -0
- package/lib/typescript/src/compat/types.d.ts +33 -0
- package/lib/typescript/src/compat/types.d.ts.map +1 -0
- package/lib/typescript/src/compat/useDraxPanGesture.d.ts +8 -0
- package/lib/typescript/src/compat/useDraxPanGesture.d.ts.map +1 -0
- package/lib/typescript/src/hooks/index.d.ts +3 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useCallbackDispatch.d.ts +40 -0
- package/lib/typescript/src/hooks/useCallbackDispatch.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useDragGesture.d.ts +17 -0
- package/lib/typescript/src/hooks/useDragGesture.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useDraxContext.d.ts +2 -0
- package/lib/typescript/src/hooks/useDraxContext.d.ts.map +1 -0
- package/{build → lib/typescript/src}/hooks/useDraxId.d.ts +1 -0
- package/lib/typescript/src/hooks/useDraxId.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useDraxMethods.d.ts +13 -0
- package/lib/typescript/src/hooks/useDraxMethods.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useDraxScrollHandler.d.ts +27 -0
- package/lib/typescript/src/hooks/useDraxScrollHandler.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useSortableBoard.d.ts +10 -0
- package/lib/typescript/src/hooks/useSortableBoard.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useSortableList.d.ts +11 -0
- package/lib/typescript/src/hooks/useSortableList.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useSpatialIndex.d.ts +22 -0
- package/lib/typescript/src/hooks/useSpatialIndex.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useViewStyles.d.ts +183 -0
- package/lib/typescript/src/hooks/useViewStyles.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useWebScrollFreeze.d.ts +14 -0
- package/lib/typescript/src/hooks/useWebScrollFreeze.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +25 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/math.d.ts +52 -0
- package/lib/typescript/src/math.d.ts.map +1 -0
- package/{build → lib/typescript/src}/params.d.ts +13 -9
- package/lib/typescript/src/params.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +743 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +164 -34
- package/src/DebugOverlay.tsx +140 -0
- package/src/Drax.ts +33 -0
- package/src/DraxContext.ts +8 -0
- package/src/DraxHandle.tsx +52 -0
- package/src/DraxHandleContext.ts +15 -0
- package/src/DraxList.tsx +181 -0
- package/src/DraxProvider.tsx +224 -0
- package/src/DraxScrollView.tsx +180 -0
- package/src/DraxSubprovider.tsx +22 -0
- package/src/DraxView.tsx +430 -0
- package/src/HoverLayer.tsx +167 -0
- package/src/SortableBoardContainer.tsx +439 -0
- package/src/SortableBoardContext.ts +6 -0
- package/src/SortableContainer.tsx +642 -0
- package/src/SortableItem.tsx +264 -0
- package/src/SortableItemContext.ts +46 -0
- package/src/compat/detectVersion.ts +17 -0
- package/src/compat/index.ts +7 -0
- package/src/compat/types.ts +35 -0
- package/src/compat/useDraxPanGesture.ts +112 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useCallbackDispatch.tsx +823 -0
- package/src/hooks/useDragGesture.ts +273 -0
- package/src/hooks/useDraxContext.ts +11 -0
- package/src/hooks/useDraxId.ts +11 -0
- package/src/hooks/useDraxMethods.ts +71 -0
- package/src/hooks/useDraxScrollHandler.ts +121 -0
- package/src/hooks/useSortableBoard.ts +44 -0
- package/src/hooks/useSortableList.ts +868 -0
- package/src/hooks/useSpatialIndex.ts +336 -0
- package/src/hooks/useViewStyles.ts +180 -0
- package/src/hooks/useWebScrollFreeze.ts +60 -0
- package/src/index.ts +110 -0
- package/src/math.ts +251 -0
- package/src/params.ts +74 -0
- package/src/types.ts +919 -0
- package/.editorconfig +0 -15
- package/.eslintrc.js +0 -4
- package/.prettierrc +0 -16
- package/CHANGELOG.md +0 -270
- package/CODE-OF-CONDUCT.md +0 -85
- package/CONTRIBUTING.md +0 -15
- package/FUNDING.yml +0 -4
- package/build/AllHoverViews.d.ts +0 -0
- package/build/AllHoverViews.js +0 -30
- package/build/DraxContext.d.ts +0 -2
- package/build/DraxContext.js +0 -6
- package/build/DraxList.d.ts +0 -8
- package/build/DraxList.js +0 -512
- package/build/DraxListItem.d.ts +0 -7
- package/build/DraxListItem.js +0 -121
- package/build/DraxProvider.d.ts +0 -2
- package/build/DraxProvider.js +0 -704
- package/build/DraxScrollView.d.ts +0 -6
- package/build/DraxScrollView.js +0 -136
- package/build/DraxSubprovider.d.ts +0 -3
- package/build/DraxSubprovider.js +0 -18
- package/build/DraxView.d.ts +0 -8
- package/build/DraxView.js +0 -93
- package/build/HoverView.d.ts +0 -8
- package/build/HoverView.js +0 -40
- package/build/PanGestureDetector.d.ts +0 -3
- package/build/PanGestureDetector.js +0 -49
- package/build/hooks/index.d.ts +0 -4
- package/build/hooks/index.js +0 -11
- package/build/hooks/useContent.d.ts +0 -23
- package/build/hooks/useContent.js +0 -212
- package/build/hooks/useDraxContext.d.ts +0 -1
- package/build/hooks/useDraxContext.js +0 -13
- package/build/hooks/useDraxId.js +0 -13
- package/build/hooks/useDraxProtocol.d.ts +0 -5
- package/build/hooks/useDraxProtocol.js +0 -32
- package/build/hooks/useDraxRegistry.d.ts +0 -78
- package/build/hooks/useDraxRegistry.js +0 -714
- package/build/hooks/useDraxScrollHandler.d.ts +0 -25
- package/build/hooks/useDraxScrollHandler.js +0 -89
- package/build/hooks/useDraxState.d.ts +0 -10
- package/build/hooks/useDraxState.js +0 -132
- package/build/hooks/useMeasurements.d.ts +0 -8
- package/build/hooks/useMeasurements.js +0 -118
- package/build/hooks/useStatus.d.ts +0 -11
- package/build/hooks/useStatus.js +0 -96
- package/build/index.d.ts +0 -9
- package/build/index.js +0 -33
- package/build/math.d.ts +0 -22
- package/build/math.js +0 -68
- package/build/params.js +0 -27
- package/build/transform.d.ts +0 -11
- package/build/transform.js +0 -59
- package/build/types.d.ts +0 -807
- package/build/types.js +0 -46
- package/docs/concept.md +0 -79
- package/docs/images/color-drag-drop.gif +0 -0
- package/docs/images/deck-cards.gif +0 -0
- package/docs/images/drag-drop-events.jpg +0 -0
- package/docs/images/knight-moves.gif +0 -0
- package/docs/images/reorderable-list.gif +0 -0
package/src/DraxView.tsx
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import type { ComponentRef, ReactNode } from 'react';
|
|
2
|
+
import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import { Platform } from 'react-native';
|
|
4
|
+
import { GestureDetector } from 'react-native-gesture-handler';
|
|
5
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
6
|
+
import Reanimated, { useAnimatedReaction, useSharedValue } from 'react-native-reanimated';
|
|
7
|
+
|
|
8
|
+
import { DraxHandleContext } from './DraxHandleContext';
|
|
9
|
+
import { DraxSubprovider } from './DraxSubprovider';
|
|
10
|
+
import { useDraxContext, useDraxId } from './hooks';
|
|
11
|
+
import { useDragGesture } from './hooks/useDragGesture';
|
|
12
|
+
import { isDraggable as computeIsDraggable } from './hooks/useSpatialIndex';
|
|
13
|
+
import { useViewStyles } from './hooks/useViewStyles';
|
|
14
|
+
import { defaultLongPressDelay } from './params';
|
|
15
|
+
import type {
|
|
16
|
+
DraxViewMeasurementHandler,
|
|
17
|
+
DraxViewMeasurements,
|
|
18
|
+
DraxViewProps,
|
|
19
|
+
Position,
|
|
20
|
+
} from './types';
|
|
21
|
+
import { DraxViewDragStatus, DraxViewReceiveStatus } from './types';
|
|
22
|
+
|
|
23
|
+
/** Keys that should NOT be passed through to Reanimated.View */
|
|
24
|
+
const DRAX_PROP_KEYS: ReadonlySet<string> = new Set([
|
|
25
|
+
'renderContent',
|
|
26
|
+
'renderHoverContent',
|
|
27
|
+
'noHover',
|
|
28
|
+
'registration',
|
|
29
|
+
'onMeasure',
|
|
30
|
+
'parent',
|
|
31
|
+
'isParent',
|
|
32
|
+
'scrollPosition',
|
|
33
|
+
'longPressDelay',
|
|
34
|
+
'lockDragXPosition',
|
|
35
|
+
'lockDragYPosition',
|
|
36
|
+
'id',
|
|
37
|
+
// Callback props
|
|
38
|
+
'onDragStart',
|
|
39
|
+
'onDrag',
|
|
40
|
+
'onDragEnter',
|
|
41
|
+
'onDragOver',
|
|
42
|
+
'onDragExit',
|
|
43
|
+
'onDragEnd',
|
|
44
|
+
'onDragDrop',
|
|
45
|
+
'onSnapEnd',
|
|
46
|
+
'onReceiveSnapEnd',
|
|
47
|
+
'onReceiveDragEnter',
|
|
48
|
+
'onReceiveDragOver',
|
|
49
|
+
'onReceiveDragExit',
|
|
50
|
+
'onReceiveDragDrop',
|
|
51
|
+
'onMonitorDragStart',
|
|
52
|
+
'onMonitorDragEnter',
|
|
53
|
+
'onMonitorDragOver',
|
|
54
|
+
'onMonitorDragExit',
|
|
55
|
+
'onMonitorDragEnd',
|
|
56
|
+
'onMonitorDragDrop',
|
|
57
|
+
'animateSnap',
|
|
58
|
+
'snapDelay',
|
|
59
|
+
'snapDuration',
|
|
60
|
+
'snapAnimator',
|
|
61
|
+
'dragPayload',
|
|
62
|
+
'receiverPayload',
|
|
63
|
+
'payload',
|
|
64
|
+
'draggable',
|
|
65
|
+
'receptive',
|
|
66
|
+
'monitoring',
|
|
67
|
+
'rejectOwnChildren',
|
|
68
|
+
'disableHoverViewMeasurementsOnLayout',
|
|
69
|
+
'dynamicReceptiveCallback',
|
|
70
|
+
'acceptsDrag',
|
|
71
|
+
'dragBoundsRef',
|
|
72
|
+
// Style props (handled by useViewStyles)
|
|
73
|
+
'style',
|
|
74
|
+
'dragInactiveStyle',
|
|
75
|
+
'draggingStyle',
|
|
76
|
+
'draggingWithReceiverStyle',
|
|
77
|
+
'draggingWithoutReceiverStyle',
|
|
78
|
+
'dragReleasedStyle',
|
|
79
|
+
'hoverStyle',
|
|
80
|
+
'hoverDraggingStyle',
|
|
81
|
+
'hoverDraggingWithReceiverStyle',
|
|
82
|
+
'hoverDraggingWithoutReceiverStyle',
|
|
83
|
+
'hoverDragReleasedStyle',
|
|
84
|
+
'receiverInactiveStyle',
|
|
85
|
+
'receivingStyle',
|
|
86
|
+
'otherDraggingStyle',
|
|
87
|
+
'otherDraggingWithReceiverStyle',
|
|
88
|
+
'otherDraggingWithoutReceiverStyle',
|
|
89
|
+
'dragHandle',
|
|
90
|
+
'dragActivationFailOffset',
|
|
91
|
+
'collisionAlgorithm',
|
|
92
|
+
'scrollHorizontal',
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
/** Extract only ViewProps-compatible props by filtering out Drax-specific keys */
|
|
96
|
+
function extractViewProps(props: DraxViewProps): Record<string, unknown> {
|
|
97
|
+
const viewProps: Record<string, unknown> = {};
|
|
98
|
+
for (const [key, value] of Object.entries(props)) {
|
|
99
|
+
if (!DRAX_PROP_KEYS.has(key)) {
|
|
100
|
+
viewProps[key] = value;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return viewProps;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Isolated hook for scroll-position → spatial-index sync.
|
|
108
|
+
* Kept separate from DraxView so the worklet closure only captures
|
|
109
|
+
* SharedValues — never React refs from the component scope. The worklets
|
|
110
|
+
* serializer recursively freezes all plain objects in a worklet's closure,
|
|
111
|
+
* which would freeze useRef objects and trigger "Tried to modify key `current`"
|
|
112
|
+
* warnings when React nullifies refs on unmount.
|
|
113
|
+
*/
|
|
114
|
+
function useScrollPositionSync(
|
|
115
|
+
scrollPosition: SharedValue<Position> | undefined,
|
|
116
|
+
spatialIndexSV: SharedValue<number>,
|
|
117
|
+
scrollOffsetsSV: SharedValue<Position[]>
|
|
118
|
+
) {
|
|
119
|
+
useAnimatedReaction(
|
|
120
|
+
() => scrollPosition?.value,
|
|
121
|
+
(pos, prev) => {
|
|
122
|
+
'worklet';
|
|
123
|
+
if (!pos) return;
|
|
124
|
+
if (prev && pos.x === prev.x && pos.y === prev.y) return;
|
|
125
|
+
const idx = spatialIndexSV.value;
|
|
126
|
+
if (idx < 0) return;
|
|
127
|
+
scrollOffsetsSV.modify((offsets) => {
|
|
128
|
+
if (idx >= 0 && idx < offsets.length) {
|
|
129
|
+
offsets[idx] = pos;
|
|
130
|
+
}
|
|
131
|
+
return offsets;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const DraxView = memo((props: DraxViewProps): ReactNode => {
|
|
138
|
+
const {
|
|
139
|
+
renderContent,
|
|
140
|
+
registration,
|
|
141
|
+
onMeasure,
|
|
142
|
+
parent: parentProp,
|
|
143
|
+
isParent,
|
|
144
|
+
scrollPosition,
|
|
145
|
+
longPressDelay = defaultLongPressDelay,
|
|
146
|
+
lockDragXPosition,
|
|
147
|
+
lockDragYPosition,
|
|
148
|
+
scrollHorizontal,
|
|
149
|
+
dragHandle,
|
|
150
|
+
dragBoundsRef,
|
|
151
|
+
children,
|
|
152
|
+
style,
|
|
153
|
+
id: idProp,
|
|
154
|
+
} = props;
|
|
155
|
+
|
|
156
|
+
// Determine capabilities from props (shared with useSpatialIndex)
|
|
157
|
+
const draggable = computeIsDraggable(props);
|
|
158
|
+
|
|
159
|
+
// Unique id
|
|
160
|
+
const id = useDraxId(idProp);
|
|
161
|
+
|
|
162
|
+
// Connect with Drax context
|
|
163
|
+
const {
|
|
164
|
+
registerView,
|
|
165
|
+
unregisterView,
|
|
166
|
+
updateMeasurements: updateMeasurementsCtx,
|
|
167
|
+
updateViewProps,
|
|
168
|
+
getViewEntry,
|
|
169
|
+
rootViewRef,
|
|
170
|
+
scrollOffsetsSV,
|
|
171
|
+
parent: contextParent,
|
|
172
|
+
} = useDraxContext();
|
|
173
|
+
|
|
174
|
+
// Parent view (from prop or context)
|
|
175
|
+
const parent = parentProp ?? contextParent;
|
|
176
|
+
const parentId = parent?.id;
|
|
177
|
+
const parentViewRef = parent ? parent.viewRef : rootViewRef;
|
|
178
|
+
|
|
179
|
+
// View ref for measuring
|
|
180
|
+
const viewRef = useRef<ComponentRef<typeof Reanimated.View>>(null);
|
|
181
|
+
const measurementsRef = useRef<DraxViewMeasurements | undefined>(undefined);
|
|
182
|
+
|
|
183
|
+
// ── Measurement ────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
/** Finalize measurements and notify consumers.
|
|
186
|
+
* `transformDetected` = 1 when auto-detection found transform-based positioning
|
|
187
|
+
* (visual measurement used instead of Yoga layout). Consumers can check
|
|
188
|
+
* `measurements._transformDetected` to know whether shift subtraction is needed. */
|
|
189
|
+
const finalizeMeasurement = useCallback(
|
|
190
|
+
(x: number, y: number, width: number, height: number, handler?: DraxViewMeasurementHandler, transformDetected = 0) => {
|
|
191
|
+
const measurements: DraxViewMeasurements = { height, x, y, width, _transformDetected: transformDetected };
|
|
192
|
+
measurementsRef.current = measurements;
|
|
193
|
+
updateMeasurementsCtx(id, measurements);
|
|
194
|
+
onMeasure?.(measurements);
|
|
195
|
+
handler?.(measurements);
|
|
196
|
+
},
|
|
197
|
+
[id, updateMeasurementsCtx, onMeasure],
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const measureWithHandler = useCallback((handler?: DraxViewMeasurementHandler) => {
|
|
201
|
+
const view = viewRef.current;
|
|
202
|
+
if (!view || !parentViewRef.current) return;
|
|
203
|
+
|
|
204
|
+
view.measureLayout(
|
|
205
|
+
parentViewRef.current,
|
|
206
|
+
(x, y, width, height) => {
|
|
207
|
+
if (Platform.OS === 'web') {
|
|
208
|
+
// On web, measureLayout returns visual positions — add scroll to
|
|
209
|
+
// convert to content-relative.
|
|
210
|
+
const parentData = parentId ? getViewEntry(parentId) : undefined;
|
|
211
|
+
const parentScroll = parentData?.scrollPosition?.value ?? { x: 0, y: 0 };
|
|
212
|
+
finalizeMeasurement(x! + parentScroll.x, y! + parentScroll.y, width!, height!, handler);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// On Fabric, measureLayout uses includeTransform=false → returns Yoga
|
|
217
|
+
// layout positions. This is correct for FlatList/FlashList, but wrong
|
|
218
|
+
// for LegendList (which positions items via translateY, so all report y=0).
|
|
219
|
+
//
|
|
220
|
+
// Auto-detect: when measureLayout returns position=0 (the hallmark of
|
|
221
|
+
// transform-positioned items: position:absolute, top:0), also call
|
|
222
|
+
// measure() to get the visual position. If it differs, the item is
|
|
223
|
+
// transform-positioned. Only check when layoutPosition=0 to avoid
|
|
224
|
+
// false positives on shifted items (whose measureLayout is non-zero).
|
|
225
|
+
const layoutX = x!;
|
|
226
|
+
const layoutY = y!;
|
|
227
|
+
if (layoutX !== 0 && layoutY !== 0) {
|
|
228
|
+
// Non-zero layout position → normal Yoga layout, trust measureLayout.
|
|
229
|
+
finalizeMeasurement(layoutX, layoutY, width!, height!, handler);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const parentView = parentViewRef.current;
|
|
233
|
+
if (!parentView) {
|
|
234
|
+
finalizeMeasurement(layoutX, layoutY, width!, height!, handler);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
view.measure((_vx: number, _vy: number, _vw: number, _vh: number, pageX: number, pageY: number) => {
|
|
238
|
+
parentView.measure((_px: number, _py: number, _pw: number, _ph: number, parentPageX: number, parentPageY: number) => {
|
|
239
|
+
const parentData = parentId ? getViewEntry(parentId) : undefined;
|
|
240
|
+
const parentScroll = parentData?.scrollPosition?.value ?? { x: 0, y: 0 };
|
|
241
|
+
const visualX = pageX - parentPageX + parentScroll.x;
|
|
242
|
+
const visualY = pageY - parentPageY + parentScroll.y;
|
|
243
|
+
// If visual position differs from layout, the view is transform-positioned.
|
|
244
|
+
if (Math.abs(visualX - layoutX) > 1 || Math.abs(visualY - layoutY) > 1) {
|
|
245
|
+
finalizeMeasurement(visualX, visualY, width!, height!, handler, 1);
|
|
246
|
+
} else {
|
|
247
|
+
finalizeMeasurement(layoutX, layoutY, width!, height!, handler);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
},
|
|
252
|
+
() => {}
|
|
253
|
+
);
|
|
254
|
+
}, [id, parentId, viewRef, parentViewRef, getViewEntry, finalizeMeasurement]);
|
|
255
|
+
|
|
256
|
+
// ── Register/unregister with context ────────────────────────────────
|
|
257
|
+
// Keep a ref to the latest props so registry always has current callbacks
|
|
258
|
+
const propsRef = useRef(props);
|
|
259
|
+
propsRef.current = props;
|
|
260
|
+
|
|
261
|
+
useEffect(() => {
|
|
262
|
+
registerView({
|
|
263
|
+
id,
|
|
264
|
+
parentId,
|
|
265
|
+
scrollPosition,
|
|
266
|
+
props: propsRef.current,
|
|
267
|
+
});
|
|
268
|
+
// Re-measure after registration. onLayout may have fired before
|
|
269
|
+
// registerView (useEffect runs after paint), causing updateMeasurements
|
|
270
|
+
// to silently drop data (entry didn't exist yet in registry).
|
|
271
|
+
measureWithHandler();
|
|
272
|
+
return () => unregisterView(id);
|
|
273
|
+
}, [id, parentId, scrollPosition, registerView, unregisterView, measureWithHandler]);
|
|
274
|
+
|
|
275
|
+
// ── Update registry when props change ────────────────────────────────
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
updateViewProps(id, propsRef.current);
|
|
278
|
+
}, [id, updateViewProps, draggable, props.receptive, props.monitoring, props.collisionAlgorithm]);
|
|
279
|
+
|
|
280
|
+
const onLayout = () => {
|
|
281
|
+
measureWithHandler();
|
|
282
|
+
// Re-measure drag bounds on every layout change. The initial useEffect
|
|
283
|
+
// measurement may fire before the parent flex layout has settled (especially
|
|
284
|
+
// on native where Fabric commits layout asynchronously). By the time this
|
|
285
|
+
// DraxView receives onLayout, the bounds view's layout is also finalized.
|
|
286
|
+
if (dragBoundsRef?.current && rootViewRef.current) {
|
|
287
|
+
dragBoundsRef.current.measureLayout(
|
|
288
|
+
rootViewRef.current,
|
|
289
|
+
(x: number, y: number, width: number, height: number) => {
|
|
290
|
+
dragBoundsSV.value = { x, y, width, height };
|
|
291
|
+
},
|
|
292
|
+
() => {}
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// External registration — useLayoutEffect so SortableItem's FLIP
|
|
298
|
+
// useLayoutEffect (which runs after children) sees measureFnRef.
|
|
299
|
+
useLayoutEffect(() => {
|
|
300
|
+
if (registration) {
|
|
301
|
+
registration({ id, measure: measureWithHandler });
|
|
302
|
+
return () => registration(undefined);
|
|
303
|
+
}
|
|
304
|
+
return undefined;
|
|
305
|
+
}, [id, measureWithHandler, registration]);
|
|
306
|
+
|
|
307
|
+
// ── Gesture (per-view, UI thread) ──────────────────────────────────
|
|
308
|
+
// Use a SharedValue for spatialIndex so it updates reactively after registration
|
|
309
|
+
const spatialIndexSV = useSharedValue(-1);
|
|
310
|
+
|
|
311
|
+
// Update spatialIndex after registration completes
|
|
312
|
+
useEffect(() => {
|
|
313
|
+
const entry = getViewEntry(id);
|
|
314
|
+
const index = entry?.spatialIndex ?? -1;
|
|
315
|
+
spatialIndexSV.value = index;
|
|
316
|
+
}, [id, getViewEntry, spatialIndexSV]);
|
|
317
|
+
|
|
318
|
+
// Sync scroll position to spatial index — delegated to a separate hook
|
|
319
|
+
// so the worklet closure only contains SharedValues (no refs from DraxView scope).
|
|
320
|
+
useScrollPositionSync(scrollPosition, spatialIndexSV, scrollOffsetsSV);
|
|
321
|
+
|
|
322
|
+
// SharedValues for gesture config — RNGH 3.0 reconfigures the native
|
|
323
|
+
// handler on the UI thread, bypassing JS→native bridge entirely.
|
|
324
|
+
const draggableSV = useSharedValue(draggable);
|
|
325
|
+
const longPressDelaySV = useSharedValue(longPressDelay);
|
|
326
|
+
|
|
327
|
+
// Update SharedValues when props change (in useEffect to avoid render-time writes)
|
|
328
|
+
useEffect(() => {
|
|
329
|
+
draggableSV.value = draggable;
|
|
330
|
+
longPressDelaySV.value = longPressDelay;
|
|
331
|
+
}, [draggable, longPressDelay, draggableSV, longPressDelaySV]);
|
|
332
|
+
|
|
333
|
+
// Drag bounds: measure the bounds view relative to root and store in SharedValue
|
|
334
|
+
const dragBoundsSV = useSharedValue<{ x: number; y: number; width: number; height: number } | null>(null);
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
if (dragBoundsRef?.current && rootViewRef.current) {
|
|
337
|
+
dragBoundsRef.current.measureLayout(
|
|
338
|
+
rootViewRef.current,
|
|
339
|
+
(x: number, y: number, width: number, height: number) => {
|
|
340
|
+
dragBoundsSV.value = { x, y, width, height };
|
|
341
|
+
},
|
|
342
|
+
() => {}
|
|
343
|
+
);
|
|
344
|
+
} else {
|
|
345
|
+
dragBoundsSV.value = null;
|
|
346
|
+
}
|
|
347
|
+
}, [dragBoundsRef, rootViewRef, dragBoundsSV]);
|
|
348
|
+
|
|
349
|
+
const gesture = useDragGesture(
|
|
350
|
+
id,
|
|
351
|
+
spatialIndexSV,
|
|
352
|
+
draggableSV,
|
|
353
|
+
longPressDelaySV,
|
|
354
|
+
lockDragXPosition,
|
|
355
|
+
lockDragYPosition,
|
|
356
|
+
dragBoundsSV,
|
|
357
|
+
props.dragActivationFailOffset,
|
|
358
|
+
scrollHorizontal
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
// ── Animated styles ────────────────────────────────────────────────
|
|
362
|
+
const { animatedDragStyle } = useViewStyles(id, props);
|
|
363
|
+
|
|
364
|
+
// ── Memoize parent for DraxSubprovider ──────────────────────────────
|
|
365
|
+
const subproviderParent = useMemo(
|
|
366
|
+
() => ({ id, viewRef }),
|
|
367
|
+
[id, viewRef]
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
// ── Rendered children ──────────────────────────────────────────────
|
|
371
|
+
let renderedContent: ReactNode;
|
|
372
|
+
if (renderContent) {
|
|
373
|
+
renderedContent = renderContent({
|
|
374
|
+
viewState: {
|
|
375
|
+
dragStatus: DraxViewDragStatus.Inactive,
|
|
376
|
+
receiveStatus: DraxViewReceiveStatus.Inactive,
|
|
377
|
+
},
|
|
378
|
+
hover: false,
|
|
379
|
+
children,
|
|
380
|
+
dimensions: measurementsRef.current
|
|
381
|
+
? {
|
|
382
|
+
width: measurementsRef.current.width,
|
|
383
|
+
height: measurementsRef.current.height,
|
|
384
|
+
}
|
|
385
|
+
: undefined,
|
|
386
|
+
});
|
|
387
|
+
} else {
|
|
388
|
+
renderedContent = children;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (isParent) {
|
|
392
|
+
renderedContent = (
|
|
393
|
+
<DraxSubprovider parent={subproviderParent}>{renderedContent}</DraxSubprovider>
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// When dragHandle is true, provide the gesture via context so DraxHandle can attach it
|
|
398
|
+
if (dragHandle) {
|
|
399
|
+
renderedContent = (
|
|
400
|
+
<DraxHandleContext.Provider value={{ gesture }}>
|
|
401
|
+
{renderedContent}
|
|
402
|
+
</DraxHandleContext.Provider>
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ── Extract view-safe props ─────────────────────────────────────
|
|
407
|
+
// DraxView is memo()'d so props identity is stable between renders.
|
|
408
|
+
const viewProps = useMemo(() => extractViewProps(props), [props]);
|
|
409
|
+
|
|
410
|
+
// ── Render ─────────────────────────────────────────────────────────
|
|
411
|
+
const viewElement = (
|
|
412
|
+
<Reanimated.View
|
|
413
|
+
{...viewProps}
|
|
414
|
+
style={[style, animatedDragStyle]}
|
|
415
|
+
ref={viewRef}
|
|
416
|
+
onLayout={onLayout}
|
|
417
|
+
collapsable={false}
|
|
418
|
+
>
|
|
419
|
+
{renderedContent}
|
|
420
|
+
</Reanimated.View>
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
// When dragHandle is true, skip the GestureDetector wrapper —
|
|
424
|
+
// the gesture is attached to the DraxHandle child instead.
|
|
425
|
+
if (dragHandle) {
|
|
426
|
+
return viewElement;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return <GestureDetector gesture={gesture}>{viewElement}</GestureDetector>;
|
|
430
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { ReactNode, RefObject } from 'react';
|
|
2
|
+
import { memo, useLayoutEffect } from 'react';
|
|
3
|
+
import type { ViewStyle } from 'react-native';
|
|
4
|
+
import { StyleSheet } from 'react-native';
|
|
5
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
6
|
+
import Reanimated, { useAnimatedStyle } from 'react-native-reanimated';
|
|
7
|
+
import { runOnUI } from 'react-native-worklets';
|
|
8
|
+
|
|
9
|
+
import type { DragPhase, Position } from './types';
|
|
10
|
+
|
|
11
|
+
/** Flattened hover styles for the currently dragged view */
|
|
12
|
+
export interface FlattenedHoverStyles {
|
|
13
|
+
hoverStyle: ViewStyle | null;
|
|
14
|
+
hoverDraggingStyle: ViewStyle | null;
|
|
15
|
+
hoverDraggingWithReceiverStyle: ViewStyle | null;
|
|
16
|
+
hoverDraggingWithoutReceiverStyle: ViewStyle | null;
|
|
17
|
+
hoverDragReleasedStyle: ViewStyle | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface HoverLayerProps {
|
|
21
|
+
hoverContentRef: RefObject<ReactNode>;
|
|
22
|
+
/** Changing this value triggers a re-render to pick up new ref content */
|
|
23
|
+
hoverVersion: number;
|
|
24
|
+
hoverPositionSV: SharedValue<Position>;
|
|
25
|
+
dragPhaseSV: SharedValue<DragPhase>;
|
|
26
|
+
receiverIdSV: SharedValue<string>;
|
|
27
|
+
/** Set to true after hover content is committed — SortableItem reads this for visibility */
|
|
28
|
+
hoverReadySV: SharedValue<boolean>;
|
|
29
|
+
/** Animated hover content dimensions. x=width, y=height. {0,0}=no constraint. */
|
|
30
|
+
hoverDimsSV: SharedValue<Position>;
|
|
31
|
+
/** Ref to flattened hover styles of the currently dragged view */
|
|
32
|
+
hoverStylesRef: RefObject<FlattenedHoverStyles | null>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Single hover layer component that renders the hover content during drag.
|
|
37
|
+
*
|
|
38
|
+
* This is the ONLY component that reads hoverPositionSV (changes every frame).
|
|
39
|
+
* All other DraxViews read draggedIdSV/receiverIdSV/dragPhaseSV which change ~5x per drag.
|
|
40
|
+
*
|
|
41
|
+
* Content is passed via ref to avoid re-rendering the entire DraxProvider tree.
|
|
42
|
+
* Only this component re-renders when hover content changes (via hoverVersion).
|
|
43
|
+
*/
|
|
44
|
+
export const HoverLayer = memo(
|
|
45
|
+
({ hoverContentRef, hoverVersion, hoverPositionSV, dragPhaseSV, receiverIdSV, hoverReadySV, hoverDimsSV, hoverStylesRef }: HoverLayerProps) => {
|
|
46
|
+
// After hover content is committed to the DOM, activate drag phase + signal readiness.
|
|
47
|
+
// dragPhaseSV is NOT set in the gesture handler — it's set HERE, ensuring:
|
|
48
|
+
// 1. HoverLayer becomes visible (opacity 1) only AFTER content is rendered
|
|
49
|
+
// 2. SortableItem hides only AFTER hover is visible (reads hoverReadySV)
|
|
50
|
+
// Both writes happen in the same runOnUI call → same UI frame → no blink.
|
|
51
|
+
useLayoutEffect(() => {
|
|
52
|
+
if (hoverContentRef.current != null) {
|
|
53
|
+
runOnUI((_dragPhaseSV: SharedValue<DragPhase>, _hoverReadySV: SharedValue<boolean>) => {
|
|
54
|
+
'worklet';
|
|
55
|
+
_dragPhaseSV.value = 'dragging';
|
|
56
|
+
_hoverReadySV.value = true;
|
|
57
|
+
})(dragPhaseSV, hoverReadySV);
|
|
58
|
+
}
|
|
59
|
+
}, [hoverVersion]);
|
|
60
|
+
|
|
61
|
+
// Read hover styles from ref in the component body — they're captured by the
|
|
62
|
+
// worklet closure when the component re-renders (on hoverVersion change).
|
|
63
|
+
// This ensures the latest styles are available without SharedValues.
|
|
64
|
+
const hs = hoverStylesRef.current;
|
|
65
|
+
const flatHoverStyle = hs?.hoverStyle ?? null;
|
|
66
|
+
const flatHoverDraggingStyle = hs?.hoverDraggingStyle ?? null;
|
|
67
|
+
const flatHoverDraggingWithReceiverStyle = hs?.hoverDraggingWithReceiverStyle ?? null;
|
|
68
|
+
const flatHoverDraggingWithoutReceiverStyle = hs?.hoverDraggingWithoutReceiverStyle ?? null;
|
|
69
|
+
const flatHoverDragReleasedStyle = hs?.hoverDragReleasedStyle ?? null;
|
|
70
|
+
|
|
71
|
+
// Position style: applied to the outer full-screen container.
|
|
72
|
+
// Only handles positioning (translate) and visibility (opacity).
|
|
73
|
+
const positionStyle = useAnimatedStyle(() => {
|
|
74
|
+
const phase = dragPhaseSV.value;
|
|
75
|
+
if (phase === 'idle') {
|
|
76
|
+
return { opacity: 0 };
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
opacity: 1,
|
|
80
|
+
transform: [
|
|
81
|
+
{ translateX: hoverPositionSV.value.x },
|
|
82
|
+
{ translateY: hoverPositionSV.value.y },
|
|
83
|
+
] as const,
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Visual style: applied to the inner content wrapper.
|
|
88
|
+
// Handles user hover styles (border, shadow, scale, rotate, etc.)
|
|
89
|
+
// so they apply to the content bounds, not the full-screen container.
|
|
90
|
+
const visualStyle = useAnimatedStyle(() => {
|
|
91
|
+
const phase = dragPhaseSV.value;
|
|
92
|
+
if (phase === 'idle') {
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let hoverStyles: ViewStyle;
|
|
97
|
+
if (phase === 'dragging') {
|
|
98
|
+
const hasReceiver = receiverIdSV.value !== '';
|
|
99
|
+
hoverStyles = {
|
|
100
|
+
...(flatHoverStyle ?? {}),
|
|
101
|
+
...(flatHoverDraggingStyle ?? {}),
|
|
102
|
+
...(hasReceiver
|
|
103
|
+
? (flatHoverDraggingWithReceiverStyle ?? {})
|
|
104
|
+
: (flatHoverDraggingWithoutReceiverStyle ?? {})),
|
|
105
|
+
};
|
|
106
|
+
} else {
|
|
107
|
+
// phase === 'releasing'
|
|
108
|
+
hoverStyles = {
|
|
109
|
+
...(flatHoverStyle ?? {}),
|
|
110
|
+
...(flatHoverDragReleasedStyle ?? flatHoverDraggingStyle ?? {}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// User transforms (rotate, scale, etc.) stay on the content wrapper.
|
|
115
|
+
const { transform: userTransform, ...restStyles } = hoverStyles;
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
...restStyles,
|
|
119
|
+
...(userTransform ? { transform: userTransform as { [key: string]: number }[] } : {}),
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Animated dimensions for the inner content wrapper.
|
|
124
|
+
// When hoverDimsSV is non-zero, constrains hover content to those dimensions
|
|
125
|
+
// so cross-container transfers animate smoothly from source to target size.
|
|
126
|
+
const dimensionStyle = useAnimatedStyle(() => {
|
|
127
|
+
const dims = hoverDimsSV.value;
|
|
128
|
+
if (dims.x > 0 && dims.y > 0) {
|
|
129
|
+
return {
|
|
130
|
+
width: dims.x,
|
|
131
|
+
height: dims.y,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return {};
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Always render the Reanimated.View — never conditionally unmount it.
|
|
138
|
+
// If we returned null when content is empty, remounting causes a one-frame
|
|
139
|
+
// flash (the view renders at default position before useAnimatedStyle kicks in).
|
|
140
|
+
return (
|
|
141
|
+
<Reanimated.View
|
|
142
|
+
style={[styles.container, positionStyle]}
|
|
143
|
+
pointerEvents="none"
|
|
144
|
+
>
|
|
145
|
+
<Reanimated.View style={[styles.content, dimensionStyle, visualStyle]}>
|
|
146
|
+
{hoverContentRef.current}
|
|
147
|
+
</Reanimated.View>
|
|
148
|
+
</Reanimated.View>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const styles = StyleSheet.create({
|
|
154
|
+
container: {
|
|
155
|
+
...StyleSheet.absoluteFillObject,
|
|
156
|
+
// Default hidden — useAnimatedStyle overrides to opacity:1 when dragging.
|
|
157
|
+
// Prevents a one-frame flash on first mount before the animated style evaluates.
|
|
158
|
+
opacity: 0,
|
|
159
|
+
transformOrigin: 'top left',
|
|
160
|
+
},
|
|
161
|
+
content: {
|
|
162
|
+
// Shrink-wrap to content width — without this the inner view stretches
|
|
163
|
+
// to fill the full-screen absolute parent, causing hover styles (border,
|
|
164
|
+
// shadow) to render at screen width instead of content width.
|
|
165
|
+
alignSelf: 'flex-start',
|
|
166
|
+
},
|
|
167
|
+
});
|