@svadmin/lite 0.6.0 → 0.8.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.
Files changed (104) hide show
  1. package/README.md +85 -9
  2. package/dist/components/LiteActivityFeed.svelte +98 -0
  3. package/dist/components/LiteActivityFeed.svelte.d.ts +19 -0
  4. package/dist/components/LiteApprovalActionCard.svelte +75 -0
  5. package/dist/components/LiteApprovalActionCard.svelte.d.ts +10 -0
  6. package/dist/components/LiteCanvasAnnotation.svelte +136 -0
  7. package/dist/components/LiteCanvasAnnotation.svelte.d.ts +16 -0
  8. package/dist/components/LiteColumnHeaderFilter.svelte +38 -0
  9. package/dist/components/LiteColumnHeaderFilter.svelte.d.ts +8 -0
  10. package/dist/components/LiteColumnSettings.svelte +82 -0
  11. package/dist/components/LiteColumnSettings.svelte.d.ts +15 -0
  12. package/dist/components/LiteDecisionTable.svelte +178 -0
  13. package/dist/components/LiteDecisionTable.svelte.d.ts +21 -0
  14. package/dist/components/LiteDraggableRowTable.svelte +70 -0
  15. package/dist/components/LiteDraggableRowTable.svelte.d.ts +14 -0
  16. package/dist/components/LiteDrawerForm.svelte +85 -0
  17. package/dist/components/LiteDrawerForm.svelte.d.ts +13 -0
  18. package/dist/components/LiteDynamicFormList.svelte +169 -0
  19. package/dist/components/LiteDynamicFormList.svelte.d.ts +45 -0
  20. package/dist/components/LiteEditableTable.svelte +84 -0
  21. package/dist/components/LiteEditableTable.svelte.d.ts +19 -0
  22. package/dist/components/LiteFilterBuilder.svelte +205 -0
  23. package/dist/components/LiteFilterBuilder.svelte.d.ts +19 -0
  24. package/dist/components/LiteGanttChart.svelte +144 -0
  25. package/dist/components/LiteGanttChart.svelte.d.ts +17 -0
  26. package/dist/components/LiteImageCropper.svelte +81 -0
  27. package/dist/components/LiteImageCropper.svelte.d.ts +9 -0
  28. package/dist/components/LiteImportWizard.svelte +99 -0
  29. package/dist/components/LiteImportWizard.svelte.d.ts +11 -0
  30. package/dist/components/LiteJsonSchemaForm.svelte +102 -0
  31. package/dist/components/LiteJsonSchemaForm.svelte.d.ts +10 -0
  32. package/dist/components/LiteKanbanBoard.svelte +169 -0
  33. package/dist/components/LiteKanbanBoard.svelte.d.ts +21 -0
  34. package/dist/components/LiteMasterDetailView.svelte +113 -0
  35. package/dist/components/LiteMasterDetailView.svelte.d.ts +11 -0
  36. package/dist/components/LiteMediaLibraryModal.svelte +110 -0
  37. package/dist/components/LiteMediaLibraryModal.svelte.d.ts +17 -0
  38. package/dist/components/LiteMentionsInput.svelte +98 -0
  39. package/dist/components/LiteMentionsInput.svelte.d.ts +19 -0
  40. package/dist/components/LiteModalForm.svelte +84 -0
  41. package/dist/components/LiteModalForm.svelte.d.ts +13 -0
  42. package/dist/components/LiteMultiTabKeepAlive.svelte +79 -0
  43. package/dist/components/LiteMultiTabKeepAlive.svelte.d.ts +15 -0
  44. package/dist/components/LiteOfflineSyncBanner.svelte +75 -0
  45. package/dist/components/LiteOfflineSyncBanner.svelte.d.ts +15 -0
  46. package/dist/components/LitePdfDocumentViewer.svelte +138 -0
  47. package/dist/components/LitePdfDocumentViewer.svelte.d.ts +17 -0
  48. package/dist/components/LitePivotTable.svelte +163 -0
  49. package/dist/components/LitePivotTable.svelte.d.ts +15 -0
  50. package/dist/components/LitePresenceAvatarGroup.svelte +52 -0
  51. package/dist/components/LitePresenceAvatarGroup.svelte.d.ts +12 -0
  52. package/dist/components/LitePrintableBill.svelte +119 -0
  53. package/dist/components/LitePrintableBill.svelte.d.ts +19 -0
  54. package/dist/components/LiteSensitiveDataMask.svelte +35 -0
  55. package/dist/components/LiteSensitiveDataMask.svelte.d.ts +7 -0
  56. package/dist/components/LiteShowField.svelte +22 -0
  57. package/dist/components/LiteSignaturePad.svelte +99 -0
  58. package/dist/components/LiteSignaturePad.svelte.d.ts +11 -0
  59. package/dist/components/LiteSplitPaneLayout.svelte +66 -0
  60. package/dist/components/LiteSplitPaneLayout.svelte.d.ts +9 -0
  61. package/dist/components/LiteSpreadsheetView.svelte +148 -0
  62. package/dist/components/LiteSpreadsheetView.svelte.d.ts +16 -0
  63. package/dist/components/LiteStepForm.svelte +144 -0
  64. package/dist/components/LiteStepForm.svelte.d.ts +18 -0
  65. package/dist/components/LiteTableSummary.svelte +93 -0
  66. package/dist/components/LiteTableSummary.svelte.d.ts +17 -0
  67. package/dist/components/LiteTransfer.svelte +185 -0
  68. package/dist/components/LiteTransfer.svelte.d.ts +18 -0
  69. package/dist/components/LiteTreeTable.svelte +85 -0
  70. package/dist/components/LiteTreeTable.svelte.d.ts +13 -0
  71. package/dist/components/LiteVersionDiffViewer.svelte +129 -0
  72. package/dist/components/LiteVersionDiffViewer.svelte.d.ts +10 -0
  73. package/dist/components/LiteVirtualTable.svelte +52 -0
  74. package/dist/components/LiteVirtualTable.svelte.d.ts +14 -0
  75. package/dist/components/LiteWatermark.svelte +35 -0
  76. package/dist/components/LiteWatermark.svelte.d.ts +11 -0
  77. package/dist/components/fields/LiteAvatarField.svelte +127 -0
  78. package/dist/components/fields/LiteAvatarField.svelte.d.ts +21 -0
  79. package/dist/components/fields/LiteCascader.svelte +119 -0
  80. package/dist/components/fields/LiteCascader.svelte.d.ts +24 -0
  81. package/dist/components/fields/LiteCodeField.svelte +71 -0
  82. package/dist/components/fields/LiteCodeField.svelte.d.ts +16 -0
  83. package/dist/components/fields/LiteCopyField.svelte +74 -0
  84. package/dist/components/fields/LiteCopyField.svelte.d.ts +18 -0
  85. package/dist/components/fields/LiteCurrencyField.svelte +126 -0
  86. package/dist/components/fields/LiteCurrencyField.svelte.d.ts +21 -0
  87. package/dist/components/fields/LiteDateRangeField.svelte +147 -0
  88. package/dist/components/fields/LiteDateRangeField.svelte.d.ts +26 -0
  89. package/dist/components/fields/LitePercentField.svelte +124 -0
  90. package/dist/components/fields/LitePercentField.svelte.d.ts +17 -0
  91. package/dist/components/fields/LitePhoneField.svelte +81 -0
  92. package/dist/components/fields/LitePhoneField.svelte.d.ts +17 -0
  93. package/dist/components/fields/LiteRatingField.svelte +105 -0
  94. package/dist/components/fields/LiteRatingField.svelte.d.ts +16 -0
  95. package/dist/components/fields/LiteTreeSelect.svelte +143 -0
  96. package/dist/components/fields/LiteTreeSelect.svelte.d.ts +23 -0
  97. package/dist/components/fields/index.d.ts +12 -0
  98. package/dist/components/fields/index.js +10 -0
  99. package/dist/components/pages/LiteListPage.svelte +14 -12
  100. package/dist/index.d.ts +40 -0
  101. package/dist/index.js +37 -0
  102. package/dist/lite.css +205 -0
  103. package/dist/schema-generator.js +15 -4
  104. package/package.json +4 -3
