@lobb-js/studio 0.42.0 → 0.44.0
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/components/confirmationDialog/confirmationDialog.svelte +1 -1
- package/dist/components/dataTable/dataTable.svelte +49 -17
- package/dist/components/dataTable/filter.svelte +26 -23
- package/dist/components/dataTable/header.svelte +17 -13
- package/dist/components/dataTable/header.svelte.d.ts +1 -0
- package/dist/components/dataTable/listViewChildren.svelte +60 -77
- package/dist/components/dataTable/listViewChildren.svelte.d.ts +1 -1
- package/dist/components/dataTable/table.svelte +20 -61
- package/dist/components/dataTable/table.svelte.d.ts +2 -2
- package/dist/components/detailView/changeTreeUtils.d.ts +7 -0
- package/dist/components/detailView/changeTreeUtils.js +47 -0
- package/dist/components/detailView/create/createDetailView.svelte +17 -13
- package/dist/components/detailView/detailView.svelte +7 -2
- package/dist/components/detailView/detailView.svelte.d.ts +1 -0
- package/dist/components/detailView/fieldInput.svelte +10 -9
- package/dist/components/detailView/fieldInput.svelte.d.ts +1 -0
- package/dist/components/detailView/update/updateDetailView.svelte +22 -18
- package/dist/components/drawer.svelte +15 -2
- package/dist/components/foreingKeyInput.svelte +32 -60
- package/dist/components/foreingKeyInput.svelte.d.ts +1 -1
- package/dist/components/polymorphicInput.svelte +42 -66
- package/dist/components/polymorphicInput.svelte.d.ts +1 -0
- package/package.json +2 -2
- package/src/app.css +2 -2
- package/src/lib/components/confirmationDialog/confirmationDialog.svelte +1 -1
- package/src/lib/components/dataTable/dataTable.svelte +49 -17
- package/src/lib/components/dataTable/filter.svelte +26 -23
- package/src/lib/components/dataTable/header.svelte +17 -13
- package/src/lib/components/dataTable/listViewChildren.svelte +60 -77
- package/src/lib/components/dataTable/table.svelte +20 -61
- package/src/lib/components/detailView/changeTreeUtils.ts +39 -0
- package/src/lib/components/detailView/create/createDetailView.svelte +17 -13
- package/src/lib/components/detailView/detailView.svelte +7 -2
- package/src/lib/components/detailView/fieldInput.svelte +10 -9
- package/src/lib/components/detailView/update/updateDetailView.svelte +22 -18
- package/src/lib/components/drawer.svelte +15 -2
- package/src/lib/components/foreingKeyInput.svelte +32 -60
- package/src/lib/components/polymorphicInput.svelte +42 -66
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<AlertDialog.Content>
|
|
18
18
|
<AlertDialog.Header>
|
|
19
19
|
<AlertDialog.Title>{title}</AlertDialog.Title>
|
|
20
|
-
<AlertDialog.Description class="whitespace-pre-
|
|
20
|
+
<AlertDialog.Description class="whitespace-pre-wrap font-mono text-xs">
|
|
21
21
|
{description}
|
|
22
22
|
</AlertDialog.Description>
|
|
23
23
|
</AlertDialog.Header>
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
import Table, { type TableProps } from "./table.svelte";
|
|
15
15
|
import { getCollectionColumns, getCollectionParamsFields } from "./utils";
|
|
16
16
|
import CanAccess from "../canAccess.svelte";
|
|
17
|
-
import { Pencil, Trash, Unlink, RotateCcw } from "lucide-svelte";
|
|
17
|
+
import { Pencil, Trash, Unlink, RotateCcw, Network } from "lucide-svelte";
|
|
18
18
|
import ListViewChildren from "./listViewChildren.svelte";
|
|
19
|
+
import Drawer from "../drawer.svelte";
|
|
20
|
+
import { ArrowLeft } from "lucide-svelte";
|
|
19
21
|
import FieldCell from "./fieldCell.svelte";
|
|
20
22
|
import Skeleton from "../ui/skeleton/skeleton.svelte";
|
|
21
23
|
import Button from "../ui/button/button.svelte";
|
|
@@ -73,6 +75,7 @@
|
|
|
73
75
|
}: Props = $props();
|
|
74
76
|
|
|
75
77
|
const isRecordingMode = onChanges !== undefined;
|
|
78
|
+
const isSelectMode = $derived(tableProps?.select != null);
|
|
76
79
|
let localChanges = $state<ChildrenChanges>(
|
|
77
80
|
untrack(() => changes) ?? { created: [], updated: [], deleted: [], linked: [], unlinked: [] }
|
|
78
81
|
);
|
|
@@ -135,17 +138,17 @@
|
|
|
135
138
|
const state = entry._recordingState;
|
|
136
139
|
const border = cellIndex === 0 ? {
|
|
137
140
|
deleted: 'border-l-2 border-l-red-500',
|
|
138
|
-
unlinked: 'border-l-2 border-l-
|
|
141
|
+
unlinked: 'border-l-2 border-l-slate-500',
|
|
139
142
|
created: 'border-l-2 border-l-green-500',
|
|
140
143
|
linked: 'border-l-2 border-l-blue-500',
|
|
141
|
-
updated: 'border-l-2 border-l-
|
|
144
|
+
updated: 'border-l-2 border-l-orange-500',
|
|
142
145
|
}[state as string] ?? '' : '';
|
|
143
146
|
const bg: Record<string, string> = {
|
|
144
|
-
deleted: '!bg-red-500/5
|
|
145
|
-
unlinked: '!bg-
|
|
147
|
+
deleted: '!bg-red-500/5',
|
|
148
|
+
unlinked: '!bg-slate-500/5',
|
|
146
149
|
created: '!bg-green-500/5',
|
|
147
150
|
linked: '!bg-blue-500/5',
|
|
148
|
-
updated: '!bg-
|
|
151
|
+
updated: '!bg-orange-500/5',
|
|
149
152
|
};
|
|
150
153
|
return `${bg[state as string] ?? ''} ${border}`.trim();
|
|
151
154
|
}
|
|
@@ -220,6 +223,10 @@
|
|
|
220
223
|
(ctx.meta.collections[collectionName]?.children ?? [])
|
|
221
224
|
.some((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic")
|
|
222
225
|
);
|
|
226
|
+
// Select-mode drawer has no checkbox, edit, delete, or unlink — the
|
|
227
|
+
// left-sticky tools column would render empty, so drop it entirely.
|
|
228
|
+
const showLeftTools = $derived(!isSelectMode);
|
|
229
|
+
let childrenDrawerEntry = $state<Record<string, any> | null>(null);
|
|
223
230
|
|
|
224
231
|
// requests the data from the server when the params is changed
|
|
225
232
|
$effect(() => {
|
|
@@ -404,6 +411,7 @@
|
|
|
404
411
|
bind:selectedRecords
|
|
405
412
|
{showImport}
|
|
406
413
|
{showFilter}
|
|
414
|
+
showCreate={!isSelectMode}
|
|
407
415
|
{loading}
|
|
408
416
|
{parentContext}
|
|
409
417
|
onLink={isRecordingMode ? handleLink : undefined}
|
|
@@ -453,7 +461,6 @@
|
|
|
453
461
|
<Table
|
|
454
462
|
{data}
|
|
455
463
|
{columns}
|
|
456
|
-
showCollapsible={doesCollectionHasChildren}
|
|
457
464
|
selectByColumn="id"
|
|
458
465
|
showLastRowBorder={true}
|
|
459
466
|
showLastColumnBorder={true}
|
|
@@ -461,11 +468,25 @@
|
|
|
461
468
|
bind:selectedRecords
|
|
462
469
|
bind:tableWidth={dataTableWidth}
|
|
463
470
|
{...tableProps}
|
|
471
|
+
{showLeftTools}
|
|
464
472
|
rowActions={hasRowActions ? rowActionsSnippet : undefined}
|
|
465
473
|
onCellClass={isRecordingMode ? onCellClass : undefined}>
|
|
474
|
+
{#snippet preTools(entry)}
|
|
475
|
+
{#if doesCollectionHasChildren}
|
|
476
|
+
<Button
|
|
477
|
+
class="h-6 w-6 text-muted-foreground hover:bg-transparent"
|
|
478
|
+
variant="ghost"
|
|
479
|
+
size="icon"
|
|
480
|
+
onclick={() => { childrenDrawerEntry = entry; }}
|
|
481
|
+
Icon={Network}
|
|
482
|
+
title="Show children"
|
|
483
|
+
aria-label={`Show children of record ${entry.id}`}
|
|
484
|
+
></Button>
|
|
485
|
+
{/if}
|
|
486
|
+
{/snippet}
|
|
466
487
|
{#snippet tools(entry)}
|
|
467
488
|
{#if entry._recordingState !== 'deleted' && entry._recordingState !== 'unlinked'}
|
|
468
|
-
{#if showEdit}
|
|
489
|
+
{#if showEdit && !isSelectMode}
|
|
469
490
|
{@const isPending = entry._recordingState === 'created'}
|
|
470
491
|
<CanAccess collection={collectionName} action="update">
|
|
471
492
|
<UpdateDetailViewButton
|
|
@@ -524,15 +545,6 @@
|
|
|
524
545
|
refresh={() => { params = { ...params }; }}
|
|
525
546
|
/>
|
|
526
547
|
{/snippet}
|
|
527
|
-
{#snippet collapsible(entry)}
|
|
528
|
-
<ListViewChildren
|
|
529
|
-
{collectionName}
|
|
530
|
-
recordId={entry.id}
|
|
531
|
-
width={dataTableWidth > dataTableContainerWidth
|
|
532
|
-
? dataTableContainerWidth
|
|
533
|
-
: dataTableWidth}
|
|
534
|
-
/>
|
|
535
|
-
{/snippet}
|
|
536
548
|
</Table>
|
|
537
549
|
{/if}
|
|
538
550
|
</div>
|
|
@@ -546,3 +558,23 @@
|
|
|
546
558
|
/>
|
|
547
559
|
{/if}
|
|
548
560
|
</div>
|
|
561
|
+
|
|
562
|
+
{#if childrenDrawerEntry}
|
|
563
|
+
<Drawer position="bottom" onHide={async () => { childrenDrawerEntry = null; }}>
|
|
564
|
+
<div class="flex h-12 items-center gap-4 border-b px-4 shrink-0">
|
|
565
|
+
<Button
|
|
566
|
+
variant="outline"
|
|
567
|
+
onclick={() => { childrenDrawerEntry = null; }}
|
|
568
|
+
class="h-8 w-8 rounded-full text-xs font-normal"
|
|
569
|
+
Icon={ArrowLeft}
|
|
570
|
+
></Button>
|
|
571
|
+
<div class="flex items-center gap-2 text-sm">
|
|
572
|
+
<span>Children of</span>
|
|
573
|
+
<span class="rounded-md border bg-muted px-2 py-0.5">{collectionName} #{childrenDrawerEntry.id}</span>
|
|
574
|
+
</div>
|
|
575
|
+
</div>
|
|
576
|
+
<div class="flex-1 overflow-y-auto">
|
|
577
|
+
<ListViewChildren {collectionName} recordId={String(childrenDrawerEntry.id)} />
|
|
578
|
+
</div>
|
|
579
|
+
</Drawer>
|
|
580
|
+
{/if}
|
|
@@ -20,11 +20,13 @@
|
|
|
20
20
|
// • anything else → explicit $and/$or arrays.
|
|
21
21
|
|
|
22
22
|
import * as Select from "../ui/select/index.js";
|
|
23
|
+
import * as Popover from "../ui/popover/index.js";
|
|
23
24
|
import Button from "../ui/button/button.svelte";
|
|
24
|
-
import { Plus, X, Boxes, Settings2 } from "lucide-svelte";
|
|
25
|
+
import { ChevronDown, Plus, X, Boxes, Settings2 } from "lucide-svelte";
|
|
25
26
|
import { getStudioContext } from "../../context";
|
|
26
27
|
import { getFieldIcon } from "./utils";
|
|
27
28
|
import { getFieldRelationTarget } from "../../relations";
|
|
29
|
+
import FieldPicker from "./fieldPicker.svelte";
|
|
28
30
|
|
|
29
31
|
const { ctx } = getStudioContext();
|
|
30
32
|
|
|
@@ -43,6 +45,9 @@
|
|
|
43
45
|
isEmpty = $bindable(true),
|
|
44
46
|
}: Props = $props();
|
|
45
47
|
|
|
48
|
+
// Per-rule open state for the field-picker popovers.
|
|
49
|
+
let fieldPickerOpen = $state<Record<string, boolean>>({});
|
|
50
|
+
|
|
46
51
|
type OperatorDef = {
|
|
47
52
|
value: string;
|
|
48
53
|
label: string;
|
|
@@ -471,30 +476,28 @@
|
|
|
471
476
|
{@const currentOp = ops.find((o) => o.value === rule.operator) ?? ops[0]}
|
|
472
477
|
{@const kind = getFieldKind(rule.field)}
|
|
473
478
|
{@const FieldIcon = getFieldIcon(ctx, rule.field, collectionName)}
|
|
474
|
-
<!-- Field picker -->
|
|
475
|
-
<
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
<Select.Trigger class="bg-muted h-7 w-36 text-xs">
|
|
481
|
-
<div class="inline-flex items-center gap-1.5">
|
|
479
|
+
<!-- Field picker — typeahead popover, same widget Sort uses -->
|
|
480
|
+
<Popover.Root bind:open={fieldPickerOpen[rule.id]}>
|
|
481
|
+
<Popover.Trigger
|
|
482
|
+
class="inline-flex h-7 w-36 items-center justify-between gap-1.5 rounded-md border bg-muted px-2 text-xs"
|
|
483
|
+
>
|
|
484
|
+
<div class="inline-flex items-center gap-1.5 truncate">
|
|
482
485
|
<FieldIcon size="13" />
|
|
483
|
-
{rule.field}
|
|
486
|
+
<span class="truncate">{rule.field}</span>
|
|
484
487
|
</div>
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
</
|
|
497
|
-
</
|
|
488
|
+
<ChevronDown size="13" class="text-muted-foreground shrink-0" />
|
|
489
|
+
</Popover.Trigger>
|
|
490
|
+
<Popover.Content class="w-64 p-2">
|
|
491
|
+
<FieldPicker
|
|
492
|
+
{collectionName}
|
|
493
|
+
placeholder="Pick a field…"
|
|
494
|
+
onPick={(fname: string) => {
|
|
495
|
+
patchRuleInPlace(rule.id, { field: fname });
|
|
496
|
+
fieldPickerOpen[rule.id] = false;
|
|
497
|
+
}}
|
|
498
|
+
/>
|
|
499
|
+
</Popover.Content>
|
|
500
|
+
</Popover.Root>
|
|
498
501
|
|
|
499
502
|
<!-- Operator picker -->
|
|
500
503
|
<Select.Root
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
onCreate?: (changes: Changes) => void;
|
|
27
27
|
showImport?: boolean;
|
|
28
28
|
showFilter?: boolean;
|
|
29
|
+
showCreate?: boolean;
|
|
29
30
|
loading?: boolean;
|
|
30
31
|
left?: Snippet<[]>;
|
|
31
32
|
excludeIds?: (string | number)[];
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
onCreate,
|
|
41
42
|
showImport = true,
|
|
42
43
|
showFilter = true,
|
|
44
|
+
showCreate = true,
|
|
43
45
|
loading = false,
|
|
44
46
|
left,
|
|
45
47
|
excludeIds = [],
|
|
@@ -154,7 +156,7 @@
|
|
|
154
156
|
{headerIsSmall ? "" : "Refresh"}
|
|
155
157
|
{/if}
|
|
156
158
|
</Button>
|
|
157
|
-
{#if showImport}
|
|
159
|
+
{#if showImport && showCreate}
|
|
158
160
|
<CanAccess collection={collectionName} action="create">
|
|
159
161
|
<ImportButton
|
|
160
162
|
{collectionName}
|
|
@@ -183,17 +185,19 @@
|
|
|
183
185
|
{headerIsSmall ? "" : "Link"}
|
|
184
186
|
</SelectRecord>
|
|
185
187
|
{/if}
|
|
186
|
-
|
|
187
|
-
<
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
188
|
+
{#if showCreate}
|
|
189
|
+
<CanAccess collection={collectionName} action="create">
|
|
190
|
+
<CreateDetailViewButton
|
|
191
|
+
{collectionName}
|
|
192
|
+
variant="default"
|
|
193
|
+
size="sm"
|
|
194
|
+
Icon={Plus}
|
|
195
|
+
onChanges={onCreate ? handleCreate : undefined}
|
|
196
|
+
onSuccessfullSave={onCreate ? undefined : handleCreate}
|
|
197
|
+
>
|
|
198
|
+
{headerIsSmall ? "" : "Create"}
|
|
199
|
+
</CreateDetailViewButton>
|
|
200
|
+
</CanAccess>
|
|
201
|
+
{/if}
|
|
198
202
|
</div>
|
|
199
203
|
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getStudioContext } from "../../context";
|
|
3
|
-
import {
|
|
3
|
+
import { Table, Plus } from "lucide-svelte";
|
|
4
4
|
import DataTable from "./dataTable.svelte";
|
|
5
5
|
import CreateDetailViewButton from "../detailView/create/createDetailViewButton.svelte";
|
|
6
6
|
import ExtensionsComponents from "../extensionsComponents.svelte";
|
|
@@ -11,93 +11,76 @@
|
|
|
11
11
|
interface Props {
|
|
12
12
|
collectionName: string;
|
|
13
13
|
recordId: string;
|
|
14
|
-
width
|
|
14
|
+
width?: number;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
let { collectionName, recordId, width }: Props = $props();
|
|
17
|
+
let { collectionName, recordId, width = 0 }: Props = $props();
|
|
18
18
|
|
|
19
19
|
const children = (ctx.meta.collections[collectionName]?.children ?? [])
|
|
20
20
|
.filter((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic");
|
|
21
21
|
|
|
22
|
-
let
|
|
23
|
-
let
|
|
24
|
-
let
|
|
22
|
+
let activeTab = $state(children[0]?.collection ?? '');
|
|
23
|
+
let refreshKey = $state(0);
|
|
24
|
+
let counts = $state<Record<string, number>>({});
|
|
25
|
+
|
|
26
|
+
const activeChild = $derived(children.find((c: any) => c.collection === activeTab));
|
|
25
27
|
</script>
|
|
26
28
|
|
|
27
|
-
<div class="flex
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
<div class="flex flex-col h-full overflow-hidden">
|
|
30
|
+
<!-- Tab bar -->
|
|
31
|
+
<div class="flex border-b shrink-0 overflow-x-auto">
|
|
32
|
+
{#each children as child}
|
|
33
|
+
<button
|
|
34
|
+
class="flex items-center gap-1.5 px-4 h-10 text-xs font-medium whitespace-nowrap border-b-2 transition-colors
|
|
35
|
+
{activeTab === child.collection
|
|
36
|
+
? 'border-foreground text-foreground'
|
|
37
|
+
: 'border-transparent text-muted-foreground hover:text-foreground'}"
|
|
38
|
+
onclick={() => { activeTab = child.collection; }}
|
|
39
|
+
>
|
|
40
|
+
<Table size="11" class="opacity-50" />
|
|
41
|
+
{child.collection}
|
|
42
|
+
{#if counts[child.collection] !== undefined}
|
|
43
|
+
<span class="rounded-full bg-muted px-1.5 py-0.5 text-[0.65rem] text-muted-foreground">{counts[child.collection]}</span>
|
|
44
|
+
{/if}
|
|
45
|
+
</button>
|
|
46
|
+
{/each}
|
|
47
|
+
<!-- Create button for FK children -->
|
|
48
|
+
{#if activeChild?.type === "fk"}
|
|
49
|
+
<div class="ml-auto flex items-center px-2">
|
|
50
|
+
<CreateDetailViewButton
|
|
51
|
+
collectionName={activeChild.collection}
|
|
52
|
+
variant="ghost"
|
|
53
|
+
size="sm"
|
|
54
|
+
Icon={Plus}
|
|
55
|
+
values={{ [activeChild.field]: recordId }}
|
|
56
|
+
onSuccessfullSave={async () => { refreshKey++; }}
|
|
39
57
|
>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
Create
|
|
59
|
+
</CreateDetailViewButton>
|
|
60
|
+
</div>
|
|
61
|
+
{/if}
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- Tab content — render all but hide inactive so counts load for all tabs -->
|
|
65
|
+
<div class="flex-1 overflow-hidden relative">
|
|
66
|
+
{#each children as child}
|
|
67
|
+
<div class="absolute inset-0 {activeTab === child.collection ? '' : 'invisible pointer-events-none'}">
|
|
68
|
+
{#key refreshKey}
|
|
69
|
+
<ExtensionsComponents
|
|
70
|
+
name="listView.entry.children.{child.collection}"
|
|
71
|
+
collectionName={child.collection}
|
|
72
|
+
searchParams={{ children_of: collectionName, parent_id: recordId }}
|
|
73
|
+
utils={getExtensionUtils(lobb, ctx)}
|
|
43
74
|
>
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
75
|
+
<DataTable
|
|
76
|
+
collectionName={child.collection}
|
|
77
|
+
searchParams={{ children_of: collectionName, parent_id: recordId }}
|
|
78
|
+
showDelete={child.type === "fk" || child.type === "m2m"}
|
|
79
|
+
tableProps={{ showLastRowBorder: true, showLastColumnBorder: true }}
|
|
80
|
+
onDataLoad={(total) => { counts[child.collection] = total; }}
|
|
48
81
|
/>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{#if child.type === "fk"}
|
|
52
|
-
<span title="Direct (FK)"><Link size="13" class="text-muted-foreground/50" /></span>
|
|
53
|
-
{:else if child.type === "m2m"}
|
|
54
|
-
<span title="Many to Many"><ArrowLeftRight size="13" class="text-muted-foreground/50" /></span>
|
|
55
|
-
{:else if child.type === "polymorphic"}
|
|
56
|
-
<span title="Polymorphic"><GitFork size="13" class="text-muted-foreground/50" /></span>
|
|
57
|
-
{/if}
|
|
58
|
-
</button>
|
|
59
|
-
{#if child.type === "fk"}
|
|
60
|
-
<div class="flex items-center px-2">
|
|
61
|
-
<CreateDetailViewButton
|
|
62
|
-
collectionName={child.collection}
|
|
63
|
-
variant="ghost"
|
|
64
|
-
size="sm"
|
|
65
|
-
Icon={Plus}
|
|
66
|
-
values={{ [child.field]: recordId }}
|
|
67
|
-
onSuccessfullSave={async () => { refreshDataTable = !refreshDataTable; }}
|
|
68
|
-
>
|
|
69
|
-
Create
|
|
70
|
-
</CreateDetailViewButton>
|
|
71
|
-
</div>
|
|
72
|
-
{/if}
|
|
73
|
-
</div>
|
|
74
|
-
{#if expandedRows[index]}
|
|
75
|
-
<div class="flex max-h-96 overflow-auto {lastRow ? '' : 'border-b'}">
|
|
76
|
-
<div
|
|
77
|
-
class="border-r"
|
|
78
|
-
style="width: 100vw; max-width: 40px"
|
|
79
|
-
></div>
|
|
80
|
-
<div class="flex-1" style="width: {tableHeaderWidth - 40}px;">
|
|
81
|
-
{#key refreshDataTable}
|
|
82
|
-
<ExtensionsComponents
|
|
83
|
-
name="listView.entry.children.{child.collection}"
|
|
84
|
-
collectionName={child.collection}
|
|
85
|
-
searchParams={{ children_of: collectionName, parent_id: recordId }}
|
|
86
|
-
utils={getExtensionUtils(lobb, ctx)}
|
|
87
|
-
>
|
|
88
|
-
<DataTable
|
|
89
|
-
collectionName={child.collection}
|
|
90
|
-
searchParams={{ children_of: collectionName, parent_id: recordId }}
|
|
91
|
-
showHeader={false}
|
|
92
|
-
showFooter={false}
|
|
93
|
-
showDelete={child.type === "fk"}
|
|
94
|
-
tableProps={{ showLastRowBorder: false, showLastColumnBorder: false, showCheckboxes: false }}
|
|
95
|
-
/>
|
|
96
|
-
</ExtensionsComponents>
|
|
97
|
-
{/key}
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
{/if}
|
|
82
|
+
</ExtensionsComponents>
|
|
83
|
+
{/key}
|
|
101
84
|
</div>
|
|
102
85
|
{/each}
|
|
103
86
|
</div>
|
|
@@ -15,8 +15,6 @@
|
|
|
15
15
|
export interface TableProps {
|
|
16
16
|
data: Entry[];
|
|
17
17
|
columns?: Column[];
|
|
18
|
-
showCollapsible?: boolean;
|
|
19
|
-
|
|
20
18
|
// sorting
|
|
21
19
|
sort?: Record<string, "asc" | "desc">;
|
|
22
20
|
localSorting?: boolean;
|
|
@@ -33,15 +31,19 @@
|
|
|
33
31
|
|
|
34
32
|
// snippets
|
|
35
33
|
overrideCell?: Snippet<[any, Column, Entry]>;
|
|
34
|
+
preTools?: Snippet<[Entry, number]>;
|
|
36
35
|
tools?: Snippet<[Entry, number]>;
|
|
37
36
|
rowActions?: Snippet<[Entry, number]>;
|
|
38
|
-
collapsible?: Snippet<[Entry, number]>;
|
|
39
|
-
|
|
40
37
|
// other
|
|
41
38
|
parentWidth?: number;
|
|
42
39
|
select?: Select;
|
|
43
40
|
tableWidth?: number;
|
|
44
41
|
|
|
42
|
+
// Hide the left sticky tools column entirely when the caller knows
|
|
43
|
+
// nothing inside it would render (e.g. select-mode drawer with no
|
|
44
|
+
// checkboxes, no edit/delete buttons, and no children-network icon).
|
|
45
|
+
showLeftTools?: boolean;
|
|
46
|
+
|
|
45
47
|
// recording mode row visuals — cellIndex 0 = tools cell, 1+ = data/action cells
|
|
46
48
|
onCellClass?: (entry: Entry, cellIndex: number) => string;
|
|
47
49
|
}
|
|
@@ -51,7 +53,6 @@
|
|
|
51
53
|
import {
|
|
52
54
|
ArrowDownNarrowWide,
|
|
53
55
|
ArrowUpWideNarrow,
|
|
54
|
-
ChevronRight,
|
|
55
56
|
CircleOff,
|
|
56
57
|
} from "lucide-svelte";
|
|
57
58
|
import Checkbox from "../ui/checkbox/checkbox.svelte";
|
|
@@ -66,7 +67,6 @@
|
|
|
66
67
|
id: key,
|
|
67
68
|
};
|
|
68
69
|
}),
|
|
69
|
-
showCollapsible = false,
|
|
70
70
|
sort = $bindable({}),
|
|
71
71
|
localSorting = false,
|
|
72
72
|
selectedRecords = $bindable(),
|
|
@@ -77,20 +77,19 @@
|
|
|
77
77
|
headerBorderTop = false,
|
|
78
78
|
parentWidth,
|
|
79
79
|
overrideCell,
|
|
80
|
+
preTools,
|
|
80
81
|
tools,
|
|
81
82
|
rowActions,
|
|
82
|
-
collapsible,
|
|
83
83
|
select,
|
|
84
84
|
tableWidth = $bindable(),
|
|
85
|
+
showLeftTools = true,
|
|
85
86
|
onCellClass,
|
|
86
87
|
}: TableProps = $props();
|
|
87
88
|
|
|
88
|
-
let expandedRows: boolean[] = $state(new Array(data.length).fill(false));
|
|
89
|
-
|
|
90
89
|
// calculate columns count
|
|
91
|
-
const toolsExists = selectedRecords || tools ? 1 : 0;
|
|
90
|
+
const toolsExists = $derived(showLeftTools && (selectedRecords || tools || preTools) ? 1 : 0);
|
|
92
91
|
const rowActionsExists = $derived(rowActions ? 1 : 0);
|
|
93
|
-
const columnsLength = columns.length + toolsExists;
|
|
92
|
+
const columnsLength = $derived(columns.length + toolsExists);
|
|
94
93
|
|
|
95
94
|
// set table width
|
|
96
95
|
let columnsWidths: number[] = $state([]);
|
|
@@ -173,13 +172,14 @@
|
|
|
173
172
|
</script>
|
|
174
173
|
|
|
175
174
|
<div
|
|
175
|
+
class="min-w-max"
|
|
176
176
|
style="
|
|
177
177
|
display: grid;
|
|
178
|
-
grid-template-columns:
|
|
178
|
+
grid-template-columns: fit-content(10rem) repeat({columnsLength - 1}, fit-content(15rem)){rowActionsExists ? ' fit-content(7.5rem)' : ''};
|
|
179
179
|
grid-template-rows: 2.5rem;
|
|
180
180
|
"
|
|
181
181
|
>
|
|
182
|
-
{#if selectedRecords || tools}
|
|
182
|
+
{#if showLeftTools && (selectedRecords || tools || preTools)}
|
|
183
183
|
<div
|
|
184
184
|
bind:clientWidth={columnsWidths[0]}
|
|
185
185
|
class="
|
|
@@ -187,13 +187,9 @@
|
|
|
187
187
|
flex items-center p-2.5 text-xs h-10
|
|
188
188
|
border-r border-b gap-2
|
|
189
189
|
{headerBorderTop ? 'border-t' : ''}
|
|
190
|
-
bg-muted
|
|
190
|
+
bg-muted
|
|
191
191
|
"
|
|
192
192
|
>
|
|
193
|
-
<!-- collapsable toggle -->
|
|
194
|
-
{#if showCollapsible}
|
|
195
|
-
<div class="w-[20px]"></div>
|
|
196
|
-
{/if}
|
|
197
193
|
{#if selectedRecords && showCheckboxes}
|
|
198
194
|
<Checkbox
|
|
199
195
|
class="border-muted-foreground hover:border-foreground"
|
|
@@ -212,7 +208,7 @@
|
|
|
212
208
|
class="
|
|
213
209
|
sticky top-0 z-10
|
|
214
210
|
flex items-center p-2.5 text-xs h-10
|
|
215
|
-
bg-muted
|
|
211
|
+
bg-muted
|
|
216
212
|
{lastColumn && !showLastColumnBorder ? '' : 'border-r'}
|
|
217
213
|
border-b gap-2
|
|
218
214
|
{headerBorderTop ? 'border-t' : ''}
|
|
@@ -239,7 +235,7 @@
|
|
|
239
235
|
class="
|
|
240
236
|
sticky top-0 right-0 z-20
|
|
241
237
|
flex items-center p-2.5 h-10
|
|
242
|
-
bg-muted
|
|
238
|
+
bg-muted
|
|
243
239
|
border-l border-b
|
|
244
240
|
{headerBorderTop ? 'border-t' : ''}
|
|
245
241
|
"
|
|
@@ -249,25 +245,12 @@
|
|
|
249
245
|
{#each data as entry, index}
|
|
250
246
|
{@const isDisabled = Boolean(entry.__disabled)}
|
|
251
247
|
{@const lastRow = data.length - 1 === index}
|
|
252
|
-
{#if selectedRecords || tools}
|
|
248
|
+
{#if showLeftTools && (selectedRecords || tools || preTools)}
|
|
253
249
|
<div
|
|
254
250
|
class="sticky left-0 flex items-center p-2.5 text-xs h-10 bg-card border-r gap-2 {onCellClass?.(entry, 0) ?? ''}"
|
|
255
251
|
>
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
<Button
|
|
259
|
-
variant="ghost"
|
|
260
|
-
class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent transition-transform"
|
|
261
|
-
style={expandedRows[index]
|
|
262
|
-
? "transform: rotate(90deg);"
|
|
263
|
-
: "transform: rotate(0deg);"}
|
|
264
|
-
Icon={ChevronRight}
|
|
265
|
-
onclick={() => {
|
|
266
|
-
expandedRows[index] = !expandedRows[index];
|
|
267
|
-
expandedRows = [...expandedRows];
|
|
268
|
-
}}
|
|
269
|
-
disabled={isDisabled}
|
|
270
|
-
></Button>
|
|
252
|
+
{#if preTools && !isDisabled}
|
|
253
|
+
{@render preTools(entry, index)}
|
|
271
254
|
{/if}
|
|
272
255
|
{#if selectedRecords && showCheckboxes}
|
|
273
256
|
<Checkbox
|
|
@@ -309,31 +292,7 @@
|
|
|
309
292
|
{@render rowActions?.(entry, index)}
|
|
310
293
|
</div>
|
|
311
294
|
{/if}
|
|
312
|
-
|
|
313
|
-
<div
|
|
314
|
-
style="grid-column: span {columnsLength + rowActionsExists};"
|
|
315
|
-
class="
|
|
316
|
-
{!showLastColumnBorder ? '' : 'border-r'}
|
|
317
|
-
{lastRow && !showLastRowBorder ? '' : 'border-b'}
|
|
318
|
-
"
|
|
319
|
-
>
|
|
320
|
-
<div
|
|
321
|
-
style="
|
|
322
|
-
{parentWidth ? `width: ${parentWidth}px` : ''};
|
|
323
|
-
max-width: 100vw;
|
|
324
|
-
{expandedRows[index] ? '' : 'height: 0px;'}
|
|
325
|
-
"
|
|
326
|
-
class="
|
|
327
|
-
sticky left-0 top-0 overflow-auto bg-muted
|
|
328
|
-
|
|
329
|
-
{expandedRows[index] ? 'border-t' : ''}
|
|
330
|
-
"
|
|
331
|
-
>
|
|
332
|
-
{#if collapsible && expandedRows[index]}
|
|
333
|
-
{@render collapsible(entry, index)}
|
|
334
|
-
{/if}
|
|
335
|
-
</div>
|
|
336
|
-
</div>
|
|
295
|
+
<div style="grid-column: span {columnsLength + rowActionsExists};" class="{!showLastColumnBorder ? '' : 'border-r'} {lastRow && !showLastRowBorder ? '' : 'border-b'}"></div>
|
|
337
296
|
{/each}
|
|
338
297
|
{/if}
|
|
339
298
|
</div>
|
|
@@ -11,7 +11,6 @@ interface Select {
|
|
|
11
11
|
export interface TableProps {
|
|
12
12
|
data: Entry[];
|
|
13
13
|
columns?: Column[];
|
|
14
|
-
showCollapsible?: boolean;
|
|
15
14
|
sort?: Record<string, "asc" | "desc">;
|
|
16
15
|
localSorting?: boolean;
|
|
17
16
|
selectedRecords?: Array<any>;
|
|
@@ -21,12 +20,13 @@ export interface TableProps {
|
|
|
21
20
|
showLastColumnBorder?: boolean;
|
|
22
21
|
headerBorderTop?: boolean;
|
|
23
22
|
overrideCell?: Snippet<[any, Column, Entry]>;
|
|
23
|
+
preTools?: Snippet<[Entry, number]>;
|
|
24
24
|
tools?: Snippet<[Entry, number]>;
|
|
25
25
|
rowActions?: Snippet<[Entry, number]>;
|
|
26
|
-
collapsible?: Snippet<[Entry, number]>;
|
|
27
26
|
parentWidth?: number;
|
|
28
27
|
select?: Select;
|
|
29
28
|
tableWidth?: number;
|
|
29
|
+
showLeftTools?: boolean;
|
|
30
30
|
onCellClass?: (entry: Entry, cellIndex: number) => string;
|
|
31
31
|
}
|
|
32
32
|
import type { Snippet } from "svelte";
|