@svadmin/lite 0.8.2 → 0.9.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/README.md CHANGED
@@ -263,7 +263,7 @@ IE11 before the decision is made.
263
263
  | `LiteProfilePage` | User profile management page with avatar and info |
264
264
  | `LiteRegisterPage` | User registration page with server-side validation |
265
265
  | `LiteForgotPasswordPage` / `LiteUpdatePasswordPage` | Password recovery and credential rotation flows |
266
- | `LiteTable` | HTML table with sort links, selectable rows, and delete confirmation |
266
+ | `LiteTable` | HTML table with sort links, selectable rows, delete confirmation, and optional sticky edge/action columns |
267
267
  | `LiteForm` | Schema-driven form renderer supporting all field definitions |
268
268
  | `LiteShow` | Detailed field-by-field record viewer |
269
269
  | `LitePagination` / `LiteSearch` | URL-driven pagination and GET search controls |
@@ -272,14 +272,13 @@ IE11 before the decision is made.
272
272
  | `LiteCanAccess` / `LiteErrorBoundary` | Server-side access gate and constrained-environment error boundary |
273
273
  | `LiteSplitPaneLayout` / `LiteMultiTabKeepAlive` | Dense two-pane layouts and multi-workspace navigation |
274
274
 
275
- ### Field Components (33 Fields)
275
+ ### Field Components (32 Fields)
276
276
 
277
277
  | Category | Components |
278
278
  |----------|------------|
279
279
  | **Text & Numeric** | `LiteTextField`, `LiteNumberField`, `LiteCurrencyField`, `LitePercentField`, `LitePhoneField`, `LiteEmailField`, `LiteUrlField`, `LiteRatingField`, `LiteCopyField` |
280
280
  | **Date & Choice** | `LiteDateField`, `LiteDateRangeField`, `LiteBooleanField`, `LiteSelectField`, `LiteMultiSelectField`, `LiteTreeSelect`, `LiteCascader`, `LiteTagField`, `LiteRelationField` |
281
281
  | **Media & Rich & Array** | `LiteAvatarField`, `LiteImageField`, `LiteFileField`, `LiteCodeField`, `LiteMarkdownField`, `LiteRichTextField`, `LiteJsonField`, `LiteArrayField`, `LiteDynamicFormList`, `LiteTransfer`, `LiteImageCropper`, `LiteJsonSchemaForm`, `LiteMentionsInput`, `LiteSignaturePad` |
282
- | **SPA-only** | `VoiceInput` (Web Speech API; no SSR counterpart) |
283
282
 
284
283
  ### Action Buttons (10 Buttons)
285
284
 
@@ -293,7 +292,7 @@ IE11 before the decision is made.
293
292
 
294
293
  | Component | Description |
295
294
  |-----------|-------------|
296
- | `LiteStatsCard`, `LiteInsightCard`, `LiteAnomalyBadge` | Metric KPI cards and status badges |
295
+ | `LiteStatsCard`, `LiteMetricStrip`, `LiteInsightCard`, `LiteAnomalyBadge`, `LiteBadge` | Metric KPI cards/strips and semantic status badges |
297
296
  | `LiteBarChart`, `LiteLineChart`, `LitePieChart` | Server-rendered SVG/HTML charts with fallback data tables |
298
297
  | `LitePresenceAvatarGroup`, `LiteGanttChart`, `LiteOfflineSyncBanner` | Presence, schedule, and offline mutation status rendered without client hydration |
299
298
 
@@ -320,7 +319,7 @@ IE11 before the decision is made.
320
319
  | `LiteStepForm`, `LiteTableSummary`, `LiteVersionDiffViewer`, `LiteEditableTable`, `LiteDraggableRowTable` | Server-driven enterprise data interaction components |
321
320
  | `LiteMediaLibraryModal`, `LiteActivityFeed`, `LiteKanbanBoard`, `LitePivotTable` | Media, activity, workflow, and analysis views |
322
321
  | `LiteCanvasAnnotation`, `LiteSpreadsheetView`, `LiteDecisionTable` | SSR-compatible annotation, spreadsheet, and rules views |
323
- | `DevTools`, `CopilotPanel` | SPA-only components with no Lite counterpart |
322
+ | `DevTools` | SPA-only component with no Lite counterpart |
324
323
 
325
324
  ## Parity Tracking & Visualization
326
325
 
