@invopop/popui 0.1.73 → 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>
@@ -484,19 +428,29 @@
484
428
  {#if item.separator}
485
429
  <DrawerContextSeparator />
486
430
  {:else}
487
- <div class:px-1={!item.groupBy} class:cursor-grab={draggable && !item.locked}>
431
+ {#snippet itemChildren()}
432
+ {@render item.content?.(item)}
433
+ {/snippet}
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
+ >
488
440
  <DrawerContextItem
489
- item={{ ...item, focused: item.value === focusedItemValue, flagPosition: item.flagPosition || flagPosition }}
441
+ item={{ ...item, flagPosition: item.flagPosition || flagPosition }}
490
442
  {multiple}
491
443
  {onclick}
492
444
  onchange={updateItem}
445
+ children={item.content ? itemChildren : undefined}
493
446
  />
494
- </div>
447
+ </Command.Item>
495
448
  {/if}
496
449
  {/snippet}
497
450
 
498
- <div
499
- bind:this={containerRef}
451
+ <Command.Root
452
+ shouldFilter={false}
453
+ loop
500
454
  class={containerClasses}
501
455
  {...rest}
502
456
  >
@@ -640,4 +594,4 @@
640
594
  </div>
641
595
  {/if}
642
596
  {/if}
643
- </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;
@@ -42,6 +41,7 @@ export type DrawerOption = SelectOption & {
42
41
  groupBy?: string;
43
42
  useAvatar?: boolean;
44
43
  action?: Snippet<[DrawerOption]>;
44
+ content?: Snippet<[DrawerOption]>;
45
45
  flagPosition?: 'before' | 'after';
46
46
  };
47
47
  export type Company = {
@@ -351,6 +351,7 @@ export interface DrawerContextProps {
351
351
  ondropitem?: (groups: Record<string, DrawerOption[]>) => void;
352
352
  children?: Snippet;
353
353
  groups?: DrawerGroup[];
354
+ class?: string;
354
355
  [key: string]: unknown;
355
356
  }
356
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.73",
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
- }