@svadmin/surface 0.7.1 → 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.
- package/README.md +11 -0
- package/compatibility.json +1 -1
- package/dist/catalog.d.ts +3 -0
- package/dist/components/BarChartWidget.svelte +7 -5
- package/dist/components/LineChartWidget.svelte +7 -5
- package/dist/components/MetricWidget.svelte +7 -5
- package/dist/components/ResourceTableWidget.svelte +11 -5
- package/dist/components/SurfaceRenderer.svelte +14 -2
- package/dist/components/SurfaceRenderer.svelte.d.ts +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/localization.d.ts +18 -0
- package/dist/localization.js +45 -0
- package/dist/svelte.d.ts +1 -0
- package/dist/widget-data.d.ts +8 -2
- package/dist/widget-data.js +26 -5
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -78,6 +78,17 @@ import {
|
|
|
78
78
|
|
|
79
79
|
When rendered inside `AdminApp`, the renderer resolves the configured provider for each resource. A trusted host may instead pass `dataProvider`, which is still narrowed to `getList` and `getOne`.
|
|
80
80
|
|
|
81
|
+
The renderer also follows the active `AdminApp` i18n locale. Built-in loading, empty, error, boolean, number, currency, percent, and date presentation updates automatically. A standalone host can pass `locale`, and can override individual built-in strings with `messages`. Business copy such as the surface title, widget titles, labels, and column headers remains part of the JSON spec; build or select a localized spec when the host locale changes.
|
|
82
|
+
|
|
83
|
+
```svelte
|
|
84
|
+
<SurfaceRenderer
|
|
85
|
+
{spec}
|
|
86
|
+
{policy}
|
|
87
|
+
locale="zh-CN"
|
|
88
|
+
messages={{ tableNoRecords: '没有符合条件的记录' }}
|
|
89
|
+
/>
|
|
90
|
+
```
|
|
91
|
+
|
|
81
92
|
## AI proposals
|
|
82
93
|
|
|
83
94
|
The DOM-free root entry also exposes an opt-in Agent protocol. `buildSurfaceAgentPrompt()` constrains a model to return a proposal envelope and includes the host's widget/resource/field allowlists. `parseSurfaceAgentProposal()` parses and validates the complete `SurfaceSpec` against the same catalog and policy without querying a provider:
|
package/compatibility.json
CHANGED
package/dist/catalog.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { Component } from 'svelte';
|
|
2
2
|
import type { JsonObject, SurfaceCatalog, SurfaceWidgetDataState, SurfaceWidgetDefinition } from './types.js';
|
|
3
|
+
import type { SurfaceMessages } from './localization.js';
|
|
3
4
|
export declare const DEFAULT_SURFACE_CATALOG_VERSION: "svadmin/v1";
|
|
4
5
|
export interface SurfaceWidgetRendererProps {
|
|
5
6
|
readonly widgetId: string;
|
|
6
7
|
readonly props: JsonObject;
|
|
7
8
|
readonly data: SurfaceWidgetDataState;
|
|
9
|
+
readonly locale?: string;
|
|
10
|
+
readonly messages?: SurfaceMessages;
|
|
8
11
|
}
|
|
9
12
|
export interface SurfaceWidgetRegistration extends SurfaceWidgetDefinition {
|
|
10
13
|
readonly component: Component<SurfaceWidgetRendererProps>;
|
|
@@ -7,16 +7,18 @@
|
|
|
7
7
|
import Card from '@svadmin/ui/components/ui/card/card.svelte';
|
|
8
8
|
import { barChartPropsSchema } from '../builtin-schemas.js';
|
|
9
9
|
import type { SurfaceWidgetRendererProps } from '../catalog.js';
|
|
10
|
+
import { formatSurfaceMessage, resolveSurfaceMessages } from '../localization.js';
|
|
10
11
|
import { asChartPoints, compactChartLabel } from '../widget-data.js';
|
|
11
12
|
|
|
12
|
-
let { props, data }: SurfaceWidgetRendererProps = $props();
|
|
13
|
+
let { props, data, locale = 'en-US', messages }: SurfaceWidgetRendererProps = $props();
|
|
14
|
+
const activeMessages = $derived(resolveSurfaceMessages(locale, messages));
|
|
13
15
|
|
|
14
16
|
const chartProps = $derived(Value.Decode(barChartPropsSchema, props));
|
|
15
17
|
const points = $derived(data.status === 'ready'
|
|
16
18
|
? asChartPoints(data.value, chartProps.labelField, chartProps.valueField)
|
|
17
19
|
: null);
|
|
18
20
|
const chartData = $derived(points?.ok
|
|
19
|
-
? points.value.map((point) => ({ ...point, label: compactChartLabel(point.label, 12) }))
|
|
21
|
+
? points.value.map((point) => ({ ...point, label: compactChartLabel(point.label, 12, locale) }))
|
|
20
22
|
: []);
|
|
21
23
|
</script>
|
|
22
24
|
|
|
@@ -26,9 +28,9 @@
|
|
|
26
28
|
</CardHeader>
|
|
27
29
|
<CardContent>
|
|
28
30
|
{#if data.status === 'loading'}
|
|
29
|
-
<div class="chart-state chart-loading" role="status" aria-label=
|
|
31
|
+
<div class="chart-state chart-loading" role="status" aria-label={formatSurfaceMessage(activeMessages.chartLoading, { title: chartProps.title })}></div>
|
|
30
32
|
{:else if data.status === 'empty'}
|
|
31
|
-
<p class="chart-state" role="status">
|
|
33
|
+
<p class="chart-state" role="status">{activeMessages.chartNoData}</p>
|
|
32
34
|
{:else if data.status === 'error'}
|
|
33
35
|
<p class="chart-state" role="alert">{data.error.message}</p>
|
|
34
36
|
{:else if data.status === 'ready' && points?.ok}
|
|
@@ -36,7 +38,7 @@
|
|
|
36
38
|
<BarChart data={chartData} showValues={chartProps.showValues ?? true} />
|
|
37
39
|
</div>
|
|
38
40
|
{:else}
|
|
39
|
-
<p class="chart-state" role="alert">{points && !points.ok ?
|
|
41
|
+
<p class="chart-state" role="alert">{points && !points.ok ? activeMessages.chartInvalidData : activeMessages.chartUnavailable}</p>
|
|
40
42
|
{/if}
|
|
41
43
|
</CardContent>
|
|
42
44
|
</Card>
|
|
@@ -7,16 +7,18 @@
|
|
|
7
7
|
import Card from '@svadmin/ui/components/ui/card/card.svelte';
|
|
8
8
|
import { lineChartPropsSchema } from '../builtin-schemas.js';
|
|
9
9
|
import type { SurfaceWidgetRendererProps } from '../catalog.js';
|
|
10
|
+
import { formatSurfaceMessage, resolveSurfaceMessages } from '../localization.js';
|
|
10
11
|
import { asChartPoints, compactChartLabel } from '../widget-data.js';
|
|
11
12
|
|
|
12
|
-
let { props, data }: SurfaceWidgetRendererProps = $props();
|
|
13
|
+
let { props, data, locale = 'en-US', messages }: SurfaceWidgetRendererProps = $props();
|
|
14
|
+
const activeMessages = $derived(resolveSurfaceMessages(locale, messages));
|
|
13
15
|
|
|
14
16
|
const chartProps = $derived(Value.Decode(lineChartPropsSchema, props));
|
|
15
17
|
const points = $derived(data.status === 'ready'
|
|
16
18
|
? asChartPoints(data.value, chartProps.labelField, chartProps.valueField)
|
|
17
19
|
: null);
|
|
18
20
|
const chartData = $derived(points?.ok
|
|
19
|
-
? points.value.map((point) => ({ ...point, label: compactChartLabel(point.label, 8) }))
|
|
21
|
+
? points.value.map((point) => ({ ...point, label: compactChartLabel(point.label, 8, locale) }))
|
|
20
22
|
: []);
|
|
21
23
|
</script>
|
|
22
24
|
|
|
@@ -26,9 +28,9 @@
|
|
|
26
28
|
</CardHeader>
|
|
27
29
|
<CardContent>
|
|
28
30
|
{#if data.status === 'loading'}
|
|
29
|
-
<div class="chart-state chart-loading" role="status" aria-label=
|
|
31
|
+
<div class="chart-state chart-loading" role="status" aria-label={formatSurfaceMessage(activeMessages.chartLoading, { title: chartProps.title })}></div>
|
|
30
32
|
{:else if data.status === 'empty'}
|
|
31
|
-
<p class="chart-state" role="status">
|
|
33
|
+
<p class="chart-state" role="status">{activeMessages.chartNoData}</p>
|
|
32
34
|
{:else if data.status === 'error'}
|
|
33
35
|
<p class="chart-state" role="alert">{data.error.message}</p>
|
|
34
36
|
{:else if data.status === 'ready' && points?.ok}
|
|
@@ -40,7 +42,7 @@
|
|
|
40
42
|
/>
|
|
41
43
|
</div>
|
|
42
44
|
{:else}
|
|
43
|
-
<p class="chart-state" role="alert">{points && !points.ok ?
|
|
45
|
+
<p class="chart-state" role="alert">{points && !points.ok ? activeMessages.chartInvalidData : activeMessages.chartUnavailable}</p>
|
|
44
46
|
{/if}
|
|
45
47
|
</CardContent>
|
|
46
48
|
</Card>
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
import StatsCard from '@svadmin/ui/components/StatsCard.svelte';
|
|
4
4
|
import { metricPropsSchema } from '../builtin-schemas.js';
|
|
5
5
|
import type { SurfaceWidgetRendererProps } from '../catalog.js';
|
|
6
|
+
import { resolveSurfaceMessages } from '../localization.js';
|
|
6
7
|
|
|
7
|
-
let { widgetId, props, data }: SurfaceWidgetRendererProps = $props();
|
|
8
|
+
let { widgetId, props, data, locale = 'en-US', messages }: SurfaceWidgetRendererProps = $props();
|
|
9
|
+
const activeMessages = $derived(resolveSurfaceMessages(locale, messages));
|
|
8
10
|
|
|
9
11
|
const metricProps = $derived(Value.Decode(metricPropsSchema, props));
|
|
10
12
|
const formattedValue = $derived.by(() => {
|
|
@@ -12,15 +14,15 @@
|
|
|
12
14
|
const value = data.value;
|
|
13
15
|
if (typeof value !== 'number') return '—';
|
|
14
16
|
if (metricProps.format === 'currency') {
|
|
15
|
-
return new Intl.NumberFormat(
|
|
17
|
+
return new Intl.NumberFormat(locale, {
|
|
16
18
|
style: 'currency',
|
|
17
19
|
currency: metricProps.currency,
|
|
18
20
|
}).format(value);
|
|
19
21
|
}
|
|
20
22
|
if (metricProps.format === 'percent') {
|
|
21
|
-
return new Intl.NumberFormat(
|
|
23
|
+
return new Intl.NumberFormat(locale, { style: 'percent', maximumFractionDigits: 1 }).format(value);
|
|
22
24
|
}
|
|
23
|
-
return new Intl.NumberFormat(
|
|
25
|
+
return new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }).format(value);
|
|
24
26
|
});
|
|
25
27
|
</script>
|
|
26
28
|
|
|
@@ -36,7 +38,7 @@
|
|
|
36
38
|
{:else}
|
|
37
39
|
<div class="metric-state" role={data.status === 'error' ? 'alert' : 'status'}>
|
|
38
40
|
<strong>{metricProps.label}</strong>
|
|
39
|
-
<span>{data.status === 'error' ? data.error.message : data.status === 'empty' ?
|
|
41
|
+
<span>{data.status === 'error' ? data.error.message : data.status === 'empty' ? activeMessages.metricNoData : activeMessages.metricInvalidData}</span>
|
|
40
42
|
</div>
|
|
41
43
|
{/if}
|
|
42
44
|
</article>
|
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
import Table from '@svadmin/ui/components/ui/table/table.svelte';
|
|
13
13
|
import { resourceTablePropsSchema } from '../builtin-schemas.js';
|
|
14
14
|
import type { SurfaceWidgetRendererProps } from '../catalog.js';
|
|
15
|
+
import { resolveSurfaceMessages } from '../localization.js';
|
|
15
16
|
import { asRecordArray, displayTableValue } from '../widget-data.js';
|
|
16
17
|
|
|
17
|
-
let { props, data }: SurfaceWidgetRendererProps = $props();
|
|
18
|
+
let { props, data, locale = 'en-US', messages }: SurfaceWidgetRendererProps = $props();
|
|
19
|
+
const activeMessages = $derived(resolveSurfaceMessages(locale, messages));
|
|
18
20
|
|
|
19
21
|
const tableProps = $derived(Value.Decode(resourceTablePropsSchema, props));
|
|
20
22
|
const records = $derived(data.status === 'ready' ? asRecordArray(data.value) : null);
|
|
@@ -26,9 +28,9 @@
|
|
|
26
28
|
</CardHeader>
|
|
27
29
|
<CardContent>
|
|
28
30
|
{#if data.status === 'loading'}
|
|
29
|
-
<div class="table-state table-loading" role="status">
|
|
31
|
+
<div class="table-state table-loading" role="status">{activeMessages.tableLoading}</div>
|
|
30
32
|
{:else if data.status === 'empty'}
|
|
31
|
-
<p class="table-state" role="status">{tableProps.emptyLabel ??
|
|
33
|
+
<p class="table-state" role="status">{tableProps.emptyLabel ?? activeMessages.tableNoRecords}</p>
|
|
32
34
|
{:else if data.status === 'error'}
|
|
33
35
|
<p class="table-state" role="alert">{data.error.message}</p>
|
|
34
36
|
{:else if data.status === 'ready' && records?.ok}
|
|
@@ -45,7 +47,11 @@
|
|
|
45
47
|
{#each records.value as record, index (index)}
|
|
46
48
|
<TableRow>
|
|
47
49
|
{#each tableProps.columns as column (column.field)}
|
|
48
|
-
<TableCell>{displayTableValue(record[column.field],
|
|
50
|
+
<TableCell>{displayTableValue(record[column.field], {
|
|
51
|
+
format: column.format,
|
|
52
|
+
locale,
|
|
53
|
+
messages: activeMessages,
|
|
54
|
+
})}</TableCell>
|
|
49
55
|
{/each}
|
|
50
56
|
</TableRow>
|
|
51
57
|
{/each}
|
|
@@ -53,7 +59,7 @@
|
|
|
53
59
|
</Table>
|
|
54
60
|
</div>
|
|
55
61
|
{:else}
|
|
56
|
-
<p class="table-state" role="alert">{records && !records.ok ?
|
|
62
|
+
<p class="table-state" role="alert">{records && !records.ok ? activeMessages.tableInvalidData : activeMessages.tableUnavailable}</p>
|
|
57
63
|
{/if}
|
|
58
64
|
</CardContent>
|
|
59
65
|
</Card>
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { canAccessAsync, captureAdminContext } from '@svadmin/core';
|
|
3
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
3
4
|
import { untrack } from 'svelte';
|
|
4
5
|
import { resolveSurfaceWidgetData } from '../binding.js';
|
|
5
6
|
import { defaultSurfaceCatalog } from '../catalog.js';
|
|
6
7
|
import type { SurfaceRenderCatalog } from '../catalog.js';
|
|
7
8
|
import { loadSurfaceSource } from '../runtime.js';
|
|
9
|
+
import { resolveSurfaceMessages } from '../localization.js';
|
|
10
|
+
import type { SurfaceMessages } from '../localization.js';
|
|
8
11
|
import type {
|
|
9
12
|
SurfaceDataError,
|
|
10
13
|
SurfaceDataProvider,
|
|
@@ -24,6 +27,8 @@
|
|
|
24
27
|
readonly policy: SurfacePolicy;
|
|
25
28
|
readonly catalog?: SurfaceRenderCatalog;
|
|
26
29
|
readonly dataProvider?: SurfaceDataProvider;
|
|
30
|
+
readonly locale?: string;
|
|
31
|
+
readonly messages?: Partial<SurfaceMessages>;
|
|
27
32
|
readonly class?: string;
|
|
28
33
|
readonly onError?: (error: SurfaceRendererError) => void;
|
|
29
34
|
}
|
|
@@ -33,11 +38,16 @@
|
|
|
33
38
|
policy,
|
|
34
39
|
catalog = defaultSurfaceCatalog,
|
|
35
40
|
dataProvider,
|
|
41
|
+
locale,
|
|
42
|
+
messages,
|
|
36
43
|
class: className = '',
|
|
37
44
|
onError,
|
|
38
45
|
}: SurfaceRendererProps = $props();
|
|
39
46
|
|
|
40
47
|
const adminContext = captureAdminContext();
|
|
48
|
+
const i18n = useTranslation();
|
|
49
|
+
const activeLocale = $derived(locale ?? i18n.locale);
|
|
50
|
+
const activeMessages = $derived(resolveSurfaceMessages(activeLocale, messages));
|
|
41
51
|
const validation = $derived(validateSurfaceSpec(spec, catalog, policy));
|
|
42
52
|
const widgetRegistrations = $derived(new Map(catalog.widgets.map((widget) => [widget.type, widget])));
|
|
43
53
|
const currentSpec = $derived(validation.ok ? validation.value : null);
|
|
@@ -63,7 +73,7 @@
|
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
function providerError(sourceId: string, failure: unknown): SurfaceSourceDataState {
|
|
66
|
-
const message = failure instanceof Error ? failure.message :
|
|
76
|
+
const message = failure instanceof Error ? failure.message : activeMessages.providerUnavailable;
|
|
67
77
|
return {
|
|
68
78
|
status: 'error',
|
|
69
79
|
sourceId,
|
|
@@ -151,6 +161,8 @@
|
|
|
151
161
|
widgetId={widget.id}
|
|
152
162
|
props={widget.props}
|
|
153
163
|
data={resolveSurfaceWidgetData(widget, sourceStates)}
|
|
164
|
+
locale={activeLocale}
|
|
165
|
+
messages={activeMessages}
|
|
154
166
|
/>
|
|
155
167
|
{/if}
|
|
156
168
|
</div>
|
|
@@ -159,7 +171,7 @@
|
|
|
159
171
|
</section>
|
|
160
172
|
{:else}
|
|
161
173
|
<section class="surface-error {className}" role="alert" data-surface-error>
|
|
162
|
-
<h2>
|
|
174
|
+
<h2>{activeMessages.renderErrorTitle}</h2>
|
|
163
175
|
<ul>
|
|
164
176
|
{#each validation.issues as issue, issueIndex (`${issue.code}:${issue.path}:${issueIndex}`)}
|
|
165
177
|
<li><code>{issue.code}</code>: {issue.message}</li>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SurfaceRenderCatalog } from '../catalog.js';
|
|
2
|
+
import type { SurfaceMessages } from '../localization.js';
|
|
2
3
|
import type { SurfaceDataError, SurfaceDataProvider, SurfacePolicy, SurfaceValidationIssue } from '../types.js';
|
|
3
4
|
export type SurfaceRendererError = {
|
|
4
5
|
readonly type: 'validation';
|
|
@@ -12,6 +13,8 @@ export interface SurfaceRendererProps {
|
|
|
12
13
|
readonly policy: SurfacePolicy;
|
|
13
14
|
readonly catalog?: SurfaceRenderCatalog;
|
|
14
15
|
readonly dataProvider?: SurfaceDataProvider;
|
|
16
|
+
readonly locale?: string;
|
|
17
|
+
readonly messages?: Partial<SurfaceMessages>;
|
|
15
18
|
readonly class?: string;
|
|
16
19
|
readonly onError?: (error: SurfaceRendererError) => void;
|
|
17
20
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { SURFACE_LIMITS, SURFACE_SCHEMA_VERSION } from './types.js';
|
|
2
2
|
export type { JsonObject, JsonPrimitive, JsonValue, ResourceListDataSource, ResourceListSource, ResourceOneDataSource, ResourceOneSource, SurfaceBinding, SurfaceCatalog, SurfaceCatalogDataKind, SurfaceDataError, SurfaceDataProvider, SurfaceDataSource, SurfaceFilter, SurfaceGridLayout, SurfaceGridSpan, SurfacePolicy, SurfaceResourcePolicy, SurfaceSort, SurfaceSpec, SurfaceValidationCode, SurfaceValidationIssue, SurfaceValidationResult, SurfaceWidget, SurfaceWidgetDataState, SurfaceWidgetDefinition, } from './types.js';
|
|
3
3
|
export { validateSurfaceSpec } from './validation.js';
|
|
4
|
+
export type { SurfaceMessages } from './localization.js';
|
|
4
5
|
export { SURFACE_AGENT_SCHEMA_VERSION, buildSurfaceAgentPrompt, parseSurfaceAgentProposal, } from './agent.js';
|
|
5
6
|
export type { SurfaceAgentProposal, SurfaceAgentValidationResult, } from './agent.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface SurfaceMessages {
|
|
2
|
+
readonly renderErrorTitle: string;
|
|
3
|
+
readonly providerUnavailable: string;
|
|
4
|
+
readonly metricNoData: string;
|
|
5
|
+
readonly metricInvalidData: string;
|
|
6
|
+
readonly chartLoading: string;
|
|
7
|
+
readonly chartNoData: string;
|
|
8
|
+
readonly chartInvalidData: string;
|
|
9
|
+
readonly chartUnavailable: string;
|
|
10
|
+
readonly tableLoading: string;
|
|
11
|
+
readonly tableNoRecords: string;
|
|
12
|
+
readonly tableInvalidData: string;
|
|
13
|
+
readonly tableUnavailable: string;
|
|
14
|
+
readonly booleanTrue: string;
|
|
15
|
+
readonly booleanFalse: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function resolveSurfaceMessages(locale: string, overrides?: Partial<SurfaceMessages>): SurfaceMessages;
|
|
18
|
+
export declare function formatSurfaceMessage(template: string, params: Readonly<Record<string, string | number>>): string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const englishMessages = {
|
|
2
|
+
renderErrorTitle: 'Surface could not be rendered',
|
|
3
|
+
providerUnavailable: 'Data provider is unavailable',
|
|
4
|
+
metricNoData: 'No data',
|
|
5
|
+
metricInvalidData: 'Metric data is invalid',
|
|
6
|
+
chartLoading: 'Loading {title}',
|
|
7
|
+
chartNoData: 'No chart data',
|
|
8
|
+
chartInvalidData: 'Chart data is invalid',
|
|
9
|
+
chartUnavailable: 'Chart data is unavailable',
|
|
10
|
+
tableLoading: 'Loading table',
|
|
11
|
+
tableNoRecords: 'No records',
|
|
12
|
+
tableInvalidData: 'Table data is invalid',
|
|
13
|
+
tableUnavailable: 'Table data is unavailable',
|
|
14
|
+
booleanTrue: 'Yes',
|
|
15
|
+
booleanFalse: 'No',
|
|
16
|
+
};
|
|
17
|
+
const simplifiedChineseMessages = {
|
|
18
|
+
renderErrorTitle: '无法渲染 Surface',
|
|
19
|
+
providerUnavailable: '数据提供器不可用',
|
|
20
|
+
metricNoData: '暂无数据',
|
|
21
|
+
metricInvalidData: '指标数据无效',
|
|
22
|
+
chartLoading: '正在加载{title}',
|
|
23
|
+
chartNoData: '暂无图表数据',
|
|
24
|
+
chartInvalidData: '图表数据无效',
|
|
25
|
+
chartUnavailable: '图表数据不可用',
|
|
26
|
+
tableLoading: '正在加载表格',
|
|
27
|
+
tableNoRecords: '暂无记录',
|
|
28
|
+
tableInvalidData: '表格数据无效',
|
|
29
|
+
tableUnavailable: '表格数据不可用',
|
|
30
|
+
booleanTrue: '是',
|
|
31
|
+
booleanFalse: '否',
|
|
32
|
+
};
|
|
33
|
+
export function resolveSurfaceMessages(locale, overrides) {
|
|
34
|
+
const defaults = locale.toLowerCase().startsWith('zh')
|
|
35
|
+
? simplifiedChineseMessages
|
|
36
|
+
: englishMessages;
|
|
37
|
+
return { ...defaults, ...overrides };
|
|
38
|
+
}
|
|
39
|
+
export function formatSurfaceMessage(template, params) {
|
|
40
|
+
let formattedMessage = template;
|
|
41
|
+
for (const [key, value] of Object.entries(params)) {
|
|
42
|
+
formattedMessage = formattedMessage.replaceAll(`{${key}}`, String(value));
|
|
43
|
+
}
|
|
44
|
+
return formattedMessage;
|
|
45
|
+
}
|
package/dist/svelte.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { default as SurfaceRenderer } from './components/SurfaceRenderer.svelte';
|
|
2
2
|
export type { SurfaceRendererError, SurfaceRendererProps, } from './components/SurfaceRenderer.svelte';
|
|
3
|
+
export type { SurfaceMessages } from './localization.js';
|
|
3
4
|
export { DEFAULT_SURFACE_CATALOG_VERSION, defaultSurfaceCatalog, defineSurfaceCatalog, } from './catalog.js';
|
|
4
5
|
export type { SurfaceRenderCatalog, SurfaceWidgetRegistration, SurfaceWidgetRendererProps, } from './catalog.js';
|
package/dist/widget-data.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JsonObject, JsonValue } from './types.js';
|
|
2
|
+
import type { SurfaceMessages } from './localization.js';
|
|
2
3
|
export interface SurfaceChartPoint {
|
|
3
4
|
readonly label: string;
|
|
4
5
|
readonly value: number;
|
|
@@ -10,7 +11,12 @@ export type WidgetValueResult<T> = {
|
|
|
10
11
|
readonly ok: false;
|
|
11
12
|
readonly message: string;
|
|
12
13
|
};
|
|
13
|
-
export declare function compactChartLabel(label: string, maxLength: number): string;
|
|
14
|
+
export declare function compactChartLabel(label: string, maxLength: number, locale?: string): string;
|
|
14
15
|
export declare function asRecordArray(value: JsonValue): WidgetValueResult<readonly JsonObject[]>;
|
|
15
16
|
export declare function asChartPoints(value: JsonValue, labelField: string, valueField: string): WidgetValueResult<readonly SurfaceChartPoint[]>;
|
|
16
|
-
export
|
|
17
|
+
export interface TableValueFormatOptions {
|
|
18
|
+
readonly format: string | undefined;
|
|
19
|
+
readonly locale: string;
|
|
20
|
+
readonly messages: SurfaceMessages;
|
|
21
|
+
}
|
|
22
|
+
export declare function displayTableValue(value: JsonValue | undefined, options: TableValueFormatOptions): string;
|
package/dist/widget-data.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
export function compactChartLabel(label, maxLength) {
|
|
1
|
+
export function compactChartLabel(label, maxLength, locale = 'en-US') {
|
|
2
2
|
const isoDate = /^(?:\d{4})-(\d{2})-(\d{2})$/u.exec(label);
|
|
3
|
-
if (isoDate)
|
|
4
|
-
|
|
3
|
+
if (isoDate) {
|
|
4
|
+
const date = new Date(`${label}T00:00:00Z`);
|
|
5
|
+
return new Intl.DateTimeFormat(locale, {
|
|
6
|
+
month: 'numeric',
|
|
7
|
+
day: 'numeric',
|
|
8
|
+
timeZone: 'UTC',
|
|
9
|
+
}).format(date);
|
|
10
|
+
}
|
|
5
11
|
const characters = [...label];
|
|
6
12
|
return characters.length <= maxLength
|
|
7
13
|
? label
|
|
@@ -30,11 +36,26 @@ export function asChartPoints(value, labelField, valueField) {
|
|
|
30
36
|
}
|
|
31
37
|
return { ok: true, value: points };
|
|
32
38
|
}
|
|
33
|
-
|
|
39
|
+
function formatTableDate(value, locale) {
|
|
40
|
+
const date = new Date(typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/u.test(value)
|
|
41
|
+
? `${value}T00:00:00Z`
|
|
42
|
+
: value);
|
|
43
|
+
return Number.isNaN(date.getTime())
|
|
44
|
+
? null
|
|
45
|
+
: new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeZone: 'UTC' }).format(date);
|
|
46
|
+
}
|
|
47
|
+
export function displayTableValue(value, options) {
|
|
48
|
+
const { format, locale, messages } = options;
|
|
34
49
|
if (value === undefined || value === null)
|
|
35
50
|
return '—';
|
|
36
51
|
if (format === 'boolean')
|
|
37
|
-
return value === true ?
|
|
52
|
+
return value === true ? messages.booleanTrue : value === false ? messages.booleanFalse : '—';
|
|
53
|
+
if (format === 'number' && typeof value === 'number') {
|
|
54
|
+
return new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }).format(value);
|
|
55
|
+
}
|
|
56
|
+
if (format === 'date' && (typeof value === 'string' || typeof value === 'number')) {
|
|
57
|
+
return formatTableDate(value, locale) ?? String(value);
|
|
58
|
+
}
|
|
38
59
|
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')
|
|
39
60
|
return String(value);
|
|
40
61
|
return JSON.stringify(value);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/surface",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Validated declarative surfaces for svadmin dashboards",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@svadmin/core": ">=0.34.2 <0.
|
|
29
|
-
"@svadmin/ui": ">=0.40.6 <0.
|
|
28
|
+
"@svadmin/core": ">=0.34.2 <0.49.0",
|
|
29
|
+
"@svadmin/ui": ">=0.40.6 <0.68.0",
|
|
30
30
|
"svelte": "^5.56.8"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|