@svadmin/lite 0.3.7 → 0.3.12

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 (172) hide show
  1. package/README.md +19 -13
  2. package/dist/components/LiteAlert.svelte +19 -0
  3. package/dist/components/LiteAlert.svelte.d.ts +11 -0
  4. package/dist/components/LiteArrayField.svelte +60 -0
  5. package/dist/components/LiteArrayField.svelte.d.ts +17 -0
  6. package/dist/components/LiteArrayItem.svelte +146 -0
  7. package/dist/components/LiteArrayItem.svelte.d.ts +13 -0
  8. package/dist/components/LiteAuditLog.svelte +104 -0
  9. package/dist/components/LiteAuditLog.svelte.d.ts +23 -0
  10. package/dist/components/LiteBreadcrumbs.svelte +39 -0
  11. package/dist/components/LiteBreadcrumbs.svelte.d.ts +16 -0
  12. package/dist/components/LiteChatDialog.svelte +101 -0
  13. package/dist/components/LiteChatDialog.svelte.d.ts +20 -0
  14. package/dist/components/LiteConfirmDialog.svelte +60 -0
  15. package/dist/components/LiteConfirmDialog.svelte.d.ts +22 -0
  16. package/dist/components/LiteEmptyState.svelte +39 -0
  17. package/dist/components/LiteEmptyState.svelte.d.ts +16 -0
  18. package/dist/components/LiteForm.svelte +165 -0
  19. package/dist/components/LiteForm.svelte.d.ts +26 -0
  20. package/dist/components/LiteLayout.svelte +147 -0
  21. package/dist/components/LiteLayout.svelte.d.ts +19 -0
  22. package/dist/components/LiteLogin.svelte +41 -0
  23. package/dist/components/LiteLogin.svelte.d.ts +8 -0
  24. package/dist/components/LitePagination.svelte +73 -0
  25. package/dist/components/LitePagination.svelte.d.ts +11 -0
  26. package/dist/components/LitePermissionMatrix.svelte +147 -0
  27. package/dist/components/LitePermissionMatrix.svelte.d.ts +32 -0
  28. package/dist/components/LiteSearch.svelte +41 -0
  29. package/dist/components/LiteSearch.svelte.d.ts +9 -0
  30. package/dist/components/LiteShow.svelte +61 -0
  31. package/dist/components/LiteShow.svelte.d.ts +14 -0
  32. package/dist/components/LiteShowField.svelte +62 -0
  33. package/dist/components/LiteShowField.svelte.d.ts +12 -0
  34. package/dist/components/LiteStatsCard.svelte +70 -0
  35. package/dist/components/LiteStatsCard.svelte.d.ts +24 -0
  36. package/dist/components/LiteTable.svelte +132 -0
  37. package/dist/components/LiteTable.svelte.d.ts +19 -0
  38. package/dist/components/LiteTabs.svelte +57 -0
  39. package/dist/components/LiteTabs.svelte.d.ts +16 -0
  40. package/dist/components/advanced/LiteAutoSaveIndicator.svelte +26 -0
  41. package/dist/components/advanced/LiteAutoSaveIndicator.svelte.d.ts +12 -0
  42. package/dist/components/advanced/LiteDraggableHeader.svelte +33 -0
  43. package/dist/components/advanced/LiteDraggableHeader.svelte.d.ts +15 -0
  44. package/dist/components/advanced/LiteDrawerForm.svelte +42 -0
  45. package/dist/components/advanced/LiteDrawerForm.svelte.d.ts +16 -0
  46. package/dist/components/advanced/LiteInlineEdit.svelte +32 -0
  47. package/dist/components/advanced/LiteInlineEdit.svelte.d.ts +15 -0
  48. package/dist/components/advanced/LiteModalForm.svelte +44 -0
  49. package/dist/components/advanced/LiteModalForm.svelte.d.ts +17 -0
  50. package/dist/components/advanced/LiteToast.svelte +34 -0
  51. package/dist/components/advanced/LiteToast.svelte.d.ts +14 -0
  52. package/dist/components/advanced/LiteUndoableNotification.svelte +25 -0
  53. package/dist/components/advanced/LiteUndoableNotification.svelte.d.ts +13 -0
  54. package/dist/components/advanced/LiteVirtualTable.svelte +44 -0
  55. package/dist/components/advanced/LiteVirtualTable.svelte.d.ts +20 -0
  56. package/dist/components/advanced/index.js +8 -0
  57. package/dist/components/buttons/LiteCloneButton.svelte +33 -0
  58. package/dist/components/buttons/LiteCloneButton.svelte.d.ts +11 -0
  59. package/dist/components/buttons/LiteCreateButton.svelte +31 -0
  60. package/dist/components/buttons/LiteCreateButton.svelte.d.ts +10 -0
  61. package/dist/components/buttons/LiteDeleteButton.svelte +57 -0
  62. package/dist/components/buttons/LiteDeleteButton.svelte.d.ts +12 -0
  63. package/dist/components/buttons/LiteEditButton.svelte +33 -0
  64. package/dist/components/buttons/LiteEditButton.svelte.d.ts +11 -0
  65. package/dist/components/buttons/LiteExportButton.svelte +31 -0
  66. package/dist/components/buttons/LiteExportButton.svelte.d.ts +10 -0
  67. package/dist/components/buttons/LiteImportButton.svelte +50 -0
  68. package/dist/components/buttons/LiteImportButton.svelte.d.ts +10 -0
  69. package/dist/components/buttons/LiteListButton.svelte +31 -0
  70. package/dist/components/buttons/LiteListButton.svelte.d.ts +10 -0
  71. package/dist/components/buttons/LiteRefreshButton.svelte +34 -0
  72. package/dist/components/buttons/LiteRefreshButton.svelte.d.ts +8 -0
  73. package/dist/components/buttons/LiteSaveButton.svelte +27 -0
  74. package/dist/components/buttons/LiteSaveButton.svelte.d.ts +8 -0
  75. package/dist/components/buttons/LiteShowButton.svelte +33 -0
  76. package/dist/components/buttons/LiteShowButton.svelte.d.ts +11 -0
  77. package/dist/components/buttons/index.js +10 -0
  78. package/dist/components/fields/LiteBooleanField.svelte +42 -0
  79. package/dist/components/fields/LiteBooleanField.svelte.d.ts +10 -0
  80. package/dist/components/fields/LiteDateField.svelte +51 -0
  81. package/dist/components/fields/LiteDateField.svelte.d.ts +10 -0
  82. package/dist/components/fields/LiteEmailField.svelte +41 -0
  83. package/dist/components/fields/LiteEmailField.svelte.d.ts +10 -0
  84. package/dist/components/fields/LiteFileField.svelte +59 -0
  85. package/dist/components/fields/LiteFileField.svelte.d.ts +10 -0
  86. package/dist/components/fields/LiteImageField.svelte +57 -0
  87. package/dist/components/fields/LiteImageField.svelte.d.ts +10 -0
  88. package/dist/components/fields/LiteJsonField.svelte +43 -0
  89. package/dist/components/fields/LiteJsonField.svelte.d.ts +10 -0
  90. package/dist/components/fields/LiteMarkdownField.svelte +33 -0
  91. package/dist/components/fields/LiteMarkdownField.svelte.d.ts +10 -0
  92. package/dist/components/fields/LiteMultiSelectField.svelte +57 -0
  93. package/dist/components/fields/LiteMultiSelectField.svelte.d.ts +10 -0
  94. package/dist/components/fields/LiteNumberField.svelte +35 -0
  95. package/dist/components/fields/LiteNumberField.svelte.d.ts +10 -0
  96. package/dist/components/fields/LiteRelationField.svelte +67 -0
  97. package/dist/components/fields/LiteRelationField.svelte.d.ts +10 -0
  98. package/dist/components/fields/LiteRichTextField.svelte +44 -0
  99. package/dist/components/fields/LiteRichTextField.svelte.d.ts +10 -0
  100. package/dist/components/fields/LiteSelectField.svelte +47 -0
  101. package/dist/components/fields/LiteSelectField.svelte.d.ts +10 -0
  102. package/dist/components/fields/LiteTagField.svelte +44 -0
  103. package/dist/components/fields/LiteTagField.svelte.d.ts +10 -0
  104. package/dist/components/fields/LiteTextField.svelte +35 -0
  105. package/dist/components/fields/LiteTextField.svelte.d.ts +10 -0
  106. package/dist/components/fields/LiteUrlField.svelte +52 -0
  107. package/dist/components/fields/LiteUrlField.svelte.d.ts +10 -0
  108. package/dist/components/fields/index.js +15 -0
  109. package/dist/components/layout/LiteAuthenticated.svelte +23 -0
  110. package/dist/components/layout/LiteAuthenticated.svelte.d.ts +10 -0
  111. package/dist/components/layout/LiteCanAccess.svelte +27 -0
  112. package/dist/components/layout/LiteCanAccess.svelte.d.ts +10 -0
  113. package/dist/components/layout/LiteCatchAllNavigate.svelte +20 -0
  114. package/dist/components/layout/LiteCatchAllNavigate.svelte.d.ts +7 -0
  115. package/dist/components/layout/LiteErrorBoundary.svelte +20 -0
  116. package/dist/components/layout/LiteErrorBoundary.svelte.d.ts +13 -0
  117. package/dist/components/layout/LiteErrorComponent.svelte +37 -0
  118. package/dist/components/layout/LiteErrorComponent.svelte.d.ts +9 -0
  119. package/dist/components/layout/LiteHeader.svelte +19 -0
  120. package/dist/components/layout/LiteHeader.svelte.d.ts +8 -0
  121. package/dist/components/layout/LiteNavigateToResource.svelte +25 -0
  122. package/dist/components/layout/LiteNavigateToResource.svelte.d.ts +7 -0
  123. package/dist/components/layout/LiteSidebar.svelte +93 -0
  124. package/dist/components/layout/LiteSidebar.svelte.d.ts +12 -0
  125. package/dist/components/layout/index.js +8 -0
  126. package/dist/components/pages/LiteCreatePage.svelte +39 -0
  127. package/dist/components/pages/LiteCreatePage.svelte.d.ts +10 -0
  128. package/dist/components/pages/LiteEditPage.svelte +54 -0
  129. package/dist/components/pages/LiteEditPage.svelte.d.ts +12 -0
  130. package/dist/components/pages/LiteForgotPasswordPage.svelte +60 -0
  131. package/dist/components/pages/LiteForgotPasswordPage.svelte.d.ts +11 -0
  132. package/dist/components/pages/LiteListPage.svelte +81 -0
  133. package/dist/components/pages/LiteListPage.svelte.d.ts +20 -0
  134. package/dist/components/pages/LiteProfilePage.svelte +61 -0
  135. package/dist/components/pages/LiteProfilePage.svelte.d.ts +14 -0
  136. package/dist/components/pages/LiteRegisterPage.svelte +63 -0
  137. package/dist/components/pages/LiteRegisterPage.svelte.d.ts +10 -0
  138. package/dist/components/pages/LiteShowPage.svelte +61 -0
  139. package/dist/components/pages/LiteShowPage.svelte.d.ts +11 -0
  140. package/dist/components/pages/LiteUpdatePasswordPage.svelte +51 -0
  141. package/dist/components/pages/LiteUpdatePasswordPage.svelte.d.ts +9 -0
  142. package/dist/components/pages/index.js +8 -0
  143. package/dist/components/widgets/LiteAnomalyBadge.svelte +40 -0
  144. package/dist/components/widgets/LiteAnomalyBadge.svelte.d.ts +15 -0
  145. package/dist/components/widgets/LiteBarChart.svelte +45 -0
  146. package/dist/components/widgets/LiteBarChart.svelte.d.ts +17 -0
  147. package/dist/components/widgets/LiteInsightCard.svelte +31 -0
  148. package/dist/components/widgets/LiteInsightCard.svelte.d.ts +13 -0
  149. package/dist/components/widgets/LiteLineChart.svelte +48 -0
  150. package/dist/components/widgets/LiteLineChart.svelte.d.ts +17 -0
  151. package/dist/components/widgets/LitePieChart.svelte +44 -0
  152. package/dist/components/widgets/LitePieChart.svelte.d.ts +17 -0
  153. package/dist/components/widgets/index.js +5 -0
  154. package/dist/enhance.js +112 -0
  155. package/dist/index.d.ts +27 -0
  156. package/{src/index.ts → dist/index.js} +3 -26
  157. package/dist/lite.css +817 -0
  158. package/dist/schema-generator.d.ts +29 -0
  159. package/dist/schema-generator.js +226 -0
  160. package/dist/security.d.ts +14 -0
  161. package/dist/security.js +59 -0
  162. package/dist/server-adapter.d.ts +111 -0
  163. package/dist/server-adapter.js +422 -0
  164. package/package.json +57 -20
  165. package/src/schema-generator.ts +0 -147
  166. package/src/server-adapter.ts +0 -307
  167. /package/{src/components/advanced/index.ts → dist/components/advanced/index.d.ts} +0 -0
  168. /package/{src/components/buttons/index.ts → dist/components/buttons/index.d.ts} +0 -0
  169. /package/{src/components/fields/index.ts → dist/components/fields/index.d.ts} +0 -0
  170. /package/{src/components/layout/index.ts → dist/components/layout/index.d.ts} +0 -0
  171. /package/{src/components/pages/index.ts → dist/components/pages/index.d.ts} +0 -0
  172. /package/{src/components/widgets/index.ts → dist/components/widgets/index.d.ts} +0 -0