@@ -0,0 +1,43 @@
1
+ <script lang="ts">
2
+ import type { HTMLAnchorAttributes } from 'svelte/elements';
3
+
4
+ export type LiteBadgeVariant =
5
+ | 'default'
6
+ | 'secondary'
7
+ | 'destructive'
8
+ | 'subtle'
9
+ | 'subtle-success'
10
+ | 'subtle-warning'
11
+ | 'subtle-destructive'
12
+ | 'subtle-pill'
13
+ | 'outline'
14
+ | 'ghost'
15
+ | 'link'
16
+ | 'info'
17
+ | 'success'
18
+ | 'warning';
19
+
20
+ interface Props {
21
+ variant?: LiteBadgeVariant;
22
+ href?: string;
23
+ class?: string;
24
+ children?: import('svelte').Snippet;
25
+ }
26
+
27
+ let {
28
+ variant = 'default',
29
+ href,
30
+ class: className = '',
31
+ children,
32
+ ...restProps
33
+ }: Props & HTMLAnchorAttributes = $props();
34
+ </script>
35
+
36
+ <svelte:element
37
+ this={href ? 'a' : 'span'}
38
+ {href}
39
+ class={'lite-badge lite-badge-' + variant + (className ? ' ' + className : '')}
40
+ {...restProps}
41
+ >
42
+ {@render children?.()}
43
+ </svelte:element>
@@ -0,0 +1,12 @@
1
+ import type { HTMLAnchorAttributes } from 'svelte/elements';
2
+ export type LiteBadgeVariant = 'default' | 'secondary' | 'destructive' | 'subtle' | 'subtle-success' | 'subtle-warning' | 'subtle-destructive' | 'subtle-pill' | 'outline' | 'ghost' | 'link' | 'info' | 'success' | 'warning';
3
+ interface Props {
4
+ variant?: LiteBadgeVariant;
5
+ href?: string;
6
+ class?: string;
7
+ children?: import('svelte').Snippet;
8
+ }
9
+ type $$ComponentProps = Props & HTMLAnchorAttributes;
10
+ declare const LiteBadge: import("svelte").Component<$$ComponentProps, {}, "">;
11
+ type LiteBadge = ReturnType<typeof LiteBadge>;
12
+ export default LiteBadge;
@@ -32,6 +32,13 @@
32
32
  return new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
33
33
  }
34
34
 
35
+ function messageText(message: ChatMessage): string {
36
+ return message.parts
37
+ .filter((part): part is Extract<ChatMessage['parts'][number], { type: 'text' }> => part.type === 'text')
38
+ .map((part) => part.text)
39
+ .join('');
40
+ }
41
+
35
42
  // Auto-scroll anchor logic: pure HTML way to scroll to bottom after page load
36
43
  // User should include "#latest-msg" in the form action if possible
37
44
  </script>
@@ -66,10 +73,10 @@
66
73
  ? 'background:#4f46e5;color:#fff;border-bottom-right-radius:2px;'
67
74
  : 'background:#e2e8f0;color:#0f172a;border-bottom-left-radius:2px;'}"
68
75
  >
69
- <div style="white-space:pre-wrap;word-break:break-word;">{msg.content}</div>
76
+ <div style="white-space:pre-wrap;word-break:break-word;">{messageText(msg)}</div>
70
77
  </div>
71
78
  <div style="font-size:11px;color:#94a3b8;margin-top:4px;">
72
- {msg.role === 'user' ? 'You' : 'Assistant'} {formatDate(msg.timestamp)}
79
+ {msg.role === 'user' ? 'You' : 'Assistant'} · {formatDate(msg.createdAt)}
73
80
  </div>
74
81
  </div>
75
82
  {/if}
