@r2digisolutions/components 0.9.0 → 0.9.1
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.
|
@@ -90,7 +90,11 @@
|
|
|
90
90
|
let path = $state<string[]>([]);
|
|
91
91
|
let lastSelectedId = $state<string | null>(null);
|
|
92
92
|
|
|
93
|
-
function findPathToItem(
|
|
93
|
+
function findPathToItem(
|
|
94
|
+
list: DropdownItem[],
|
|
95
|
+
targetId: string,
|
|
96
|
+
parents: string[] = []
|
|
97
|
+
): string[] | null {
|
|
94
98
|
for (const item of list) {
|
|
95
99
|
if (item.id === targetId) return parents;
|
|
96
100
|
if (item.children?.length) {
|
|
@@ -101,7 +105,10 @@
|
|
|
101
105
|
return null;
|
|
102
106
|
}
|
|
103
107
|
|
|
104
|
-
function levelFromPath(
|
|
108
|
+
function levelFromPath(
|
|
109
|
+
root: DropdownItem[],
|
|
110
|
+
pathIds: string[]
|
|
111
|
+
): { title: string; items: DropdownItem[] } {
|
|
105
112
|
let current = root;
|
|
106
113
|
let title = label;
|
|
107
114
|
for (const id of pathIds) {
|
|
@@ -147,7 +154,8 @@
|
|
|
147
154
|
path = nextPath;
|
|
148
155
|
const level = levelFromPath(items, nextPath);
|
|
149
156
|
const idx = level.items.findIndex((item) => item.id === lastSelectedId);
|
|
150
|
-
highlighted =
|
|
157
|
+
highlighted =
|
|
158
|
+
idx >= 0 ? idx : (level.items.findIndex((i) => !i.separator && !i.disabled) ?? -1);
|
|
151
159
|
}
|
|
152
160
|
|
|
153
161
|
/** Measure natural height without expanding the positioned popover (avoids jump). */
|
|
@@ -240,6 +248,24 @@
|
|
|
240
248
|
if (menuEl?.matches(':popover-open')) menuEl.hidePopover();
|
|
241
249
|
}
|
|
242
250
|
|
|
251
|
+
$effect(() => {
|
|
252
|
+
const btn = triggerEl;
|
|
253
|
+
if (!btn) return;
|
|
254
|
+
const onClick = (event: MouseEvent) => {
|
|
255
|
+
event.stopPropagation();
|
|
256
|
+
if (disabled) return;
|
|
257
|
+
const el = menuEl;
|
|
258
|
+
if (!el) return;
|
|
259
|
+
const isOpen = el.matches(':popover-open');
|
|
260
|
+
setTimeout(() => {
|
|
261
|
+
if (isOpen) closeMenu();
|
|
262
|
+
else openMenu();
|
|
263
|
+
}, 0);
|
|
264
|
+
};
|
|
265
|
+
btn.addEventListener('click', onClick);
|
|
266
|
+
return () => btn.removeEventListener('click', onClick);
|
|
267
|
+
});
|
|
268
|
+
|
|
243
269
|
function handleBeforeToggle(event: ToggleEvent) {
|
|
244
270
|
if (event.newState === 'open') {
|
|
245
271
|
if (disabled) {
|
|
@@ -404,18 +430,16 @@
|
|
|
404
430
|
<div class={['relative inline-flex', className]}>
|
|
405
431
|
<button
|
|
406
432
|
bind:this={triggerEl}
|
|
407
|
-
|
|
433
|
+
{id}
|
|
408
434
|
type="button"
|
|
409
435
|
{disabled}
|
|
410
|
-
popovertarget={menuId}
|
|
411
|
-
popovertargetaction="toggle"
|
|
412
436
|
onkeydown={onTriggerKeydown}
|
|
413
437
|
class={[
|
|
414
|
-
'
|
|
415
|
-
'hover:bg-surface-overlay focus-visible:
|
|
438
|
+
'gap-2 rounded-lg border-border bg-surface-elevated font-medium text-primary inline-flex items-center border transition-colors',
|
|
439
|
+
'hover:bg-surface-overlay focus-visible:ring-brand-500/30 focus-visible:ring-2 focus-visible:outline-none',
|
|
416
440
|
size === 'sm' ? 'min-h-8 px-2.5 text-xs' : 'min-h-9 px-3 text-sm',
|
|
417
441
|
disabled && 'cursor-not-allowed opacity-50',
|
|
418
|
-
trigger && 'border-0 bg-transparent
|
|
442
|
+
trigger && 'p-0 border-0 bg-transparent hover:bg-transparent'
|
|
419
443
|
]}
|
|
420
444
|
aria-label={trigger ? label : undefined}
|
|
421
445
|
aria-haspopup="menu"
|
|
@@ -452,20 +476,20 @@
|
|
|
452
476
|
ontoggle={handleToggle}
|
|
453
477
|
onbeforetoggle={handleBeforeToggle}
|
|
454
478
|
onkeydown={onMenuKeydown}
|
|
455
|
-
class="dropdown-menu m-0
|
|
479
|
+
class="dropdown-menu m-0 rounded-xl border-border bg-surface-elevated p-1.5 shadow-xl inset-auto flex-col overflow-hidden border text-left outline-none"
|
|
456
480
|
>
|
|
457
481
|
{#if canGoBack}
|
|
458
482
|
<button
|
|
459
483
|
type="button"
|
|
460
484
|
onclick={goBack}
|
|
461
485
|
class={[
|
|
462
|
-
'mb-0.5
|
|
463
|
-
'hover:bg-surface-overlay focus-visible:
|
|
486
|
+
'mb-0.5 gap-2 rounded-lg font-medium text-primary flex w-full items-center text-left transition-colors',
|
|
487
|
+
'hover:bg-surface-overlay focus-visible:ring-brand-500/30 focus-visible:ring-2 focus-visible:outline-none',
|
|
464
488
|
sizePad
|
|
465
489
|
]}
|
|
466
490
|
>
|
|
467
491
|
<svg
|
|
468
|
-
class="shrink-0
|
|
492
|
+
class="text-secondary shrink-0"
|
|
469
493
|
width={iconSize}
|
|
470
494
|
height={iconSize}
|
|
471
495
|
viewBox="0 0 24 24"
|
|
@@ -478,13 +502,13 @@
|
|
|
478
502
|
</svg>
|
|
479
503
|
<span class="truncate">{current.title}</span>
|
|
480
504
|
</button>
|
|
481
|
-
<div class="mb-1 h-px
|
|
505
|
+
<div class="mb-1 bg-border h-px" role="separator"></div>
|
|
482
506
|
{/if}
|
|
483
507
|
|
|
484
508
|
<div bind:this={listEl} class="dropdown-menu-list min-h-0 flex-1">
|
|
485
509
|
{#each current.items as item, index (item.id)}
|
|
486
510
|
{#if item.separator}
|
|
487
|
-
<div class="my-1 h-px
|
|
511
|
+
<div class="my-1 bg-border h-px" role="separator"></div>
|
|
488
512
|
{:else}
|
|
489
513
|
{@const Icon = item.icon}
|
|
490
514
|
{@const hasChildren = Boolean(item.children?.length)}
|
|
@@ -501,7 +525,7 @@
|
|
|
501
525
|
if (!item.disabled) highlighted = index;
|
|
502
526
|
}}
|
|
503
527
|
class={[
|
|
504
|
-
'flex w-full cursor-pointer
|
|
528
|
+
'gap-2 rounded-lg flex w-full cursor-pointer text-left transition-colors',
|
|
505
529
|
item.description ? 'items-start' : 'items-center',
|
|
506
530
|
sizePad,
|
|
507
531
|
item.disabled && 'cursor-not-allowed opacity-40',
|
|
@@ -512,7 +536,7 @@
|
|
|
512
536
|
{#if item.checked !== undefined}
|
|
513
537
|
<span
|
|
514
538
|
class={[
|
|
515
|
-
'
|
|
539
|
+
'h-4 w-4 flex shrink-0 items-center justify-center',
|
|
516
540
|
item.description && 'mt-0.5'
|
|
517
541
|
]}
|
|
518
542
|
aria-hidden="true"
|
|
@@ -533,7 +557,7 @@
|
|
|
533
557
|
{:else if showIconColumn}
|
|
534
558
|
<span
|
|
535
559
|
class={[
|
|
536
|
-
'
|
|
560
|
+
'h-4 w-4 flex shrink-0 items-center justify-center',
|
|
537
561
|
item.description && 'mt-0.5'
|
|
538
562
|
]}
|
|
539
563
|
aria-hidden="true"
|
|
@@ -544,24 +568,26 @@
|
|
|
544
568
|
</span>
|
|
545
569
|
{/if}
|
|
546
570
|
|
|
547
|
-
<span class="min-w-0 flex-1
|
|
548
|
-
<span class="
|
|
571
|
+
<span class="min-w-0 leading-tight flex-1">
|
|
572
|
+
<span class="font-medium leading-tight block truncate">{item.label}</span>
|
|
549
573
|
{#if item.description}
|
|
550
|
-
<span class="
|
|
574
|
+
<span class="font-normal leading-snug text-secondary block truncate text-[11px]">
|
|
551
575
|
{item.description}
|
|
552
576
|
</span>
|
|
553
577
|
{/if}
|
|
554
578
|
</span>
|
|
555
579
|
|
|
556
580
|
{#if item.shortcut && !hasChildren}
|
|
557
|
-
<kbd
|
|
581
|
+
<kbd
|
|
582
|
+
class="ml-2 rounded border-border bg-surface-overlay px-1.5 py-0.5 font-mono text-secondary shrink-0 border text-[10px]"
|
|
583
|
+
>
|
|
558
584
|
{item.shortcut}
|
|
559
585
|
</kbd>
|
|
560
586
|
{/if}
|
|
561
587
|
|
|
562
588
|
{#if hasChildren}
|
|
563
589
|
<svg
|
|
564
|
-
class={['ml-1 shrink-0
|
|
590
|
+
class={['ml-1 text-secondary shrink-0', item.description && 'mt-0.5']}
|
|
565
591
|
width={iconSize}
|
|
566
592
|
height={iconSize}
|
|
567
593
|
viewBox="0 0 24 24"
|
|
@@ -594,8 +620,4 @@
|
|
|
594
620
|
.dropdown-menu:popover-open {
|
|
595
621
|
display: flex;
|
|
596
622
|
}
|
|
597
|
-
|
|
598
|
-
.dropdown-menu:not(:popover-open) {
|
|
599
|
-
display: none;
|
|
600
|
-
}
|
|
601
623
|
</style>
|
|
@@ -118,6 +118,13 @@
|
|
|
118
118
|
if (sortDir) onsort?.(column.id, sortDir);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
function isInteractiveTarget(target: EventTarget | null): boolean {
|
|
122
|
+
return (
|
|
123
|
+
target instanceof Element &&
|
|
124
|
+
Boolean(target.closest('a, button, input, select, textarea, label, [role="menuitem"]'))
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
121
128
|
const alignClass = {
|
|
122
129
|
left: 'text-left',
|
|
123
130
|
center: 'text-center',
|
|
@@ -125,12 +132,14 @@
|
|
|
125
132
|
} as const;
|
|
126
133
|
</script>
|
|
127
134
|
|
|
128
|
-
<div
|
|
129
|
-
|
|
135
|
+
<div
|
|
136
|
+
class={['rounded-xl border-border bg-surface-elevated w-full overflow-x-auto border', className]}
|
|
137
|
+
>
|
|
138
|
+
<table class="text-sm w-full border-collapse">
|
|
130
139
|
<thead
|
|
131
140
|
class={[
|
|
132
|
-
'border-
|
|
133
|
-
stickyHeader && '
|
|
141
|
+
'border-border bg-surface-overlay/80 text-xs font-semibold tracking-wide text-secondary border-b uppercase',
|
|
142
|
+
stickyHeader && 'top-0 sticky z-10'
|
|
134
143
|
]}
|
|
135
144
|
>
|
|
136
145
|
<tr>
|
|
@@ -141,22 +150,20 @@
|
|
|
141
150
|
'px-3 font-semibold',
|
|
142
151
|
compact ? 'py-2' : 'py-2.5',
|
|
143
152
|
alignClass[column.align ?? 'left'],
|
|
144
|
-
canSort(column) && 'cursor-pointer select-none
|
|
153
|
+
canSort(column) && 'hover:text-primary cursor-pointer select-none',
|
|
145
154
|
column.class
|
|
146
155
|
]}
|
|
147
156
|
style={column.width ? `width:${column.width}` : undefined}
|
|
148
|
-
aria-sort={
|
|
149
|
-
|
|
150
|
-
?
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
: undefined
|
|
156
|
-
}
|
|
157
|
+
aria-sort={sortId === column.id
|
|
158
|
+
? sortDir === 'asc'
|
|
159
|
+
? 'ascending'
|
|
160
|
+
: 'descending'
|
|
161
|
+
: canSort(column)
|
|
162
|
+
? 'none'
|
|
163
|
+
: undefined}
|
|
157
164
|
onclick={() => canSort(column) && toggleSort(column)}
|
|
158
165
|
>
|
|
159
|
-
<span class="inline-flex items-center
|
|
166
|
+
<span class="gap-1 inline-flex items-center">
|
|
160
167
|
{column.header}
|
|
161
168
|
{#if canSort(column)}
|
|
162
169
|
<span class="text-muted" aria-hidden="true">
|
|
@@ -174,7 +181,7 @@
|
|
|
174
181
|
{#if sortedRows.length === 0}
|
|
175
182
|
<tr>
|
|
176
183
|
<td
|
|
177
|
-
class="px-3 py-8 text-
|
|
184
|
+
class="px-3 py-8 text-sm text-muted text-center"
|
|
178
185
|
colspan={Math.max(columns.length, 1)}
|
|
179
186
|
>
|
|
180
187
|
{empty}
|
|
@@ -184,12 +191,15 @@
|
|
|
184
191
|
{#each sortedRows as row, index (getKey(row, index))}
|
|
185
192
|
<tr
|
|
186
193
|
class={[
|
|
187
|
-
'border-
|
|
194
|
+
'border-border border-b last:border-b-0',
|
|
188
195
|
striped && index % 2 === 1 && 'bg-surface-overlay/40',
|
|
189
196
|
hoverable && 'hover:bg-surface-overlay/70',
|
|
190
197
|
onrowclick && 'cursor-pointer'
|
|
191
198
|
]}
|
|
192
|
-
onclick={() =>
|
|
199
|
+
onclick={(e) => {
|
|
200
|
+
if (isInteractiveTarget(e.target)) return;
|
|
201
|
+
onrowclick?.(row);
|
|
202
|
+
}}
|
|
193
203
|
>
|
|
194
204
|
{#each columns as column (column.id)}
|
|
195
205
|
<td
|