@invopop/popui 0.1.4-beta.46 → 0.1.4-beta.48

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.
@@ -3,8 +3,8 @@
3
3
  import { createFloatingActions } from 'svelte-floating-ui'
4
4
  import { clickOutside } from './clickOutside.js'
5
5
  import { portal } from 'svelte-portal'
6
- import { slide } from 'svelte/transition'
7
6
  import type { BaseDropdownProps } from './types.js'
7
+ import type { TransitionConfig } from 'svelte/transition'
8
8
 
9
9
  let {
10
10
  isOpen = $bindable(false),
@@ -46,6 +46,33 @@
46
46
  middleware
47
47
  })
48
48
 
49
+ // Custom transition that mimics shadcn style
50
+ function dropdownTransition(
51
+ node: HTMLElement,
52
+ { duration = 150 }: { duration?: number } = {}
53
+ ): TransitionConfig {
54
+ const side = placement.split('-')[0]
55
+
56
+ // Calculate slide direction
57
+ let slideY = 0
58
+ let slideX = 0
59
+ if (side === 'bottom') slideY = -8
60
+ if (side === 'top') slideY = 8
61
+ if (side === 'left') slideX = 8
62
+ if (side === 'right') slideX = -8
63
+
64
+ return {
65
+ duration,
66
+ css: (t) => {
67
+ const eased = t * (2 - t) // ease-out
68
+ return `
69
+ opacity: ${eased};
70
+ transform: scale(${0.95 + eased * 0.05}) translate(${slideX * (1 - eased)}px, ${slideY * (1 - eased)}px);
71
+ `
72
+ }
73
+ }
74
+ }
75
+
49
76
  export const toggle = () => {
50
77
  isOpen = !isOpen
51
78
  }
@@ -83,7 +110,7 @@
83
110
  }, 100)
84
111
  isOpen = false
85
112
  }}
86
- transition:slide={{ duration: 100 }}
113
+ transition:dropdownTransition={{ duration: 150 }}
87
114
  >
88
115
  {@render children?.()}
89
116
  </div>
@@ -45,14 +45,14 @@
45
45
  </script>
46
46
 
47
47
  <div
48
- class="overflow-hidden transition-all duration-300 ease-in-out relative rounded-md"
48
+ class="overflow-hidden transition-all duration-150 ease-in-out relative rounded-md"
49
49
  class:w-[280px]={expanded}
50
50
  class:w-10={!expanded}
51
51
  use:clickOutside
52
52
  onclick_outside={handleClickOutside}
53
53
  >
54
54
  <div
55
- class="w-[280px] transition-opacity duration-200 absolute left-0 top-0"
55
+ class="w-[280px] transition-opacity duration-100 absolute left-0 top-0"
56
56
  class:opacity-0={!expanded}
57
57
  class:opacity-100={expanded}
58
58
  class:pointer-events-none={!expanded}
@@ -68,7 +68,7 @@
68
68
  />
69
69
  </div>
70
70
  <div
71
- class="transition-opacity duration-200"
71
+ class="transition-opacity duration-100"
72
72
  class:opacity-0={expanded}
73
73
  class:opacity-100={!expanded}
74
74
  class:pointer-events-none={expanded}
@@ -8,8 +8,26 @@
8
8
  import { ChevronRight } from '@steeze-ui/heroicons'
9
9
  import { slide } from 'svelte/transition'
10
10
  import { flip } from 'svelte/animate'
11
- import { dndzone } from 'svelte-dnd-action'
12
- import { onMount } from 'svelte'
11
+ import {
12
+ draggable as makeDraggable,
13
+ dropTargetForElements
14
+ } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
15
+ import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine'
16
+ import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element'
17
+ import { reorder } from '@atlaskit/pragmatic-drag-and-drop/reorder'
18
+ import {
19
+ attachClosestEdge,
20
+ extractClosestEdge,
21
+ type Edge
22
+ } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge'
23
+ import { onMount, onDestroy, untrack } from 'svelte'
24
+ import {
25
+ shouldShowDropIndicator,
26
+ reorderItems,
27
+ moveItemBetweenGroups,
28
+ type DropIndicatorState,
29
+ type DndItem as DndItemType
30
+ } from './drawer-dnd-helpers'
13
31
 