@@ -0,0 +1,147 @@
1
+ <script lang="ts">
2
+ /**
3
+ * LitePermissionMatrix — SSR-compatible permission matrix.
4
+ * Pure HTML table with checkbox inputs. No client-side JS required.
5
+ * Works with form POST actions for state changes.
6
+ */
7
+
8
+ interface LiteRole {
9
+ code: string;
10
+ name: string;
11
+ }
12
+
13
+ interface LiteResource {
14
+ code: string;
15
+ name: string;
16
+ section?: string;
17
+ }
18
+
19
+ interface LiteAction {
20
+ code: string;
21
+ name: string;
22
+ }
23
+
24
+ interface Props {
25
+ roles: LiteRole[];
26
+ resources: LiteResource[];
27
+ actions: LiteAction[];
28
+ /** Current permissions map: { "resource:action": true } */
29
+ permissions: Record<string, boolean>;
30
+ selectedRole?: string;
31
+ /** Form POST action URL */
32
+ actionUrl?: string;
33
+ disabled?: boolean;
34
+ }
35
+
36
+ let {
37
+ roles,
38
+ resources,
39
+ actions,
40
+ permissions,
41
+ selectedRole = roles[0]?.code || '',
42
+ actionUrl = '?/updatePermissions',
43
+ disabled = false,
44
+ }: Props = $props();
45
+
46
+ function isGranted(resource: string, action: string): boolean {
47
+ return permissions[`${resource}:${action}`] === true;
48
+ }
49
+ </script>
50
+
51
+ <div class="lite-permission-matrix">
52
+ <!-- Role Tabs -->
53
+ <div class="lite-role-tabs">
54
+ {#each roles as role, _i (_i)}
55
+ <a
56
+ href="?role={role.code}"
57
+ class="lite-role-tab {selectedRole === role.code ? 'active' : ''}"
58
+ >
59
+ {role.name}
60
+ </a>
61
+ {/each}
62
+ </div>
63
+
64
+ <!-- Matrix Table -->
65
+ <form method="POST" action={actionUrl}>
66
+ <input type="hidden" name="role" value={selectedRole} />
67
+
68
+ <table class="lite-table" style="margin-top:0;">
69
+ <thead>
70
+ <tr>
71
+ <th style="width:200px;">Resource</th>
72
+ {#each actions as action, _i (_i)}
73
+ <th style="text-align:center;">{action.name}</th>
74
+ {/each}
75
+ </tr>
76
+ </thead>
77
+ <tbody>
78
+ {#each resources as resource, i (i)}
79
+ {#if resource.section && (i === 0 || resource.section !== resources[i-1].section)}
80
+ <tr>
81
+ <td colspan={actions.length + 1} style="background:#f1f5f9;font-weight:700;font-size:12px;text-transform:uppercase;letter-spacing:0.05em;color:#475569;">
82
+ {resource.section}
83
+ </td>
84
+ </tr>
85
+ {/if}
86
+ <tr>
87
+ <td><strong>{resource.name}</strong><br/><small style="color:#94a3b8;">{resource.code}</small></td>
88
+ {#each actions as action, _i (_i)}
89
+ <td style="text-align:center;">
90
+ <input
91
+ type="checkbox"
92
+ name="perm_{resource.code}_{action.code}"
93
+ checked={isGranted(resource.code, action.code)}
94
+ {disabled}
95
+ style="width:18px;height:18px;cursor:{disabled ? 'not-allowed' : 'pointer'};"
96
+ />
97
+ </td>
98
+ {/each}
99
+ </tr>
100
+ {/each}
101
+ {#if resources.length === 0}
102
+ <tr>
103
+ <td colspan={actions.length + 1} style="text-align:center;padding:24px;color:#94a3b8;">
104
+ No resources configured.
105
+ </td>
106
+ </tr>
107
+ {/if}
108
+ </tbody>
109
+ </table>
110
+
111
+ {#if !disabled}
112
+ <div style="margin-top:12px;text-align:right;">
113
+ <button type="submit" class="lite-btn lite-btn-primary">Save Permissions</button>
114
+ </div>
115
+ {/if}
116
+ </form>
117
+ </div>
118
+
119
+ <style>
120
+ .lite-role-tabs {
121
+ display: flex;
122
+ flex-wrap: wrap;
123
+ margin-bottom: 16px;
124
+ border-bottom: 2px solid #e2e8f0;
125
+ padding-bottom: 0;
126
+ }
127
+ .lite-role-tab {
128
+ padding: 8px 16px;
129
+ margin-right: 4px;
130
+ text-decoration: none;
131
+ color: #64748b;
132
+ font-size: 14px;
133
+ font-weight: 500;
134
+ border-bottom: 2px solid transparent;
135
+ margin-bottom: -2px;
136
+ transition: color 0.15s ease, border-color 0.15s ease;
137
+ }
138
+ .lite-role-tab:hover {
139
+ color: #0f172a;
140
+ text-decoration: none;
141
+ }
142
+ .lite-role-tab.active {
143
+ color: #4f46e5;
144
+ border-bottom-color: #4f46e5;
145
+ font-weight: 600;
146
+ }
147
+ </style>
@@ -0,0 +1,32 @@
1
+ /**
2
+ * LitePermissionMatrix — SSR-compatible permission matrix.
3
+ * Pure HTML table with checkbox inputs. No client-side JS required.
4
+ * Works with form POST actions for state changes.
5
+ */
6
+ interface LiteRole {
7
+ code: string;
8
+ name: string;
9
+ }
10
+ interface LiteResource {
11
+ code: string;
12
+ name: string;
13
+ section?: string;
14
+ }
15
+ interface LiteAction {
16
+ code: string;
17
+ name: string;
18
+ }
19
+ interface Props {
20
+ roles: LiteRole[];
21
+ resources: LiteResource[];
22
+ actions: LiteAction[];
23
+ /** Current permissions map: { "resource:action": true } */
24
+ permissions: Record<string, boolean>;
25
+ selectedRole?: string;
26
+ /** Form POST action URL */
27
+ actionUrl?: string;
28
+ disabled?: boolean;
29
+ }
30
+ declare const LitePermissionMatrix: import("svelte").Component<Props, {}, "">;
31
+ type LitePermissionMatrix = ReturnType<typeof LitePermissionMatrix>;
32
+ export default LitePermissionMatrix;
@@ -0,0 +1,41 @@
1
+ <script lang="ts">
2
+ /**
3
+ * LiteSearch — Search bar using native <form> GET submission.
4
+ * No JS needed — submits as ?q=searchterm via page navigation.
5
+ */
6
+ import { t } from '@svadmin/core/i18n';
7
+
8
+ interface Props {
9
+ value?: string;
10
+ placeholder?: string;
11
+ /** Additional hidden fields to preserve (e.g. sort, order) */
12
+ preserveParams?: Record<string, string>;
13
+ }
14
+
15
+ let {
16
+ value = '',
17
+ placeholder,
18
+ preserveParams = {},
19
+ }: Props = $props();
20
+ </script>
21
+
22
+ <form method="GET" class="lite-search">
23
+ {#each Object.entries(preserveParams) as [key, val] (key)}
24
+ <input type="hidden" name={key} value={val} />
25
+ {/each}
26
+ <input
27
+ type="text"
28
+ name="q"
29
+ value={value}
30
+ class="lite-input"
31
+ placeholder={placeholder || t('common.search') || 'Search...'}
32
+ />
33
+ <button type="submit" class="lite-btn lite-btn-primary">
34
+ {t('common.search') || 'Search'}
35
+ </button>
36
+ {#if value}
37
+ <a href="?" class="lite-btn" style="margin-left:4px;">
38
+ {t('common.clear') || 'Clear'}
39
+ </a>
40
+ {/if}
41
+ </form>
@@ -0,0 +1,9 @@
1
+ interface Props {
2
+ value?: string;
3
+ placeholder?: string;
4
+ /** Additional hidden fields to preserve (e.g. sort, order) */
5
+ preserveParams?: Record<string, string>;
6
+ }
7
+ declare const LiteSearch: import("svelte").Component<Props, {}, "">;
8
+ type LiteSearch = ReturnType<typeof LiteSearch>;
9
+ export default LiteSearch;
@@ -0,0 +1,61 @@
1
+ <script lang="ts">
2
+ /**
3
+ * LiteShow — Detail/view page for a single record.
4
+ * Renders field labels and values in a key-value layout.
5
+ */
6
+ import type { ResourceDefinition } from '@svadmin/core';
7
+ import { t } from '@svadmin/core/i18n';
8
+ import LiteShowField from './LiteShowField.svelte';
9
+
10
+ interface Props {
11
+ record: Record<string, unknown>;
12
+ resource: ResourceDefinition;
13
+ basePath?: string;
14
+ canEdit?: boolean;
15
+ }
16
+
17
+ let {
18
+ record,
19
+ resource,
20
+ basePath = '/lite',
21
+ canEdit = true,
22
+ }: Props = $props();
23
+
24
+ let pk = $derived(resource.primaryKey ?? 'id');
25
+ let id = $derived(record[pk]);
26
+ const showFields = $derived(
27
+ resource.fields.filter(f => f.showInShow !== false)
28
+ );
29
+ </script>
30
+
31
+ <div class="lite-card">
32
+ <div class="lite-header">
33
+ <h1>{resource.label} #{id}</h1>
34
+ <div>
35
+ {#if canEdit}
36
+ <a href={`${basePath}/${resource.name}/edit/${id}`} class="lite-btn lite-btn-primary">
37
+ {t('common.edit') || 'Edit'}
38
+ </a>
39
+ {/if}
40
+ <a href={`${basePath}/${resource.name}`} class="lite-btn" style="margin-left:8px;">
41
+ {t('common.backToList') || 'Back to List'}
42
+ </a>
43
+ </div>
44
+ </div>
45
+
46
+ <table class="lite-table">
47
+ <tbody>
48
+ {#each showFields as field, _i (_i)}
49
+ {@const value = record[field.key]}
50
+ <tr>
51
+ <td style="width:200px;font-weight:600;color:#64748b;vertical-align:top;">
52
+ {field.label}
53
+ </td>
54
+ <td>
55
+ <LiteShowField {field} {value} />
56
+ </td>
57
+ </tr>
58
+ {/each}
59
+ </tbody>
60
+ </table>
61
+ </div>
@@ -0,0 +1,14 @@
1
+ /**
2
+ * LiteShow — Detail/view page for a single record.
3
+ * Renders field labels and values in a key-value layout.
4
+ */
5
+ import type { ResourceDefinition } from '@svadmin/core';
6
+ interface Props {
7
+ record: Record<string, unknown>;
8
+ resource: ResourceDefinition;
9
+ basePath?: string;
10
+ canEdit?: boolean;
11
+ }
12
+ declare const LiteShow: import("svelte").Component<Props, {}, "">;
13
+ type LiteShow = ReturnType<typeof LiteShow>;
14
+ export default LiteShow;
@@ -0,0 +1,62 @@
1
+ <script lang="ts">
2
+ /**
3
+ * LiteShowField — SSR-compatible field renderer for detail views.
4
+ * Renders a single field value based on its type definition.
5
+ */
6
+ import type { FieldDefinition } from '@svadmin/core';
7
+ import { toSafeHref, toSafeText } from '../security';
8
+
9
+ interface Props {
10
+ field: FieldDefinition;
11
+ value: unknown;
12
+ }
13
+
14
+ let { field, value }: Props = $props();
15
+
16
+ function formatValue(v: unknown, f: FieldDefinition): string {
17
+ if (v == null) return '—';
18
+ if (f.type === 'date') {
19
+ try { return new Date(v as string).toLocaleString(); } catch { return String(v); }
20
+ }
21
+ if (f.type === 'select' && f.options) {
22
+ const opt = f.options.find(o => o.value === v);
23
+ return opt?.label ?? String(v);
24
+ }
25
+ if (f.type === 'url') return String(v);
26
+ if (f.type === 'email') return String(v);
27
+ if (Array.isArray(v)) return v.join(', ');
28
+ if (typeof v === 'object') {
29
+ try { return JSON.stringify(v, null, 2); } catch { return String(v); }
30
+ }
31
+ return String(v);
32
+ }
33
+ </script>
34
+
35
+ {#if field.type === 'boolean'}
36
+ <span class="lite-bool {value ? 'lite-bool-true' : ''}"></span>
37
+ {value ? '✓ Yes' : '✗ No'}
38
+ {:else if field.type === 'url' && value}
39
+ {@const href = toSafeHref(value)}
40
+ {#if href}
41
+ <a {href} target="_blank" rel="noopener noreferrer">{toSafeText(value)}</a>
42
+ {:else}
43
+ {toSafeText(value)}
44
+ {/if}
45
+ {:else if field.type === 'email' && value}
46
+ {@const href = toSafeHref(`mailto:${toSafeText(value)}`)}
47
+ {#if href}
48
+ <a {href}>{toSafeText(value)}</a>
49
+ {:else}
50
+ {toSafeText(value)}
51
+ {/if}
52
+ {:else if field.type === 'image' && value}
53
+ <img src={String(value)} alt={field.label} style="max-width:300px;max-height:200px;border-radius:6px;border:1px solid #e2e8f0;" />
54
+ {:else if field.type === 'tags' && Array.isArray(value)}
55
+ {#each value as tag, _i (_i)}
56
+ <span class="lite-badge">{tag}</span>
57
+ {/each}
58
+ {:else if field.type === 'json' && value}
59
+ <pre style="margin:0;font-size:12px;background:#f8fafc;padding:12px;border-radius:6px;border:1px solid #e2e8f0;overflow-x:auto;">{typeof value === 'string' ? value : JSON.stringify(value, null, 2)}</pre>
60
+ {:else}
61
+ {formatValue(value, field)}
62
+ {/if}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * LiteShowField — SSR-compatible field renderer for detail views.
3
+ * Renders a single field value based on its type definition.
4
+ */
5
+ import type { FieldDefinition } from '@svadmin/core';
6
+ interface Props {
7
+ field: FieldDefinition;
8
+ value: unknown;
9
+ }
10
+ declare const LiteShowField: import("svelte").Component<Props, {}, "">;
11
+ type LiteShowField = ReturnType<typeof LiteShowField>;
12
+ export default LiteShowField;
@@ -0,0 +1,70 @@
1
+ <script lang="ts">
2
+ /**
3
+ * LiteStatsCard — SSR-compatible KPI statistics card.
4
+ * Pure HTML card with number, label, and optional trend indicator.
5
+ * No client-side JS required.
6
+ */
7
+
8
+ interface Props {
9
+ /** The main number/value to display */
10
+ value: string | number;
11
+ /** Label/title for the stat */
12
+ label: string;
13
+ /** Optional description text below the label */
14
+ description?: string;
15
+ /** Trend direction: 'up' shows green arrow, 'down' shows red arrow */
16
+ trend?: 'up' | 'down' | 'neutral';
17
+ /** Trend value text (e.g. "+12%", "-3%") */
18
+ trendValue?: string;
19
+ /** Optional icon character or HTML entity to display */
20
+ icon?: string;
21
+ /** Link to detail page */
22
+ href?: string;
23
+ }
24
+
25
+ let {
26
+ value,
27
+ label,
28
+ description,
29
+ trend,
30
+ trendValue,
31
+ icon,
32
+ href,
33
+ }: Props = $props();
34
+ </script>
35
+
36
+ {#if href}
37
+ <a href={href} class="lite-stats-card lite-card" style="text-decoration:none;display:block;">
38
+ {#if icon}
39
+ <div class="lite-stats-icon">{icon}</div>
40
+ {/if}
41
+ <div class="lite-stats-value">{value}</div>
42
+ <div class="lite-stats-label">{label}</div>
43
+ {#if trendValue}
44
+ <div class="lite-stats-trend lite-stats-trend-{trend || 'neutral'}">
45
+ {#if trend === 'up'}&#9650;{:else if trend === 'down'}&#9660;{:else}&#9644;{/if}
46
+ {trendValue}
47
+ </div>
48
+ {/if}
49
+ {#if description}
50
+ <div class="lite-stats-desc">{description}</div>
51
+ {/if}
52
+ </a>
53
+ {:else}
54
+ <div class="lite-stats-card lite-card">
55
+ {#if icon}
56
+ <div class="lite-stats-icon">{icon}</div>
57
+ {/if}
58
+ <div class="lite-stats-value">{value}</div>
59
+ <div class="lite-stats-label">{label}</div>
60
+ {#if trendValue}
61
+ <div class="lite-stats-trend lite-stats-trend-{trend || 'neutral'}">
62
+ {#if trend === 'up'}&#9650;{:else if trend === 'down'}&#9660;{:else}&#9644;{/if}
63
+ {trendValue}
64
+ </div>
65
+ {/if}
66
+ {#if description}
67
+ <div class="lite-stats-desc">{description}</div>
68
+ {/if}
69
+ </div>
70
+ {/if}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * LiteStatsCard — SSR-compatible KPI statistics card.
3
+ * Pure HTML card with number, label, and optional trend indicator.
4
+ * No client-side JS required.
5
+ */
6
+ interface Props {
7
+ /** The main number/value to display */
8
+ value: string | number;
9
+ /** Label/title for the stat */
10
+ label: string;
11
+ /** Optional description text below the label */
12
+ description?: string;
13
+ /** Trend direction: 'up' shows green arrow, 'down' shows red arrow */
14
+ trend?: 'up' | 'down' | 'neutral';
15
+ /** Trend value text (e.g. "+12%", "-3%") */
16
+ trendValue?: string;
17
+ /** Optional icon character or HTML entity to display */
18
+ icon?: string;
19
+ /** Link to detail page */
20
+ href?: string;
21
+ }
22
+ declare const LiteStatsCard: import("svelte").Component<Props, {}, "">;
23
+ type LiteStatsCard = ReturnType<typeof LiteStatsCard>;
24
+ export default LiteStatsCard;
@@ -0,0 +1,132 @@
1
+ <script lang="ts">
2
+ /**
3
+ * LiteTable — Pure HTML table with <a> sort links.
4
+ * No JavaScript required — sorting and pagination are URL-driven.
5
+ */
6
+ import type { ResourceDefinition, FieldDefinition } from '@svadmin/core';
7
+ import { t } from '@svadmin/core/i18n';
8
+
9
+ interface Props {
10
+ records: Record<string, unknown>[];
11
+ resource: ResourceDefinition;
12
+ currentSort?: string;
13
+ currentOrder?: 'asc' | 'desc';
14
+ currentSearch?: string;
15
+ basePath?: string;
16
+ /** Show edit/delete action buttons */
17
+ canEdit?: boolean;
18
+ canDelete?: boolean;
19
+ }
20
+
21
+ let {
22
+ records,
23
+ resource,
24
+ currentSort,
25
+ currentOrder = 'asc',
26
+ currentSearch,
27
+ basePath = '/lite',
28
+ canEdit = true,
29
+ canDelete = true,
30
+ }: Props = $props();
31
+
32
+ let pk = $derived(resource.primaryKey ?? 'id');
33
+ const listFields = $derived(
34
+ resource.fields.filter(f => f.showInList !== false)
35
+ );
36
+
37
+ function sortUrl(field: FieldDefinition): string {
38
+ const newOrder = currentSort === field.key && currentOrder === 'asc' ? 'desc' : 'asc';
39
+ const params = new URLSearchParams({ sort: field.key, order: newOrder });
40
+ if (currentSearch) params.set('q', currentSearch);
41
+ return `?${params.toString()}`;
42
+ }
43
+
44
+ function sortIndicator(field: FieldDefinition): string {
45
+ if (currentSort !== field.key) return '⇅';
46
+ return currentOrder === 'asc' ? '↑' : '↓';
47
+ }
48
+
49
+ function formatValue(value: unknown, field: FieldDefinition): string {
50
+ if (value == null) return '—';
51
+ if (field.type === 'boolean') return ''; // handled in template
52
+ if (field.type === 'date') {
53
+ try { return new Date(value as string).toLocaleDateString(); } catch { return String(value); }
54
+ }
55
+ if (field.type === 'select' && field.options) {
56
+ const opt = field.options.find(o => o.value === value);
57
+ return opt?.label ?? String(value);
58
+ }
59
+ if (Array.isArray(value)) return value.join(', ');
60
+ return String(value);
61
+ }
62
+ </script>
63
+
64
+ <table class="lite-table">
65
+ <thead>
66
+ <tr>
67
+ {#each listFields as field, _i (_i)}
68
+ <th>
69
+ {#if field.sortable !== false}
70
+ <a href={sortUrl(field)}>
71
+ {field.label}
72
+ <span class="sort-indicator">{sortIndicator(field)}</span>
73
+ </a>
74
+ {:else}
75
+ {field.label}
76
+ {/if}
77
+ </th>
78
+ {/each}
79
+ {#if canEdit || canDelete}
80
+ <th style="text-align:right;">{t('common.actions') || 'Actions'}</th>
81
+ {/if}
82
+ </tr>
83
+ </thead>
84
+ <tbody>
85
+ {#each records as record, _i (_i)}
86
+ {@const id = record[pk]}
87
+ <tr>
88
+ {#each listFields as field, _i (_i)}
89
+ <td>
90
+ {#if field.type === 'boolean'}
91
+ <span class="lite-bool {record[field.key] ? 'lite-bool-true' : ''}"></span>
92
+ {:else if field.type === 'tags' && Array.isArray(record[field.key])}
93
+ {#each (record[field.key] as string[]).slice(0, 3) as tag, _i (_i)}
94
+ <span class="lite-badge">{tag}</span>
95
+ {/each}
96
+ {:else if field.type === 'select' && field.options}
97
+ <span class="lite-badge">{formatValue(record[field.key], field)}</span>
98
+ {:else}
99
+ {formatValue(record[field.key], field)}
100
+ {/if}
101
+ </td>
102
+ {/each}
103
+ {#if canEdit || canDelete}
104
+ <td class="actions">
105
+ {#if canEdit}
106
+ <a href={`${basePath}/${resource.name}/edit/${id}`} class="lite-btn lite-btn-sm">{t('common.edit') || 'Edit'}</a>
107
+ {/if}
108
+ {#if canDelete}
109
+ <!-- Delete uses <details> for no-JS confirmation -->
110
+ <details class="lite-confirm-details">
111
+ <summary class="lite-btn lite-btn-sm lite-btn-danger">{t('common.delete') || 'Delete'}</summary>
112
+ <div class="lite-confirm-panel">
113
+ <p style="margin:0 0 8px;font-size:13px;">{t('common.areYouSure') || 'Are you sure?'}</p>
114
+ <form method="POST" action="?/delete" style="display:inline;">
115
+ <input type="hidden" name="id" value={String(id)} />
116
+ <button type="submit" class="lite-btn lite-btn-sm lite-btn-danger">{t('common.confirm') || 'Confirm'}</button>
117
+ </form>
118
+ </div>
119
+ </details>
120
+ {/if}
121
+ </td>
122
+ {/if}
123
+ </tr>
124
+ {:else}
125
+ <tr>
126
+ <td colspan={listFields.length + (canEdit || canDelete ? 1 : 0)} style="text-align:center;padding:40px;color:#9ca3af;">
127
+ {t('common.noData') || 'No records found.'}
128
+ </td>
129
+ </tr>
130
+ {/each}
131
+ </tbody>
132
+ </table>
@@ -0,0 +1,19 @@
1
+ /**
2
+ * LiteTable — Pure HTML table with <a> sort links.
3
+ * No JavaScript required — sorting and pagination are URL-driven.
4
+ */
5
+ import type { ResourceDefinition } from '@svadmin/core';
6
+ interface Props {
7
+ records: Record<string, unknown>[];
8
+ resource: ResourceDefinition;
9
+ currentSort?: string;
10
+ currentOrder?: 'asc' | 'desc';
11
+ currentSearch?: string;
12
+ basePath?: string;
13
+ /** Show edit/delete action buttons */
14
+ canEdit?: boolean;
15
+ canDelete?: boolean;
16
+ }
17
+ declare const LiteTable: import("svelte").Component<Props, {}, "">;
18
+ type LiteTable = ReturnType<typeof LiteTable>;
19
+ export default LiteTable;
@@ -0,0 +1,57 @@
1
+ <script lang="ts">
2
+ /**
3
+ * LiteTabs — SSR-compatible tab navigation.
4
+ * Uses simple <a> tags and flexbox. No JS required.
5
+ */
6
+
7
+ interface TabItem {
8
+ label: string;
9
+ value: string;
10
+ href: string;
11
+ }
12
+
13
+ interface Props {
14
+ items: TabItem[];
15
+ activeValue: string;
16
+ }
17
+
18
+ let { items, activeValue }: Props = $props();
19
+ </script>
20
+
21
+ <div class="lite-tabs">
22
+ {#each items as item, _i (_i)}
23
+ <a href={item.href} class="lite-tab {activeValue === item.value ? 'active' : ''}">
24
+ {item.label}
25
+ </a>
26
+ {/each}
27
+ </div>
28
+
29
+ <style>
30
+ .lite-tabs {
31
+ display: flex;
32
+ flex-wrap: wrap;
33
+ border-bottom: 2px solid #e2e8f0;
34
+ margin-bottom: 24px;
35
+ padding-bottom: 0;
36
+ }
37
+ .lite-tab {
38
+ padding: 8px 16px;
39
+ margin-right: 4px;
40
+ font-size: 14px;
41
+ font-weight: 500;
42
+ color: #64748b;
43
+ border-bottom: 2px solid transparent;
44
+ margin-bottom: -2px;
45
+ text-decoration: none;
46
+ transition: color 0.15s ease, border-color 0.15s ease;
47
+ }
48
+ .lite-tab:hover {
49
+ color: #0f172a;
50
+ text-decoration: none;
51
+ }
52
+ .lite-tab.active {
53
+ color: #4f46e5;
54
+ border-bottom-color: #4f46e5;
55
+ font-weight: 600;
56
+ }
57
+ </style>