react-native-gtkx 0.3.0-alpha.1 → 0.4.0-alpha.2
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/README.md +1 -1
- package/dist/adw/index.d.ts +3 -2
- package/dist/adw/index.js +21 -4
- package/dist/adw/index.js.map +1 -1
- package/dist/adw/widgets.generated.d.ts +1 -1
- package/dist/adw/widgets.generated.js +2 -2
- package/dist/adw/widgets.generated.js.map +1 -1
- package/dist/animated/create-animated.d.ts +2 -0
- package/dist/animated/create-animated.js +2 -0
- package/dist/animated/create-animated.js.map +1 -1
- package/dist/animated/event.d.ts +15 -0
- package/dist/animated/event.js +80 -0
- package/dist/animated/event.js.map +1 -0
- package/dist/animated/index.d.ts +4 -0
- package/dist/animated/index.js +8 -0
- package/dist/animated/index.js.map +1 -1
- package/dist/animated/spring.d.ts +10 -0
- package/dist/animated/spring.js +20 -6
- package/dist/animated/spring.js.map +1 -1
- package/dist/animated/timing.d.ts +10 -0
- package/dist/animated/timing.js +21 -13
- package/dist/animated/timing.js.map +1 -1
- package/dist/apis/host.gtkx.js +70 -11
- package/dist/apis/host.gtkx.js.map +1 -1
- package/dist/common/navigation-stack.d.ts +6 -3
- package/dist/common/navigation-stack.js +8 -1
- package/dist/common/navigation-stack.js.map +1 -1
- package/dist/common/widget.js +2 -11
- package/dist/common/widget.js.map +1 -1
- package/dist/components/animated.d.ts +1 -0
- package/dist/components/animated.js +28 -4
- package/dist/components/animated.js.map +1 -1
- package/dist/components/app-registry.js +32 -8
- package/dist/components/app-registry.js.map +1 -1
- package/dist/components/request-animation-frame.d.ts +33 -0
- package/dist/components/request-animation-frame.js +83 -0
- package/dist/components/request-animation-frame.js.map +1 -0
- package/dist/components/scroll-view.js +60 -2
- package/dist/components/scroll-view.js.map +1 -1
- package/dist/components/virtualized-list.js +1 -4
- package/dist/components/virtualized-list.js.map +1 -1
- package/dist/dnd/autoscroll.d.ts +44 -0
- package/dist/dnd/autoscroll.js +176 -0
- package/dist/dnd/autoscroll.js.map +1 -0
- package/dist/dnd/grid-order.d.ts +45 -0
- package/dist/dnd/grid-order.js +151 -0
- package/dist/dnd/grid-order.js.map +1 -0
- package/dist/dnd/grid.d.ts +44 -0
- package/dist/dnd/grid.js +397 -0
- package/dist/dnd/grid.js.map +1 -0
- package/dist/dnd/gtk-controllers.d.ts +16 -2
- package/dist/dnd/gtk-controllers.js +23 -3
- package/dist/dnd/gtk-controllers.js.map +1 -1
- package/dist/dnd/index.d.ts +4 -2
- package/dist/dnd/index.js +9 -2
- package/dist/dnd/index.js.map +1 -1
- package/dist/dnd/order-state.d.ts +14 -0
- package/dist/dnd/order-state.js +55 -0
- package/dist/dnd/order-state.js.map +1 -0
- package/dist/dnd/order.d.ts +19 -0
- package/dist/dnd/order.js +19 -0
- package/dist/dnd/order.js.map +1 -1
- package/dist/dnd/sortable.d.ts +29 -4
- package/dist/dnd/sortable.js +308 -115
- package/dist/dnd/sortable.js.map +1 -1
- package/dist/dnd/types.d.ts +236 -14
- package/dist/dnd/types.js +30 -0
- package/dist/dnd/types.js.map +1 -1
- package/dist/gesture-handler-compat/attach-context.d.ts +29 -0
- package/dist/gesture-handler-compat/attach-context.js +90 -0
- package/dist/gesture-handler-compat/attach-context.js.map +1 -0
- package/dist/gesture-handler-compat/detector-runtime.d.ts +14 -3
- package/dist/gesture-handler-compat/detector-runtime.js +15 -5
- package/dist/gesture-handler-compat/detector-runtime.js.map +1 -1
- package/dist/gesture-handler-compat/detector.js +57 -2
- package/dist/gesture-handler-compat/detector.js.map +1 -1
- package/dist/gesture-handler-compat/gesture-state-manager.d.ts +5 -0
- package/dist/gesture-handler-compat/gesture-state-manager.js +77 -0
- package/dist/gesture-handler-compat/gesture-state-manager.js.map +1 -0
- package/dist/gesture-handler-compat/index.d.ts +2 -2
- package/dist/gesture-handler-compat/index.js +38 -33
- package/dist/gesture-handler-compat/index.js.map +1 -1
- package/dist/gesture-handler-compat/recognizer.d.ts +12 -1
- package/dist/gesture-handler-compat/recognizer.js +23 -9
- package/dist/gesture-handler-compat/recognizer.js.map +1 -1
- package/dist/gesture-handler-compat/tag-registry.d.ts +14 -0
- package/dist/gesture-handler-compat/tag-registry.js +19 -0
- package/dist/gesture-handler-compat/tag-registry.js.map +1 -0
- package/dist/globals/index.d.ts +47 -0
- package/dist/globals/index.js +181 -0
- package/dist/globals/index.js.map +1 -0
- package/dist/globals/install.d.ts +1 -0
- package/dist/globals/install.js +14 -0
- package/dist/globals/install.js.map +1 -0
- package/dist/gtkx/bridge/adw-namespace.d.ts +2 -0
- package/dist/gtkx/bridge/adw-namespace.js +26 -0
- package/dist/gtkx/bridge/adw-namespace.js.map +1 -0
- package/dist/gtkx/bridge/adw.d.ts +23 -0
- package/dist/gtkx/bridge/adw.js +93 -0
- package/dist/gtkx/bridge/adw.js.map +1 -0
- package/dist/gtkx/bridge/color-scheme-parse.d.ts +11 -0
- package/dist/gtkx/bridge/color-scheme-parse.js +42 -0
- package/dist/gtkx/bridge/color-scheme-parse.js.map +1 -0
- package/dist/gtkx/bridge/color-scheme-portal.d.ts +24 -0
- package/dist/gtkx/bridge/color-scheme-portal.js +172 -0
- package/dist/gtkx/bridge/color-scheme-portal.js.map +1 -0
- package/dist/gtkx/bridge/core.d.ts +25 -0
- package/dist/gtkx/bridge/core.js +69 -0
- package/dist/gtkx/bridge/core.js.map +1 -0
- package/dist/gtkx/bridge/drag-layer.d.ts +27 -0
- package/dist/gtkx/bridge/drag-layer.js +177 -0
- package/dist/gtkx/bridge/drag-layer.js.map +1 -0
- package/dist/gtkx/bridge/index.d.ts +1 -27
- package/dist/gtkx/bridge/index.js +12 -71
- package/dist/gtkx/bridge/index.js.map +1 -1
- package/dist/gtkx/bridge/widgets.generated.adw.d.ts +1 -0
- package/dist/gtkx/bridge/widgets.generated.adw.js +16 -0
- package/dist/gtkx/bridge/widgets.generated.adw.js.map +1 -0
- package/dist/gtkx/bridge/widgets.generated.d.ts +0 -1
- package/dist/gtkx/bridge/widgets.generated.js +3 -3
- package/dist/gtkx/bridge/widgets.generated.js.map +1 -1
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/data/generated.d.ts +41 -20
- package/dist/mcp/data/generated.js +46 -20
- package/dist/mcp/data/generated.js.map +1 -1
- package/dist/metro/index.d.ts +1 -0
- package/dist/metro/index.js +12 -0
- package/dist/metro/index.js.map +1 -1
- package/dist/navigation/sidebar.js +2 -45
- package/dist/navigation/sidebar.js.map +1 -1
- package/dist/reanimated-compat/animatable-value.d.ts +158 -0
- package/dist/reanimated-compat/animatable-value.js +316 -0
- package/dist/reanimated-compat/animatable-value.js.map +1 -0
- package/dist/reanimated-compat/animation.d.ts +34 -13
- package/dist/reanimated-compat/animation.js +97 -25
- package/dist/reanimated-compat/animation.js.map +1 -1
- package/dist/reanimated-compat/mutable.js +6 -6
- package/dist/reanimated-compat/mutable.js.map +1 -1
- package/dist/reanimated-compat/updater-animations.js +32 -12
- package/dist/reanimated-compat/updater-animations.js.map +1 -1
- package/dist/runner/host-dev.js +5 -1
- package/dist/runner/host-dev.js.map +1 -1
- package/dist/runner/host.js +9 -1
- package/dist/runner/host.js.map +1 -1
- package/dist/vite/index.js +130 -0
- package/dist/vite/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/gtkx/bridge/theme.d.ts +0 -4
- package/dist/gtkx/bridge/theme.js +0 -5
- package/dist/gtkx/bridge/theme.js.map +0 -1
package/dist/dnd/sortable.js
CHANGED
|
@@ -1,75 +1,60 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
2
|
-
// `Sortable`, `SortableItem`, `useSortable
|
|
3
|
-
//
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// `Sortable`, `SortableItem`, `useSortable`, `useSortableList` and their
|
|
3
|
+
// horizontal counterparts (`useHorizontalSortable`, `useHorizontalSortableList`)
|
|
4
|
+
// — the drag-to-reorder list, which is the shape most apps actually reach
|
|
5
|
+
// for, in either direction.
|
|
4
6
|
//
|
|
5
7
|
// The reorder is LIVE: crossing another row moves the dragged row into its
|
|
6
8
|
// place immediately, so the list rearranges under the drag icon. That is
|
|
7
9
|
// upstream's behaviour too — its `onMove` fires as rows cross, not at the
|
|
8
10
|
// end — minus the spring, because here the rows are laid out by Yoga rather
|
|
9
|
-
// than transformed.
|
|
11
|
+
// than transformed. The mechanism does not care which axis the list scrolls
|
|
12
|
+
// along, which is why `SortableDirection.Horizontal` is a render-time branch
|
|
13
|
+
// here (a horizontal `ScrollView`, `leftBound`/`autoScrollHorizontalDirection`
|
|
14
|
+
// plumbing) rather than a second implementation.
|
|
15
|
+
//
|
|
16
|
+
// WHAT DECIDES A CROSSING, and why it changed: this used to be GDK
|
|
17
|
+
// hit-testing the raw pointer against a neighbour row's full rect (a row's
|
|
18
|
+
// own `onEnter`) — grab-point DEPENDENT, unlike upstream's own item-rect
|
|
19
|
+
// reasoning (`docs/research/dnd-collision-feel.md`). It is now the dragged
|
|
20
|
+
// row's own TRACKED position, upstream's shape without upstream's asymmetry:
|
|
21
|
+
// `useEdgeAutoscroll`'s `GtkDropControllerMotion` (already watching every
|
|
22
|
+
// motion event for edge-autoscroll) also reports each one here via
|
|
23
|
+
// `onDragMotion`, and `handleDragMotion` below tracks `fromIndex * slotSize +
|
|
24
|
+
// (pointer delta since the drag began)`, resolving the row it lands on by
|
|
25
|
+
// ROUNDING rather than upstream's own floor — the dragged row's CENTRE
|
|
26
|
+
// against a slot's centre, not its top-left corner against the slot's origin
|
|
27
|
+
// — see `order.ts`'s `resolveTrackedIndex` for the arithmetic. The origin
|
|
28
|
+
// that delta is measured from is `DragSourceControllers`'s `onGrab`,
|
|
29
|
+
// converted to the list's own container coordinates with `computePointIn`,
|
|
30
|
+
// NEVER the first motion sample: a fast drag's first sample arrives already
|
|
31
|
+
// displaced past GDK's own drag-start threshold, undercounting travel.
|
|
32
|
+
// `SortableItem`'s own `onEnter` no longer drives a reorder at all — only
|
|
33
|
+
// `onMotion` still does, for the public `onDragging`/`onDraggingHorizontal`
|
|
34
|
+
// callbacks, unrelated to this decision.
|
|
10
35
|
//
|
|
11
36
|
// The component owns the order, exactly as upstream requires ("do NOT update
|
|
12
37
|
// external state in `onMove`"). An app reads the settled order from
|
|
13
38
|
// `onDrop`'s `allPositions`.
|
|
14
39
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
40
|
+
import { widgetForHandle } from "../components/measure";
|
|
15
41
|
import { ScrollView } from "../components/scroll-view";
|
|
16
42
|
import { View } from "../components/view";
|
|
43
|
+
import { computePointIn } from "../gtkx/bridge/index";
|
|
44
|
+
import { useEdgeAutoscroll } from "./autoscroll";
|
|
17
45
|
import { DraggableContext, DraggableHandle } from "./draggable";
|
|
18
46
|
import { DragSourceControllers, DropTargetControllers } from "./gtk-controllers";
|
|
19
|
-
import { listToObject } from "./order";
|
|
20
|
-
import {
|
|
21
|
-
import { ScrollDirection, SortableDirection, } from "./types";
|
|
47
|
+
import { listToObject, resolveTrackedIndex } from "./order";
|
|
48
|
+
import { keyOf, useOrder } from "./order-state";
|
|
49
|
+
import { HorizontalScrollDirection, ScrollDirection, SortableDirection, } from "./types";
|
|
22
50
|
// Upstream exports these from the same entry point as the components, so
|
|
23
51
|
// they are re-exported here rather than moved; the implementations live in
|
|
24
52
|
// ./order, which imports nothing from GTK.
|
|
25
53
|
export { clamp, listToObject, objectMove } from "./order";
|
|
26
54
|
const SortableContext = createContext(null);
|
|
27
|
-
const keyOf = (item, index, extractor) => extractor?.(item, index) ?? item.id;
|
|
28
|
-
/** The order state, shared by `useSortableList` and `Sortable`. */
|
|
29
|
-
const useOrder = (data, itemKeyExtractor) => {
|
|
30
|
-
// Lazy initial state, not a ref: stable for the list's lifetime, and a ref
|
|
31
|
-
// may not be read during render.
|
|
32
|
-
const [scope] = useState(() => `sortable-${nextDraggableId()}`);
|
|
33
|
-
const incoming = useMemo(() => data.map((item, index) => keyOf(item, index, itemKeyExtractor)), [data, itemKeyExtractor]);
|
|
34
|
-
const [order, setOrder] = useState(incoming);
|
|
35
|
-
// The list owns the ORDER; the app owns the SET. Adding or removing an item
|
|
36
|
-
// has to reach the order without discarding a reorder the user already
|
|
37
|
-
// made — so new ids go to the end, departed ids drop out, and a pure
|
|
38
|
-
// reorder of `data` by the app is ignored (upstream's contract too: the
|
|
39
|
-
// component owns the order).
|
|
40
|
-
//
|
|
41
|
-
// Adjusted DURING RENDER rather than in an effect. React documents this as
|
|
42
|
-
// the way to derive state from changed props, and it matters here: an
|
|
43
|
-
// effect would paint one frame in the stale order every time the app
|
|
44
|
-
// appends an item.
|
|
45
|
-
const signature = incoming.join(" ");
|
|
46
|
-
const [seenSignature, setSeenSignature] = useState(signature);
|
|
47
|
-
if (seenSignature !== signature) {
|
|
48
|
-
setSeenSignature(signature);
|
|
49
|
-
const known = new Set(incoming);
|
|
50
|
-
const next = [
|
|
51
|
-
...order.filter((id) => known.has(id)),
|
|
52
|
-
...incoming.filter((id) => !order.includes(id)),
|
|
53
|
-
];
|
|
54
|
-
if (next.length !== order.length ||
|
|
55
|
-
next.some((id, index) => id !== order[index])) {
|
|
56
|
-
setOrder(next);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
const items = useMemo(() => {
|
|
60
|
-
const byId = new Map();
|
|
61
|
-
data.forEach((item, index) => {
|
|
62
|
-
byId.set(keyOf(item, index, itemKeyExtractor), item);
|
|
63
|
-
});
|
|
64
|
-
return order
|
|
65
|
-
.map((id) => byId.get(id))
|
|
66
|
-
.filter((item) => item !== undefined);
|
|
67
|
-
}, [order, data, itemKeyExtractor]);
|
|
68
|
-
return { scope, order, setOrder, items };
|
|
69
|
-
};
|
|
70
55
|
/** The `SharedValue`-shaped boxes upstream hands to `renderItem`. Real,
|
|
71
56
|
* readable and writable here — just not animated. See `SharedValueLike`. */
|
|
72
|
-
const useSharedBoxes = (order) => {
|
|
57
|
+
const useSharedBoxes = (order, direction, autoScrollDirection) => {
|
|
73
58
|
// Rebuilt when the order changes rather than mutated in place. Upstream's
|
|
74
59
|
// box keeps its identity and changes its `.value`; here the identity tracks
|
|
75
60
|
// the value, which is the closest thing available without a mutable cell
|
|
@@ -79,28 +64,59 @@ const useSharedBoxes = (order) => {
|
|
|
79
64
|
const positions = useMemo(() => ({
|
|
80
65
|
value: listToObject(order),
|
|
81
66
|
}), [order]);
|
|
82
|
-
//
|
|
83
|
-
// position
|
|
84
|
-
//
|
|
85
|
-
//
|
|
67
|
+
// Two of these three this platform never writes: there is no UI-thread
|
|
68
|
+
// scroll position and no measured row heights, because Yoga lays rows out
|
|
69
|
+
// at their natural height. `autoScrollDirection` IS written now — see
|
|
70
|
+
// `autoscroll.tsx` — which is why it comes in as a parameter rather than
|
|
71
|
+
// living in this idle bag.
|
|
86
72
|
const idle = useMemo(() => ({
|
|
87
73
|
scrollY: { value: 0 },
|
|
88
|
-
|
|
74
|
+
scrollX: { value: 0 },
|
|
89
75
|
itemHeights: { value: {} },
|
|
90
76
|
}), []);
|
|
91
77
|
const itemsCount = order.length;
|
|
92
78
|
return useMemo(() => ({
|
|
93
79
|
positions,
|
|
94
80
|
...idle,
|
|
95
|
-
plumbing: () =>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
81
|
+
plumbing: (itemWidth, gap, paddingHorizontal) => direction === SortableDirection.Horizontal
|
|
82
|
+
? {
|
|
83
|
+
positions,
|
|
84
|
+
leftBound: idle.scrollX,
|
|
85
|
+
autoScrollHorizontalDirection: autoScrollDirection,
|
|
86
|
+
itemsCount,
|
|
87
|
+
itemWidth,
|
|
88
|
+
gap,
|
|
89
|
+
paddingHorizontal,
|
|
90
|
+
}
|
|
91
|
+
: {
|
|
92
|
+
positions,
|
|
93
|
+
lowerBound: idle.scrollY,
|
|
94
|
+
autoScrollDirection: autoScrollDirection,
|
|
95
|
+
itemHeights: idle.itemHeights,
|
|
96
|
+
itemsCount,
|
|
97
|
+
},
|
|
98
|
+
}), [positions, idle, itemsCount, direction, autoScrollDirection]);
|
|
99
|
+
};
|
|
100
|
+
const noneDirectionFor = (direction) => direction === SortableDirection.Horizontal
|
|
101
|
+
? HorizontalScrollDirection.None
|
|
102
|
+
: ScrollDirection.None;
|
|
103
|
+
const directionForDelta = (direction) => (dx, dy) => {
|
|
104
|
+
if (direction === SortableDirection.Horizontal) {
|
|
105
|
+
if (dx < 0) {
|
|
106
|
+
return HorizontalScrollDirection.Left;
|
|
107
|
+
}
|
|
108
|
+
if (dx > 0) {
|
|
109
|
+
return HorizontalScrollDirection.Right;
|
|
110
|
+
}
|
|
111
|
+
return HorizontalScrollDirection.None;
|
|
112
|
+
}
|
|
113
|
+
if (dy < 0) {
|
|
114
|
+
return ScrollDirection.Up;
|
|
115
|
+
}
|
|
116
|
+
if (dy > 0) {
|
|
117
|
+
return ScrollDirection.Down;
|
|
118
|
+
}
|
|
119
|
+
return ScrollDirection.None;
|
|
104
120
|
};
|
|
105
121
|
/**
|
|
106
122
|
* Owns the order of a sortable list, for an app rendering its own container.
|
|
@@ -111,16 +127,21 @@ const useSharedBoxes = (order) => {
|
|
|
111
127
|
* `SortableItem` outside one throws rather than quietly not dragging. This
|
|
112
128
|
* hook is for reading the order and the `SharedValueLike` boxes, which is
|
|
113
129
|
* what most callers of it actually want.
|
|
130
|
+
*
|
|
131
|
+
* No autoscroll wiring: this hook builds no `ScrollView` of its own to drive
|
|
132
|
+
* one, hence the no-op `handleScroll`/`handleScrollEnd` below, unchanged from
|
|
133
|
+
* before. `Sortable` is where the edge autoscroll in `autoscroll.tsx` lives.
|
|
114
134
|
*/
|
|
115
135
|
export const useSortableList = (options) => {
|
|
116
136
|
const { order, items } = useOrder(options.data, options.itemKeyExtractor);
|
|
117
|
-
const
|
|
137
|
+
const idleAutoScroll = useMemo(() => ({ value: ScrollDirection.None }), []);
|
|
138
|
+
const boxes = useSharedBoxes(order, SortableDirection.Vertical, idleAutoScroll);
|
|
118
139
|
const dropProviderRef = useRef(null);
|
|
119
140
|
const noop = useCallback(() => { }, []);
|
|
120
141
|
return {
|
|
121
142
|
positions: boxes.positions,
|
|
122
143
|
scrollY: boxes.scrollY,
|
|
123
|
-
autoScroll:
|
|
144
|
+
autoScroll: idleAutoScroll,
|
|
124
145
|
itemHeights: boxes.itemHeights,
|
|
125
146
|
dropProviderRef,
|
|
126
147
|
handleScroll: noop,
|
|
@@ -128,7 +149,53 @@ export const useSortableList = (options) => {
|
|
|
128
149
|
contentHeight: 0,
|
|
129
150
|
isDynamicHeight: false,
|
|
130
151
|
items,
|
|
131
|
-
getItemProps: boxes.plumbing,
|
|
152
|
+
getItemProps: () => boxes.plumbing(),
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* The horizontal counterpart of {@link useSortableList} — its own hook
|
|
157
|
+
* upstream, so its own hook here, even though the order state underneath
|
|
158
|
+
* (`order-state.ts`) is identical.
|
|
159
|
+
*/
|
|
160
|
+
export const useHorizontalSortableList = (options) => {
|
|
161
|
+
const { order, items } = useOrder(options.data, options.itemKeyExtractor);
|
|
162
|
+
const idleAutoScroll = useMemo(() => ({
|
|
163
|
+
value: HorizontalScrollDirection.None,
|
|
164
|
+
}), []);
|
|
165
|
+
const boxes = useSharedBoxes(order, SortableDirection.Horizontal, idleAutoScroll);
|
|
166
|
+
const dropProviderRef = useRef(null);
|
|
167
|
+
const noop = useCallback(() => { }, []);
|
|
168
|
+
const { itemWidth, gap = 0, paddingHorizontal = 0, itemKeyExtractor, } = options;
|
|
169
|
+
const getItemProps = useCallback((item, index) => {
|
|
170
|
+
const plumbing = boxes.plumbing(itemWidth, gap, paddingHorizontal);
|
|
171
|
+
return {
|
|
172
|
+
id: keyOf(item, index, itemKeyExtractor),
|
|
173
|
+
positions: plumbing.positions,
|
|
174
|
+
leftBound: plumbing.leftBound,
|
|
175
|
+
autoScrollDirection: idleAutoScroll,
|
|
176
|
+
itemsCount: plumbing.itemsCount,
|
|
177
|
+
itemWidth: itemWidth ?? 0,
|
|
178
|
+
gap,
|
|
179
|
+
paddingHorizontal,
|
|
180
|
+
};
|
|
181
|
+
}, [
|
|
182
|
+
boxes,
|
|
183
|
+
itemWidth,
|
|
184
|
+
gap,
|
|
185
|
+
paddingHorizontal,
|
|
186
|
+
itemKeyExtractor,
|
|
187
|
+
idleAutoScroll,
|
|
188
|
+
]);
|
|
189
|
+
return {
|
|
190
|
+
positions: boxes.positions,
|
|
191
|
+
scrollX: boxes.scrollX,
|
|
192
|
+
autoScroll: idleAutoScroll,
|
|
193
|
+
dropProviderRef,
|
|
194
|
+
handleScroll: noop,
|
|
195
|
+
handleScrollEnd: noop,
|
|
196
|
+
contentWidth: 0,
|
|
197
|
+
items,
|
|
198
|
+
getItemProps,
|
|
132
199
|
};
|
|
133
200
|
};
|
|
134
201
|
/**
|
|
@@ -153,10 +220,14 @@ export const useSortableList = (options) => {
|
|
|
153
220
|
*
|
|
154
221
|
* The rows carry a real GTK drag icon — a picture of the row itself, lifted
|
|
155
222
|
* at the point it was grabbed — and the list rearranges live underneath it.
|
|
223
|
+
* Near either end of the visible list, the drag keeps the `ScrollView`
|
|
224
|
+
* scrolling toward it for as long as it stays there (`autoscroll.tsx`).
|
|
156
225
|
*/
|
|
157
|
-
export const Sortable = ({ data, renderItem, itemKeyExtractor, style, contentContainerStyle, testID, direction = SortableDirection.Vertical, onMove, onDragStart, onDrop, onDragging, }) => {
|
|
226
|
+
export const Sortable = ({ data, renderItem, itemKeyExtractor, style, contentContainerStyle, testID, direction = SortableDirection.Vertical, itemWidth, gap, paddingHorizontal, onMove, onDragStart, onDrop, onDragging, onDraggingHorizontal, }) => {
|
|
158
227
|
const { scope, order, setOrder, items } = useOrder(data, itemKeyExtractor);
|
|
159
|
-
const
|
|
228
|
+
const isHorizontal = direction === SortableDirection.Horizontal;
|
|
229
|
+
const containerRef = useRef(null);
|
|
230
|
+
const scrollViewRef = useRef(null);
|
|
160
231
|
// The order as it was when the drag began, so a cancelled drag puts the
|
|
161
232
|
// list back rather than leaving it wherever the pointer happened to be.
|
|
162
233
|
const beforeDrag = useRef(null);
|
|
@@ -167,10 +238,6 @@ export const Sortable = ({ data, renderItem, itemKeyExtractor, style, contentCon
|
|
|
167
238
|
useEffect(() => {
|
|
168
239
|
orderRef.current = order;
|
|
169
240
|
}, [order]);
|
|
170
|
-
const beginDrag = useCallback((draggedId) => {
|
|
171
|
-
beforeDrag.current = orderRef.current;
|
|
172
|
-
onDragStart?.(draggedId, orderRef.current.indexOf(draggedId));
|
|
173
|
-
}, [onDragStart]);
|
|
174
241
|
const moveOnto = useCallback((draggedId, targetId) => {
|
|
175
242
|
setOrder((current) => {
|
|
176
243
|
const from = current.indexOf(draggedId);
|
|
@@ -185,7 +252,71 @@ export const Sortable = ({ data, renderItem, itemKeyExtractor, style, contentCon
|
|
|
185
252
|
return next;
|
|
186
253
|
});
|
|
187
254
|
}, [setOrder, onMove]);
|
|
255
|
+
// The dragged row's own tracked position, and the slot it last resolved
|
|
256
|
+
// to — null whenever no drag from this list is in flight, or `beginDrag`
|
|
257
|
+
// could not establish an origin (see below). A ref, not state: written
|
|
258
|
+
// from a GTK motion callback outside React's commit cycle, exactly like
|
|
259
|
+
// `beforeDrag`/`orderRef` above.
|
|
260
|
+
const tracking = useRef(null);
|
|
261
|
+
const handleDragMotion = useCallback((x, y) => {
|
|
262
|
+
const state = tracking.current;
|
|
263
|
+
if (!state) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const trackedPosition = (isHorizontal ? x : y) + state.base;
|
|
267
|
+
const resolvedIndex = resolveTrackedIndex(trackedPosition, state.slotSize, orderRef.current.length);
|
|
268
|
+
if (resolvedIndex === state.lastResolvedIndex) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
state.lastResolvedIndex = resolvedIndex;
|
|
272
|
+
const targetId = orderRef.current[resolvedIndex];
|
|
273
|
+
if (targetId !== undefined && targetId !== state.draggedId) {
|
|
274
|
+
moveOnto(state.draggedId, targetId);
|
|
275
|
+
}
|
|
276
|
+
}, [isHorizontal, moveOnto]);
|
|
277
|
+
const autoscroll = useEdgeAutoscroll({
|
|
278
|
+
containerRef,
|
|
279
|
+
scrollViewRef,
|
|
280
|
+
axes: isHorizontal ? "horizontal" : "vertical",
|
|
281
|
+
none: noneDirectionFor(direction),
|
|
282
|
+
directionFor: directionForDelta(direction),
|
|
283
|
+
onDragMotion: handleDragMotion,
|
|
284
|
+
});
|
|
285
|
+
const boxes = useSharedBoxes(order, direction, autoscroll.direction);
|
|
286
|
+
const beginDrag = useCallback((draggedId, grabWidget, grabX, grabY) => {
|
|
287
|
+
beforeDrag.current = orderRef.current;
|
|
288
|
+
autoscroll.setActive(true);
|
|
289
|
+
const fromIndex = orderRef.current.indexOf(draggedId);
|
|
290
|
+
onDragStart?.(draggedId, fromIndex);
|
|
291
|
+
tracking.current = null;
|
|
292
|
+
const container = widgetForHandle(containerRef.current);
|
|
293
|
+
if (grabWidget && container && fromIndex !== -1) {
|
|
294
|
+
const origin = computePointIn(grabWidget, container, grabX, grabY);
|
|
295
|
+
if (origin) {
|
|
296
|
+
// The row's own real size, measured rather than a hint: rows are
|
|
297
|
+
// Yoga-natural-height (docs/api.md), so there is no `itemHeight`
|
|
298
|
+
// prop to trust — the dragged row's own allocation, at the moment
|
|
299
|
+
// it was grabbed, is what the tracked position's slots are sized
|
|
300
|
+
// to. `gap` (a real Yoga gap on the content container either
|
|
301
|
+
// direction) folds into the slot period the same way upstream's
|
|
302
|
+
// own `itemHeight` would if it accounted for one.
|
|
303
|
+
const rawSize = isHorizontal
|
|
304
|
+
? grabWidget.getWidth()
|
|
305
|
+
: grabWidget.getHeight();
|
|
306
|
+
const slotSize = rawSize + (gap ?? 0);
|
|
307
|
+
const origin1D = isHorizontal ? origin.x : origin.y;
|
|
308
|
+
tracking.current = {
|
|
309
|
+
draggedId,
|
|
310
|
+
base: fromIndex * slotSize - origin1D,
|
|
311
|
+
slotSize,
|
|
312
|
+
lastResolvedIndex: fromIndex,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}, [onDragStart, autoscroll, isHorizontal, gap]);
|
|
188
317
|
const endDrag = useCallback((draggedId, cancelled) => {
|
|
318
|
+
autoscroll.setActive(false);
|
|
319
|
+
tracking.current = null;
|
|
189
320
|
const restore = beforeDrag.current;
|
|
190
321
|
beforeDrag.current = null;
|
|
191
322
|
if (cancelled) {
|
|
@@ -196,37 +327,59 @@ export const Sortable = ({ data, renderItem, itemKeyExtractor, style, contentCon
|
|
|
196
327
|
}
|
|
197
328
|
const settled = orderRef.current;
|
|
198
329
|
onDrop?.(draggedId, settled.indexOf(draggedId), listToObject(settled));
|
|
199
|
-
}, [setOrder, onDrop]);
|
|
200
|
-
const contextValue = useMemo(() => ({
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
330
|
+
}, [setOrder, onDrop, autoscroll]);
|
|
331
|
+
const contextValue = useMemo(() => ({
|
|
332
|
+
scope,
|
|
333
|
+
direction,
|
|
334
|
+
moveOnto,
|
|
335
|
+
beginDrag,
|
|
336
|
+
endDrag,
|
|
337
|
+
onDragging,
|
|
338
|
+
onDraggingHorizontal,
|
|
339
|
+
}), [
|
|
340
|
+
scope,
|
|
341
|
+
direction,
|
|
342
|
+
moveOnto,
|
|
343
|
+
beginDrag,
|
|
344
|
+
endDrag,
|
|
345
|
+
onDragging,
|
|
346
|
+
onDraggingHorizontal,
|
|
347
|
+
]);
|
|
348
|
+
return (_jsx(SortableContext.Provider, { value: contextValue, children: _jsxs(View, { ref: containerRef, style: { flex: 1 }, children: [autoscroll.controllers, _jsx(ScrollView, { ref: scrollViewRef, horizontal: isHorizontal, style: style,
|
|
349
|
+
// No `alignItems: "stretch"` override here any more. It used to be
|
|
350
|
+
// needed because this platform's ScrollView content container
|
|
351
|
+
// defaulted to `flex-start`, which shrank every row to its intrinsic
|
|
352
|
+
// width and collapsed the `flex: 1` text column inside it. That
|
|
353
|
+
// default was a parity bug — RN's content container is a plain
|
|
354
|
+
// `View`, whose default `alignItems` is `stretch` — and it is fixed
|
|
355
|
+
// at the source now (components/scroll-view.tsx), so a `Sortable`
|
|
356
|
+
// gets full-width rows the same way any other RN list does.
|
|
357
|
+
//
|
|
358
|
+
// An app that wants the old behaviour writes it, exactly as it
|
|
359
|
+
// would on iOS and Android: `contentContainerStyle={{ alignItems:
|
|
360
|
+
// "flex-start" }}`.
|
|
361
|
+
contentContainerStyle: [
|
|
362
|
+
{ gap, paddingHorizontal },
|
|
363
|
+
contentContainerStyle,
|
|
364
|
+
], testID: testID, children: items.map((item, index) => renderItem({
|
|
365
|
+
item,
|
|
366
|
+
index,
|
|
367
|
+
id: keyOf(item, index, itemKeyExtractor),
|
|
368
|
+
direction,
|
|
369
|
+
...boxes.plumbing(itemWidth, gap, paddingHorizontal),
|
|
370
|
+
})) })] }) }));
|
|
223
371
|
};
|
|
224
372
|
/**
|
|
225
373
|
* The reorder half of the module, as a hook, for a row that owns its own
|
|
226
|
-
* view.
|
|
374
|
+
* view.
|
|
375
|
+
*
|
|
376
|
+
* Shared by both directions: which coordinate reaches `onDragging`/
|
|
377
|
+
* `onDraggingHorizontal` (and which of the two fires at all) comes from the
|
|
378
|
+
* enclosing `Sortable`'s own `direction`, read through context — the reorder
|
|
379
|
+
* itself (cross into another cell's drop target) never looks at an axis.
|
|
227
380
|
*/
|
|
228
381
|
export const useSortable = (options) => {
|
|
229
|
-
const { id, onDragging } = options;
|
|
382
|
+
const { id, onDragging, onDraggingHorizontal } = options;
|
|
230
383
|
const list = useContext(SortableContext);
|
|
231
384
|
if (list === null) {
|
|
232
385
|
// Loudly, because a silent no-op is the worst failure mode this repo has
|
|
@@ -249,27 +402,49 @@ export const useSortable = (options) => {
|
|
|
249
402
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
250
403
|
useEffect(() => setMounted(true), []);
|
|
251
404
|
// Same list only — and INCLUDING this row itself. Refusing the self-drop
|
|
252
|
-
// looks right and is wrong:
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
405
|
+
// looks right and is wrong: the reorder tracking (`Sortable`'s own
|
|
406
|
+
// `handleDragMotion`) can settle the dragged row back onto its OWN slot,
|
|
407
|
+
// so by the time the pointer releases it may well be over the DRAGGED row.
|
|
408
|
+
// A row that refuses its own payload leaves GDK with no target, cancels
|
|
409
|
+
// the drag, and the list snaps back to where it started. Accepting is also
|
|
410
|
+
// the honest reading: dropping a row on its own current position means
|
|
411
|
+
// "leave it here", which is a completed drag, not an abandoned one.
|
|
258
412
|
const accepts = useCallback((payload) => payload.scope === list.scope, [list]);
|
|
259
|
-
|
|
413
|
+
// `DragSourceControllers`'s own grab point (widget-local), captured here
|
|
414
|
+
// and forwarded to `beginDrag` — see the module comment for why this, and
|
|
415
|
+
// never the first motion sample, is the reorder tracking's origin.
|
|
416
|
+
const grab = useRef({
|
|
417
|
+
widget: null,
|
|
418
|
+
x: 0,
|
|
419
|
+
y: 0,
|
|
420
|
+
});
|
|
421
|
+
const dragControllers = (_jsx(DragSourceControllers, { payload: { scope: list.scope, id }, onGrab: (x, y, widget) => {
|
|
422
|
+
grab.current = { widget, x, y };
|
|
423
|
+
}, onDragBegin: () => {
|
|
260
424
|
setIsMoving(true);
|
|
261
|
-
list.beginDrag(id);
|
|
425
|
+
list.beginDrag(id, grab.current.widget, grab.current.x, grab.current.y);
|
|
262
426
|
}, onDragEnd: (dropped) => {
|
|
263
427
|
setIsMoving(false);
|
|
264
428
|
list.endDrag(id, !dropped);
|
|
265
429
|
} }));
|
|
266
430
|
const dropControllers = (_jsx(DropTargetControllers, { accepts: accepts,
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
431
|
+
// No `onEnter` any more: crossing this row's drop target used to be
|
|
432
|
+
// what moved the dragged row into its place. That decision is now
|
|
433
|
+
// `Sortable`'s own `handleDragMotion`, driven by the list's shared
|
|
434
|
+
// motion controller rather than each row's own — see the module
|
|
435
|
+
// comment. This target still has to exist and accept, or GDK would
|
|
436
|
+
// refuse the drop and snap the drag back; `onMotion` below still
|
|
437
|
+
// reports the public `onDragging`/`onDraggingHorizontal` callbacks,
|
|
438
|
+
// unrelated to the reorder decision.
|
|
439
|
+
onMotion: (payload, x, y) => {
|
|
440
|
+
if (list.direction === SortableDirection.Horizontal) {
|
|
441
|
+
list.onDraggingHorizontal?.(payload.id, id, x);
|
|
442
|
+
onDraggingHorizontal?.(payload.id, id, x);
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
list.onDragging?.(payload.id, id, y);
|
|
446
|
+
onDragging?.(payload.id, id, y);
|
|
447
|
+
}
|
|
273
448
|
},
|
|
274
449
|
// Nothing left to do: the order is already right, and `drag-end` on the
|
|
275
450
|
// source is what settles it.
|
|
@@ -283,9 +458,26 @@ export const useSortable = (options) => {
|
|
|
283
458
|
children: (_jsxs(_Fragment, { children: [mounted && !hasHandle ? dragControllers : null, dropControllers] })),
|
|
284
459
|
};
|
|
285
460
|
};
|
|
461
|
+
/**
|
|
462
|
+
* The horizontal counterpart of {@link useSortable} — upstream's own separate
|
|
463
|
+
* hook, kept separate here too, though it is the same implementation under a
|
|
464
|
+
* shape matching `UseHorizontalSortableOptions`. Must be inside a
|
|
465
|
+
* `<Sortable direction="horizontal">`, same as `useSortable` must be inside a
|
|
466
|
+
* `Sortable`.
|
|
467
|
+
*/
|
|
468
|
+
export const useHorizontalSortable = (options) => useSortable({
|
|
469
|
+
id: options.id,
|
|
470
|
+
data: options.data,
|
|
471
|
+
positions: options.positions,
|
|
472
|
+
itemsCount: options.itemsCount,
|
|
473
|
+
onMove: options.onMove,
|
|
474
|
+
onDragStart: options.onDragStart,
|
|
475
|
+
onDrop: options.onDrop,
|
|
476
|
+
onDraggingHorizontal: options.onDragging,
|
|
477
|
+
});
|
|
286
478
|
/** One row of a {@link Sortable}. Both a drag source and a drop target: the
|
|
287
479
|
* row you drag onto is the position the dragged row takes. */
|
|
288
|
-
export const SortableItem = ({ id, data, children, style, animatedStyle, testID, onMove, onDragStart, onDrop, onDragging, }) => {
|
|
480
|
+
export const SortableItem = ({ id, data, children, style, animatedStyle, testID, onMove, onDragStart, onDrop, onDragging, onDraggingHorizontal, }) => {
|
|
289
481
|
const sortable = useSortable({
|
|
290
482
|
id,
|
|
291
483
|
data,
|
|
@@ -293,6 +485,7 @@ export const SortableItem = ({ id, data, children, style, animatedStyle, testID,
|
|
|
293
485
|
onDragStart,
|
|
294
486
|
onDrop,
|
|
295
487
|
onDragging,
|
|
488
|
+
onDraggingHorizontal,
|
|
296
489
|
});
|
|
297
490
|
// `SortableItem.Handle` is `Draggable.Handle`: the same context carries the
|
|
298
491
|
// drag source to whichever view claims it, so one implementation serves
|