@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.
@@ -0,0 +1,301 @@
1
+ import { DragDropManager, Draggable, Droppable, Sensor } from '@dnd-kit/abstract'
2
+ import { defaultCollisionDetection } from '@dnd-kit/collision'
3
+ import { batch, effect, signal, untracked } from '@dnd-kit/state'
4
+ import type { Rectangle } from '@dnd-kit/geometry'
5
+ import type { PanEvent } from '@octane-xplat/ui'
6
+ import type {
7
+ DndContextProps,
8
+ DndId,
9
+ DndSnapshot,
10
+ DragEvent,
11
+ DraggableOptions,
12
+ DroppableOptions,
13
+ } from './props'
14
+
15
+ import { measure } from './geometry'
16
+
17
+ class PanDraggable extends Draggable {
18
+ onPan?: (event: PanEvent) => void
19
+ }
20
+
21
+ /** Xplat View supplies normalized pan input; the abstract core owns the drag lifecycle. */
22
+ class PanSensor extends Sensor {
23
+ bind(input: Draggable) {
24
+ const source = input as PanDraggable
25
+ let origin: Rectangle | null = null
26
+ let bound = true
27
+ let base = { x: 0, y: 0 }
28
+ let queue = Promise.resolve()
29
+ const handle = async (event: PanEvent) => {
30
+ const manager = this.manager as DndController
31
+ if (!bound) {
32
+ return
33
+ }
34
+
35
+ if (event.state === 'began') {
36
+ if (source.disabled || !manager.dragOperation.status.idle) {
37
+ return
38
+ }
39
+
40
+ origin = measure(manager.draggableNodes.get(source.id))
41
+ if (!origin) {
42
+ return
43
+ }
44
+
45
+ base = { x: event.dx, y: event.dy }
46
+ manager.scrollShift$.value = { x: 0, y: 0 }
47
+ manager.actions.start({ source, coordinates: origin.center })
48
+ manager.dragOperation.shape = origin
49
+ manager.refresh()
50
+ await manager.renderer.rendering
51
+ return
52
+ }
53
+
54
+ if (
55
+ !origin ||
56
+ manager.dragOperation.source?.id !== source.id ||
57
+ !manager.dragOperation.status.dragging
58
+ ) {
59
+ return
60
+ }
61
+
62
+ const x = event.dx - base.x
63
+ const y = event.dy - base.y
64
+ manager.actions.move({ to: { x: origin.center.x + x, y: origin.center.y + y } })
65
+ // The core commits movement in a microtask. Serialize release after it.
66
+ await Promise.resolve()
67
+ manager.dragOperation.shape = origin.translate(x, y)
68
+ manager.refresh()
69
+ if (event.state === 'ended' || event.state === 'cancelled') {
70
+ manager.actions.stop({ canceled: event.state === 'cancelled' })
71
+ origin = null
72
+ } else {
73
+ manager.startAutoScroll()
74
+ }
75
+ }
76
+
77
+ source.onPan = (event) => {
78
+ queue = queue
79
+ .then(() => handle(event))
80
+ .catch((error) => {
81
+ origin = null
82
+
83
+ ;(this.manager as DndController).cancel()
84
+ console.error('[dnd-kit] pan failed', error)
85
+ })
86
+ }
87
+
88
+ return () => {
89
+ bound = false
90
+ source.onPan = undefined
91
+ origin = null
92
+ }
93
+ }
94
+ }
95
+
96
+ /** One controller per DndContext; no global registry or cross-root context assumptions. */
97
+ export class DndController extends DragDropManager<PanDraggable, Droppable> {
98
+ options: DndContextProps = {}
99
+ readonly draggableNodes = new Map<DndId, any>()
100
+ readonly droppableNodes = new Map<DndId, any>()
101
+ readonly scrollShift$ = signal({ x: 0, y: 0 })
102
+ private listeners = new Set<() => void>()
103
+ private snapshot: DndSnapshot = {
104
+ active: null,
105
+ over: null,
106
+ transform: { x: 0, y: 0 },
107
+ isDragging: false,
108
+ }
109
+ private dispose: () => void
110
+ private scrollTimer: ReturnType<typeof setInterval> | undefined
111
+ private destroyed = false
112
+ constructor() {
113
+ super({ sensors: [PanSensor] })
114
+ this.dispose = effect(() => {
115
+ const operation = this.dragOperation
116
+ const active = operation.source
117
+ const over = operation.target
118
+ const transform = operation.transform
119
+ const isDragging = operation.status.dragging
120
+ this.snapshot = {
121
+ active: active && !operation.status.idle ? { id: active.id, data: active.data } : null,
122
+ over: over && !operation.status.idle ? { id: over.id, data: over.data } : null,
123
+ transform: {
124
+ x: operation.status.idle ? 0 : transform.x + this.scrollShift$.value.x,
125
+ y: operation.status.idle ? 0 : transform.y + this.scrollShift$.value.y,
126
+ },
127
+ isDragging,
128
+ }
129
+
130
+ untracked(() => {
131
+ for (const listener of this.listeners) {
132
+ listener()
133
+ }
134
+ })
135
+ })
136
+
137
+ const event = (canceled = false): DragEvent => {
138
+ const operation = this.dragOperation
139
+ return {
140
+ active: operation.source ? { id: operation.source.id, data: operation.source.data } : null,
141
+ over: operation.target ? { id: operation.target.id, data: operation.target.data } : null,
142
+ transform: { ...operation.transform },
143
+ isDragging: operation.status.dragging,
144
+ canceled,
145
+ }
146
+ }
147
+
148
+ this.monitor.addEventListener('dragstart', () => this.options.onDragStart?.(event()))
149
+ this.monitor.addEventListener('dragover', () => this.options.onDragOver?.(event()))
150
+ // dragmove fires before position is committed, so deliver the committed snapshot.
151
+ this.monitor.addEventListener('dragmove', () =>
152
+ queueMicrotask(() =>
153
+ queueMicrotask(() => {
154
+ if (!this.destroyed && this.dragOperation.status.dragging) {
155
+ this.options.onDragMove?.(event())
156
+ }
157
+ }),
158
+ ),
159
+ )
160
+
161
+ this.monitor.addEventListener('dragend', ({ canceled }) => {
162
+ this.stopAutoScroll()
163
+ if (canceled) {
164
+ this.options.onDragCancel?.(event(true))
165
+ } else {
166
+ this.options.onDragEnd?.(event())
167
+ }
168
+ })
169
+ }
170
+ getSnapshot = (): DndSnapshot => this.snapshot
171
+ subscribe = (listener: () => void) => {
172
+ this.listeners.add(listener)
173
+ return () => {
174
+ this.listeners.delete(listener)
175
+ }
176
+ }
177
+ registerDraggable(options: DraggableOptions) {
178
+ if (this.registry.draggables.has(options.id)) {
179
+ throw new Error(`Duplicate draggable id: ${options.id}`)
180
+ }
181
+
182
+ const source: PanDraggable = new PanDraggable(
183
+ {
184
+ ...options,
185
+ register: false,
186
+ effects: () => [() => this.registry.sensors.get(PanSensor)!.bind(source)],
187
+ },
188
+ this,
189
+ )
190
+
191
+ source.register()
192
+ return source
193
+ }
194
+ registerDroppable(options: DroppableOptions) {
195
+ if (this.registry.droppables.has(options.id)) {
196
+ throw new Error(`Duplicate droppable id: ${options.id}`)
197
+ }
198
+
199
+ const target = new Droppable(
200
+ {
201
+ ...options,
202
+ register: false,
203
+ accept: options.accept
204
+ ? (source) => options.accept!({ id: source.id, data: source.data })
205
+ : undefined,
206
+ collisionDetector: (input) =>
207
+ input.dragOperation.source?.id === input.droppable.id
208
+ ? null
209
+ : (this.options.collisionDetection ?? defaultCollisionDetection)(input),
210
+ },
211
+ this,
212
+ )
213
+
214
+ target.register()
215
+ return target
216
+ }
217
+ refresh() {
218
+ batch(() => {
219
+ for (const droppable of this.registry.droppables) {
220
+ droppable.shape = measure(this.droppableNodes.get(droppable.id)) ?? undefined
221
+ }
222
+ })
223
+
224
+ this.collisionObserver.forceUpdate()
225
+ }
226
+ cancel() {
227
+ this.stopAutoScroll()
228
+ this.actions.stop({ canceled: true })
229
+ }
230
+ startAutoScroll() {
231
+ if (this.scrollTimer || !this.options.autoScroll) {
232
+ return
233
+ }
234
+
235
+ this.scrollTimer = setInterval(() => {
236
+ const scroll = this.options.autoScroll
237
+ if (!scroll || !this.dragOperation.status.dragging) {
238
+ this.stopAutoScroll()
239
+ return
240
+ }
241
+
242
+ const bounds = scroll.bounds()
243
+ if (!bounds) {
244
+ return
245
+ }
246
+
247
+ const horizontal = scroll.axis === 'x'
248
+ const start = horizontal ? bounds.x : bounds.y
249
+ const size = horizontal ? bounds.width : bounds.height
250
+ const point = horizontal
251
+ ? this.dragOperation.position.current.x
252
+ : this.dragOperation.position.current.y
253
+
254
+ const threshold = Math.min(scroll.threshold ?? 40, size / 2)
255
+ if (threshold <= 0 || point < start || point > start + size) {
256
+ return
257
+ }
258
+
259
+ const strength =
260
+ point < start + threshold
261
+ ? -(1 - (point - start) / threshold)
262
+ : point > start + size - threshold
263
+ ? 1 - (start + size - point) / threshold
264
+ : 0
265
+
266
+ const next = Math.max(
267
+ 0,
268
+ Math.min(scroll.maxOffset(), scroll.offsetRef.current + strength * (scroll.speed ?? 12)),
269
+ )
270
+
271
+ if (next !== scroll.offsetRef.current) {
272
+ const before = scroll.offsetRef.current
273
+ scroll.scrollTo(next)
274
+ const delta = scroll.offsetRef.current - before
275
+ const shift = this.scrollShift$.peek()
276
+ this.scrollShift$.value = {
277
+ x: shift.x + (horizontal ? delta : 0),
278
+ y: shift.y + (horizontal ? 0 : delta),
279
+ }
280
+
281
+ this.refresh()
282
+ }
283
+ }, 16)
284
+ }
285
+ private stopAutoScroll() {
286
+ if (this.scrollTimer) {
287
+ clearInterval(this.scrollTimer)
288
+ }
289
+
290
+ this.scrollTimer = undefined
291
+ }
292
+ disposeController() {
293
+ this.destroyed = true
294
+ this.cancel()
295
+ this.dispose()
296
+ this.listeners.clear()
297
+ this.draggableNodes.clear()
298
+ this.droppableNodes.clear()
299
+ this.destroy()
300
+ }
301
+ }
@@ -0,0 +1,34 @@
1
+ import { it, expect, vi } from 'vitest'
2
+ import { measure, dragStyle } from './geometry.macos'
3
+
4
+ it('uses converted window bounds and normalizes unflipped coordinates', () => {
5
+ const content = {
6
+ bounds: { origin: { x: 5, y: 10 }, size: { width: 500, height: 400 } },
7
+ isFlipped: false,
8
+ }
9
+
10
+ const convertRectToView = vi.fn(() => ({
11
+ origin: { x: 25, y: 310 },
12
+ size: { width: 100, height: 40 },
13
+ }))
14
+
15
+ const view = { window: { contentView: content }, bounds: {}, convertRectToView }
16
+ const rect = measure(view)!
17
+ expect(rect.boundingRectangle).toMatchObject({ left: 20, top: 60, width: 100, height: 40 })
18
+ expect(convertRectToView).toHaveBeenCalledWith(view.bounds, content)
19
+ content.isFlipped = true
20
+ expect(measure(view)!.boundingRectangle.top).toBe(300)
21
+ })
22
+
23
+ it('ignores detached and zero-size views and exposes reversible drag styles', () => {
24
+ expect(measure({})).toBeNull()
25
+ expect(
26
+ measure({
27
+ window: { contentView: {} },
28
+ convertRectToView: () => ({ size: { width: 0, height: 10 } }),
29
+ }),
30
+ ).toBeNull()
31
+
32
+ expect(dragStyle(12, 30)).toEqual({ translateX: 12, translateY: 30, zIndex: 1 })
33
+ expect(dragStyle(0, 0)).toEqual({ translateX: 0, translateY: 0, zIndex: 0 })
34
+ })
@@ -0,0 +1,32 @@
1
+ import { Rectangle } from '@dnd-kit/geometry'
2
+
3
+ /** AppKit points in top-left window-content coordinates, independent of parent and scroll offsets. */
4
+ export function measure(view: any): Rectangle | null {
5
+ const content = view?.window?.contentView
6
+ if (!content || !view?.convertRectToView) {
7
+ return null
8
+ }
9
+
10
+ const frame = view.convertRectToView(view.bounds, content)
11
+ if (frame.size.width <= 0 || frame.size.height <= 0) {
12
+ return null
13
+ }
14
+
15
+ const bounds = content.bounds
16
+ const flipped = typeof content.isFlipped === 'function' ? content.isFlipped() : content.isFlipped
17
+ const x = Number(frame.origin.x) - Number(bounds.origin.x)
18
+ const y = flipped
19
+ ? Number(frame.origin.y) - Number(bounds.origin.y)
20
+ : Number(bounds.origin.y) +
21
+ Number(bounds.size.height) -
22
+ Number(frame.origin.y) -
23
+ Number(frame.size.height)
24
+
25
+ return frame.size.width > 0 && frame.size.height > 0
26
+ ? new Rectangle(x, y, Number(frame.size.width), Number(frame.size.height))
27
+ : null
28
+ }
29
+
30
+ export function dragStyle(x: number, y: number): Record<string, number | string> {
31
+ return { translateX: x, translateY: y, zIndex: x || y ? 1 : 0 }
32
+ }
@@ -0,0 +1,18 @@
1
+ import { Rectangle } from '@dnd-kit/geometry'
2
+
3
+ /** NativeScript screen coordinates and dimensions are in DIPs. */
4
+ export function measure(element: any): Rectangle | null {
5
+ if (!element || element.isLoaded === false) {
6
+ return null
7
+ }
8
+
9
+ const point = element.getLocationOnScreen?.()
10
+ const size = element.getActualSize?.()
11
+ return point && size && size.width > 0 && size.height > 0
12
+ ? new Rectangle(point.x, point.y, size.width, size.height)
13
+ : null
14
+ }
15
+
16
+ export function dragStyle(x: number, y: number): Record<string, number | string> {
17
+ return { translateX: x, translateY: y, zIndex: x || y ? 1 : 0 }
18
+ }
@@ -0,0 +1,14 @@
1
+ import { Rectangle } from '@dnd-kit/geometry'
2
+
3
+ export function measure(element: any): Rectangle | null {
4
+ if (!element?.isConnected) {
5
+ return null
6
+ }
7
+
8
+ const rect = element.getBoundingClientRect()
9
+ return rect.width > 0 && rect.height > 0 ? Rectangle.from(rect) : null
10
+ }
11
+
12
+ export function dragStyle(x: number, y: number): Record<string, number | string> {
13
+ return { transform: `translate(${x}px, ${y}px)`, touchAction: 'none', zIndex: x || y ? 1 : 0 }
14
+ }
@@ -0,0 +1,23 @@
1
+ import './abort.macos'
2
+
3
+ export {
4
+ DndContext,
5
+ useDndContext,
6
+ useDraggable,
7
+ useDroppable,
8
+ SortableContext,
9
+ useSortable,
10
+ SortableList,
11
+ } from './components'
12
+
13
+ export { arrayMove } from './sort'
14
+ export {
15
+ closestCenter,
16
+ closestCorners,
17
+ pointerIntersection,
18
+ shapeIntersection,
19
+ defaultCollisionDetection,
20
+ } from '@dnd-kit/collision'
21
+
22
+ export type { CollisionDetector } from '@dnd-kit/abstract'
23
+ export type * from './props'
package/src/index.ts ADDED
@@ -0,0 +1,21 @@
1
+ export {
2
+ DndContext,
3
+ useDndContext,
4
+ useDraggable,
5
+ useDroppable,
6
+ SortableContext,
7
+ useSortable,
8
+ SortableList,
9
+ } from './components'
10
+
11
+ export { arrayMove } from './sort'
12
+ export {
13
+ closestCenter,
14
+ closestCorners,
15
+ pointerIntersection,
16
+ shapeIntersection,
17
+ defaultCollisionDetection,
18
+ } from '@dnd-kit/collision'
19
+
20
+ export type { CollisionDetector } from '@dnd-kit/abstract'
21
+ export type * from './props'
@@ -0,0 +1,21 @@
1
+ export {
2
+ DndContext,
3
+ useDndContext,
4
+ useDraggable,
5
+ useDroppable,
6
+ SortableContext,
7
+ useSortable,
8
+ SortableList,
9
+ } from './components'
10
+
11
+ export { arrayMove } from './sort'
12
+ export {
13
+ closestCenter,
14
+ closestCorners,
15
+ pointerIntersection,
16
+ shapeIntersection,
17
+ defaultCollisionDetection,
18
+ } from '@dnd-kit/collision'
19
+
20
+ export type { CollisionDetector } from '@dnd-kit/abstract'
21
+ export type * from './props'
package/src/props.ts ADDED
@@ -0,0 +1,83 @@
1
+ import type { CollisionDetector, Data, UniqueIdentifier } from '@dnd-kit/abstract'
2
+ import type { PanEvent, ViewProps } from '@octane-xplat/ui'
3
+
4
+ export type DndId = UniqueIdentifier
5
+ export interface DndItem {
6
+ id: DndId
7
+ data: Data
8
+ }
9
+
10
+ export interface DndSnapshot {
11
+ active: DndItem | null
12
+ over: DndItem | null
13
+ transform: { x: number; y: number }
14
+ isDragging: boolean
15
+ }
16
+
17
+ export interface DragEvent extends DndSnapshot {
18
+ canceled: boolean
19
+ }
20
+
21
+ /** Supply current viewport bounds and offsets; scrollTo must update offsetRef synchronously. */
22
+ export interface AutoScroll {
23
+ bounds(): { x: number; y: number; width: number; height: number } | null
24
+ offsetRef: { current: number }
25
+ maxOffset(): number
26
+ scrollTo(offset: number): void
27
+ axis?: 'x' | 'y'
28
+ threshold?: number
29
+ speed?: number
30
+ }
31
+
32
+ export interface DndContextProps {
33
+ children?: ViewProps['children']
34
+ collisionDetection?: CollisionDetector
35
+ autoScroll?: AutoScroll
36
+ onDragStart?: (event: DragEvent) => void
37
+ onDragMove?: (event: DragEvent) => void
38
+ onDragOver?: (event: DragEvent) => void
39
+ onDragEnd?: (event: DragEvent) => void
40
+ onDragCancel?: (event: DragEvent) => void
41
+ }
42
+
43
+ export interface DraggableOptions {
44
+ id: DndId
45
+ data?: Data
46
+ disabled?: boolean
47
+ }
48
+
49
+ export interface DroppableOptions extends DraggableOptions {
50
+ accept?: (source: DndItem) => boolean
51
+ }
52
+
53
+ export interface DraggableResult {
54
+ ref(element: any): void
55
+ onPan(event: PanEvent): void
56
+ isDragging: boolean
57
+ transform: { x: number; y: number }
58
+ /** Spread into View with ref and onPan. */
59
+ style: NonNullable<ViewProps['style']>
60
+ }
61
+
62
+ export interface DroppableResult {
63
+ ref(element: any): void
64
+ isOver: boolean
65
+ }
66
+
67
+ export interface SortableResult extends DraggableResult {
68
+ isOver: boolean
69
+ }
70
+
71
+ export interface SortableContextProps {
72
+ items: readonly DndId[]
73
+ children?: ViewProps['children']
74
+ }
75
+
76
+ export interface SortableListProps<T extends DndId = DndId> extends Omit<ViewProps, 'children'> {
77
+ items: readonly T[]
78
+ renderItem(id: T, index: number): ViewProps['children']
79
+ /** Called after a successful drop within this list; caller owns the item order. */
80
+ onReorder(items: T[], event: DragEvent): void
81
+ disabled?: boolean
82
+ dnd?: Omit<DndContextProps, 'children'>
83
+ }
package/src/sort.ts ADDED
@@ -0,0 +1,19 @@
1
+ /** Return a new order. Invalid indices leave the order unchanged. */
2
+ export function arrayMove<T>(items: readonly T[], from: number, to: number): T[] {
3
+ const result = [...items]
4
+ if (
5
+ !Number.isInteger(from) ||
6
+ !Number.isInteger(to) ||
7
+ from < 0 ||
8
+ to < 0 ||
9
+ from >= items.length ||
10
+ to >= items.length ||
11
+ from === to
12
+ ) {
13
+ return result
14
+ }
15
+
16
+ const [item] = result.splice(from, 1)
17
+ result.splice(to, 0, item!)
18
+ return result
19
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 1,
3
+ "files": [
4
+ "components.d.ts",
5
+ "controller.d.ts",
6
+ "geometry.web.d.ts",
7
+ "index.d.ts",
8
+ "props.d.ts",
9
+ "sort.d.ts"
10
+ ]
11
+ }
@@ -0,0 +1,13 @@
1
+ import type { DndId, DndContextProps, DndSnapshot, DraggableOptions, DraggableResult, DroppableOptions, DroppableResult, SortableContextProps, SortableResult, SortableListProps } from './props.js';
2
+ /** Scope registrations to one renderer root. Nested roots need their own DndContext. */
3
+ export declare function DndContext(props: DndContextProps): import("octane/jsx-runtime").JSX.Element;
4
+ /** Subscribes independently so retained native children observe drag state. */
5
+ export declare function useDndContext(): DndSnapshot;
6
+ export declare function useDraggable(options: DraggableOptions): DraggableResult;
7
+ export declare function useDroppable(options: DroppableOptions): DroppableResult;
8
+ /** Declares list membership for useSortable; it does not own item order. */
9
+ export declare function SortableContext(props: SortableContextProps): import("octane/jsx-runtime").JSX.Element;
10
+ /** Bind and spread onPan/style onto one View. Order changes only when the owner commits a drop. */
11
+ export declare function useSortable(options: DraggableOptions): SortableResult;
12
+ /** A controlled sortable list. Provide stable unique ids and save the order in onReorder. */
13
+ export declare function SortableList<T extends DndId>(props: SortableListProps<T>): import("octane/jsx-runtime").JSX.Element;
@@ -0,0 +1,32 @@
1
+ import { DragDropManager, Draggable, Droppable } from '@dnd-kit/abstract';
2
+ import type { PanEvent } from '@octane-xplat/ui';
3
+ import type { DndContextProps, DndSnapshot, DraggableOptions, DroppableOptions } from './props.js';
4
+ declare class PanDraggable extends Draggable {
5
+ onPan?: (event: PanEvent) => void;
6
+ }
7
+ /** One controller per DndContext; no global registry or cross-root context assumptions. */
8
+ export declare class DndController extends DragDropManager<PanDraggable, Droppable> {
9
+ options: DndContextProps;
10
+ readonly draggableNodes: Map<import("@dnd-kit/abstract").UniqueIdentifier, any>;
11
+ readonly droppableNodes: Map<import("@dnd-kit/abstract").UniqueIdentifier, any>;
12
+ readonly scrollShift$: import("@dnd-kit/state").Signal<{
13
+ x: number;
14
+ y: number;
15
+ }>;
16
+ private listeners;
17
+ private snapshot;
18
+ private dispose;
19
+ private scrollTimer;
20
+ private destroyed;
21
+ constructor();
22
+ getSnapshot: () => DndSnapshot;
23
+ subscribe: (listener: () => void) => () => void;
24
+ registerDraggable(options: DraggableOptions): PanDraggable;
25
+ registerDroppable(options: DroppableOptions): Droppable<import("@dnd-kit/abstract").Data, this>;
26
+ refresh(): void;
27
+ cancel(): void;
28
+ startAutoScroll(): void;
29
+ private stopAutoScroll;
30
+ disposeController(): void;
31
+ }
32
+ export {};
@@ -0,0 +1,3 @@
1
+ import { Rectangle } from '@dnd-kit/geometry';
2
+ export declare function measure(element: any): Rectangle | null;
3
+ export declare function dragStyle(x: number, y: number): Record<string, number | string>;
@@ -0,0 +1,5 @@
1
+ export { DndContext, useDndContext, useDraggable, useDroppable, SortableContext, useSortable, SortableList, } from './components.js';
2
+ export { arrayMove } from './sort.js';
3
+ export { closestCenter, closestCorners, pointerIntersection, shapeIntersection, defaultCollisionDetection, } from '@dnd-kit/collision';
4
+ export type { CollisionDetector } from '@dnd-kit/abstract';
5
+ export type * from './props.js';