@invopop/popui 0.1.74 → 0.1.75

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.
@@ -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
 
@@ -50,18 +47,18 @@
50
47
  ondropitem,
51
48
  children,
52
49
  groups,
50
+ class: className,
53
51
  ...rest
54
52
  }: DrawerContextProps = $props()
55
53
 
56
54
  type DndItem = DrawerOption & { id: string }
57
55
 
58
56
  let containerClasses = $derived(
59
- clsx(
57
+ cn(
60
58
  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
- '[scrollbar-width:none] [&::-webkit-scrollbar]:hidden': draggable
64
- }
59
+ 'border border-border rounded-2xl shadow-lg bg-background flex flex-col py-1 max-h-[568px] overflow-y-auto list-none outline-none',
60
+ clsx({ '[scrollbar-width:none] [&::-webkit-scrollbar]:hidden': draggable }),
61
+ className
65
62
  )
66
63
  )
67
64
 
@@ -88,27 +85,6 @@
88
85
  return { groupedItems: grouped, ungroupedItems: ungrouped }
89
86
  })
90
87
 
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
88
  let openGroups = $state<Record<string, boolean>>({})
113
89
  let groupDndItems = $state<Record<string, DndItem[]>>({})
114
90
  let ungroupedDndItems = $state<DndItem[]>([])
@@ -120,8 +96,6 @@
120
96
  let draggedOverGroup = $state<string | null>(null)
121
97
  let dropIndicator = $state<DropIndicatorState>(null)
122
98
  let cleanupFunctions: (() => void)[] = []
123
- let focusedIndex = $state<number>(-1)
124
- let containerRef = $state<HTMLDivElement | null>(null)
125
99
 
126
100
  // Build internal DND items from external items
127
101
  function buildListIn() {
@@ -204,19 +178,15 @@
204
178
  buildListIn()
205
179
  mounted = true
206
180
 
207
- // Set up auto-scroll
208
181
  const autoScrollCleanup = autoScrollForElements({
209
182
  element: document.documentElement
210
183
  })
211
184
  cleanupFunctions.push(autoScrollCleanup)
212
-
213
- window.addEventListener('keydown', handleKeyDown)
214
185
  })
215
186
 
216
187
  onDestroy(() => {
217
188
  cleanupFunctions.forEach((cleanup) => cleanup())
218
189
  cleanupFunctions = []
219
- window.removeEventListener('keydown', handleKeyDown)
220
190
  })
221
191
 
222
192
  function emitGroupDistribution() {
@@ -445,37 +415,11 @@
445
415
  openGroups = openGroups[groupSlug] ? {} : { [groupSlug]: true }
446
416
  }
447
417
 
448
- let focusedItemValue = $derived.by(() => {
449
- const focusableItems = getFocusableItems(itemsInDisplayOrder)
450
- if (focusedIndex >= 0 && focusedIndex < focusableItems.length) {
451
- return focusableItems[focusedIndex].value
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
- }
418
+ function handleItemSelect(item: DrawerOption) {
419
+ if (multiple) {
420
+ updateItem({ ...item, selected: !item.selected })
421
+ } else {
422
+ onclick?.(item.value)
479
423
  }
480
424
  }
481
425
  </script>
@@ -487,20 +431,26 @@
487
431
  {#snippet itemChildren()}
488
432
  {@render item.content?.(item)}
489
433
  {/snippet}
490
- <div class:px-1={!item.groupBy} class:cursor-grab={draggable && !item.locked}>
434
+ <Command.Item
435
+ value={String(item.value)}
436
+ disabled={item.disabled || item.locked}
437
+ onSelect={() => handleItemSelect(item)}
438
+ class={clsx('group outline-none', { 'px-1': !item.groupBy, 'cursor-grab': draggable && !item.locked })}
439
+ >
491
440
  <DrawerContextItem
492
- item={{ ...item, focused: item.value === focusedItemValue, flagPosition: item.flagPosition || flagPosition }}
441
+ item={{ ...item, flagPosition: item.flagPosition || flagPosition }}
493
442
  {multiple}
494
443
  {onclick}
495
444
  onchange={updateItem}
496
445
  children={item.content ? itemChildren : undefined}
497
446
  />
498
- </div>
447
+ </Command.Item>
499
448
  {/if}
500
449
  {/snippet}
501
450
 
502
- <div
503
- bind:this={containerRef}
451
+ <Command.Root
452
+ shouldFilter={false}
453
+ loop
504
454
  class={containerClasses}
505
455
  {...rest}
506
456
  >
@@ -644,4 +594,4 @@
644
594
  </div>
645
595
  {/if}
646
596
  {/if}
647
- </div>
597
+ </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
- { 'bg-background-default-secondary': item?.focused && shouldShowHoverStyle },
35
- { 'group-hover:bg-background-default-secondary': shouldShowHoverStyle }
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'}
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;
@@ -352,6 +351,7 @@ export interface DrawerContextProps {
352
351
  ondropitem?: (groups: Record<string, DrawerOption[]>) => void;
353
352
  children?: Snippet;
354
353
  groups?: DrawerGroup[];
354
+ class?: string;
355
355
  [key: string]: unknown;
356
356
  }
357
357
  export interface DrawerContextItemProps {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@invopop/popui",
3
3
  "license": "MIT",
4
- "version": "0.1.74",
4
+ "version": "0.1.75",
5
5
  "repository": {
6
6
  "url": "https://github.com/invopop/popui"
7
7
  },
@@ -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
- }