@invopop/popui 0.1.74 → 0.1.76
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/dist/BaseTableHeaderOrderBy.svelte +1 -0
- package/dist/DrawerContext.svelte +33 -74
- package/dist/DrawerContextItem.svelte +5 -3
- package/dist/DropdownSelect.svelte +5 -0
- package/dist/data-table/data-table-helpers.js +7 -2
- package/dist/types.d.ts +2 -1
- package/package.json +1 -1
- package/dist/drawer-keyboard-helpers.d.ts +0 -16
- package/dist/drawer-keyboard-helpers.js +0 -41
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { slide } from 'svelte/transition'
|
|
10
10
|
import { flip } from 'svelte/animate'
|
|
11
11
|
import clsx from 'clsx'
|
|
12
|
+
import { cn } from './utils.js'
|
|
12
13
|
import {
|
|
13
14
|
draggable as makeDraggable,
|
|
14
15
|
dropTargetForElements
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
type Edge
|
|
23
24
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge'
|
|
24
25
|
import { onMount, onDestroy, untrack } from 'svelte'
|
|
26
|
+
import { Command } from 'bits-ui'
|
|
25
27
|
import {
|
|
26
28
|
shouldShowDropIndicator,
|
|
27
29
|
reorderItems,
|
|
@@ -29,11 +31,6 @@
|
|
|
29
31
|
type DropIndicatorState,
|
|
30
32
|
type DndItem as DndItemType
|
|
31
33
|
} from './drawer-dnd-helpers'
|
|
32
|
-
import {
|
|
33
|
-
getFocusableItems,
|
|
34
|
-
getNextFocusedIndex,
|
|
35
|
-
selectFocusedItem
|
|
36
|
-
} from './drawer-keyboard-helpers'
|
|
37
34
|
|
|
38
35
|
const flipDurationMs = 150
|
|
39
36
|
|
|
@@ -44,24 +41,25 @@
|
|
|
44
41
|
widthClass = 'w-60',
|
|
45
42
|
collapsibleGroups = true,
|
|
46
43
|
flagPosition = 'after',
|
|
44
|
+
autofocus = false,
|
|
47
45
|
onclick,
|
|
48
46
|
onselect,
|
|
49
47
|
onreorder,
|
|
50
48
|
ondropitem,
|
|
51
49
|
children,
|
|
52
50
|
groups,
|
|
51
|
+
class: className,
|
|
53
52
|
...rest
|
|
54
53
|
}: DrawerContextProps = $props()
|
|
55
54
|
|
|
56
55
|
type DndItem = DrawerOption & { id: string }
|
|
57
56
|
|
|
58
57
|
let containerClasses = $derived(
|
|
59
|
-
|
|
58
|
+
cn(
|
|
60
59
|
widthClass,
|
|
61
|
-
'border border-border rounded-2xl shadow-lg bg-background flex flex-col py-1 max-h-[568px] overflow-y-auto list-none',
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
}
|
|
60
|
+
'border border-border rounded-2xl shadow-lg bg-background flex flex-col py-1 max-h-[568px] overflow-y-auto list-none outline-none',
|
|
61
|
+
clsx({ '[scrollbar-width:none] [&::-webkit-scrollbar]:hidden': draggable }),
|
|
62
|
+
className
|
|
65
63
|
)
|
|
66
64
|
)
|
|
67
65
|
|
|
@@ -88,31 +86,11 @@
|
|
|
88
86
|
return { groupedItems: grouped, ungroupedItems: ungrouped }
|
|
89
87
|
})
|
|
90
88
|
|
|
91
|
-
// Items in display order (matches visual rendering order)
|
|
92
|
-
let itemsInDisplayOrder = $derived.by(() => {
|
|
93
|
-
const displayOrder: DrawerOption[] = []
|
|
94
|
-
|
|
95
|
-
if (hasGroups && groups) {
|
|
96
|
-
// Add grouped items in group order, only if group is open (when collapsible)
|
|
97
|
-
groups.forEach((group) => {
|
|
98
|
-
const isOpen = collapsibleGroups ? openGroups[group.slug] : true
|
|
99
|
-
if (isOpen) {
|
|
100
|
-
const groupItems = groupedItems.get(group.slug) || []
|
|
101
|
-
displayOrder.push(...groupItems)
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Add ungrouped items
|
|
107
|
-
displayOrder.push(...ungroupedItems)
|
|
108
|
-
|
|
109
|
-
return displayOrder
|
|
110
|
-
})
|
|
111
|
-
|
|
112
89
|
let openGroups = $state<Record<string, boolean>>({})
|
|
113
90
|
let groupDndItems = $state<Record<string, DndItem[]>>({})
|
|
114
91
|
let ungroupedDndItems = $state<DndItem[]>([])
|
|
115
92
|
let mounted = $state(false)
|
|
93
|
+
let commandRootEl = $state<HTMLElement | null>(null)
|
|
116
94
|
let itemsCache = $state<DrawerOption[]>([])
|
|
117
95
|
let isDragging = $state(false)
|
|
118
96
|
let draggedItemId = $state<string | null>(null)
|
|
@@ -120,8 +98,6 @@
|
|
|
120
98
|
let draggedOverGroup = $state<string | null>(null)
|
|
121
99
|
let dropIndicator = $state<DropIndicatorState>(null)
|
|
122
100
|
let cleanupFunctions: (() => void)[] = []
|
|
123
|
-
let focusedIndex = $state<number>(-1)
|
|
124
|
-
let containerRef = $state<HTMLDivElement | null>(null)
|
|
125
101
|
|
|
126
102
|
// Build internal DND items from external items
|
|
127
103
|
function buildListIn() {
|
|
@@ -204,19 +180,21 @@
|
|
|
204
180
|
buildListIn()
|
|
205
181
|
mounted = true
|
|
206
182
|
|
|
207
|
-
|
|
183
|
+
if (autofocus) {
|
|
184
|
+
setTimeout(() => {
|
|
185
|
+
commandRootEl?.focus()
|
|
186
|
+
}, 10)
|
|
187
|
+
}
|
|
188
|
+
|
|
208
189
|
const autoScrollCleanup = autoScrollForElements({
|
|
209
190
|
element: document.documentElement
|
|
210
191
|
})
|
|
211
192
|
cleanupFunctions.push(autoScrollCleanup)
|
|
212
|
-
|
|
213
|
-
window.addEventListener('keydown', handleKeyDown)
|
|
214
193
|
})
|
|
215
194
|
|
|
216
195
|
onDestroy(() => {
|
|
217
196
|
cleanupFunctions.forEach((cleanup) => cleanup())
|
|
218
197
|
cleanupFunctions = []
|
|
219
|
-
window.removeEventListener('keydown', handleKeyDown)
|
|
220
198
|
})
|
|
221
199
|
|
|
222
200
|
function emitGroupDistribution() {
|
|
@@ -445,37 +423,11 @@
|
|
|
445
423
|
openGroups = openGroups[groupSlug] ? {} : { [groupSlug]: true }
|
|
446
424
|
}
|
|
447
425
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
return null
|
|
454
|
-
})
|
|
455
|
-
|
|
456
|
-
function handleKeyDown(event: KeyboardEvent) {
|
|
457
|
-
// Don't handle if container doesn't exist
|
|
458
|
-
if (!containerRef || !document.body.contains(containerRef)) return
|
|
459
|
-
|
|
460
|
-
const focusableItems = getFocusableItems(itemsInDisplayOrder)
|
|
461
|
-
if (focusableItems.length === 0) return
|
|
462
|
-
|
|
463
|
-
if (event.key === 'ArrowDown') {
|
|
464
|
-
event.preventDefault()
|
|
465
|
-
focusedIndex = getNextFocusedIndex(focusedIndex, 'down', focusableItems.length)
|
|
466
|
-
} else if (event.key === 'ArrowUp') {
|
|
467
|
-
event.preventDefault()
|
|
468
|
-
focusedIndex = getNextFocusedIndex(focusedIndex, 'up', focusableItems.length)
|
|
469
|
-
} else if (event.key === ' ' || event.key === 'Enter') {
|
|
470
|
-
event.preventDefault()
|
|
471
|
-
const result = selectFocusedItem(itemsInDisplayOrder, focusedIndex, multiple)
|
|
472
|
-
if (result) {
|
|
473
|
-
if (result.shouldUpdate) {
|
|
474
|
-
updateItem(result.item)
|
|
475
|
-
} else {
|
|
476
|
-
onclick?.(result.item.value)
|
|
477
|
-
}
|
|
478
|
-
}
|
|
426
|
+
function handleItemSelect(item: DrawerOption) {
|
|
427
|
+
if (multiple) {
|
|
428
|
+
updateItem({ ...item, selected: !item.selected })
|
|
429
|
+
} else {
|
|
430
|
+
onclick?.(item.value)
|
|
479
431
|
}
|
|
480
432
|
}
|
|
481
433
|
</script>
|
|
@@ -487,21 +439,28 @@
|
|
|
487
439
|
{#snippet itemChildren()}
|
|
488
440
|
{@render item.content?.(item)}
|
|
489
441
|
{/snippet}
|
|
490
|
-
<
|
|
442
|
+
<Command.Item
|
|
443
|
+
value={String(item.value)}
|
|
444
|
+
disabled={item.disabled || item.locked}
|
|
445
|
+
onSelect={() => handleItemSelect(item)}
|
|
446
|
+
class={clsx('group outline-none', { 'px-1': !item.groupBy, 'cursor-grab': draggable && !item.locked })}
|
|
447
|
+
>
|
|
491
448
|
<DrawerContextItem
|
|
492
|
-
item={{ ...item,
|
|
449
|
+
item={{ ...item, flagPosition: item.flagPosition || flagPosition }}
|
|
493
450
|
{multiple}
|
|
494
451
|
{onclick}
|
|
495
452
|
onchange={updateItem}
|
|
496
453
|
children={item.content ? itemChildren : undefined}
|
|
497
454
|
/>
|
|
498
|
-
</
|
|
455
|
+
</Command.Item>
|
|
499
456
|
{/if}
|
|
500
457
|
{/snippet}
|
|
501
458
|
|
|
502
|
-
<
|
|
503
|
-
|
|
459
|
+
<Command.Root
|
|
460
|
+
shouldFilter={false}
|
|
461
|
+
loop
|
|
504
462
|
class={containerClasses}
|
|
463
|
+
bind:ref={commandRootEl}
|
|
505
464
|
{...rest}
|
|
506
465
|
>
|
|
507
466
|
{@render children?.()}
|
|
@@ -644,4 +603,4 @@
|
|
|
644
603
|
</div>
|
|
645
604
|
{/if}
|
|
646
605
|
{/if}
|
|
647
|
-
</
|
|
606
|
+
</Command.Root>
|
|
@@ -31,8 +31,10 @@
|
|
|
31
31
|
clsx(
|
|
32
32
|
'px-2 py-1.5 space-x-1.5',
|
|
33
33
|
{ 'bg-background-selected': item?.selected && !multiple },
|
|
34
|
-
{
|
|
35
|
-
|
|
34
|
+
{
|
|
35
|
+
'group-hover:bg-background-default-secondary group-data-[selected]:bg-background-default-secondary':
|
|
36
|
+
shouldShowHoverStyle
|
|
37
|
+
}
|
|
36
38
|
)
|
|
37
39
|
)
|
|
38
40
|
|
|
@@ -66,7 +68,7 @@
|
|
|
66
68
|
|
|
67
69
|
<button
|
|
68
70
|
bind:this={el}
|
|
69
|
-
class="cursor-pointer w-full disabled:opacity-30 group"
|
|
71
|
+
class="cursor-pointer w-full disabled:opacity-30 group focus:outline-none"
|
|
70
72
|
disabled={item?.disabled}
|
|
71
73
|
onclick={handleClick}
|
|
72
74
|
>
|
|
@@ -170,6 +170,10 @@
|
|
|
170
170
|
<BaseFlag country={selectedItem.country} />
|
|
171
171
|
{@render label()}
|
|
172
172
|
</div>
|
|
173
|
+
{:else if !multiple && selectedItem?.content}
|
|
174
|
+
<div class="flex items-center gap-1 flex-1 min-w-0">
|
|
175
|
+
{@render selectedItem.content(selectedItem)}
|
|
176
|
+
</div>
|
|
173
177
|
{:else if selectedIcon || resolvedIcon}
|
|
174
178
|
<div class="flex items-center gap-1 flex-1 min-w-0">
|
|
175
179
|
{#if typeof selectedIcon === 'string'}
|
|
@@ -189,6 +193,7 @@
|
|
|
189
193
|
<DrawerContext
|
|
190
194
|
data-dropdown-select-content
|
|
191
195
|
widthClass="min-w-[256px]"
|
|
196
|
+
autofocus
|
|
192
197
|
{multiple}
|
|
193
198
|
{items}
|
|
194
199
|
{flagPosition}
|
|
@@ -99,8 +99,13 @@ export function handleScrollEvent(event, lastScrollLeft, columnDropdowns) {
|
|
|
99
99
|
}
|
|
100
100
|
// Keyboard navigation helpers
|
|
101
101
|
export function shouldIgnoreKeyEvent(event) {
|
|
102
|
-
const
|
|
103
|
-
|
|
102
|
+
const target = event.target;
|
|
103
|
+
const targetTag = target.tagName;
|
|
104
|
+
if (targetTag === 'INPUT' || targetTag === 'TEXTAREA')
|
|
105
|
+
return true;
|
|
106
|
+
if (target.closest('[data-command-root]'))
|
|
107
|
+
return true;
|
|
108
|
+
return false;
|
|
104
109
|
}
|
|
105
110
|
export function handleArrowDown(currentIndex, rows, shiftKey, enableSelection, onScroll) {
|
|
106
111
|
if (currentIndex === -1 && rows.length > 0) {
|
package/dist/types.d.ts
CHANGED
|
@@ -29,7 +29,6 @@ export type DrawerOption = SelectOption & {
|
|
|
29
29
|
separator?: boolean;
|
|
30
30
|
destructive?: boolean;
|
|
31
31
|
selected?: boolean;
|
|
32
|
-
focused?: boolean;
|
|
33
32
|
icon?: IconSource | string | undefined;
|
|
34
33
|
rightIcon?: IconSource | undefined;
|
|
35
34
|
country?: string;
|
|
@@ -346,12 +345,14 @@ export interface DrawerContextProps {
|
|
|
346
345
|
widthClass?: string;
|
|
347
346
|
collapsibleGroups?: boolean;
|
|
348
347
|
flagPosition?: 'before' | 'after';
|
|
348
|
+
autofocus?: boolean;
|
|
349
349
|
onclick?: (value: AnyProp) => void;
|
|
350
350
|
onselect?: (selected: DrawerOption[]) => void;
|
|
351
351
|
onreorder?: (items: DrawerOption[]) => void;
|
|
352
352
|
ondropitem?: (groups: Record<string, DrawerOption[]>) => void;
|
|
353
353
|
children?: Snippet;
|
|
354
354
|
groups?: DrawerGroup[];
|
|
355
|
+
class?: string;
|
|
355
356
|
[key: string]: unknown;
|
|
356
357
|
}
|
|
357
358
|
export interface DrawerContextItemProps {
|
package/package.json
CHANGED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { DrawerOption } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Get all focusable items (non-separator, non-disabled, non-locked)
|
|
4
|
-
*/
|
|
5
|
-
export declare function getFocusableItems(items: DrawerOption[]): DrawerOption[];
|
|
6
|
-
/**
|
|
7
|
-
* Calculate next focused index based on arrow key direction
|
|
8
|
-
*/
|
|
9
|
-
export declare function getNextFocusedIndex(currentIndex: number, direction: 'up' | 'down', itemsCount: number): number;
|
|
10
|
-
/**
|
|
11
|
-
* Handle selection of focused item
|
|
12
|
-
*/
|
|
13
|
-
export declare function selectFocusedItem(items: DrawerOption[], focusedIndex: number, multiple: boolean): {
|
|
14
|
-
item: DrawerOption;
|
|
15
|
-
shouldUpdate: boolean;
|
|
16
|
-
} | null;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Get all focusable items (non-separator, non-disabled, non-locked)
|
|
3
|
-
*/
|
|
4
|
-
export function getFocusableItems(items) {
|
|
5
|
-
return items.filter((item) => !item.separator && !item.disabled && !item.locked);
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Calculate next focused index based on arrow key direction
|
|
9
|
-
*/
|
|
10
|
-
export function getNextFocusedIndex(currentIndex, direction, itemsCount) {
|
|
11
|
-
if (itemsCount === 0)
|
|
12
|
-
return -1;
|
|
13
|
-
if (direction === 'down') {
|
|
14
|
-
return currentIndex < itemsCount - 1 ? currentIndex + 1 : 0;
|
|
15
|
-
}
|
|
16
|
-
else {
|
|
17
|
-
return currentIndex > 0 ? currentIndex - 1 : itemsCount - 1;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Handle selection of focused item
|
|
22
|
-
*/
|
|
23
|
-
export function selectFocusedItem(items, focusedIndex, multiple) {
|
|
24
|
-
const focusableItems = getFocusableItems(items);
|
|
25
|
-
if (focusedIndex >= 0 && focusedIndex < focusableItems.length) {
|
|
26
|
-
const focusedItem = focusableItems[focusedIndex];
|
|
27
|
-
if (multiple) {
|
|
28
|
-
return {
|
|
29
|
-
item: { ...focusedItem, selected: !focusedItem.selected },
|
|
30
|
-
shouldUpdate: true
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
return {
|
|
35
|
-
item: focusedItem,
|
|
36
|
-
shouldUpdate: false
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return null;
|
|
41
|
-
}
|