@@ -0,0 +1,144 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ export interface LiteFormStep {
5
+ title: string;
6
+ description?: string;
7
+ }
8
+
9
+ interface Props {
10
+ steps: LiteFormStep[];
11
+ currentStep?: number;
12
+ action?: string;
13
+ method?: "get" | "post" | "dialog" | "GET" | "POST" | "DIALOG" | null;
14
+ submitText?: string;
15
+ nextText?: string;
16
+ prevText?: string;
17
+ children?: Snippet;
18
+ }
19
+
20
+ let {
21
+ steps,
22
+ currentStep = 0,
23
+ action = '',
24
+ method = 'POST',
25
+ submitText = 'Submit',
26
+ nextText = 'Next Step',
27
+ prevText = 'Previous Step',
28
+ children,
29
+ }: Props = $props();
30
+ </script>
31
+
32
+ <div class="lite-step-form-card">
33
+ <!-- Step Indicators -->
34
+ <div class="lite-step-header">
35
+ {#each steps as step, index (index)}
36
+ {@const isCompleted = index < currentStep}
37
+ {@const isCurrent = index === currentStep}
38
+ <div class="lite-step-item {isCurrent ? 'lite-step-current' : isCompleted ? 'lite-step-completed' : ''}">
39
+ <span class="lite-step-circle">
40
+ {#if isCompleted}✔{:else}{index + 1}{/if}
41
+ </span>
42
+ <span class="lite-step-title">{step.title}</span>
43
+ </div>
44
+ {#if index < steps.length - 1}
45
+ <span class="lite-step-arrow">→</span>
46
+ {/if}
47
+ {/each}
48
+ </div>
49
+
50
+ <!-- Form Body -->
51
+ <form {action} {method} class="lite-step-body">
52
+ <input type="hidden" name="_step" value={currentStep} />
53
+
54
+ <div class="lite-step-content">
55
+ {#if children}
56
+ {@render children()}
57
+ {/if}
58
+ </div>
59
+
60
+ <!-- Actions -->
61
+ <div class="lite-step-actions">
62
+ {#if currentStep > 0}
63
+ <button type="submit" name="_step_action" value="prev" class="lite-btn lite-btn-outline lite-btn-sm">
64
+ ← {prevText}
65
+ </button>
66
+ {/if}
67
+ <div class="lite-step-right-action" style="margin-left: auto;">
68
+ {#if currentStep < steps.length - 1}
69
+ <button type="submit" name="_step_action" value="next" class="lite-btn lite-btn-primary lite-btn-sm">
70
+ {nextText} →
71
+ </button>
72
+ {:else}
73
+ <button type="submit" name="_step_action" value="finish" class="lite-btn lite-btn-primary lite-btn-sm">
74
+ {submitText}
75
+ </button>
76
+ {/if}
77
+ </div>
78
+ </div>
79
+ </form>
80
+ </div>
81
+
82
+ <style>
83
+ .lite-step-form-card {
84
+ padding: 16px;
85
+ background: #ffffff;
86
+ border: 1px solid #cbd5e1;
87
+ border-radius: 6px;
88
+ margin-bottom: 16px;
89
+ }
90
+ .lite-step-header {
91
+ display: flex;
92
+ align-items: center;
93
+ padding-bottom: 12px;
94
+ border-bottom: 1px solid #e2e8f0;
95
+ margin-bottom: 16px;
96
+ overflow-x: auto;
97
+ }
98
+ .lite-step-item {
99
+ display: flex;
100
+ align-items: center;
101
+ font-size: 12px;
102
+ color: #64748b;
103
+ }
104
+ .lite-step-item.lite-step-current {
105
+ color: #3b82f6;
106
+ font-weight: bold;
107
+ }
108
+ .lite-step-item.lite-step-completed {
109
+ color: #10b981;
110
+ }
111
+ .lite-step-circle {
112
+ display: inline-block;
113
+ width: 20px;
114
+ height: 20px;
115
+ line-height: 20px;
116
+ text-align: center;
117
+ border-radius: 50%;
118
+ background: #f1f5f9;
119
+ margin-right: 6px;
120
+ font-size: 11px;
121
+ }
122
+ .lite-step-current .lite-step-circle {
123
+ background: #3b82f6;
124
+ color: #ffffff;
125
+ }
126
+ .lite-step-completed .lite-step-circle {
127
+ background: #10b981;
128
+ color: #ffffff;
129
+ }
130
+ .lite-step-arrow {
131
+ margin: 0 10px;
132
+ color: #cbd5e1;
133
+ }
134
+ .lite-step-content {
135
+ min-height: 100px;
136
+ margin-bottom: 16px;
137
+ }
138
+ .lite-step-actions {
139
+ display: flex;
140
+ align-items: center;
141
+ padding-top: 12px;
142
+ border-top: 1px solid #e2e8f0;
143
+ }
144
+ </style>
@@ -0,0 +1,18 @@
1
+ import type { Snippet } from 'svelte';
2
+ export interface LiteFormStep {
3
+ title: string;
4
+ description?: string;
5
+ }
6
+ interface Props {
7
+ steps: LiteFormStep[];
8
+ currentStep?: number;
9
+ action?: string;
10
+ method?: "get" | "post" | "dialog" | "GET" | "POST" | "DIALOG" | null;
11
+ submitText?: string;
12
+ nextText?: string;
13
+ prevText?: string;
14
+ children?: Snippet;
15
+ }
16
+ declare const LiteStepForm: import("svelte").Component<Props, {}, "">;
17
+ type LiteStepForm = ReturnType<typeof LiteStepForm>;
18
+ export default LiteStepForm;
@@ -0,0 +1,93 @@
1
+ <script lang="ts">
2
+ interface LiteTableSummaryColumn {
3
+ key: string;
4
+ label?: string;
5
+ align?: 'left' | 'center' | 'right';
6
+ }
7
+
8
+ interface Props {
9
+ columns: LiteTableSummaryColumn[];
10
+ data?: Record<string, unknown>[];
11
+ aggregations?: Record<string, string | ((rows: Record<string, unknown>[]) => string | number)>;
12
+ title?: string;
13
+ titleColumnKey?: string;
14
+ prefix?: Record<string, string>;
15
+ suffix?: Record<string, string>;
16
+ }
17
+
18
+ let {
19
+ columns,
20
+ data = [],
21
+ aggregations = {},
22
+ title = 'Total',
23
+ titleColumnKey,
24
+ prefix = {},
25
+ suffix = {},
26
+ }: Props = $props();
27
+
28
+ const titleKey = $derived(titleColumnKey || (columns[0]?.key ?? ''));
29
+
30
+ function calculate(key: string, type: unknown): string | number {
31
+ if (!data.length) return '—';
32
+ if (typeof type === 'function') return type(data);
33
+ if (type === 'count') return data.length;
34
+
35
+ const numbers = data
36
+ .map((row) => Number(row[key]))
37
+ .filter((val) => !isNaN(val) && val !== null && val !== undefined);
38
+
39
+ if (numbers.length === 0) return '—';
40
+
41
+ if (type === 'sum') {
42
+ const sum = numbers.reduce((acc, curr) => acc + curr, 0);
43
+ return Number.isInteger(sum) ? sum : sum.toFixed(2);
44
+ }
45
+ if (type === 'avg') {
46
+ const avg = numbers.reduce((acc, curr) => acc + curr, 0) / numbers.length;
47
+ return avg.toFixed(2);
48
+ }
49
+ return '—';
50
+ }
51
+ </script>
52
+
53
+ <div class="lite-table-summary-container">
54
+ <table class="lite-table lite-summary-table">
55
+ <tbody>
56
+ <tr>
57
+ {#each columns as col (col.key)}
58
+ {@const isTitle = col.key === titleKey}
59
+ {@const aggType = aggregations[col.key]}
60
+ <td style="text-align: {col.align || 'left'}; font-weight: {isTitle || aggType ? 'bold' : 'normal'};">
61
+ {#if isTitle && !aggType}
62
+ <span>{title}</span>
63
+ {:else if aggType}
64
+ <span>
65
+ {#if prefix[col.key]}{prefix[col.key]}{/if}{calculate(col.key, aggType)}{#if suffix[col.key]}{suffix[col.key]}{/if}
66
+ </span>
67
+ {:else}
68
+ <span style="color: #94a3b8;">—</span>
69
+ {/if}
70
+ </td>
71
+ {/each}
72
+ </tr>
73
+ </tbody>
74
+ </table>
75
+ </div>
76
+
77
+ <style>
78
+ .lite-table-summary-container {
79
+ width: 100%;
80
+ overflow-x: auto;
81
+ background: #f8fafc;
82
+ border-top: 2px solid #cbd5e1;
83
+ }
84
+ .lite-summary-table {
85
+ width: 100%;
86
+ margin-bottom: 0;
87
+ }
88
+ .lite-summary-table td {
89
+ padding: 8px 12px;
90
+ font-size: 12px;
91
+ color: #1e293b;
92
+ }
93
+ </style>
@@ -0,0 +1,17 @@
1
+ interface LiteTableSummaryColumn {
2
+ key: string;
3
+ label?: string;
4
+ align?: 'left' | 'center' | 'right';
5
+ }
6
+ interface Props {
7
+ columns: LiteTableSummaryColumn[];
8
+ data?: Record<string, unknown>[];
9
+ aggregations?: Record<string, string | ((rows: Record<string, unknown>[]) => string | number)>;
10
+ title?: string;
11
+ titleColumnKey?: string;
12
+ prefix?: Record<string, string>;
13
+ suffix?: Record<string, string>;
14
+ }
15
+ declare const LiteTableSummary: import("svelte").Component<Props, {}, "">;
16
+ type LiteTableSummary = ReturnType<typeof LiteTableSummary>;
17
+ export default LiteTableSummary;
@@ -0,0 +1,185 @@
1
+ <script lang="ts">
2
+ import { t } from '@svadmin/core/i18n';
3
+
4
+ export interface TransferItem {
5
+ key: string | number;
6
+ title: string;
7
+ description?: string;
8
+ disabled?: boolean;
9
+ }
10
+
11
+ interface Props {
12
+ dataSource?: TransferItem[];
13
+ targetKeys?: (string | number)[];
14
+ titles?: [string, string];
15
+ name?: string;
16
+ disabled?: boolean;
17
+ label?: string;
18
+ description?: string;
19
+ }
20
+
21
+ let {
22
+ dataSource = [],
23
+ targetKeys = [],
24
+ titles = ['Available', 'Selected'],
25
+ name = 'targetKeys',
26
+ disabled = false,
27
+ label,
28
+ description,
29
+ }: Props = $props();
30
+
31
+ const targetKeySet = $derived(new Set(targetKeys.map(String)));
32
+ const sourceItems = $derived(dataSource.filter((item) => !targetKeySet.has(String(item.key))));
33
+ const targetItems = $derived(dataSource.filter((item) => targetKeySet.has(String(item.key))));
34
+ </script>
35
+
36
+ <div class="lite-transfer-container lite-form-group">
37
+ {#if label}
38
+ <span class="lite-label">{label}</span>
39
+ {/if}
40
+ {#if description}
41
+ <p class="lite-help-text">{description}</p>
42
+ {/if}
43
+
44
+ <div class="lite-transfer-boxes">
45
+ <!-- Source Box -->
46
+ <div class="lite-transfer-panel">
47
+ <div class="lite-transfer-header">
48
+ <strong>{titles[0]}</strong>
49
+ <span class="lite-badge">{sourceItems.length}</span>
50
+ </div>
51
+ <div class="lite-transfer-body">
52
+ {#if sourceItems.length === 0}
53
+ <div class="lite-transfer-empty">{t('common.empty') || 'No items'}</div>
54
+ {:else}
55
+ {#each sourceItems as item (item.key)}
56
+ <label class="lite-transfer-item">
57
+ <input
58
+ type="checkbox"
59
+ name="_transfer_source_keys"
60
+ value={String(item.key)}
61
+ disabled={disabled || item.disabled}
62
+ />
63
+ <span class="lite-transfer-item-title">{item.title}</span>
64
+ </label>
65
+ {/each}
66
+ {/if}
67
+ </div>
68
+ </div>
69
+
70
+ <!-- Move Actions -->
71
+ <div class="lite-transfer-operations">
72
+ <button
73
+ type="submit"
74
+ name="_action"
75
+ value="transfer_to_target"
76
+ class="lite-btn lite-btn-sm lite-btn-primary"
77
+ {disabled}
78
+ title="Move selected right"
79
+ >
80
+
81
+ </button>
82
+ <button
83
+ type="submit"
84
+ name="_action"
85
+ value="transfer_to_source"
86
+ class="lite-btn lite-btn-sm"
87
+ {disabled}
88
+ title="Move selected left"
89
+ >
90
+
91
+ </button>
92
+ </div>
93
+
94
+ <!-- Target Box -->
95
+ <div class="lite-transfer-panel">
96
+ <div class="lite-transfer-header">
97
+ <strong>{titles[1]}</strong>
98
+ <span class="lite-badge lite-badge-indigo">{targetItems.length}</span>
99
+ </div>
100
+ <div class="lite-transfer-body">
101
+ {#if targetItems.length === 0}
102
+ <div class="lite-transfer-empty">{t('common.empty') || 'No items selected'}</div>
103
+ {:else}
104
+ {#each targetItems as item (item.key)}
105
+ <label class="lite-transfer-item">
106
+ <input
107
+ type="checkbox"
108
+ name="_transfer_target_keys"
109
+ value={String(item.key)}
110
+ disabled={disabled || item.disabled}
111
+ />
112
+ <input type="hidden" {name} value={String(item.key)} />
113
+ <span class="lite-transfer-item-title">{item.title}</span>
114
+ </label>
115
+ {/each}
116
+ {/if}
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </div>
121
+
122
+ <style>
123
+ .lite-transfer-container {
124
+ margin-bottom: 16px;
125
+ }
126
+ .lite-transfer-boxes {
127
+ display: flex;
128
+ align-items: center;
129
+ }
130
+ .lite-transfer-panel {
131
+ flex: 1;
132
+ border: 1px solid #cbd5e1;
133
+ border-radius: 6px;
134
+ background: #ffffff;
135
+ min-height: 180px;
136
+ max-height: 280px;
137
+ display: flex;
138
+ flex-direction: column;
139
+ }
140
+ .lite-transfer-header {
141
+ display: flex;
142
+ justify-content: space-between;
143
+ align-items: center;
144
+ padding: 8px 12px;
145
+ background: #f8fafc;
146
+ border-bottom: 1px solid #e2e8f0;
147
+ font-size: 13px;
148
+ }
149
+ .lite-transfer-body {
150
+ flex: 1;
151
+ overflow-y: auto;
152
+ padding: 6px;
153
+ }
154
+ .lite-transfer-item {
155
+ display: flex;
156
+ align-items: center;
157
+ padding: 6px 8px;
158
+ border-radius: 4px;
159
+ cursor: pointer;
160
+ font-size: 13px;
161
+ }
162
+ .lite-transfer-item:hover {
163
+ background: #f1f5f9;
164
+ }
165
+ .lite-transfer-item input {
166
+ margin-right: 8px;
167
+ }
168
+ .lite-transfer-operations {
169
+ display: flex;
170
+ flex-direction: column;
171
+ margin: 0 12px;
172
+ }
173
+ .lite-transfer-operations .lite-btn:first-child {
174
+ margin-bottom: 6px;
175
+ }
176
+ .lite-transfer-boxes > .lite-transfer-panel:first-child {
177
+ margin-right: 0;
178
+ }
179
+ .lite-transfer-empty {
180
+ text-align: center;
181
+ padding: 30px 10px;
182
+ color: #94a3b8;
183
+ font-size: 12px;
184
+ }
185
+ </style>
@@ -0,0 +1,18 @@
1
+ export interface TransferItem {
2
+ key: string | number;
3
+ title: string;
4
+ description?: string;
5
+ disabled?: boolean;
6
+ }
7
+ interface Props {
8
+ dataSource?: TransferItem[];
9
+ targetKeys?: (string | number)[];
10
+ titles?: [string, string];
11
+ name?: string;
12
+ disabled?: boolean;
13
+ label?: string;
14
+ description?: string;
15
+ }
16
+ declare const LiteTransfer: import("svelte").Component<Props, {}, "">;
17
+ type LiteTransfer = ReturnType<typeof LiteTransfer>;
18
+ export default LiteTransfer;
@@ -0,0 +1,85 @@
1
+ <script lang="ts">
2
+ export interface LiteTreeColumn {
3
+ key: string;
4
+ label: string;
5
+ }
6
+
7
+ interface Props {
8
+ data: Record<string, unknown>[];
9
+ columns: LiteTreeColumn[];
10
+ primaryKey?: string;
11
+ childrenKey?: string;
12
+ }
13
+
14
+ let {
15
+ data = [],
16
+ columns = [],
17
+ primaryKey = 'id',
18
+ childrenKey = 'children',
19
+ }: Props = $props();
20
+
21
+ interface FlatLiteItem {
22
+ record: Record<string, unknown>;
23
+ id: string | number;
24
+ level: number;
25
+ hasChildren: boolean;
26
+ }
27
+
28
+ function flatten(items: Record<string, unknown>[], level = 0): FlatLiteItem[] {
29
+ const flat: FlatLiteItem[] = [];
30
+ for (const item of items) {
31
+ const id = item[primaryKey] as string | number;
32
+ const children = item[childrenKey] as Record<string, unknown>[] | undefined;
33
+ const hasChildren = Array.isArray(children) && children.length > 0;
34
+ flat.push({ record: item, id, level, hasChildren });
35
+ if (hasChildren && children) {
36
+ flat.push(...flatten(children, level + 1));
37
+ }
38
+ }
39
+ return flat;
40
+ }
41
+
42
+ const flattened = $derived(flatten(data));
43
+ </script>
44
+
45
+ <div class="lite-tree-table lite-form-group">
46
+ <table class="lite-table">
47
+ <thead>
48
+ <tr>
49
+ {#each columns as col (col.key)}
50
+ <th>{col.label}</th>
51
+ {/each}
52
+ </tr>
53
+ </thead>
54
+ <tbody>
55
+ {#each flattened as row (row.id)}
56
+ <tr>
57
+ {#each columns as col, idx (col.key)}
58
+ <td>
59
+ {#if idx === 0}
60
+ <span style="display: inline-block; padding-left: {row.level * 16}px;">
61
+ {#if row.level > 0}↳ {/if}
62
+ {row.record[col.key] ?? '—'}
63
+ </span>
64
+ {:else}
65
+ {row.record[col.key] ?? '—'}
66
+ {/if}
67
+ </td>
68
+ {/each}
69
+ </tr>
70
+ {:else}
71
+ <tr>
72
+ <td colspan={columns.length} style="text-align: center; color: #94a3b8; padding: 24px;">
73
+ No tree data
74
+ </td>
75
+ </tr>
76
+ {/each}
77
+ </tbody>
78
+ </table>
79
+ </div>
80
+
81
+ <style>
82
+ .lite-tree-table {
83
+ overflow-x: auto;
84
+ }
85
+ </style>
@@ -0,0 +1,13 @@
1
+ export interface LiteTreeColumn {
2
+ key: string;
3
+ label: string;
4
+ }
5
+ interface Props {
6
+ data: Record<string, unknown>[];
7
+ columns: LiteTreeColumn[];
8
+ primaryKey?: string;
9
+ childrenKey?: string;
10
+ }
11
+ declare const LiteTreeTable: import("svelte").Component<Props, {}, "">;
12
+ type LiteTreeTable = ReturnType<typeof LiteTreeTable>;
13
+ export default LiteTreeTable;
@@ -0,0 +1,129 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ oldValue?: Record<string, unknown>;
4
+ newValue?: Record<string, unknown>;
5
+ oldTitle?: string;
6
+ newTitle?: string;
7
+ fieldLabels?: Record<string, string>;
8
+ }
9
+
10
+ let {
11
+ oldValue = {},
12
+ newValue = {},
13
+ oldTitle = 'Previous Version',
14
+ newTitle = 'Current Version',
15
+ fieldLabels = {},
16
+ }: Props = $props();
17
+
18
+ const allKeys = $derived(
19
+ Array.from(new Set([...Object.keys(oldValue || {}), ...Object.keys(newValue || {})]))
20
+ );
21
+
22
+ function getStatus(key: string): 'added' | 'removed' | 'modified' | 'unchanged' {
23
+ const hasOld = key in oldValue && oldValue[key] !== undefined;
24
+ const hasNew = key in newValue && newValue[key] !== undefined;
25
+ if (!hasOld && hasNew) return 'added';
26
+ if (hasOld && !hasNew) return 'removed';
27
+ if (JSON.stringify(oldValue[key]) !== JSON.stringify(newValue[key])) return 'modified';
28
+ return 'unchanged';
29
+ }
30
+
31
+ function formatVal(val: unknown): string {
32
+ if (val === undefined || val === null) return '—';
33
+ if (typeof val === 'object') return JSON.stringify(val);
34
+ return String(val);
35
+ }
36
+ </script>
37
+
38
+ <div class="lite-diff-viewer">
39
+ <div class="lite-diff-header">
40
+ <strong>Record Comparison</strong>
41
+ </div>
42
+
43
+ <table class="lite-table lite-diff-table">
44
+ <thead>
45
+ <tr>
46
+ <th style="width: 25%;">Field</th>
47
+ <th style="width: 37.5%;">{oldTitle}</th>
48
+ <th style="width: 37.5%;">{newTitle}</th>
49
+ </tr>
50
+ </thead>
51
+ <tbody>
52
+ {#each allKeys as key (key)}
53
+ {@const status = getStatus(key)}
54
+ <tr class="lite-diff-row lite-diff-{status}">
55
+ <td>
56
+ <strong>{fieldLabels[key] || key}</strong>
57
+ {#if status !== 'unchanged'}
58
+ <span class="lite-badge lite-badge-{status}">[{status.toUpperCase()}]</span>
59
+ {/if}
60
+ </td>
61
+ <td class="lite-diff-old">
62
+ {#if status === 'removed'}
63
+ <del>{formatVal(oldValue[key])}</del>
64
+ {:else}
65
+ {formatVal(oldValue[key])}
66
+ {/if}
67
+ </td>
68
+ <td class="lite-diff-new">
69
+ {#if status === 'added'}
70
+ <ins>{formatVal(newValue[key])}</ins>
71
+ {:else if status === 'modified'}
72
+ <strong>{formatVal(newValue[key])}</strong>
73
+ {:else}
74
+ {formatVal(newValue[key])}
75
+ {/if}
76
+ </td>
77
+ </tr>
78
+ {/each}
79
+ </tbody>
80
+ </table>
81
+ </div>
82
+
83
+ <style>
84
+ .lite-diff-viewer {
85
+ background: #ffffff;
86
+ border: 1px solid #cbd5e1;
87
+ border-radius: 6px;
88
+ margin-bottom: 16px;
89
+ }
90
+ .lite-diff-header {
91
+ padding: 10px 12px;
92
+ background: #f8fafc;
93
+ border-bottom: 1px solid #e2e8f0;
94
+ font-size: 13px;
95
+ }
96
+ .lite-diff-table {
97
+ width: 100%;
98
+ margin-bottom: 0;
99
+ font-size: 12px;
100
+ }
101
+ .lite-diff-table th {
102
+ background: #f1f5f9;
103
+ padding: 8px 12px;
104
+ text-align: left;
105
+ }
106
+ .lite-diff-table td {
107
+ padding: 8px 12px;
108
+ border-top: 1px solid #e2e8f0;
109
+ }
110
+ .lite-diff-added {
111
+ background: #ecfdf5;
112
+ }
113
+ .lite-diff-removed {
114
+ background: #fef2f2;
115
+ }
116
+ .lite-diff-modified {
117
+ background: #fffbeb;
118
+ }
119
+ .lite-diff-old del {
120
+ color: #ef4444;
121
+ }
122
+ .lite-diff-new ins {
123
+ color: #10b981;
124
+ text-decoration: none;
125
+ }
126
+ .lite-diff-new strong {
127
+ color: #f59e0b;
128
+ }
129
+ </style>