@octane-xplat/dnd-kit 0.0.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +306 -2
- package/dist/native/components.js +275 -0
- package/dist/native/controller.js +236 -0
- package/dist/native/geometry.js +18 -0
- package/dist/native/index.js +4 -0
- package/dist/native/octane-client-build.json +8 -0
- package/dist/native/sort.js +11 -0
- package/dist/web/components.js +215 -0
- package/dist/web/controller.js +236 -0
- package/dist/web/geometry.web.js +16 -0
- package/dist/web/index.js +4 -0
- package/dist/web/octane-client-build.json +8 -0
- package/dist/web/sort.js +11 -0
- package/examples/sortable.macos.tsx +128 -0
- package/examples/sortable.tsx +22 -0
- package/package.json +83 -3
- package/src/abort.macos.ts +7 -0
- package/src/components.tsx +262 -0
- package/src/components.web.test.tsx +118 -0
- package/src/controller.mobile.test.ts +176 -0
- package/src/controller.ts +301 -0
- package/src/geometry.macos.test.ts +34 -0
- package/src/geometry.macos.ts +32 -0
- package/src/geometry.ts +18 -0
- package/src/geometry.web.ts +14 -0
- package/src/index.macos.ts +23 -0
- package/src/index.ts +21 -0
- package/src/index.web.ts +21 -0
- package/src/props.ts +83 -0
- package/src/sort.ts +19 -0
- package/types/generated/.tsrx-typegen-manifest.json +11 -0
- package/types/generated/components.d.ts +13 -0
- package/types/generated/controller.d.ts +32 -0
- package/types/generated/geometry.web.d.ts +3 -0
- package/types/generated/index.d.ts +5 -0
- package/types/generated/props.d.ts +84 -0
- package/types/generated/sort.d.ts +2 -0
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
|
-
|
|
4
|
-
|
|
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 };
|