@octane-xplat/dnd-kit 0.0.1 → 0.10.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 ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Alec Larson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md CHANGED
@@ -1,4 +1,308 @@
1
1
  # @octane-xplat/dnd-kit
2
2
 
3
- This is a placeholder stub published to reserve the package name ahead of the
4
- first automated release. It has no supported API.
3
+ Use draggable and droppable Xplat Views on web, iOS, Android, and native macOS (AppKit). The leaf shares
4
+ `@dnd-kit/abstract`'s manager, sensor interface, and drag state machine plus
5
+ `@dnd-kit/collision` algorithms and `@dnd-kit/state` reactivity. It implements
6
+ input and geometry through Xplat primitives; it does not load `@dnd-kit/dom`
7
+ on native or use a collection widget's reorder mechanism. This is an Xplat
8
+ facade, not a drop-in replacement for the upstream hook signatures.
9
+
10
+ ## Install and reorder
11
+
12
+ In an Octane Xplat app with `@octane-xplat/ui` and its renderer configured:
13
+
14
+ ```sh
15
+ pnpm add @octane-xplat/dnd-kit
16
+ ```
17
+
18
+ Import from the root package; web/native/macos export conditions select the platform
19
+ implementation. The app keeps one Octane runtime. Native apps also need the
20
+ existing `@nativescript-community/octane` and `@nativescript/core` setup.
21
+ The AppKit entry ships source, like other macOS leaves; include its `src/**/*.tsx`
22
+ and `src/**/*.tsrx` in the macOS renderer transform. The workspace config already
23
+ includes these paths. Its entry supplies AbortController/AbortSignal when absent
24
+ in JavaScriptCore. macOS WebView apps select the web entry instead.
25
+ No leaf CSS build is required; drag feedback uses style objects.
26
+
27
+ ```tsx
28
+ import { useState } from 'octane'
29
+ import { Text } from '@octane-xplat/ui'
30
+ import { SortableList } from '@octane-xplat/dnd-kit'
31
+
32
+ export function Tasks() {
33
+ const [items, setItems] = useState(['alpha', 'beta', 'gamma'])
34
+ return (
35
+ <SortableList
36
+ items={items}
37
+ onReorder={setItems}
38
+ renderItem={(id) => <Text>{String(id)}</Text>}
39
+ />
40
+ )
41
+ }
42
+ ```
43
+
44
+ Use `.tsx`/`.tsrx` files inside the app's renderer include globs. IDs must be
45
+ stable and unique within a context. `SortableList` creates its own DndContext
46
+ and commits a new array only on a successful drop over a different list item.
47
+ The caller saves that array. Cancellation, release outside a target, and drops
48
+ onto a disabled target leave the order unchanged. The maintained
49
+ [example](examples/sortable.tsx) displays the committed order above the rows.
50
+
51
+ ```tsx
52
+ // Tasks above owns items; successful drops publish a replacement array.
53
+ export function EmptyTasks() {
54
+ const [items, setItems] = useState<string[]>([])
55
+ return <SortableList items={items} onReorder={setItems} renderItem={(id) => <Text>{id}</Text>} />
56
+ }
57
+ ```
58
+
59
+ ## Compose drag and drop
60
+
61
+ Use `DndContext` around cooperating children. A draggable spreads `ref`,
62
+ `onPan`, and `style` onto **one View**; a droppable supplies its `ref`. `ref`
63
+ is the Xplat View binding callback, not a DOM ref prop. `useMeasure` observes
64
+ layout; live geometry is read again during drag and auto-scroll.
65
+
66
+ ```tsx
67
+ import { View, Text } from '@octane-xplat/ui'
68
+ import { DndContext, useDraggable, useDroppable } from '@octane-xplat/dnd-kit'
69
+
70
+ function Card() {
71
+ const drag = useDraggable({ id: 'card', data: { container: 'inbox' } })
72
+ return (
73
+ <View ref={drag.ref} onPan={drag.onPan} style={drag.style}>
74
+ <Text>Move me</Text>
75
+ </View>
76
+ )
77
+ }
78
+ function Target() {
79
+ const drop = useDroppable({ id: 'done' })
80
+ return (
81
+ <View ref={drop.ref} className="drop-target">
82
+ <Text>{drop.isOver ? 'Release here' : 'Done'}</Text>
83
+ </View>
84
+ )
85
+ }
86
+ export function Board() {
87
+ return (
88
+ <DndContext
89
+ onDragEnd={({ active, over }) => {
90
+ if (active && over) console.log(active.id, over.id)
91
+ }}
92
+ >
93
+ <Card />
94
+ <Target />
95
+ </DndContext>
96
+ )
97
+ }
98
+ ```
99
+
100
+ ```css
101
+ .drop-target {
102
+ min-height: 100px;
103
+ }
104
+ ```
105
+
106
+ `useDndContext()` subscribes each reader and returns `active`, `over`,
107
+ `transform`, and `isDragging`. Event callbacks are `onDragStart`, `onDragMove`,
108
+ `onDragOver`, `onDragEnd`, and `onDragCancel`; events add `canceled`.
109
+ `onDragEnd` fires only for a completed gesture; check `over` before accepting it.
110
+ Set `disabled` on either hook to exclude it. Droppable `accept(source)` can
111
+ filter by source data. Unmounting or disabling an active draggable cancels it;
112
+ unmounting a target removes it from collision detection. Context disposal
113
+ cleans registrations, subscriptions, queued input, and auto-scroll timers.
114
+
115
+ ```tsx
116
+ import { Text, View } from '@octane-xplat/ui'
117
+ import { useDndContext, useDroppable } from '@octane-xplat/dnd-kit'
118
+
119
+ // Render inside the Board's DndContext above.
120
+ export function Inbox() {
121
+ const context = useDndContext()
122
+ const drop = useDroppable({
123
+ id: 'inbox',
124
+ disabled: false,
125
+ accept: (source) => source.data.container === 'inbox',
126
+ })
127
+ return (
128
+ <View ref={drop.ref} className="drop-target">
129
+ <Text>{context.isDragging ? 'Dragging' : 'Ready'}</Text>
130
+ </View>
131
+ )
132
+ }
133
+ ```
134
+
135
+ The default detector prefers pointer intersection then shape overlap. Supply
136
+ `collisionDetection={closestCenter}` to choose another exported algorithm.
137
+ The pan facade uses the dragged View's center as its detection position; it
138
+ does not preserve the finger's grab offset. A draggable's own ID is excluded
139
+ from drop targets. `SortableContext` declares
140
+ item membership for `useSortable`, which combines both hooks; use `arrayMove`
141
+ in `onDragEnd` to save an order. Items do not shift to preview their destination
142
+ before release; the dragged View translates over the stationary rows.
143
+
144
+ ```tsx
145
+ import { useState } from 'octane'
146
+ import { Text, View } from '@octane-xplat/ui'
147
+ import {
148
+ DndContext,
149
+ SortableContext,
150
+ useSortable,
151
+ closestCenter,
152
+ arrayMove,
153
+ } from '@octane-xplat/dnd-kit'
154
+
155
+ function SortableRow({ id }: { id: string }) {
156
+ const drag = useSortable({ id })
157
+ return (
158
+ <View ref={drag.ref} onPan={drag.onPan} style={drag.style}>
159
+ <Text>{id}</Text>
160
+ </View>
161
+ )
162
+ }
163
+ export function OrderedTasks() {
164
+ const [items, setItems] = useState(['alpha', 'beta'])
165
+ return (
166
+ <DndContext
167
+ collisionDetection={closestCenter}
168
+ onDragEnd={({ active, over }) => {
169
+ if (!active || !over) return
170
+ const from = items.indexOf(String(active.id))
171
+ const to = items.indexOf(String(over.id))
172
+ if (from >= 0 && to >= 0) setItems(arrayMove(items, from, to))
173
+ }}
174
+ >
175
+ <SortableContext items={items}>
176
+ {items.map((id) => (
177
+ <SortableRow id={id} />
178
+ ))}
179
+ </SortableContext>
180
+ </DndContext>
181
+ )
182
+ }
183
+ ```
184
+
185
+ Cross-container drops work within one DndContext: `active.data` and `over.data`
186
+ can identify containers. The caller moves data between its arrays at drop time.
187
+ Separate `SortableList` instances each own a context; use the hooks and
188
+ `SortableContext` for a board with cooperating lists. Automatic transfer,
189
+ placeholder layout, and empty-container insertion are not built in.
190
+
191
+ ```ts
192
+ // Add to a cooperating Board's DndContext; app arrays own the transfer.
193
+ function recordTransfer(event: import('@octane-xplat/dnd-kit').DragEvent) {
194
+ if (event.active && event.over) {
195
+ console.log(event.active.data.container, event.over.data.container)
196
+ }
197
+ }
198
+ ```
199
+
200
+ ## Auto-scroll
201
+
202
+ Pass `autoScroll` to DndContext, or `dnd={{ autoScroll }}` to SortableList.
203
+ The adapter makes the existing scroll owner explicit:
204
+
205
+ ```ts
206
+ import type { AutoScroll } from '@octane-xplat/dnd-kit'
207
+
208
+ // Supply these live callbacks from the scroll owner in your app.
209
+ export function scrollAdapter(owner: {
210
+ bounds: AutoScroll['bounds']
211
+ offsetRef: { current: number }
212
+ maxOffset: () => number
213
+ scrollTo: (offset: number) => void
214
+ }): AutoScroll {
215
+ return {
216
+ bounds: owner.bounds,
217
+ offsetRef: owner.offsetRef,
218
+ maxOffset: owner.maxOffset,
219
+ scrollTo: (offset) => {
220
+ const applied = Math.max(0, Math.min(owner.maxOffset(), offset))
221
+ owner.offsetRef.current = applied
222
+ owner.scrollTo(applied)
223
+ },
224
+ axis: 'y',
225
+ threshold: 40,
226
+ speed: 12,
227
+ }
228
+ }
229
+ ```
230
+
231
+ `owner.bounds()` returns the current measured `{ x, y, width, height }` or null;
232
+ `offsetRef` is the scroll owner's `{ current: number }`. `owner.scrollTo`
233
+ stands for the owner's existing scroll method. Read current offsets into the
234
+ ref on ordinary scroll events, following VirtualList's scrollOffsetRef pattern.
235
+ On native, use a View's `getLocationOnScreen` and `getActualSize` or `useMeasure`
236
+ for viewport bounds; on web use the corresponding measured viewport bounds.
237
+ Keep bounds and offsets in the same coordinate units: native DIPs, web CSS pixels,
238
+ and AppKit points. AppKit geometry uses top-left window-content coordinates.
239
+ The [macOS example](examples/sortable.macos.tsx) owns an NSScrollView, measures its
240
+ clip view, and converts its document offset into top-down points. It initializes
241
+ at the document top and handles flipped and unflipped document views.
242
+
243
+ ```ts
244
+ // Keep the scroll owner's offset ref in sync with ordinary scrolling too.
245
+ function recordScroll(offsetRef: { current: number }, offset: number) {
246
+ offsetRef.current = offset
247
+ }
248
+ ```
249
+
250
+ The adapter must clamp and update the ref synchronously to the applied offset.
251
+ The drag loop scrolls every 16ms while the drag center remains inside the viewport
252
+ and near an edge. `threshold` is the edge distance; `speed` is the maximum units
253
+ per tick. It remeasures drop targets and compensates the dragged View's visual
254
+ translation for scrolling. Scrolling stops on drop, cancel, and disposal.
255
+ Only one explicit scroll viewport is supported; do not attach another gesture
256
+ handler to the same drag View.
257
+
258
+ ```tsx
259
+ // Continue with scrollAdapter above and your app's existing scroll owner.
260
+ import { SortableList, type AutoScroll } from '@octane-xplat/dnd-kit'
261
+ import { Text } from '@octane-xplat/ui'
262
+
263
+ export function ScrollingTasks({ autoScroll }: { autoScroll: AutoScroll }) {
264
+ const [items, setItems] = useState(['alpha', 'beta'])
265
+ return (
266
+ <SortableList
267
+ items={items}
268
+ onReorder={setItems}
269
+ dnd={{ autoScroll }}
270
+ renderItem={(id) => <Text>{id}</Text>}
271
+ />
272
+ )
273
+ }
274
+ ```
275
+
276
+ ## Limits and verification
277
+
278
+ V1 uses pan input immediately on native gesture begin or web pointer-down.
279
+ There is no activation distance, separate handle, keyboard sensor, screen-reader
280
+ announcement layer, drag overlay, nested-scroll arbitration, or virtualized
281
+ offscreen target discovery. Native pans may compete with ScrollView gestures;
282
+ OS gesture arbitration and hit-testing require device verification. Only mounted,
283
+ measurable targets participate. Use a separate DndContext in each native renderer
284
+ root (pages and sheets do not share context).
285
+
286
+ Run `pnpm --filter @octane-xplat/dnd-kit typecheck`, `test`, `build`, and
287
+ `pack:check`. Core tests use native-shaped geometry and the real abstract core;
288
+ they do not establish OS input delivery. The repository probe doctor reports
289
+ available runtime targets. Do not infer Android runtime support from a native
290
+ library build.
291
+ The Web regression runs with `pnpm --filter @xplat/web test`; it drives pointer
292
+ events through bound Views and checks both successful drop and cancellation.
293
+ The maintained AppKit regression runs with
294
+ `pnpm probe run packages/dnd-kit/tests/appkit-scroll.macos.tsrx --target macos`; it uses
295
+ action dispatch and checks layer feedback, layout, auto-scroll, and cleanup.
296
+
297
+ Before the first automated release, the package still needs the one-time npm
298
+ stub and trusted publisher setup described in [releases](../../.agents/docs/releases.md).
299
+ This change does not publish or configure npm.
300
+
301
+ Verification: web/native/macOS source typechecks, web/native library builds,
302
+ ten core/geometry tests, and packed consumers in Bundler/NodeNext passed.
303
+ Chromium pointer-event dispatch and iOS pan-observer dispatch probes passed
304
+ for reorder and cancellation. AppKit/JavaScriptCore pan-handler dispatch passed
305
+ reorder/cancel plus NSScrollView auto-scroll, translation through layout, and
306
+ cancellation cleanup. These establish renderer runtime behavior, not OS gesture
307
+ delivery, hit-testing, or scroll gesture arbitration. Android runtime and macOS
308
+ WebView drag behavior were not run.
@@ -0,0 +1,275 @@
1
+ import { dragStyle } from "./geometry.js";
2
+ import { DndController } from "./controller.js";
3
+ import { arrayMove } from "./sort.js";
4
+ import { createContext, defineUniversalComponent, hookSlots, universalChildren, universalComponent, universalContext, universalPlan, universalProps, universalValue, useCallback, useContext, useEffect, useRef, useSyncExternalStore, withSlot } from "@nativescript-community/octane";
5
+ import { View, useMeasure } from "@octane-xplat/ui";
6
+ //#region src/components.tsx
7
+ var _hs$ = /* @__PURE__ */ hookSlots(27);
8
+ var _h$0 = /* @__PURE__ */ Symbol(_hs$);
9
+ var _h$1 = /* @__PURE__ */ Symbol(_hs$ + 1);
10
+ var _h$2 = /* @__PURE__ */ Symbol(_hs$ + 2);
11
+ var _h$3 = /* @__PURE__ */ Symbol(_hs$ + 3);
12
+ var _h$4 = /* @__PURE__ */ Symbol(_hs$ + 4);
13
+ var _h$5 = /* @__PURE__ */ Symbol(_hs$ + 5);
14
+ var _h$6 = /* @__PURE__ */ Symbol(_hs$ + 6);
15
+ var _h$7 = /* @__PURE__ */ Symbol(_hs$ + 7);
16
+ var _h$8 = /* @__PURE__ */ Symbol(_hs$ + 8);
17
+ var _h$9 = /* @__PURE__ */ Symbol(_hs$ + 9);
18
+ var _h$10 = /* @__PURE__ */ Symbol(_hs$ + 10);
19
+ var _h$11 = /* @__PURE__ */ Symbol(_hs$ + 11);
20
+ var _h$12 = /* @__PURE__ */ Symbol(_hs$ + 12);
21
+ var _h$13 = /* @__PURE__ */ Symbol(_hs$ + 13);
22
+ var _h$14 = /* @__PURE__ */ Symbol(_hs$ + 14);
23
+ var _h$15 = /* @__PURE__ */ Symbol(_hs$ + 15);
24
+ var _h$16 = /* @__PURE__ */ Symbol(_hs$ + 16);
25
+ var _h$17 = /* @__PURE__ */ Symbol(_hs$ + 17);
26
+ var _h$18 = /* @__PURE__ */ Symbol(_hs$ + 18);
27
+ var _h$19 = /* @__PURE__ */ Symbol(_hs$ + 19);
28
+ var _h$20 = /* @__PURE__ */ Symbol(_hs$ + 20);
29
+ var _h$21 = /* @__PURE__ */ Symbol(_hs$ + 21);
30
+ var _h$22 = /* @__PURE__ */ Symbol(_hs$ + 22);
31
+ var _h$23 = /* @__PURE__ */ Symbol(_hs$ + 23);
32
+ var _h$24 = /* @__PURE__ */ Symbol(_hs$ + 24);
33
+ var _h$25 = /* @__PURE__ */ Symbol(_hs$ + 25);
34
+ var _h$26 = /* @__PURE__ */ Symbol(_hs$ + 26);
35
+ var __octaneUniversalPlan0 = universalPlan("nativescript", {
36
+ "kind": "slot",
37
+ "slot": 0
38
+ });
39
+ var __octaneUniversalPlan1 = universalPlan("nativescript", {
40
+ "kind": "slot",
41
+ "slot": 0
42
+ });
43
+ var __octaneUniversalPlan2 = universalPlan("nativescript", {
44
+ "kind": "slot",
45
+ "slot": 0
46
+ });
47
+ var __octaneUniversalPlan3 = universalPlan("nativescript", {
48
+ "kind": "slot",
49
+ "slot": 0
50
+ });
51
+ var __octaneUniversalPlan4 = universalPlan("nativescript", {
52
+ "kind": "slot",
53
+ "slot": 0
54
+ });
55
+ var __octaneUniversalPlan5 = universalPlan("nativescript", {
56
+ "kind": "slot",
57
+ "slot": 0
58
+ });
59
+ var __octaneUniversalPlan6 = universalPlan("nativescript", {
60
+ "kind": "slot",
61
+ "slot": 0
62
+ });
63
+ var Context = createContext(null);
64
+ var ContextProvider = Context;
65
+ var SortContext = createContext(null);
66
+ var SortContextProvider = SortContext;
67
+ var DndContext = defineUniversalComponent("nativescript", function DndContext(props) {
68
+ const ref = useRef(null, _h$0);
69
+ if (!ref.current) ref.current = new DndController();
70
+ const controller = ref.current;
71
+ controller.options = props;
72
+ useEffect(() => () => controller.disposeController(), [controller], _h$1);
73
+ return universalValue(__octaneUniversalPlan0, [((__octaneContextProps) => universalContext(ContextProvider, __octaneContextProps.value, props.children))({ "value": controller })]);
74
+ }, { "module": "@nativescript-community/octane" });
75
+ function useController() {
76
+ const controller = useContext(Context);
77
+ if (!controller) throw new Error("Drag and drop hooks require DndContext");
78
+ return controller;
79
+ }
80
+ function useDndContext() {
81
+ const controller = withSlot(_h$2, useController);
82
+ return useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot, _h$3);
83
+ }
84
+ function useDraggable(options) {
85
+ const controller = withSlot(_h$4, useController);
86
+ const snapshot = withSlot(_h$5, useDndContext);
87
+ const entity = useRef(null, _h$6);
88
+ const node = useRef(null, _h$7);
89
+ const latest = useRef(options, _h$8);
90
+ latest.current = options;
91
+ const measurement = withSlot(_h$9, useMeasure);
92
+ useEffect(() => {
93
+ const registered = controller.registerDraggable(latest.current);
94
+ entity.current = registered;
95
+ if (node.current) controller.draggableNodes.set(options.id, node.current);
96
+ return () => {
97
+ if (controller.getSnapshot().active?.id === options.id) controller.cancel();
98
+ registered.destroy();
99
+ entity.current = null;
100
+ controller.draggableNodes.delete(options.id);
101
+ };
102
+ }, [controller, options.id], _h$10);
103
+ useEffect(() => {
104
+ if (!entity.current) return;
105
+ entity.current.data = options.data ?? {};
106
+ entity.current.disabled = options.disabled ?? false;
107
+ if (options.disabled && controller.getSnapshot().active?.id === options.id) controller.cancel();
108
+ }, [
109
+ controller,
110
+ options.id,
111
+ options.data,
112
+ options.disabled
113
+ ], _h$11);
114
+ const ref = useCallback((element) => {
115
+ node.current = element;
116
+ measurement.ref(element);
117
+ if (element) controller.draggableNodes.set(options.id, element);
118
+ else controller.draggableNodes.delete(options.id);
119
+ }, [
120
+ controller,
121
+ options.id,
122
+ measurement.ref
123
+ ], _h$12);
124
+ const onPan = useCallback((event) => entity.current?.onPan?.(event), [], _h$13);
125
+ const isDragging = snapshot.isDragging && snapshot.active?.id === options.id;
126
+ const transform = isDragging ? snapshot.transform : {
127
+ x: 0,
128
+ y: 0
129
+ };
130
+ return {
131
+ ref,
132
+ onPan,
133
+ isDragging,
134
+ transform,
135
+ style: dragStyle(transform.x, transform.y)
136
+ };
137
+ }
138
+ function useDroppable(options) {
139
+ const controller = withSlot(_h$14, useController);
140
+ const snapshot = withSlot(_h$15, useDndContext);
141
+ const entity = useRef(null, _h$16);
142
+ const node = useRef(null, _h$17);
143
+ const latest = useRef(options, _h$18);
144
+ latest.current = options;
145
+ const measurement = withSlot(_h$19, useMeasure);
146
+ useEffect(() => {
147
+ const registered = controller.registerDroppable(latest.current);
148
+ entity.current = registered;
149
+ if (node.current) controller.droppableNodes.set(options.id, node.current);
150
+ controller.refresh();
151
+ return () => {
152
+ registered.destroy();
153
+ entity.current = null;
154
+ controller.droppableNodes.delete(options.id);
155
+ controller.refresh();
156
+ };
157
+ }, [controller, options.id], _h$20);
158
+ useEffect(() => {
159
+ if (!entity.current) return;
160
+ entity.current.data = options.data ?? {};
161
+ entity.current.disabled = options.disabled ?? false;
162
+ entity.current.accept = options.accept ? (source) => options.accept({
163
+ id: source.id,
164
+ data: source.data
165
+ }) : void 0;
166
+ controller.refresh();
167
+ }, [
168
+ controller,
169
+ options.data,
170
+ options.disabled,
171
+ options.accept,
172
+ controller.options.collisionDetection,
173
+ measurement.bounds
174
+ ], _h$21);
175
+ return {
176
+ ref: useCallback((element) => {
177
+ node.current = element;
178
+ measurement.ref(element);
179
+ if (element) controller.droppableNodes.set(options.id, element);
180
+ else controller.droppableNodes.delete(options.id);
181
+ }, [
182
+ controller,
183
+ options.id,
184
+ measurement.ref
185
+ ], _h$22),
186
+ isOver: snapshot.isDragging && snapshot.over?.id === options.id
187
+ };
188
+ }
189
+ var SortableContext = defineUniversalComponent("nativescript", function SortableContext(props) {
190
+ if (new Set(props.items).size !== props.items.length) throw new Error("SortableContext requires unique item ids");
191
+ return universalValue(__octaneUniversalPlan1, [((__octaneContextProps) => universalContext(SortContextProvider, __octaneContextProps.value, props.children))({ "value": props.items })]);
192
+ }, { "module": "@nativescript-community/octane" });
193
+ function useSortable(options) {
194
+ if (!useContext(SortContext)?.includes(options.id)) throw new Error("useSortable requires membership in SortableContext");
195
+ const drag = withSlot(_h$23, useDraggable, options);
196
+ const drop = withSlot(_h$24, useDroppable, options);
197
+ const ref = useCallback((element) => {
198
+ drag.ref(element);
199
+ drop.ref(element);
200
+ }, [drag.ref, drop.ref], _h$25);
201
+ return {
202
+ ...drag,
203
+ ref,
204
+ isOver: drop.isOver
205
+ };
206
+ }
207
+ var SortableRow = defineUniversalComponent("nativescript", function SortableRow(props) {
208
+ const sortable = withSlot(_h$26, useSortable, {
209
+ id: props.id,
210
+ disabled: props.list.disabled
211
+ });
212
+ return universalValue(__octaneUniversalPlan2, [universalComponent("nativescript", View, universalProps([
213
+ [
214
+ "set",
215
+ "ref",
216
+ sortable.ref
217
+ ],
218
+ [
219
+ "set",
220
+ "onPan",
221
+ sortable.onPan
222
+ ],
223
+ [
224
+ "set",
225
+ "style",
226
+ sortable.style
227
+ ]
228
+ ], props.list.renderItem(props.id, props.index)))]);
229
+ }, { "module": "@nativescript-community/octane" });
230
+ var SortableList = defineUniversalComponent("nativescript", function SortableList(props) {
231
+ const { items, renderItem: _renderItem, onReorder, disabled: _disabled, dnd, ...viewProps } = props;
232
+ return universalValue(__octaneUniversalPlan6, [universalComponent("nativescript", DndContext, universalProps([["spread", dnd], [
233
+ "set",
234
+ "onDragEnd",
235
+ (event) => {
236
+ if (event.active && event.over) {
237
+ const from = items.findIndex((id) => id === event.active.id);
238
+ const to = items.findIndex((id) => id === event.over.id);
239
+ if (from >= 0 && to >= 0 && from !== to) onReorder(arrayMove(items, from, to), event);
240
+ }
241
+ dnd?.onDragEnd?.(event);
242
+ }
243
+ ]], universalChildren("nativescript", () => {
244
+ return universalValue(__octaneUniversalPlan5, [universalComponent("nativescript", SortableContext, universalProps([[
245
+ "set",
246
+ "items",
247
+ items
248
+ ]], universalChildren("nativescript", () => {
249
+ return universalValue(__octaneUniversalPlan4, [universalComponent("nativescript", View, universalProps([["spread", viewProps]], items.map((id, index) => universalValue(__octaneUniversalPlan3, [universalComponent("nativescript", SortableRow, universalProps([
250
+ [
251
+ "set",
252
+ "key",
253
+ id
254
+ ],
255
+ [
256
+ "set",
257
+ "id",
258
+ id
259
+ ],
260
+ [
261
+ "set",
262
+ "index",
263
+ index
264
+ ],
265
+ [
266
+ "set",
267
+ "list",
268
+ props
269
+ ]
270
+ ]))]))))]);
271
+ })))]);
272
+ })))]);
273
+ }, { "module": "@nativescript-community/octane" });
274
+ //#endregion
275
+ export { DndContext, SortableContext, SortableList, useDndContext, useDraggable, useDroppable, useSortable };