@invopop/popui 0.1.4-beta.22 → 0.1.4-beta.24
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/BaseDropdown.svelte +10 -4
- package/dist/BaseTableHeaderContent.svelte +1 -1
- package/dist/DrawerContext.svelte +230 -120
- package/dist/EmptyState.svelte +6 -2
- package/dist/data-table/data-table-toolbar.svelte +2 -2
- package/dist/data-table/data-table-toolbar.svelte.d.ts +1 -0
- package/dist/data-table/data-table-types.d.ts +2 -0
- package/dist/data-table/data-table-view-options.svelte +67 -38
- package/dist/data-table/data-table-view-options.svelte.d.ts +1 -0
- package/dist/data-table/data-table.svelte +131 -40
- package/dist/table/table-row.svelte +1 -1
- package/dist/types.d.ts +4 -0
- package/package.json +3 -4
package/dist/BaseDropdown.svelte
CHANGED
|
@@ -10,12 +10,16 @@
|
|
|
10
10
|
fullWidth = false,
|
|
11
11
|
placement = 'bottom-start',
|
|
12
12
|
matchParentWidth = false,
|
|
13
|
+
usePortal = true,
|
|
13
14
|
class: className = '',
|
|
14
15
|
trigger,
|
|
15
16
|
children,
|
|
16
17
|
...rest
|
|
17
18
|
}: BaseDropdownProps = $props()
|
|
18
19
|
|
|
20
|
+
// Conditional portal action - noop if disabled
|
|
21
|
+
const conditionalPortal = usePortal ? portal : () => {}
|
|
22
|
+
|
|
19
23
|
const middleware = [offset(6), flip(), shift()]
|
|
20
24
|
|
|
21
25
|
if (matchParentWidth) {
|
|
@@ -31,14 +35,16 @@
|
|
|
31
35
|
)
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
let closedFromClickOutside = $state(false)
|
|
39
|
+
|
|
40
|
+
// Create floating actions with strategy based on usePortal
|
|
41
|
+
const strategy = usePortal ? 'absolute' : 'fixed'
|
|
34
42
|
const [floatingRef, floatingContent] = createFloatingActions({
|
|
35
|
-
strategy
|
|
43
|
+
strategy,
|
|
36
44
|
placement,
|
|
37
45
|
middleware
|
|
38
46
|
})
|
|
39
47
|
|
|
40
|
-
let closedFromClickOutside = $state(false)
|
|
41
|
-
|
|
42
48
|
export const toggle = () => {
|
|
43
49
|
isOpen = !isOpen
|
|
44
50
|
}
|
|
@@ -62,7 +68,7 @@
|
|
|
62
68
|
{#if isOpen}
|
|
63
69
|
<div
|
|
64
70
|
class="max-h-40 absolute z-1001"
|
|
65
|
-
use:
|
|
71
|
+
use:conditionalPortal
|
|
66
72
|
use:floatingContent
|
|
67
73
|
use:clickOutside
|
|
68
74
|
onclick_outside={() => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { DrawerContextProps, DrawerOption } from './types.ts'
|
|
2
|
+
import type { DrawerContextProps, DrawerOption, AnyProp } from './types.ts'
|
|
3
3
|
import DrawerContextItem from './DrawerContextItem.svelte'
|
|
4
4
|
import DrawerContextSeparator from './DrawerContextSeparator.svelte'
|
|
5
5
|
import EmptyState from './EmptyState.svelte'
|
|
@@ -7,21 +7,28 @@
|
|
|
7
7
|
import { Icon } from '@steeze-ui/svelte-icon'
|
|
8
8
|
import { ChevronRight } from '@steeze-ui/heroicons'
|
|
9
9
|
import { slide } from 'svelte/transition'
|
|
10
|
-
import
|
|
10
|
+
import { flip } from 'svelte/animate'
|
|
11
|
+
import { dndzone } from 'svelte-dnd-action'
|
|
11
12
|
import { onMount } from 'svelte'
|
|
12
13
|
|
|
14
|
+
const flipDurationMs = 150
|
|
15
|
+
|
|
13
16
|
let {
|
|
14
17
|
items = $bindable([]),
|
|
15
18
|
multiple = false,
|
|
16
19
|
draggable = false,
|
|
17
20
|
widthClass = 'w-60',
|
|
21
|
+
collapsibleGroups = true,
|
|
18
22
|
onclick,
|
|
19
23
|
onselect,
|
|
20
24
|
onreorder,
|
|
25
|
+
ondropitem,
|
|
21
26
|
children,
|
|
22
27
|
groups
|
|
23
28
|
}: DrawerContextProps = $props()
|
|
24
29
|
|
|
30
|
+
type DndItem = DrawerOption & { id: string }
|
|
31
|
+
|
|
25
32
|
let selectedItems = $derived(items.filter((i) => i.selected))
|
|
26
33
|
let hasGroups = $derived(groups && groups.length > 0)
|
|
27
34
|
let { groupedItems, ungroupedItems } = $derived.by(() => {
|
|
@@ -46,9 +53,75 @@
|
|
|
46
53
|
})
|
|
47
54
|
|
|
48
55
|
let openGroups = $state<Record<string, boolean>>({})
|
|
49
|
-
let
|
|
50
|
-
let
|
|
56
|
+
let groupDndItems = $state<Record<string, DndItem[]>>({})
|
|
57
|
+
let ungroupedDndItems = $state<DndItem[]>([])
|
|
58
|
+
let mounted = $state(false)
|
|
59
|
+
let itemsCache = $state<DrawerOption[]>([])
|
|
60
|
+
let isDragging = $state(false)
|
|
61
|
+
let emitTimeout: number | undefined
|
|
62
|
+
|
|
63
|
+
// Build internal DND items from external items
|
|
64
|
+
function buildListIn() {
|
|
65
|
+
if (hasGroups) {
|
|
66
|
+
// Build DND items for each group
|
|
67
|
+
groups!.forEach((group) => {
|
|
68
|
+
const groupItems = groupedItems.get(group.slug) || []
|
|
69
|
+
groupDndItems[group.slug] = groupItems.map((item: DrawerOption, i: number) => ({
|
|
70
|
+
...item,
|
|
71
|
+
id: `${group.slug}-${item.value}-${i}`
|
|
72
|
+
}))
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Build DND items for ungrouped
|
|
77
|
+
ungroupedDndItems = ungroupedItems.map((item, i) => ({
|
|
78
|
+
...item,
|
|
79
|
+
id: `ungrouped-${item.value}-${i}`
|
|
80
|
+
}))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Build external items from internal DND items
|
|
84
|
+
function buildListOut() {
|
|
85
|
+
const newItems: DrawerOption[] = []
|
|
86
|
+
const used = new Set<AnyProp>()
|
|
87
|
+
|
|
88
|
+
// Add all grouped items
|
|
89
|
+
if (hasGroups) {
|
|
90
|
+
groups!.forEach((group) => {
|
|
91
|
+
const dndItems = groupDndItems[group.slug] || []
|
|
92
|
+
dndItems.forEach((dndItem) => {
|
|
93
|
+
if (!used.has(dndItem.value)) {
|
|
94
|
+
const { id, ...item } = dndItem
|
|
95
|
+
newItems.push({ ...item, groupBy: group.slug })
|
|
96
|
+
used.add(dndItem.value)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
}
|
|
51
101
|
|
|
102
|
+
// Add ungrouped items
|
|
103
|
+
ungroupedDndItems.forEach((dndItem) => {
|
|
104
|
+
if (!used.has(dndItem.value)) {
|
|
105
|
+
const { id, ...item } = dndItem
|
|
106
|
+
newItems.push(item)
|
|
107
|
+
used.add(dndItem.value)
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
return newItems
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Sync items when they change from outside
|
|
115
|
+
$effect(() => {
|
|
116
|
+
if (items && mounted && !isDragging) {
|
|
117
|
+
if (JSON.stringify(items) !== JSON.stringify(itemsCache)) {
|
|
118
|
+
buildListIn()
|
|
119
|
+
itemsCache = JSON.parse(JSON.stringify(items))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// Open group with selected item on mount
|
|
52
125
|
$effect(() => {
|
|
53
126
|
if (hasGroups) {
|
|
54
127
|
const selectedItem = items.find((i) => i.selected)
|
|
@@ -58,95 +131,78 @@
|
|
|
58
131
|
}
|
|
59
132
|
})
|
|
60
133
|
|
|
134
|
+
// Notify parent of selection changes
|
|
61
135
|
$effect(() => {
|
|
62
136
|
onselect?.(selectedItems)
|
|
63
137
|
})
|
|
64
138
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
ghostClass: 'opacity-10',
|
|
76
|
-
dragClass: 'cursor-grabbing',
|
|
77
|
-
forceFallback: true,
|
|
78
|
-
onMove: (event) => {
|
|
79
|
-
// Prevent moving items above locked items
|
|
80
|
-
return !event.related.classList.contains('no-drag')
|
|
81
|
-
},
|
|
82
|
-
onEnd: (event) => {
|
|
83
|
-
if (event.oldIndex !== undefined && event.newIndex !== undefined) {
|
|
84
|
-
const newItems = [...items]
|
|
85
|
-
const ungroupedIndices = items
|
|
86
|
-
.map((item, index) => (!item.groupBy ? index : -1))
|
|
87
|
-
.filter((i) => i !== -1)
|
|
88
|
-
|
|
89
|
-
const fromIndex = ungroupedIndices[event.oldIndex]
|
|
90
|
-
const toIndex = ungroupedIndices[event.newIndex]
|
|
91
|
-
|
|
92
|
-
const [removed] = newItems.splice(fromIndex, 1)
|
|
93
|
-
newItems.splice(toIndex, 0, removed)
|
|
94
|
-
|
|
95
|
-
items = newItems
|
|
96
|
-
onreorder?.(newItems)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
})
|
|
139
|
+
onMount(() => {
|
|
140
|
+
itemsCache = JSON.parse(JSON.stringify(items))
|
|
141
|
+
buildListIn()
|
|
142
|
+
mounted = true
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
function transformDraggedElement(draggedEl: HTMLElement | undefined) {
|
|
146
|
+
if (draggedEl) {
|
|
147
|
+
draggedEl.style.border = 'none'
|
|
148
|
+
draggedEl.style.outline = 'none'
|
|
100
149
|
}
|
|
150
|
+
}
|
|
101
151
|
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
152
|
+
function emitGroupDistribution() {
|
|
153
|
+
if (ondropitem && hasGroups) {
|
|
154
|
+
// Clear any pending emit
|
|
155
|
+
if (emitTimeout) {
|
|
156
|
+
clearTimeout(emitTimeout)
|
|
157
|
+
}
|
|
107
158
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
onMove: (event) => {
|
|
118
|
-
// Prevent moving items above locked items
|
|
119
|
-
return !event.related.classList.contains('no-drag')
|
|
120
|
-
},
|
|
121
|
-
onEnd: (event) => {
|
|
122
|
-
if (event.oldIndex !== undefined && event.newIndex !== undefined) {
|
|
123
|
-
const newItems = [...items]
|
|
124
|
-
const groupedIndices = items
|
|
125
|
-
.map((item, index) => (item.groupBy === group.slug ? index : -1))
|
|
126
|
-
.filter((i) => i !== -1)
|
|
127
|
-
|
|
128
|
-
const fromIndex = groupedIndices[event.oldIndex]
|
|
129
|
-
const toIndex = groupedIndices[event.newIndex]
|
|
130
|
-
|
|
131
|
-
const [removed] = newItems.splice(fromIndex, 1)
|
|
132
|
-
newItems.splice(toIndex, 0, removed)
|
|
133
|
-
|
|
134
|
-
items = newItems
|
|
135
|
-
onreorder?.(newItems)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
})
|
|
139
|
-
}
|
|
140
|
-
})
|
|
159
|
+
// Debounce the emit to avoid duplicate calls when dragging between groups
|
|
160
|
+
emitTimeout = window.setTimeout(() => {
|
|
161
|
+
const groupsDistribution: Record<string, DrawerOption[]> = {}
|
|
162
|
+
groups!.forEach((group) => {
|
|
163
|
+
const dndItems = groupDndItems[group.slug] || []
|
|
164
|
+
groupsDistribution[group.slug] = dndItems.map(({ id, ...item }) => item)
|
|
165
|
+
})
|
|
166
|
+
ondropitem(groupsDistribution)
|
|
167
|
+
}, 0)
|
|
141
168
|
}
|
|
142
169
|
}
|
|
143
170
|
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
setTimeout(initializeSortable, 100)
|
|
171
|
+
function handleDndConsider(groupSlug: string, e: CustomEvent<any>) {
|
|
172
|
+
if (!isDragging) {
|
|
173
|
+
isDragging = true
|
|
148
174
|
}
|
|
149
|
-
|
|
175
|
+
groupDndItems[groupSlug] = e.detail.items
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function handleDndFinalize(groupSlug: string, e: CustomEvent<any>) {
|
|
179
|
+
isDragging = false
|
|
180
|
+
groupDndItems[groupSlug] = e.detail.items
|
|
181
|
+
|
|
182
|
+
const newItems = buildListOut()
|
|
183
|
+
items = newItems
|
|
184
|
+
itemsCache = JSON.parse(JSON.stringify(items))
|
|
185
|
+
onreorder?.(newItems)
|
|
186
|
+
emitGroupDistribution()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function handleUngroupedDndConsider(e: CustomEvent<any>) {
|
|
190
|
+
if (!isDragging) {
|
|
191
|
+
isDragging = true
|
|
192
|
+
}
|
|
193
|
+
ungroupedDndItems = e.detail.items
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function handleUngroupedDndFinalize(e: CustomEvent<any>) {
|
|
197
|
+
isDragging = false
|
|
198
|
+
ungroupedDndItems = e.detail.items
|
|
199
|
+
|
|
200
|
+
const newItems = buildListOut()
|
|
201
|
+
items = newItems
|
|
202
|
+
itemsCache = JSON.parse(JSON.stringify(items))
|
|
203
|
+
onreorder?.(newItems)
|
|
204
|
+
emitGroupDistribution()
|
|
205
|
+
}
|
|
150
206
|
|
|
151
207
|
function updateItem(item: DrawerOption) {
|
|
152
208
|
items = items.map((i) => {
|
|
@@ -157,11 +213,6 @@
|
|
|
157
213
|
|
|
158
214
|
function toggleGroup(groupSlug: string) {
|
|
159
215
|
openGroups = openGroups[groupSlug] ? {} : { [groupSlug]: true }
|
|
160
|
-
|
|
161
|
-
// Reinitialize sortable when a group is toggled
|
|
162
|
-
if (draggable) {
|
|
163
|
-
setTimeout(initializeSortable, 100)
|
|
164
|
-
}
|
|
165
216
|
}
|
|
166
217
|
</script>
|
|
167
218
|
|
|
@@ -169,7 +220,7 @@
|
|
|
169
220
|
{#if item.separator}
|
|
170
221
|
<DrawerContextSeparator />
|
|
171
222
|
{:else}
|
|
172
|
-
<div class:px-1={!item.groupBy} class:
|
|
223
|
+
<div class:px-1={!item.groupBy} class:cursor-grab={draggable && !item.locked}>
|
|
173
224
|
<DrawerContextItem {item} {multiple} {onclick} onchange={updateItem} />
|
|
174
225
|
</div>
|
|
175
226
|
{/if}
|
|
@@ -184,50 +235,88 @@
|
|
|
184
235
|
{#each groups as group, index}
|
|
185
236
|
{@const groupItems = groupedItems.get(group.slug) || []}
|
|
186
237
|
{@const isLastGroup = index === groups!.length - 1}
|
|
187
|
-
{@const isOpen = openGroups[group.slug]}
|
|
188
|
-
{@const hasOpenGroup = Object.values(openGroups).some((v) => v)}
|
|
238
|
+
{@const isOpen = collapsibleGroups ? openGroups[group.slug] : true}
|
|
239
|
+
{@const hasOpenGroup = collapsibleGroups ? Object.values(openGroups).some((v) => v) : true}
|
|
189
240
|
<div
|
|
190
241
|
class="px-1"
|
|
191
|
-
class:flex-1={isOpen}
|
|
242
|
+
class:flex-1={isOpen && collapsibleGroups}
|
|
192
243
|
class:flex={isOpen}
|
|
193
244
|
class:flex-col={isOpen}
|
|
194
|
-
class:min-h-0={isOpen}
|
|
195
|
-
class:flex-shrink-0={!isOpen && hasOpenGroup}
|
|
245
|
+
class:min-h-0={isOpen && collapsibleGroups}
|
|
246
|
+
class:flex-shrink-0={!isOpen && hasOpenGroup && collapsibleGroups}
|
|
196
247
|
>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
248
|
+
{#if collapsibleGroups}
|
|
249
|
+
<button
|
|
250
|
+
class="cursor-pointer flex items-center justify-between h-8 pl-2.5 pr-2.5 py-2.5 text-base font-medium text-foreground-default-secondary w-full hover:bg-background-default-secondary rounded-lg overflow-clip flex-shrink-0"
|
|
251
|
+
onclick={() => toggleGroup(group.slug)}
|
|
252
|
+
>
|
|
253
|
+
<div class="flex items-center gap-1.5">
|
|
254
|
+
<span>{group.label}</span>
|
|
255
|
+
<Icon
|
|
256
|
+
src={ChevronRight}
|
|
257
|
+
class="size-3 text-icon-default-secondary transition-all transform {isOpen
|
|
258
|
+
? 'rotate-90'
|
|
259
|
+
: ''}"
|
|
260
|
+
/>
|
|
261
|
+
</div>
|
|
262
|
+
{#if groupItems.length && !group.hideCounter}
|
|
263
|
+
<BaseCounter value={groupItems.length} />
|
|
264
|
+
{/if}
|
|
265
|
+
</button>
|
|
266
|
+
{:else}
|
|
267
|
+
<div
|
|
268
|
+
class="flex items-center justify-between h-8 pl-2.5 pr-2.5 py-2.5 text-base font-medium text-foreground-default-secondary w-full overflow-clip flex-shrink-0"
|
|
269
|
+
>
|
|
202
270
|
<span>{group.label}</span>
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
? 'rotate-90'
|
|
207
|
-
: ''}"
|
|
208
|
-
/>
|
|
271
|
+
{#if groupItems.length && !group.hideCounter}
|
|
272
|
+
<BaseCounter value={groupItems.length} />
|
|
273
|
+
{/if}
|
|
209
274
|
</div>
|
|
210
|
-
|
|
211
|
-
<BaseCounter value={groupItems.length} />
|
|
212
|
-
{/if}
|
|
213
|
-
</button>
|
|
275
|
+
{/if}
|
|
214
276
|
|
|
215
277
|
{#if isOpen}
|
|
216
278
|
<div
|
|
217
|
-
class="w-full overflow-y-auto flex-1 min-h-0"
|
|
218
|
-
transition:slide={{ duration: 200 }}
|
|
219
|
-
bind:this={groupContainers[group.slug]}
|
|
279
|
+
class="w-full overflow-y-auto {collapsibleGroups ? 'flex-1 min-h-0' : ''}"
|
|
280
|
+
transition:slide={{ duration: collapsibleGroups ? 200 : 0 }}
|
|
220
281
|
>
|
|
221
|
-
{#if
|
|
282
|
+
{#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
|
+
>
|
|
294
|
+
{#if !groupItems.length}
|
|
295
|
+
<div class="px-1 pt-1 pb-5">
|
|
296
|
+
<EmptyState
|
|
297
|
+
iconSource={group.emptyIcon}
|
|
298
|
+
title={group.emptyTitle}
|
|
299
|
+
description={group.emptyDescription}
|
|
300
|
+
/>
|
|
301
|
+
</div>
|
|
302
|
+
{:else}
|
|
303
|
+
{#each groupDndItems[group.slug] || [] as dndItem (dndItem.id)}
|
|
304
|
+
<div animate:flip={{ duration: isDragging ? flipDurationMs : 0 }}>
|
|
305
|
+
{@render drawerItem(dndItem)}
|
|
306
|
+
</div>
|
|
307
|
+
{/each}
|
|
308
|
+
{/if}
|
|
309
|
+
</div>
|
|
310
|
+
{:else if !groupItems.length}
|
|
222
311
|
<div class="px-1 pt-1 pb-5">
|
|
223
312
|
<EmptyState
|
|
224
313
|
iconSource={group.emptyIcon}
|
|
225
|
-
title={group.emptyTitle
|
|
226
|
-
description={group.emptyDescription
|
|
314
|
+
title={group.emptyTitle}
|
|
315
|
+
description={group.emptyDescription}
|
|
227
316
|
/>
|
|
228
317
|
</div>
|
|
229
318
|
{:else}
|
|
230
|
-
{#each groupItems as item}
|
|
319
|
+
{#each groupItems as item (item.value)}
|
|
231
320
|
{@render drawerItem(item)}
|
|
232
321
|
{/each}
|
|
233
322
|
{/if}
|
|
@@ -241,10 +330,31 @@
|
|
|
241
330
|
{/if}
|
|
242
331
|
|
|
243
332
|
{#if ungroupedItems.length}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
333
|
+
{#if draggable}
|
|
334
|
+
<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}
|
|
345
|
+
>
|
|
346
|
+
{#each ungroupedDndItems as dndItem (dndItem.id)}
|
|
347
|
+
<div animate:flip={{ duration: isDragging ? flipDurationMs : 0 }}>
|
|
348
|
+
{@render drawerItem(dndItem)}
|
|
349
|
+
</div>
|
|
350
|
+
{/each}
|
|
351
|
+
</div>
|
|
352
|
+
{:else}
|
|
353
|
+
<div class="flex-shrink-0 overflow-y-auto max-h-[564px]">
|
|
354
|
+
{#each ungroupedItems as item (item.value)}
|
|
355
|
+
{@render drawerItem(item)}
|
|
356
|
+
{/each}
|
|
357
|
+
</div>
|
|
358
|
+
{/if}
|
|
249
359
|
{/if}
|
|
250
360
|
</div>
|
package/dist/EmptyState.svelte
CHANGED
|
@@ -31,8 +31,12 @@
|
|
|
31
31
|
</div>
|
|
32
32
|
{/if}
|
|
33
33
|
<div class="flex flex-col items-center gap-0.5 text-center">
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
{#if title}
|
|
35
|
+
<h4 class="font-medium text-foreground text-base">{title}</h4>
|
|
36
|
+
{/if}
|
|
37
|
+
{#if description}
|
|
38
|
+
<p class="text-foreground-default-secondary text-base">{description}</p>
|
|
39
|
+
{/if}
|
|
36
40
|
</div>
|
|
37
41
|
{#if children}
|
|
38
42
|
<div class="mt-4">
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import type { Snippet } from 'svelte'
|
|
4
4
|
import { DataTableViewOptions } from './index.js'
|
|
5
5
|
|
|
6
|
-
let { table, filters, frozenColumns }: { table: Table<TData>; filters?: Snippet; frozenColumns: Set<string
|
|
6
|
+
let { table, filters, frozenColumns, onFreezeColumn }: { table: Table<TData>; filters?: Snippet; frozenColumns: Set<string>; onFreezeColumn: (columnId: string) => void } = $props()
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
9
|
<div class="flex items-center justify-between px-4 py-3">
|
|
@@ -12,5 +12,5 @@
|
|
|
12
12
|
{@render filters()}
|
|
13
13
|
</div>
|
|
14
14
|
{/if}
|
|
15
|
-
<DataTableViewOptions {table} {frozenColumns} />
|
|
15
|
+
<DataTableViewOptions {table} {frozenColumns} {onFreezeColumn} />
|
|
16
16
|
</div>
|
|
@@ -53,6 +53,7 @@ export interface DataTableProps<TData> {
|
|
|
53
53
|
columns: DataTableColumn<TData>[];
|
|
54
54
|
disableSelection?: boolean;
|
|
55
55
|
disablePagination?: boolean;
|
|
56
|
+
disableKeyboardNavigation?: boolean;
|
|
56
57
|
rowActions?: TableAction[];
|
|
57
58
|
getRowActions?: (row: TData) => TableAction[];
|
|
58
59
|
onRowAction?: (action: AnyProp, row: TData) => void;
|
|
@@ -60,6 +61,7 @@ export interface DataTableProps<TData> {
|
|
|
60
61
|
initialPage?: number;
|
|
61
62
|
initialSortColumn?: string;
|
|
62
63
|
initialSortDirection?: 'asc' | 'desc';
|
|
64
|
+
initialFrozenColumns?: string[];
|
|
63
65
|
pageSizeOptions?: number[];
|
|
64
66
|
emptyState?: Omit<EmptyStateProps, 'children' | 'check'>;
|
|
65
67
|
onRowClick?: (row: TData) => void;
|
|
@@ -1,19 +1,39 @@
|
|
|
1
1
|
<script lang="ts" generics="TData">
|
|
2
2
|
import { Sliders, Drag } from '@invopop/ui-icons'
|
|
3
3
|
import type { Table } from '@tanstack/table-core'
|
|
4
|
-
import type { DrawerOption } from '../types.js'
|
|
4
|
+
import type { DrawerOption, DrawerGroup } from '../types.js'
|
|
5
5
|
import BaseDropdown from '../BaseDropdown.svelte'
|
|
6
6
|
import DrawerContext from '../DrawerContext.svelte'
|
|
7
7
|
import InputToggle from '../InputToggle.svelte'
|
|
8
8
|
import BaseButton from '../BaseButton.svelte'
|
|
9
9
|
import { capitalize } from '../helpers.js'
|
|
10
10
|
|
|
11
|
-
let {
|
|
11
|
+
let {
|
|
12
|
+
table,
|
|
13
|
+
frozenColumns,
|
|
14
|
+
onFreezeColumn
|
|
15
|
+
}: {
|
|
16
|
+
table: Table<TData>
|
|
17
|
+
frozenColumns: Set<string>
|
|
18
|
+
onFreezeColumn: (columnId: string) => void
|
|
19
|
+
} = $props()
|
|
12
20
|
|
|
13
|
-
|
|
21
|
+
const groups: DrawerGroup[] = [
|
|
22
|
+
{
|
|
23
|
+
label: 'Frozen columns',
|
|
24
|
+
slug: 'frozen',
|
|
25
|
+
emptyDescription: 'Drop items to this list',
|
|
26
|
+
hideCounter: true
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
label: 'Table options',
|
|
30
|
+
slug: 'regular',
|
|
31
|
+
emptyDescription: 'Drop items to this list',
|
|
32
|
+
hideCounter: true
|
|
33
|
+
}
|
|
34
|
+
]
|
|
14
35
|
|
|
15
|
-
|
|
16
|
-
$effect(() => {
|
|
36
|
+
let itemsWithActions = $derived.by(() => {
|
|
17
37
|
const columnOrder = table.getState().columnOrder
|
|
18
38
|
const allColumns = table.getAllColumns()
|
|
19
39
|
|
|
@@ -27,46 +47,53 @@
|
|
|
27
47
|
orderedColumns = allColumns.filter((col) => col.id !== 'select' && col.id !== 'actions')
|
|
28
48
|
}
|
|
29
49
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
50
|
+
return orderedColumns.map((col) => {
|
|
51
|
+
const isFrozen = frozenColumns.has(col?.id || '')
|
|
52
|
+
return {
|
|
53
|
+
label: (col?.columnDef.header as string) || capitalize(col?.id || ''),
|
|
54
|
+
value: col?.id,
|
|
55
|
+
icon: Drag,
|
|
56
|
+
action: toggleAction,
|
|
57
|
+
groupBy: isFrozen ? 'frozen' : 'regular'
|
|
58
|
+
}
|
|
59
|
+
}) as DrawerOption[]
|
|
60
|
+
})
|
|
37
61
|
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const currentLocked = itemsWithActions.map((i) => i.locked ? '1' : '0').join(',')
|
|
42
|
-
const newLocked = newItems.map((i) => i.locked ? '1' : '0').join(',')
|
|
62
|
+
function handleDropItem(groupsDistribution: Record<string, DrawerOption[]>) {
|
|
63
|
+
const frozenItems = groupsDistribution['frozen'] || []
|
|
64
|
+
const regularItems = groupsDistribution['regular'] || []
|
|
43
65
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
66
|
+
// Build sets of what should be frozen and regular after the drop
|
|
67
|
+
const shouldBeFrozen = new Set(frozenItems.map((item) => item.value as string))
|
|
68
|
+
const shouldBeRegular = new Set(regularItems.map((item) => item.value as string))
|
|
69
|
+
|
|
70
|
+
// Freeze columns that are in the frozen group but not currently frozen
|
|
71
|
+
shouldBeFrozen.forEach((columnId) => {
|
|
72
|
+
if (!frozenColumns.has(columnId)) {
|
|
73
|
+
onFreezeColumn(columnId)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
48
76
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
77
|
+
// Unfreeze columns that are in the regular group but currently frozen
|
|
78
|
+
shouldBeRegular.forEach((columnId) => {
|
|
79
|
+
if (frozenColumns.has(columnId)) {
|
|
80
|
+
onFreezeColumn(columnId)
|
|
81
|
+
}
|
|
82
|
+
})
|
|
52
83
|
|
|
53
|
-
const newOrder = reorderedItems.map((item) => item.value)
|
|
54
84
|
// Get all column IDs from the table
|
|
55
85
|
const allColumnIds = table.getAllColumns().map((col) => col.id)
|
|
56
|
-
|
|
57
|
-
// Filter to keep select and actions in their fixed positions
|
|
58
86
|
const selectColumn = 'select'
|
|
59
87
|
const actionsColumn = 'actions'
|
|
60
88
|
|
|
61
|
-
//
|
|
62
|
-
const
|
|
63
|
-
const
|
|
89
|
+
// Build the final column order: select, frozen (in order), regular (in order), actions
|
|
90
|
+
const frozenOrder = frozenItems.map((item) => item.value as string)
|
|
91
|
+
const regularOrder = regularItems.map((item) => item.value as string)
|
|
64
92
|
|
|
65
|
-
// Build final order: select first, then frozen columns (in order), then non-frozen columns, then actions
|
|
66
93
|
const finalOrder = [
|
|
67
94
|
...(allColumnIds.includes(selectColumn) ? [selectColumn] : []),
|
|
68
|
-
...
|
|
69
|
-
...
|
|
95
|
+
...frozenOrder,
|
|
96
|
+
...regularOrder,
|
|
70
97
|
...(allColumnIds.includes(actionsColumn) ? [actionsColumn] : [])
|
|
71
98
|
]
|
|
72
99
|
|
|
@@ -89,9 +116,11 @@
|
|
|
89
116
|
{#snippet trigger()}
|
|
90
117
|
<BaseButton icon={Sliders} variant="outline" size="md" />
|
|
91
118
|
{/snippet}
|
|
92
|
-
<DrawerContext
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
119
|
+
<DrawerContext
|
|
120
|
+
items={itemsWithActions}
|
|
121
|
+
{groups}
|
|
122
|
+
draggable
|
|
123
|
+
collapsibleGroups={false}
|
|
124
|
+
ondropitem={handleDropItem}
|
|
125
|
+
/>
|
|
97
126
|
</BaseDropdown>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
type VisibilityState,
|
|
11
11
|
type Column
|
|
12
12
|
} from '@tanstack/table-core'
|
|
13
|
+
import { onMount, onDestroy } from 'svelte'
|
|
13
14
|
import DataTableToolbar from './data-table-toolbar.svelte'
|
|
14
15
|
import DataTablePagination from './data-table-pagination.svelte'
|
|
15
16
|
import FlexRender from './flex-render.svelte'
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
columns: columnConfig,
|
|
34
35
|
disableSelection = false,
|
|
35
36
|
disablePagination = false,
|
|
37
|
+
disableKeyboardNavigation = false,
|
|
36
38
|
rowActions = [],
|
|
37
39
|
getRowActions,
|
|
38
40
|
onRowAction,
|
|
@@ -40,6 +42,7 @@
|
|
|
40
42
|
initialPage = 0,
|
|
41
43
|
initialSortColumn,
|
|
42
44
|
initialSortDirection,
|
|
45
|
+
initialFrozenColumns = [],
|
|
43
46
|
emptyState = {
|
|
44
47
|
iconSource: Search,
|
|
45
48
|
title: 'No results',
|
|
@@ -84,7 +87,9 @@
|
|
|
84
87
|
let columnOrder = $state<ColumnOrderState>([])
|
|
85
88
|
let containerRef = $state<HTMLDivElement | null>(null)
|
|
86
89
|
let columnDropdowns: Record<string, BaseDropdown> = {}
|
|
87
|
-
let frozenColumns = $state<Set<string>>(new Set())
|
|
90
|
+
let frozenColumns = $state<Set<string>>(new Set(initialFrozenColumns))
|
|
91
|
+
let focusedRowIndex = $state<number>(-1)
|
|
92
|
+
let tableBodyRef: HTMLTableSectionElement | null = null
|
|
88
93
|
|
|
89
94
|
// Build TanStack columns from config
|
|
90
95
|
const columns = $derived.by(() =>
|
|
@@ -112,6 +117,13 @@
|
|
|
112
117
|
pagination.pageIndex = initialPage
|
|
113
118
|
})
|
|
114
119
|
|
|
120
|
+
// Reorder initial frozen columns on mount
|
|
121
|
+
$effect(() => {
|
|
122
|
+
if (initialFrozenColumns.length > 0 && columnOrder.length === 0) {
|
|
123
|
+
initialFrozenColumns.forEach(columnId => reorderFrozenColumn(columnId))
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
115
127
|
// Track selection changes
|
|
116
128
|
$effect(() => {
|
|
117
129
|
if (onSelectionChange) {
|
|
@@ -146,49 +158,44 @@
|
|
|
146
158
|
setColumnOrder: (value) => (columnOrder = value)
|
|
147
159
|
})
|
|
148
160
|
|
|
161
|
+
function reorderFrozenColumn(columnId: string) {
|
|
162
|
+
const currentOrder = table.getState().columnOrder.length > 0
|
|
163
|
+
? table.getState().columnOrder
|
|
164
|
+
: table.getAllLeafColumns().map(col => col.id)
|
|
165
|
+
|
|
166
|
+
const newOrder = [...currentOrder]
|
|
167
|
+
const columnIndex = newOrder.indexOf(columnId)
|
|
168
|
+
|
|
169
|
+
if (columnIndex > -1) {
|
|
170
|
+
newOrder.splice(columnIndex, 1)
|
|
171
|
+
|
|
172
|
+
const selectIndex = newOrder.indexOf('select')
|
|
173
|
+
const insertIndex = selectIndex >= 0 ? selectIndex + 1 : 0
|
|
174
|
+
|
|
175
|
+
let lastFrozenIndex = insertIndex
|
|
176
|
+
for (let i = insertIndex; i < newOrder.length; i++) {
|
|
177
|
+
if (frozenColumns.has(newOrder[i])) {
|
|
178
|
+
lastFrozenIndex = i + 1
|
|
179
|
+
} else {
|
|
180
|
+
break
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
newOrder.splice(lastFrozenIndex, 0, columnId)
|
|
185
|
+
table.setColumnOrder(newOrder)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
149
189
|
function handleFreezeColumn(columnId: string) {
|
|
150
190
|
const isFrozen = frozenColumns.has(columnId)
|
|
151
191
|
|
|
152
192
|
if (isFrozen) {
|
|
153
|
-
// Unfreeze
|
|
154
193
|
frozenColumns.delete(columnId)
|
|
155
194
|
frozenColumns = new Set(frozenColumns)
|
|
156
195
|
} else {
|
|
157
|
-
// Freeze
|
|
158
196
|
frozenColumns.add(columnId)
|
|
159
197
|
frozenColumns = new Set(frozenColumns)
|
|
160
|
-
|
|
161
|
-
// Reorder columns to move frozen column to the left
|
|
162
|
-
const currentOrder = table.getState().columnOrder.length > 0
|
|
163
|
-
? table.getState().columnOrder
|
|
164
|
-
: table.getAllLeafColumns().map(col => col.id)
|
|
165
|
-
|
|
166
|
-
const newOrder = [...currentOrder]
|
|
167
|
-
const columnIndex = newOrder.indexOf(columnId)
|
|
168
|
-
|
|
169
|
-
if (columnIndex > -1) {
|
|
170
|
-
// Remove from current position
|
|
171
|
-
newOrder.splice(columnIndex, 1)
|
|
172
|
-
|
|
173
|
-
// Find position to insert (after select column if present, otherwise at start)
|
|
174
|
-
const selectIndex = newOrder.indexOf('select')
|
|
175
|
-
const insertIndex = selectIndex >= 0 ? selectIndex + 1 : 0
|
|
176
|
-
|
|
177
|
-
// Find the last frozen column position
|
|
178
|
-
let lastFrozenIndex = insertIndex
|
|
179
|
-
for (let i = insertIndex; i < newOrder.length; i++) {
|
|
180
|
-
if (frozenColumns.has(newOrder[i])) {
|
|
181
|
-
lastFrozenIndex = i + 1
|
|
182
|
-
} else {
|
|
183
|
-
break
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Insert after the last frozen column
|
|
188
|
-
newOrder.splice(lastFrozenIndex, 0, columnId)
|
|
189
|
-
|
|
190
|
-
table.setColumnOrder(newOrder)
|
|
191
|
-
}
|
|
198
|
+
reorderFrozenColumn(columnId)
|
|
192
199
|
}
|
|
193
200
|
}
|
|
194
201
|
|
|
@@ -208,6 +215,88 @@
|
|
|
208
215
|
|
|
209
216
|
return offset
|
|
210
217
|
}
|
|
218
|
+
|
|
219
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
220
|
+
const rows = table.getRowModel().rows
|
|
221
|
+
if (rows.length === 0) return
|
|
222
|
+
|
|
223
|
+
// Ignore if user is typing in an input or has a dropdown open
|
|
224
|
+
if ((event.target as HTMLElement).tagName === 'INPUT' ||
|
|
225
|
+
(event.target as HTMLElement).tagName === 'TEXTAREA') {
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
switch (event.key) {
|
|
230
|
+
case 'ArrowDown':
|
|
231
|
+
event.preventDefault()
|
|
232
|
+
if (focusedRowIndex === -1 && rows.length > 0) {
|
|
233
|
+
// No row focused, focus first row
|
|
234
|
+
focusedRowIndex = 0
|
|
235
|
+
scrollToFocusedRow()
|
|
236
|
+
if (event.shiftKey && enableSelection) {
|
|
237
|
+
rows[focusedRowIndex].toggleSelected(true)
|
|
238
|
+
}
|
|
239
|
+
} else if (focusedRowIndex < rows.length - 1) {
|
|
240
|
+
// Move down
|
|
241
|
+
focusedRowIndex++
|
|
242
|
+
scrollToFocusedRow()
|
|
243
|
+
if (event.shiftKey && enableSelection) {
|
|
244
|
+
// Always select when going down
|
|
245
|
+
rows[focusedRowIndex].toggleSelected(true)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
break
|
|
249
|
+
case 'ArrowUp':
|
|
250
|
+
event.preventDefault()
|
|
251
|
+
if (event.shiftKey && enableSelection && focusedRowIndex >= 0) {
|
|
252
|
+
// Deselect current row first when going up with shift
|
|
253
|
+
rows[focusedRowIndex].toggleSelected(false)
|
|
254
|
+
}
|
|
255
|
+
if (focusedRowIndex === -1 && rows.length > 0) {
|
|
256
|
+
// No row focused, focus first row
|
|
257
|
+
focusedRowIndex = 0
|
|
258
|
+
scrollToFocusedRow()
|
|
259
|
+
} else if (focusedRowIndex > 0) {
|
|
260
|
+
// Move up
|
|
261
|
+
focusedRowIndex--
|
|
262
|
+
scrollToFocusedRow()
|
|
263
|
+
}
|
|
264
|
+
break
|
|
265
|
+
case ' ':
|
|
266
|
+
case 'Enter':
|
|
267
|
+
event.preventDefault()
|
|
268
|
+
if (focusedRowIndex >= 0 && focusedRowIndex < rows.length && enableSelection) {
|
|
269
|
+
const row = rows[focusedRowIndex]
|
|
270
|
+
row.toggleSelected()
|
|
271
|
+
}
|
|
272
|
+
break
|
|
273
|
+
case 'Escape':
|
|
274
|
+
focusedRowIndex = -1
|
|
275
|
+
break
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function scrollToFocusedRow() {
|
|
280
|
+
if (focusedRowIndex >= 0 && tableBodyRef) {
|
|
281
|
+
const rowElement = tableBodyRef.querySelector(`[data-row-index="${focusedRowIndex}"]`)
|
|
282
|
+
if (rowElement) {
|
|
283
|
+
rowElement.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Add global keyboard navigation
|
|
289
|
+
onMount(() => {
|
|
290
|
+
if (!disableKeyboardNavigation) {
|
|
291
|
+
document.addEventListener('keydown', handleKeydown)
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
onDestroy(() => {
|
|
296
|
+
if (!disableKeyboardNavigation) {
|
|
297
|
+
document.removeEventListener('keydown', handleKeydown)
|
|
298
|
+
}
|
|
299
|
+
})
|
|
211
300
|
</script>
|
|
212
301
|
|
|
213
302
|
{#snippet StickyCellWrapper({
|
|
@@ -225,7 +314,7 @@
|
|
|
225
314
|
})}
|
|
226
315
|
<div
|
|
227
316
|
class={cn(
|
|
228
|
-
'absolute inset-0 flex items-center bg-background group-hover/row:bg-background-default-secondary group-data-[state=selected]/row:bg-background-selected px-3',
|
|
317
|
+
'absolute inset-0 flex items-center bg-background group-hover/row:bg-background-default-secondary group-data-[state=selected]/row:bg-background-selected group-data-[focused=true]/row:bg-background-default-secondary px-3',
|
|
229
318
|
align === 'right' ? 'justify-end' : '',
|
|
230
319
|
{ 'pl-4': isFirst, 'pr-4': isLast, 'border-r border-border': isFrozen }
|
|
231
320
|
)}
|
|
@@ -255,7 +344,7 @@
|
|
|
255
344
|
}: { column: Column<TData>; title?: string } & HTMLAttributes<HTMLDivElement>)}
|
|
256
345
|
{@const isCurrency = column.columnDef.meta?.cellType === 'currency'}
|
|
257
346
|
<div class={cn('flex items-center w-full', className)} {...restProps}>
|
|
258
|
-
<BaseDropdown bind:this={columnDropdowns[column.id]} fullWidth>
|
|
347
|
+
<BaseDropdown bind:this={columnDropdowns[column.id]} fullWidth usePortal={false}>
|
|
259
348
|
{#snippet trigger()}
|
|
260
349
|
<button
|
|
261
350
|
class={clsx('data-[state=open]:bg-accent w-full flex items-center gap-1 py-2.5', {
|
|
@@ -310,7 +399,7 @@
|
|
|
310
399
|
{/snippet}
|
|
311
400
|
|
|
312
401
|
<div class="flex flex-col h-full">
|
|
313
|
-
<DataTableToolbar {table} {filters} {frozenColumns} />
|
|
402
|
+
<DataTableToolbar {table} {filters} {frozenColumns} onFreezeColumn={handleFreezeColumn} />
|
|
314
403
|
<div class="flex-1 overflow-hidden flex flex-col">
|
|
315
404
|
{#if data.length === 0}
|
|
316
405
|
<div class="flex-1 flex items-center justify-center bg-background">
|
|
@@ -397,10 +486,12 @@
|
|
|
397
486
|
</Table.Row>
|
|
398
487
|
{/each}
|
|
399
488
|
</Table.Header>
|
|
400
|
-
<Table.Body>
|
|
401
|
-
{#each table.getRowModel().rows as row (row.id)}
|
|
489
|
+
<Table.Body bind:ref={tableBodyRef}>
|
|
490
|
+
{#each table.getRowModel().rows as row, rowIndex (row.id)}
|
|
402
491
|
<Table.Row
|
|
403
492
|
data-state={row.getIsSelected() ? 'selected' : undefined}
|
|
493
|
+
data-row-index={rowIndex}
|
|
494
|
+
data-focused={focusedRowIndex === rowIndex ? 'true' : undefined}
|
|
404
495
|
class={cn('border-b border-border', getRowClassName?.(row.original as TData))}
|
|
405
496
|
onclick={() => onRowClick?.(row.original as TData)}
|
|
406
497
|
>
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
bind:this={ref}
|
|
20
20
|
data-slot="table-row"
|
|
21
21
|
class={cn(
|
|
22
|
-
'group/row data-[state=selected]:bg-background-selected data-[state=checked]:bg-background-selected h-10 data-[state=selected]:hover:bg-background-selected data-[state=checked]:hover:bg-background-selected',
|
|
22
|
+
'group/row data-[state=selected]:bg-background-selected data-[state=checked]:bg-background-selected h-10 data-[state=selected]:hover:bg-background-selected data-[state=checked]:hover:bg-background-selected data-[focused=true]:bg-background-default-secondary',
|
|
23
23
|
className
|
|
24
24
|
)}
|
|
25
25
|
{oncontextmenu}
|
package/dist/types.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type DrawerGroup = {
|
|
|
23
23
|
emptyIcon?: IconSource;
|
|
24
24
|
emptyTitle?: string;
|
|
25
25
|
emptyDescription?: string;
|
|
26
|
+
hideCounter?: boolean;
|
|
26
27
|
};
|
|
27
28
|
export type DrawerOption = SelectOption & {
|
|
28
29
|
separator?: boolean;
|
|
@@ -217,6 +218,7 @@ export interface BaseDropdownProps {
|
|
|
217
218
|
fullWidth?: boolean;
|
|
218
219
|
placement?: Placement;
|
|
219
220
|
matchParentWidth?: boolean;
|
|
221
|
+
usePortal?: boolean;
|
|
220
222
|
trigger?: Snippet;
|
|
221
223
|
children?: Snippet;
|
|
222
224
|
[key: string]: any;
|
|
@@ -390,9 +392,11 @@ export interface DrawerContextProps {
|
|
|
390
392
|
multiple?: boolean;
|
|
391
393
|
draggable?: boolean;
|
|
392
394
|
widthClass?: string;
|
|
395
|
+
collapsibleGroups?: boolean;
|
|
393
396
|
onclick?: (value: AnyProp) => void;
|
|
394
397
|
onselect?: (selected: DrawerOption[]) => void;
|
|
395
398
|
onreorder?: (items: DrawerOption[]) => void;
|
|
399
|
+
ondropitem?: (groups: Record<string, DrawerOption[]>) => void;
|
|
396
400
|
children?: Snippet;
|
|
397
401
|
groups?: DrawerGroup[];
|
|
398
402
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@invopop/popui",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "0.1.4-beta.
|
|
4
|
+
"version": "0.1.4-beta.24",
|
|
5
5
|
"repository": {
|
|
6
|
-
"url":
|
|
6
|
+
"url": "https://github.com/invopop/popui"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"dev": "vite dev",
|
|
@@ -55,7 +55,6 @@
|
|
|
55
55
|
"@tailwindcss/forms": "^0.5.9",
|
|
56
56
|
"@tailwindcss/typography": "^0.5.15",
|
|
57
57
|
"@types/lodash-es": "^4.17.12",
|
|
58
|
-
"@types/sortablejs": "^1.15.9",
|
|
59
58
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
60
59
|
"@typescript-eslint/parser": "^6.0.0",
|
|
61
60
|
"eslint": "^8.28.0",
|
|
@@ -98,7 +97,7 @@
|
|
|
98
97
|
"inter-ui": "^3.19.3",
|
|
99
98
|
"lodash-es": "^4.17.21",
|
|
100
99
|
"mode-watcher": "^1.1.0",
|
|
101
|
-
"
|
|
100
|
+
"svelte-dnd-action": "^0.9.69",
|
|
102
101
|
"svelte-floating-ui": "^1.5.8",
|
|
103
102
|
"svelte-headlessui": "^0.0.46",
|
|
104
103
|
"svelte-intersection-observer-action": "^0.0.4",
|