14
32
  const flipDurationMs = 150
15
33
 
@@ -58,7 +76,11 @@
58
76
  let mounted = $state(false)
59
77
  let itemsCache = $state<DrawerOption[]>([])
60
78
  let isDragging = $state(false)
79
+ let draggedItemId = $state<string | null>(null)
61
80
  let emitTimeout: number | undefined
81
+ let draggedOverGroup = $state<string | null>(null)
82
+ let dropIndicator = $state<DropIndicatorState>(null)
83
+ let cleanupFunctions: (() => void)[] = []
62
84
 
63
85
  // Build internal DND items from external items
64
86
  function buildListIn() {
@@ -140,14 +162,18 @@
140
162
  itemsCache = JSON.parse(JSON.stringify(items))
141
163
  buildListIn()
142
164
  mounted = true
165
+
166
+ // Set up auto-scroll
167
+ const autoScrollCleanup = autoScrollForElements({
168
+ element: document.documentElement
169
+ })
170
+ cleanupFunctions.push(autoScrollCleanup)
143
171
  })
144
172
 
145
- function transformDraggedElement(draggedEl: HTMLElement | undefined) {
146
- if (draggedEl) {
147
- draggedEl.style.border = 'none'
148
- draggedEl.style.outline = 'none'
149
- }
150
- }
173
+ onDestroy(() => {
174
+ cleanupFunctions.forEach((cleanup) => cleanup())
175
+ cleanupFunctions = []
176
+ })
151
177
 
152
178
  function emitGroupDistribution() {
153
179
  if (ondropitem && hasGroups) {
@@ -168,40 +194,200 @@
168
194
  }
169
195
  }
170
196
 