@@ -0,0 +1,96 @@
1
+ <script lang="ts">
2
+ import type { Component, Snippet } from 'svelte';
3
+ import LiteBadge from './LiteBadge.svelte';
4
+
5
+ export type LiteMetricTone = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
6
+ export type LiteMetricBadgeTone = 'default' | 'success' | 'warning' | 'danger' | 'info';
7
+
8
+ export interface LiteMetricStripItem {
9
+ id?: string;
10
+ label: string;
11
+ value: string | number;
12
+ tone?: LiteMetricTone;
13
+ badge?: { text: string; tone?: LiteMetricBadgeTone };
14
+ icon?: Component<{ class?: string }>;
15
+ href?: string;
16
+ trend?: { value: number; label?: string };
17
+ loading?: boolean;
18
+ class?: string;
19
+ }
20
+
21
+ interface Props {
22
+ items?: LiteMetricStripItem[];
23
+ columns?: 2 | 3 | 4 | 5 | 6 | 'auto';
24
+ ariaLabel?: string;
25
+ class?: string;
26
+ children?: Snippet;
27
+ }
28
+
29
+ let {
30
+ items = [],
31
+ columns = 'auto',
32
+ ariaLabel = 'Metrics overview',
33
+ class: className = '',
34
+ children,
35
+ }: Props = $props();
36
+
37
+ const toneClass: Record<LiteMetricTone, string> = {
38
+ default: '',
39
+ primary: 'lite-metric-value-primary',
40
+ success: 'lite-metric-value-success',
41
+ warning: 'lite-metric-value-warning',
42
+ danger: 'lite-metric-value-danger',
43
+ info: 'lite-metric-value-info',
44
+ };
45
+
46
+ const badgeVariant: Record<LiteMetricBadgeTone, 'default' | 'subtle-success' | 'subtle-warning' | 'subtle-destructive' | 'info'> = {
47
+ default: 'default',
48
+ success: 'subtle-success',
49
+ warning: 'subtle-warning',
50
+ danger: 'subtle-destructive',
51
+ info: 'info',
52
+ };
53
+ </script>
54
+
55
+ <div
56
+ role="region"
57
+ aria-label={ariaLabel}
58
+ class={'lite-metric-strip lite-metric-strip-' + columns + (className ? ' ' + className : '')}
59
+ >
60
+ {#if children}
61
+ {@render children()}
62
+ {:else}
63
+ {#each items as item, index (item.id || index)}
64
+ {@const interactive = Boolean(item.href)}
65
+ <svelte:element
66
+ this={interactive ? 'a' : 'div'}
67
+ href={item.href}
68
+ class={'lite-metric-item' + (interactive ? ' lite-metric-item-link' : '') + (item.class ? ' ' + item.class : '')}
69
+ >
70
+ <div class="lite-metric-label">
71
+ <span>{item.label}</span>
72
+ {#if item.icon}<span class="lite-metric-icon" aria-hidden="true"><item.icon class="lite-metric-icon-svg" /></span>{/if}
73
+ </div>
74
+ {#if item.loading}
75
+ <span class="lite-metric-loading" aria-label="Loading">&nbsp;</span>
76
+ {:else}
77
+ <div class="lite-metric-value-row">
78
+ <strong class={'lite-metric-value ' + (toneClass[item.tone || 'default'] || '')}>{item.value}</strong>
79
+ {#if item.badge}
80
+ <LiteBadge variant={badgeVariant[item.badge.tone || 'default']}>{item.badge.text}</LiteBadge>
81
+ {:else if item.trend}
82
+ <span
83
+ class={'lite-metric-trend ' + (item.trend.value >= 0 ? 'lite-metric-trend-up' : 'lite-metric-trend-down')}
84
+ aria-label={(item.trend.value >= 0 ? 'Up ' : 'Down ') + Math.abs(item.trend.value) + ' percent'}
85
+ >
86
+ <span aria-hidden="true">{item.trend.value >= 0 ? '↑' : '↓'}</span>
87
+ {Math.abs(item.trend.value)}%
88
+ {#if item.trend.label}<span class="lite-metric-trend-label">{item.trend.label}</span>{/if}
89
+ </span>
90
+ {/if}
91
+ </div>
92
+ {/if}
93
+ </svelte:element>
94
+ {/each}
95
+ {/if}
96
+ </div>
@@ -0,0 +1,33 @@
1
+ import type { Component, Snippet } from 'svelte';
2
+ export type LiteMetricTone = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
3
+ export type LiteMetricBadgeTone = 'default' | 'success' | 'warning' | 'danger' | 'info';
4
+ export interface LiteMetricStripItem {
5
+ id?: string;
6
+ label: string;
7
+ value: string | number;
8
+ tone?: LiteMetricTone;
9
+ badge?: {
10
+ text: string;
11
+ tone?: LiteMetricBadgeTone;
12
+ };
13
+ icon?: Component<{
14
+ class?: string;
15
+ }>;
16
+ href?: string;
17
+ trend?: {
18
+ value: number;
19
+ label?: string;
20
+ };
21
+ loading?: boolean;
22
+ class?: string;
23
+ }
24
+ interface Props {
25
+ items?: LiteMetricStripItem[];
26
+ columns?: 2 | 3 | 4 | 5 | 6 | 'auto';
27
+ ariaLabel?: string;
28
+ class?: string;
29
+ children?: Snippet;
30
+ }
31
+ declare const LiteMetricStrip: Component<Props, {}, "">;
32
+ type LiteMetricStrip = ReturnType<typeof LiteMetricStrip>;
33
+ export default LiteMetricStrip;
@@ -20,6 +20,10 @@
20
20
  canEdit?: boolean;
21
21
  canDelete?: boolean;
22
22
  enableBatch?: boolean;
23
+ /** One field key per edge; left is ignored while batch selection is active. */
24
+ stickyColumns?: { left?: string; right?: string };
25
+ /** Pin actions on the right; this takes precedence over stickyColumns.right. */
26
+ stickyActions?: boolean;
23
27
  }
24
28
 
25
29
  let {
@@ -33,6 +37,8 @@
33
37
  canEdit,
34
38
  canDelete,
35
39
  enableBatch = false,
40
+ stickyColumns = {},
41
+ stickyActions = false,
36
42
  }: Props = $props();
37
43
 
38
44
  const tableId = $props.id();
@@ -45,6 +51,8 @@
45
51
  const listFields = $derived(
46
52
  resource.fields.filter(f => f.showInList !== false)
47
53
  );
54
+ const stickyLeft = $derived(showBatch ? undefined : stickyColumns.left);
55
+ const stickyRight = $derived(stickyActions ? undefined : stickyColumns.right);
48
56
 
49
57
  function sortUrl(field: FieldDefinition): string {
50
58
  const newOrder = currentSort === field.key && currentOrder === "asc" ? "desc" : "asc";
@@ -103,7 +111,7 @@
103
111
  </th>
104
112
  {/if}
105
113
  {#each listFields as field, _i (_i)}
106
- <th>
114
+ <th class={stickyLeft === field.key ? 'lite-table-sticky-left' : stickyRight === field.key ? 'lite-table-sticky-right' : undefined} data-sticky={stickyLeft === field.key ? 'left' : stickyRight === field.key ? 'right' : undefined}>
107
115
  {#if field.sortable !== false}
108
116
  <a href={sortUrl(field)}>
109
117
  {field.label}
@@ -115,7 +123,7 @@
115
123
  </th>
116
124
  {/each}
117
125
  {#if showView || showEdit || showDelete}
118
- <th style="text-align:right;">{t("common.actions") || "Actions"}</th>
126
+ <th class={stickyActions ? 'lite-table-sticky-right' : undefined} data-sticky={stickyActions ? 'right' : undefined} style="text-align:right;">{t("common.actions") || "Actions"}</th>
119
127
  {/if}
120
128
  </tr>
121
129
  </thead>
@@ -129,7 +137,7 @@
129
137
  </td>
130
138
  {/if}
131
139
  {#each listFields as field, _i (_i)}
132
- <td>
140
+ <td class={stickyLeft === field.key ? 'lite-table-sticky-left' : stickyRight === field.key ? 'lite-table-sticky-right' : undefined} data-sticky={stickyLeft === field.key ? 'left' : stickyRight === field.key ? 'right' : undefined}>
133
141
  {#if field.type === "boolean"}
134
142
  <span class="lite-bool {isExplicitBooleanTrue(record[field.key]) ? "lite-bool-true" : ""}"></span>
135
143
  {:else if field.type === "tags" && Array.isArray(record[field.key])}
@@ -148,7 +156,7 @@
148
156
  </td>
149
157
  {/each}
150
158
  {#if showView || showEdit || showDelete}
151
- <td class="actions">
159
+ <td class={'actions' + (stickyActions ? ' lite-table-sticky-right' : '')} data-sticky={stickyActions ? 'right' : undefined}>
152
160
  {#if showView}
153
161
  <a href={basePath + "/" + resource.name + "/show/" + id} class="lite-btn lite-btn-sm">{t("common.show") || "Show"}</a>
154
162
  {/if}
@@ -15,6 +15,13 @@ interface Props {
15
15
  canEdit?: boolean;
16
16
  canDelete?: boolean;
17
17
  enableBatch?: boolean;
18
+ /** One field key per edge; left is ignored while batch selection is active. */
19
+ stickyColumns?: {
20
+ left?: string;
21
+ right?: string;
22
+ };
23
+ /** Pin actions on the right; this takes precedence over stickyColumns.right. */
24
+ stickyActions?: boolean;
18
25
  }
19
26
  declare const LiteTable: import("svelte").Component<Props, {}, "">;
20
27
  type LiteTable = ReturnType<typeof LiteTable>;
package/dist/index.d.ts CHANGED
@@ -22,6 +22,10 @@ export { default as LiteFilterBuilder } from './components/LiteFilterBuilder.sve
22
22
  export type { FilterRuleItem } from './components/LiteFilterBuilder.svelte';
23
23
  export { default as LiteBreadcrumbs } from './components/LiteBreadcrumbs.svelte';
24
24
  export { default as LiteStatsCard } from './components/LiteStatsCard.svelte';
25
+ export { default as LiteMetricStrip } from './components/LiteMetricStrip.svelte';
26
+ export type { LiteMetricStripItem, LiteMetricTone, LiteMetricBadgeTone, } from './components/LiteMetricStrip.svelte';
27
+ export { default as LiteBadge } from './components/LiteBadge.svelte';
28
+ export type { LiteBadgeVariant } from './components/LiteBadge.svelte';
25
29
  export { default as LiteConfirmDialog } from './components/LiteConfirmDialog.svelte';
26
30
  export { default as LiteEmptyState } from './components/LiteEmptyState.svelte';
27
31
  export { default as LiteTabs } from './components/LiteTabs.svelte';
package/dist/index.js CHANGED
@@ -24,6 +24,8 @@ export { default as LiteTransfer } from './components/LiteTransfer.svelte';
24
24
  export { default as LiteFilterBuilder } from './components/LiteFilterBuilder.svelte';
25
25
  export { default as LiteBreadcrumbs } from './components/LiteBreadcrumbs.svelte';
26
26
  export { default as LiteStatsCard } from './components/LiteStatsCard.svelte';
27
+ export { default as LiteMetricStrip } from './components/LiteMetricStrip.svelte';
28
+ export { default as LiteBadge } from './components/LiteBadge.svelte';
27
29
  export { default as LiteConfirmDialog } from './components/LiteConfirmDialog.svelte';
28
30
  export { default as LiteEmptyState } from './components/LiteEmptyState.svelte';
29
31
  export { default as LiteTabs } from './components/LiteTabs.svelte';
package/dist/lite.css CHANGED
@@ -862,6 +862,22 @@ textarea.lite-input {
862
862
  overflow-x: auto;
863
863
  }
864
864
 
865
+ .lite-table .lite-table-sticky-left,
866
+ .lite-table .lite-table-sticky-right {
867
+ position: sticky;
868
+ z-index: 2;
869
+ background: #fff;
870
+ }
871
+ .lite-table th.lite-table-sticky-left,
872
+ .lite-table th.lite-table-sticky-right {
873
+ z-index: 3;
874
+ background: #f8fafc;
875
+ }
876
+ .lite-table .lite-table-sticky-left { left: 0; box-shadow: 1px 0 0 #e2e8f0; }
877
+ .lite-table .lite-table-sticky-right { right: 0; box-shadow: -1px 0 0 #e2e8f0; }
878
+ .lite-table tr:hover .lite-table-sticky-left,
879
+ .lite-table tr:hover .lite-table-sticky-right { background: #f8f7fd; }
880
+
865
881
  .lite-muted {
866
882
  color: #64748b;
867
883
  font-size: 12px;
@@ -1044,6 +1060,70 @@ textarea.lite-input {
1044
1060
  border-color: #fde68a;
1045
1061
  }
1046
1062
 
1063
+ .lite-badge-subtle {
1064
+ background: #eef2ff;
1065
+ color: #4338ca;
1066
+ border-color: #c7d2fe;
1067
+ }
1068
+ .lite-badge-default { background: #4f46e5; color: #fff; border-color: #4f46e5; }
1069
+ .lite-badge-secondary { background: #f1f5f9; color: #334155; border-color: #e2e8f0; }
1070
+ .lite-badge-destructive,
1071
+ .lite-badge-subtle-destructive { background: #fef2f2; color: #dc2626; border-color: #fecaca; }
1072
+ .lite-badge-subtle-success { background: #f0fdf4; color: #166534; border-color: #bbf7d0; }
1073
+ .lite-badge-subtle-warning { background: #fffbeb; color: #92400e; border-color: #fde68a; }
1074
+ .lite-badge-subtle-pill {
1075
+ border-radius: 999px;
1076
+ background: #f8fafc;
1077
+ color: #475569;
1078
+ font-weight: 500;
1079
+ }
1080
+ .lite-badge-outline { background: transparent; color: #0f172a; }
1081
+ .lite-badge-ghost { background: transparent; border-color: transparent; color: #475569; }
1082
+ .lite-badge-link { padding-left: 0; padding-right: 0; background: transparent; border-color: transparent; color: #4f46e5; text-decoration: underline; }
1083
+
1084
+ /* ─── Metric strip ────────────────────────────────────────── */
1085
+ .lite-metric-strip {
1086
+ display: flex;
1087
+ flex-wrap: wrap;
1088
+ overflow: hidden;
1089
+ border: 1px solid #e2e8f0;
1090
+ border-radius: 8px;
1091
+ background: #e2e8f0;
1092
+ }
1093
+ .lite-metric-item {
1094
+ flex: 1 1 160px;
1095
+ min-width: 0;
1096
+ padding: 14px 16px;
1097
+ background: #fff;
1098
+ color: #0f172a;
1099
+ text-decoration: none;
1100
+ border-right: 1px solid #e2e8f0;
1101
+ border-bottom: 1px solid #e2e8f0;
1102
+ }
1103
+ .lite-metric-item-link:hover { background: #f8fafc; text-decoration: none; }
1104
+ .lite-metric-label,
1105
+ .lite-metric-value-row { display: flex; align-items: center; justify-content: space-between; }
1106
+ .lite-metric-label { color: #64748b; font-size: 12px; font-weight: 600; }
1107
+ .lite-metric-icon { margin-left: 8px; color: #94a3b8; }
1108
+ .lite-metric-icon-svg { width: 14px; height: 14px; }
1109
+ .lite-metric-value { margin-top: 4px; font-size: 23px; line-height: 1.2; color: #0f172a; }
1110
+ .lite-metric-value-primary { color: #4f46e5; }
1111
+ .lite-metric-value-success { color: #16a34a; }
1112
+ .lite-metric-value-warning { color: #d97706; }
1113
+ .lite-metric-value-danger { color: #dc2626; }
1114
+ .lite-metric-value-info { color: #2563eb; }
1115
+ .lite-metric-loading { display: block; width: 64px; height: 24px; margin-top: 6px; background: #e2e8f0; border-radius: 4px; }
1116
+ .lite-metric-trend { margin-left: 8px; font-size: 12px; font-weight: 600; white-space: nowrap; }
1117
+ .lite-metric-trend-up { color: #16a34a; }
1118
+ .lite-metric-trend-down { color: #dc2626; }
1119
+ .lite-metric-trend-label { margin-left: 4px; color: #64748b; font-weight: 400; }
1120
+ .lite-metric-strip-2 .lite-metric-item { flex-basis: 45%; }
1121
+ .lite-metric-strip-3 .lite-metric-item { flex-basis: 30%; }
1122
+ .lite-metric-strip-4 .lite-metric-item { flex-basis: 22%; }
1123
+ .lite-metric-strip-5 .lite-metric-item { flex-basis: 18%; }
1124
+ .lite-metric-strip-6 .lite-metric-item,
1125
+ .lite-metric-strip-auto .lite-metric-item { flex-basis: 15%; }
1126
+
1047
1127
  /* ─── Boolean indicator ────────────────────────────────────── */
1048
1128
  .lite-bool {
1049
1129
  display: inline-block;
@@ -1317,6 +1397,17 @@ textarea.lite-input {
1317
1397
  .lite-realtime-status > .lite-btn {
1318
1398
  margin-top: 10px;
1319
1399
  }
1400
+ .lite-metric-strip-3 .lite-metric-item,
1401
+ .lite-metric-strip-4 .lite-metric-item,
1402
+ .lite-metric-strip-5 .lite-metric-item,
1403
+ .lite-metric-strip-6 .lite-metric-item,
1404
+ .lite-metric-strip-auto .lite-metric-item {
1405
+ flex-basis: 45%;
1406
+ }
1407
+ }
1408
+
1409
+ @media (max-width: 420px) {
1410
+ .lite-metric-strip .lite-metric-item { flex-basis: 100%; }
1320
1411
  }
1321
1412
 
1322
1413
  /* ─── Print ────────────────────────────────────────────────── */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/lite",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "SSR-first lightweight admin UI for @svadmin with optional progressive enhancement",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "peerDependencies": {
51
51
  "svelte": "^5.56.10",
52
- "@svadmin/core": ">=0.34.2 <0.48.0",
52
+ "@svadmin/core": ">=0.34.2 <0.49.0",
53
53
  "@sveltejs/kit": "^2.70.3"
54
54
  },
55
55
  "dependencies": {