171
- function handleDndConsider(groupSlug: string, e: CustomEvent<any>) {
172
- if (!isDragging) {
173
- isDragging = true
197
+ // Setup draggable item (Svelte action)
198
+ function setupDraggableItem(element: HTMLElement, params: [DndItem, string]) {
199
+ const [dndItem, groupSlug] = params
200
+ if (!element || dndItem.locked) return
201
+
202
+ const cleanup = makeDraggable({
203
+ element,
204
+ getInitialData: () => ({ id: dndItem.id, groupSlug, type: 'drawer-item' }),
205
+ onDragStart: () => {
206
+ isDragging = true
207
+ draggedItemId = dndItem.id
208
+ dropIndicator = null
209
+ },
210
+ onDrop: () => {
211
+ isDragging = false
212
+ draggedItemId = null
213
+ dropIndicator = null
214
+ }
215
+ })
216
+
217
+ return {
218
+ destroy() {
219
+ cleanup()
220
+ }
174
221
  }
175
- groupDndItems[groupSlug] = e.detail.items
176
222
  }
177
223
 
178
- function handleDndFinalize(groupSlug: string, e: CustomEvent<any>) {
179
- isDragging = false
180
- groupDndItems[groupSlug] = e.detail.items
224
+ // Setup drop zone for a container (group or ungrouped) (Svelte action)
225
+ function setupDropZone(element: HTMLElement, groupSlug: string) {
226
+ if (!element) return
227
+
228
+ const cleanup = dropTargetForElements({
229
+ element,
230
+ getData: () => {
231
+ const items = groupSlug === 'ungrouped' ? ungroupedDndItems : groupDndItems[groupSlug] || []
232
+ return { groupSlug, items: items.map((i) => i.id) }
233
+ },
234
+ canDrop: ({ source }) => source.data.type === 'drawer-item',
235
+ onDragEnter: () => {
236
+ draggedOverGroup = groupSlug
237
+ },
238
+ onDragLeave: () => {
239
+ if (draggedOverGroup === groupSlug) {
240
+ draggedOverGroup = null
241
+ }
242
+ },
243
+ onDrop: ({ source, location }) => {
244
+ draggedOverGroup = null
245
+ dropIndicator = null
246
+ const sourceId = source.data.id as string
247
+ const sourceGroup = source.data.groupSlug as string
248
+ const targetGroup = groupSlug
249
+
250
+ // Get source and target arrays
251
+ const sourceItems =
252
+ sourceGroup === 'ungrouped' ? ungroupedDndItems : groupDndItems[sourceGroup] || []
253
+ const targetItems =
254
+ targetGroup === 'ungrouped' ? ungroupedDndItems : groupDndItems[targetGroup] || []
255
+
256
+ // Find the dragged item
257
+ const sourceIndex = sourceItems.findIndex((item) => item.id === sourceId)
258
+ if (sourceIndex === -1) return
259
+
260
+ const draggedItem = sourceItems[sourceIndex]
261
+
262
+ // If moving within the same group, we need to handle reordering
263
+ if (sourceGroup === targetGroup) {
264
+ // Check if we're dropping on another item
265
+ const dropTargets = location.current.dropTargets
266
+ const itemDropTarget = dropTargets.find((target) => target.data.itemId)
267
+
268
+ if (itemDropTarget) {
269
+ const targetItemId = itemDropTarget.data.itemId as string
270
+ const edge = extractClosestEdge(itemDropTarget.data)
271
+
272
+ if (edge) {
273
+ const newItems = reorderItems(targetItems, sourceId, targetItemId, edge)
274
+
275
+ if (targetGroup === 'ungrouped') {
276
+ ungroupedDndItems = newItems
277
+ } else {
278
+ groupDndItems[targetGroup] = newItems
279
+ }
280
+ }
281
+ }
282
+ } else {
283
+ // Moving between groups
284
+ const dropTargets = location.current.dropTargets
285
+ const itemDropTarget = dropTargets.find((target) => target.data.itemId)
181
286
 
182
- const newItems = buildListOut()
183
- items = newItems
184
- itemsCache = JSON.parse(JSON.stringify(items))
185
- onreorder?.(newItems)
186
- emitGroupDistribution()
187
- }
287
+ let targetItemId: string | undefined
288
+ let edge: Edge | undefined
289
+
290
+ if (itemDropTarget) {
291
+ targetItemId = itemDropTarget.data.itemId as string
292
+ edge = extractClosestEdge(itemDropTarget.data) || undefined
293
+ }
294
+
295
+ const { newSourceItems, newTargetItems } = moveItemBetweenGroups(
296
+ sourceItems,
297
+ targetItems,
298
+ sourceId,
299
+ targetItemId,
300
+ edge
301
+ )
302
+
303
+ if (sourceGroup === 'ungrouped') {
304
+ ungroupedDndItems = newSourceItems
305
+ } else {
306
+ groupDndItems[sourceGroup] = newSourceItems
307
+ }
188
308
 
189
- function handleUngroupedDndConsider(e: CustomEvent<any>) {
190
- if (!isDragging) {
191
- isDragging = true
309
+ if (targetGroup === 'ungrouped') {
310
+ ungroupedDndItems = newTargetItems
311
+ } else {
312
+ groupDndItems[targetGroup] = newTargetItems
313
+ }
314
+ }
315
+
316
+ // Update items and notify
317
+ const newItems = buildListOut()
318
+ items = newItems
319
+ itemsCache = JSON.parse(JSON.stringify(items))
320
+ onreorder?.(newItems)
321
+ emitGroupDistribution()
322
+ }
323
+ })
324
+
325
+ return {
326
+ destroy() {
327
+ cleanup()
328
+ }
192
329
  }
193
- ungroupedDndItems = e.detail.items
194
330
  }
195
331
 
196
- function handleUngroupedDndFinalize(e: CustomEvent<any>) {
197
- isDragging = false
198
- ungroupedDndItems = e.detail.items
332
+ // Setup drop zone for individual items (for reordering within same group) (Svelte action)
333
+ function setupItemDropZone(element: HTMLElement, params: [DndItem, string]) {
334
+ const [dndItem, groupSlug] = params
335
+ if (!element) return
336
+
337
+ const cleanup = dropTargetForElements({
338
+ element,
339
+ getData: ({ input }) => {
340
+ const data = { itemId: dndItem.id, groupSlug }
341
+ return attachClosestEdge(data, {
342
+ element,
343
+ input,
344
+ allowedEdges: ['top', 'bottom']
345
+ })
346
+ },
347
+ canDrop: ({ source }) => {
348
+ return source.data.type === 'drawer-item' && source.data.id !== dndItem.id
349
+ },
350
+ onDragEnter: ({ self, source }) => {
351
+ const edge = extractClosestEdge(self.data)
352
+ if (!edge) return
353
+
354
+ const sourceId = source.data.id as string
355
+ const sourceGroup = source.data.groupSlug as string
356
+ const items = groupSlug === 'ungrouped' ? ungroupedDndItems : groupDndItems[groupSlug] || []
357
+
358
+ if (shouldShowDropIndicator(sourceId, dndItem.id, sourceGroup, groupSlug, edge, items)) {
359
+ dropIndicator = { itemId: dndItem.id, edge }
360
+ }
361
+ },
362
+ onDrag: ({ self, source }) => {
363
+ const edge = extractClosestEdge(self.data)
364
+ if (!edge) return
365
+
366
+ const sourceId = source.data.id as string
367
+ const sourceGroup = source.data.groupSlug as string
368
+ const items = groupSlug === 'ungrouped' ? ungroupedDndItems : groupDndItems[groupSlug] || []
369
+
370
+ if (shouldShowDropIndicator(sourceId, dndItem.id, sourceGroup, groupSlug, edge, items)) {
371
+ dropIndicator = { itemId: dndItem.id, edge }
372
+ } else {
373
+ dropIndicator = null
374
+ }
375
+ },
376
+ onDragLeave: () => {
377
+ if (dropIndicator?.itemId === dndItem.id) {
378
+ dropIndicator = null
379
+ }
380
+ },
381
+ onDrop: () => {
382
+ dropIndicator = null
383
+ }
384
+ })
199
385
 
200
- const newItems = buildListOut()
201
- items = newItems
202
- itemsCache = JSON.parse(JSON.stringify(items))
203
- onreorder?.(newItems)
204
- emitGroupDistribution()
386
+ return {
387
+ destroy() {
388
+ cleanup()
389
+ }
390
+ }
205
391
  }
206
392
 
207
393
  function updateItem(item: DrawerOption) {
@@ -231,7 +417,7 @@
231
417
  >
232
418
  {@render children?.()}
233
419
 
234
- {#if hasGroups}
420
+ {#if hasGroups && groups}
235
421
  {#each groups as group, index}
236
422
  {@const groupItems = groupedItems.get(group.slug) || []}
237
423
  {@const isLastGroup = index === groups!.length - 1}
@@ -280,17 +466,7 @@
280
466
  transition:slide={{ duration: collapsibleGroups ? 200 : 0 }}
281
467
  >
282
468
  {#if draggable}
283
- <div
284
- use:dndzone={{
285
- items: groupDndItems[group.slug] || [],
286
- flipDurationMs,
287
- dropTargetStyle: {},
288
- type: 'drawer-item',
289
- transformDraggedElement
290
- }}
291
- onconsider={(e) => handleDndConsider(group.slug, e)}
292
- onfinalize={(e) => handleDndFinalize(group.slug, e)}
293
- >
469
+ <div use:setupDropZone={group.slug} class="min-h-[40px]">
294
470
  {#if !groupItems.length}
295
471
  <div class="px-1 pt-1 pb-5">
296
472
  <EmptyState
@@ -301,7 +477,22 @@
301
477
  </div>
302
478
  {:else}
303
479
  {#each groupDndItems[group.slug] || [] as dndItem (dndItem.id)}
304
- <div animate:flip={{ duration: isDragging ? flipDurationMs : 0 }}>
480
+ {@const showTopIndicator =
481
+ dropIndicator?.itemId === dndItem.id && dropIndicator?.edge === 'top'}
482
+ {@const showBottomIndicator =
483
+ dropIndicator?.itemId === dndItem.id && dropIndicator?.edge === 'bottom'}
484
+ {@const isBeingDragged = draggedItemId === dndItem.id}
485
+ <div
486
+ animate:flip={{ duration: isDragging ? flipDurationMs : 0 }}
487
+ use:setupDraggableItem={[dndItem, group.slug]}
488
+ use:setupItemDropZone={[dndItem, group.slug]}
489
+ class:border-t-2={showTopIndicator}
490
+ class:border-t-accent-50={showTopIndicator}
491
+ class:border-b-2={showBottomIndicator}
492
+ class:border-b-accent-50={showBottomIndicator}
493
+ class:opacity-40={isBeingDragged}
494
+ class="transition-opacity"
495
+ >
305
496
  {@render drawerItem(dndItem)}
306
497
  </div>
307
498
  {/each}
@@ -332,19 +523,26 @@
332
523
  {#if ungroupedItems.length}
333
524
  {#if draggable}
334
525
  <div
335
- class="flex-shrink-0 overflow-y-auto max-h-[564px]"
336
- use:dndzone={{
337
- items: ungroupedDndItems,
338
- flipDurationMs,
339
- dropTargetStyle: {},
340
- type: 'drawer-item',
341
- transformDraggedElement
342
- }}
343
- onconsider={handleUngroupedDndConsider}
344
- onfinalize={handleUngroupedDndFinalize}
526
+ class="flex-shrink-0 overflow-y-auto max-h-[564px] min-h-[40px]"
527
+ use:setupDropZone={'ungrouped'}
345
528
  >
346
529
  {#each ungroupedDndItems as dndItem (dndItem.id)}
347
- <div animate:flip={{ duration: isDragging ? flipDurationMs : 0 }}>
530
+ {@const showTopIndicator =
531
+ dropIndicator?.itemId === dndItem.id && dropIndicator?.edge === 'top'}
532
+ {@const showBottomIndicator =
533
+ dropIndicator?.itemId === dndItem.id && dropIndicator?.edge === 'bottom'}
534
+ {@const isBeingDragged = draggedItemId === dndItem.id}
535
+ <div
536
+ animate:flip={{ duration: isDragging ? flipDurationMs : 0 }}
537
+ use:setupDraggableItem={[dndItem, 'ungrouped']}
538
+ use:setupItemDropZone={[dndItem, 'ungrouped']}
539
+ class:border-t-2={showTopIndicator}
540
+ class:border-t-accent-50={showTopIndicator}
541
+ class:border-b-2={showBottomIndicator}
542
+ class:border-b-accent-50={showBottomIndicator}
543
+ class:opacity-40={isBeingDragged}
544
+ class="transition-opacity"
545
+ >
348
546
  {@render drawerItem(dndItem)}
349
547
  </div>
350
548
  {/each}
@@ -79,6 +79,7 @@ export interface DataTableProps<TData> {
79
79
  initialFrozenColumns?: string[];
80
80
  initialColumnOrder?: string[];
81
81
  initialColumnVisibility?: Record<string, boolean>;
82
+ initialColumnSizing?: Record<string, number>;
82
83
  pageSizeOptions?: number[];
83
84
  emptyState?: Omit<EmptyStateProps, 'children' | 'check'>;
84
85
  onRowClick?: (row: TData) => void;
@@ -46,6 +46,7 @@
46
46
  initialFrozenColumns = [],
47
47
  initialColumnOrder = [],
48
48
  initialColumnVisibility = {},
49
+ initialColumnSizing = {},
49
50
  emptyState = {
50
51
  iconSource: Search,
51
52
  title: 'No results',
@@ -83,7 +84,7 @@
83
84
  : []
84
85
  )
85
86
  let pagination = $state<PaginationState>({ pageIndex: initialPage, pageSize: initialPageSize })
86
- let columnSizing = $state<ColumnSizingState>({})
87
+ let columnSizing = $state<ColumnSizingState>(initialColumnSizing)
87
88
  let columnSizingInfo = $state<ColumnSizingInfoState>({
88
89
  columnSizingStart: [],
89
90
  deltaOffset: null,
@@ -272,8 +273,9 @@
272
273
  if (header.id === columnId) {
273
274
  break
274
275
  }
275
- // Add width of previous frozen columns (or select column)
276
- if (frozenColumns.has(header.id) || header.id === 'select') {
276
+ // Only add width of previous frozen columns that are visible (or select column)
277
+ const isVisible = header.column?.getIsVisible?.() ?? true
278
+ if (isVisible && (frozenColumns.has(header.id) || header.id === 'select')) {
277
279
  offset += header.getSize()
278
280
  }
279
281
  }
@@ -432,7 +434,7 @@
432
434
  hasSelectColumn = false,
433
435
  ...restProps
434
436
  }: { column: Column<TData>; title?: string; isFirst?: boolean; hasSelectColumn?: boolean } & HTMLAttributes<HTMLDivElement>)}
435
- {@const isCurrency = column.columnDef.meta?.cellType === 'currency'}
437
+ {@const isCurrency = column.columnDef.cellType === 'currency'}
436
438
  {@const needsEdgePadding = isFirst && !hasSelectColumn}
437
439
  <div
438
440
  class={cn('flex items-center w-full [th[data-last-frozen=true]_&]:border-r [th[data-last-frozen=true]_&]:border-border', className)}
@@ -0,0 +1,30 @@
1
+ import type { Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
2
+ export type DndItem = {
3
+ id: string;
4
+ locked?: boolean;
5
+ [key: string]: any;
6
+ };
7
+ export type DropIndicatorState = {
8
+ itemId: string;
9
+ edge: Edge;
10
+ } | null;
11
+ /**
12
+ * Checks if dropping an item would result in no position change
13
+ * (i.e., dropping right next to its current position)
14
+ */
15
+ export declare function shouldShowDropIndicator(sourceId: string, targetId: string, sourceGroup: string, targetGroup: string, edge: Edge, items: DndItem[]): boolean;
16
+ /**
17
+ * Calculates the new index where a dragged item should be inserted
18
+ */
19
+ export declare function calculateInsertIndex(sourceIndex: number, targetIndex: number, edge: Edge): number;
20
+ /**
21
+ * Reorders items within the same group
22
+ */
23
+ export declare function reorderItems<T extends DndItem>(items: T[], sourceId: string, targetId: string, edge: Edge): T[];
24
+ /**
25
+ * Moves an item from one group to another
26
+ */
27
+ export declare function moveItemBetweenGroups<T extends DndItem>(sourceItems: T[], targetItems: T[], sourceId: string, targetId?: string, edge?: Edge): {
28
+ newSourceItems: T[];
29
+ newTargetItems: T[];
30
+ };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Checks if dropping an item would result in no position change
3
+ * (i.e., dropping right next to its current position)
4
+ */
5
+ export function shouldShowDropIndicator(sourceId, targetId, sourceGroup, targetGroup, edge, items) {
6
+ // Always show for cross-group drops
7
+ if (sourceGroup !== targetGroup) {
8
+ return true;
9
+ }
10
+ // Check if dropping would result in no position change
11
+ const sourceIndex = items.findIndex((item) => item.id === sourceId);
12
+ const targetIndex = items.findIndex((item) => item.id === targetId);
13
+ // Don't show indicator if dropping adjacent to current position
14
+ const isAdjacentTop = edge === 'top' && targetIndex === sourceIndex + 1;
15
+ const isAdjacentBottom = edge === 'bottom' && targetIndex === sourceIndex - 1;
16
+ return !isAdjacentTop && !isAdjacentBottom;
17
+ }
18
+ /**
19
+ * Calculates the new index where a dragged item should be inserted
20
+ */
21
+ export function calculateInsertIndex(sourceIndex, targetIndex, edge) {
22
+ let insertIndex = targetIndex;
23
+ // Adjust target index if source was before target (array shifts after removal)
24
+ if (sourceIndex < targetIndex) {
25
+ insertIndex = targetIndex - 1;
26
+ }
27
+ // If dropping on bottom edge, insert after
28
+ if (edge === 'bottom') {
29
+ insertIndex = insertIndex + 1;
30
+ }
31
+ return insertIndex;
32
+ }
33
+ /**
34
+ * Reorders items within the same group
35
+ */
36
+ export function reorderItems(items, sourceId, targetId, edge) {
37
+ const sourceIndex = items.findIndex((item) => item.id === sourceId);
38
+ const targetIndex = items.findIndex((item) => item.id === targetId);
39
+ if (sourceIndex === -1 || targetIndex === -1) {
40
+ return items;
41
+ }
42
+ const newItems = [...items];
43
+ const [draggedItem] = newItems.splice(sourceIndex, 1);
44
+ const insertIndex = calculateInsertIndex(sourceIndex, targetIndex, edge);
45
+ newItems.splice(insertIndex, 0, draggedItem);
46
+ return newItems;
47
+ }
48
+ /**
49
+ * Moves an item from one group to another
50
+ */
51
+ export function moveItemBetweenGroups(sourceItems, targetItems, sourceId, targetId, edge) {
52
+ const draggedItem = sourceItems.find((item) => item.id === sourceId);
53
+ if (!draggedItem) {
54
+ return { newSourceItems: sourceItems, newTargetItems: targetItems };
55
+ }
56
+ const newSourceItems = sourceItems.filter((item) => item.id !== sourceId);
57
+ // If no target specified, append to end
58
+ if (!targetId || !edge) {
59
+ const newTargetItems = [...targetItems, draggedItem];
60
+ return { newSourceItems, newTargetItems };
61
+ }
62
+ // Insert at specific position based on target and edge
63
+ const targetIndex = targetItems.findIndex((item) => item.id === targetId);
64
+ if (targetIndex === -1) {
65
+ const newTargetItems = [...targetItems, draggedItem];
66
+ return { newSourceItems, newTargetItems };
67
+ }
68
+ const newTargetItems = [...targetItems];
69
+ const insertIndex = edge === 'bottom' ? targetIndex + 1 : targetIndex;
70
+ newTargetItems.splice(insertIndex, 0, draggedItem);
71
+ return { newSourceItems, newTargetItems };
72
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@invopop/popui",
3
3
  "license": "MIT",
4
- "version": "0.1.4-beta.46",
4
+ "version": "0.1.4-beta.48",
5
5
  "repository": {
6
6
  "url": "https://github.com/invopop/popui"
7
7
  },
@@ -81,6 +81,9 @@
81
81
  "types": "./dist/index.d.ts",
82
82
  "type": "module",
83
83
  "dependencies": {
84
+ "@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
85
+ "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.5",
86
+ "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
84
87
  "@floating-ui/core": "^1.5.1",
85
88
  "@invopop/ui-icons": "^0.0.78",
86
89
  "@steeze-ui/heroicons": "^2.2.3",
@@ -97,7 +100,6 @@
97
100
  "inter-ui": "^3.19.3",
98
101
  "lodash-es": "^4.17.21",
99
102
  "mode-watcher": "^1.1.0",
100
- "svelte-dnd-action": "^0.9.69",
101
103
  "svelte-floating-ui": "^1.5.8",
102
104
  "svelte-headlessui": "^0.0.46",
103
105
  "svelte-intersection-observer-action": "^0.0.4",
@@ -106,6 +108,7 @@
106
108
  "svelte-transition": "^0.0.17",
107
109
  "svelte-viewport-info": "^1.0.2",
108
110
  "tailwind-variants": "^1.0.0",
111
+ "tailwindcss-animate": "^1.0.7",
109
112
  "zod": "^4.3.5"
110
113
  }
111
114
  }