@synopackageland/ui 0.5.8 → 0.6.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/LICENSES/THIRD_PARTY.md +450 -0
- package/README.md +18 -10
- package/dist/cli.js +296 -0
- package/dist/components/app/app-layout/AppLayout.vue.d.ts +12 -5
- package/dist/components/app/page-content/PageContent.vue.d.ts +3 -0
- package/dist/components/app/responsive-view/ResponsiveView.vue.d.ts +24 -0
- package/dist/components/composite/data-table/DataTable.vue.d.ts +9 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4280 -4105
- package/dist/json/SynoJsonRenderer.vue.d.ts +21 -0
- package/dist/json/catalog.d.ts +910 -0
- package/dist/json/describe.d.ts +31 -0
- package/dist/json/registry.d.ts +13953 -0
- package/dist/json/schema.d.ts +9 -0
- package/dist/json/validate.d.ts +63 -0
- package/dist/json-ai-prompt.md +184 -0
- package/dist/json-catalog.schema.json +2723 -0
- package/dist/json-preview/preview.js +21657 -0
- package/dist/json.d.ts +10 -0
- package/dist/json.js +1117 -0
- package/dist/lib/adaptiveLayout.d.ts +9 -0
- package/dist/reka-runtime.json +1 -1
- package/dist/theme.css +1 -1
- package/dist/themes/dsm7-embedded-theme.css +1 -1
- package/dist/themes/dsm7-embedded.css +1 -1
- package/dist/themes/dsm7.css +1 -1
- package/docs/adaptive-layout.md +143 -0
- package/docs/app-local-modal.md +4 -3
- package/docs/component-defaults-0.5.7.md +90 -0
- package/docs/json-ui.md +154 -0
- package/package.json +15 -3
package/dist/json.js
ADDED
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
import { z as o } from "zod";
|
|
2
|
+
import { z as gt } from "zod";
|
|
3
|
+
import { defineCatalog as z } from "@json-render/core";
|
|
4
|
+
import { createStateStore as yt, defineCatalog as wt } from "@json-render/core";
|
|
5
|
+
import { defineRegistry as B, useBoundProp as b, JSONUIProvider as M, Renderer as W } from "@json-render/vue";
|
|
6
|
+
import { ActionProvider as $t, JSONUIProvider as St, Renderer as jt, StateProvider as Ot, ValidationProvider as xt, VisibilityProvider as At, defineRegistry as Pt, useActions as Ct, useBoundProp as kt, useStateStore as Rt } from "@json-render/vue";
|
|
7
|
+
import { schema as H } from "@json-render/vue/schema";
|
|
8
|
+
import { schema as It } from "@json-render/vue/schema";
|
|
9
|
+
import { h as d, defineComponent as G, openBlock as X, createBlock as Y, unref as j, withCtx as Q, createVNode as Z } from "vue";
|
|
10
|
+
import { LoadingArea as ee, DataTable as I, Dialog as te, Badge as oe, Progress as ne, EmptyState as ie, Callout as se, Card as ae, Surface as re, SettingsRow as le, SettingsSection as ce, FormItem as de, Form as pe, RadioGroup as ue, Switch as me, Checkbox as he, Select as fe, Input as ge, Button as be, SectionTabs as ye, AppSidebar as we, AppToolbar as ve, PageHeader as $e, PageContent as Se, ResponsiveView as je, AppLayout as Oe, AppRoot as xe, getRegisteredIcon as Ae } from "./index.js";
|
|
11
|
+
const V = o.lazy(() => o.object({
|
|
12
|
+
key: o.string(),
|
|
13
|
+
label: o.string(),
|
|
14
|
+
icon: o.string().optional(),
|
|
15
|
+
badge: o.union([o.string(), o.number()]).optional(),
|
|
16
|
+
disabled: o.boolean().optional(),
|
|
17
|
+
selectable: o.boolean().optional(),
|
|
18
|
+
children: o.array(V).optional()
|
|
19
|
+
})), C = o.object({
|
|
20
|
+
label: o.string(),
|
|
21
|
+
value: o.string(),
|
|
22
|
+
disabled: o.boolean().optional()
|
|
23
|
+
}), Pe = C.extend({
|
|
24
|
+
description: o.string().optional()
|
|
25
|
+
}), U = o.object({
|
|
26
|
+
key: o.string(),
|
|
27
|
+
label: o.string(),
|
|
28
|
+
width: o.string().optional(),
|
|
29
|
+
align: o.enum(["left", "center", "right"]).optional()
|
|
30
|
+
}), _ = {
|
|
31
|
+
AppRoot: {
|
|
32
|
+
props: o.object({
|
|
33
|
+
modalScope: o.enum(["document", "app"]).optional()
|
|
34
|
+
}),
|
|
35
|
+
slots: ["default"],
|
|
36
|
+
description: 'Root of a DSM application. Use once around the application shell. Dialogs stay inside the app by default (modalScope "app"); "document" is only for full-page web/Portal apps.'
|
|
37
|
+
},
|
|
38
|
+
AppShell: {
|
|
39
|
+
props: o.object({
|
|
40
|
+
sidebarWidth: o.string().optional(),
|
|
41
|
+
minHeight: o.string().optional(),
|
|
42
|
+
responsive: o.boolean().optional(),
|
|
43
|
+
wideLayout: o.enum(["window", "web"]).optional()
|
|
44
|
+
}),
|
|
45
|
+
slots: ["default", "sidebar", "toolbar", "footer"],
|
|
46
|
+
description: 'DSM application interior with optional sidebar, toolbar, main content and footer regions. Set wideLayout "web" only for a standalone Portal page; DSM windows keep the default "window" at every width.'
|
|
47
|
+
},
|
|
48
|
+
ResponsiveView: {
|
|
49
|
+
props: o.object({ mode: o.enum(["auto", "mobile", "window", "web"]).optional() }),
|
|
50
|
+
slots: ["default", "mobile", "window", "web"],
|
|
51
|
+
description: 'Optionally replace a specialized element with a phone-friendly alternative. mobile/window/web slots follow AppShell container width (web only with AppShell wideLayout "web"); default is always the fallback.'
|
|
52
|
+
},
|
|
53
|
+
Page: {
|
|
54
|
+
props: o.object({
|
|
55
|
+
width: o.string().optional(),
|
|
56
|
+
surface: o.enum(["auto", "flat", "card"]).optional()
|
|
57
|
+
}),
|
|
58
|
+
slots: ["default"],
|
|
59
|
+
description: 'Page content area. Use width "fill" for data-heavy screens and "readable" for forms/settings.'
|
|
60
|
+
},
|
|
61
|
+
PageHeader: {
|
|
62
|
+
props: o.object({
|
|
63
|
+
title: o.string(),
|
|
64
|
+
description: o.string().optional()
|
|
65
|
+
}),
|
|
66
|
+
slots: ["actions"],
|
|
67
|
+
description: "Page heading with optional description and page-level actions."
|
|
68
|
+
},
|
|
69
|
+
Toolbar: {
|
|
70
|
+
props: o.object({ height: o.string().optional() }),
|
|
71
|
+
slots: ["default", "leading", "actions"],
|
|
72
|
+
description: "Application toolbar. Put primary page-independent actions in the actions slot."
|
|
73
|
+
},
|
|
74
|
+
SidebarNav: {
|
|
75
|
+
props: o.object({
|
|
76
|
+
value: o.string().optional(),
|
|
77
|
+
groups: o.array(o.object({
|
|
78
|
+
label: o.string().optional(),
|
|
79
|
+
items: o.array(V)
|
|
80
|
+
})),
|
|
81
|
+
searchable: o.boolean().optional(),
|
|
82
|
+
collapsible: o.boolean().optional(),
|
|
83
|
+
searchPlaceholder: o.string().optional()
|
|
84
|
+
}),
|
|
85
|
+
events: ["change"],
|
|
86
|
+
description: "DSM sidebar navigation. Icon values are names registered by the host application."
|
|
87
|
+
},
|
|
88
|
+
Tabs: {
|
|
89
|
+
props: o.object({
|
|
90
|
+
value: o.string(),
|
|
91
|
+
items: o.array(C),
|
|
92
|
+
ariaLabel: o.string().optional()
|
|
93
|
+
}),
|
|
94
|
+
events: ["change"],
|
|
95
|
+
description: "Section tabs for switching a local mode or filter. Bind value to state for two-way updates."
|
|
96
|
+
},
|
|
97
|
+
Stack: {
|
|
98
|
+
props: o.object({
|
|
99
|
+
direction: o.enum(["row", "column"]).optional(),
|
|
100
|
+
gap: o.enum(["none", "xs", "sm", "md", "lg"]).optional(),
|
|
101
|
+
align: o.enum(["start", "center", "end", "stretch"]).optional(),
|
|
102
|
+
justify: o.enum(["start", "center", "end", "between"]).optional(),
|
|
103
|
+
wrap: o.boolean().optional()
|
|
104
|
+
}),
|
|
105
|
+
slots: ["default"],
|
|
106
|
+
description: "Simple layout composition. Prefer semantic components first and use Stack only for local arrangement."
|
|
107
|
+
},
|
|
108
|
+
Text: {
|
|
109
|
+
props: o.object({
|
|
110
|
+
text: o.string(),
|
|
111
|
+
variant: o.enum(["body", "muted", "caption", "heading", "mono"]).optional()
|
|
112
|
+
}),
|
|
113
|
+
description: "Short application text. Do not use it when a semantic component already owns the label/description."
|
|
114
|
+
},
|
|
115
|
+
Button: {
|
|
116
|
+
props: o.object({
|
|
117
|
+
label: o.string(),
|
|
118
|
+
variant: o.enum(["primary", "secondary", "ghost", "danger"]).optional(),
|
|
119
|
+
size: o.enum(["sm", "md", "lg"]).optional(),
|
|
120
|
+
disabled: o.boolean().optional(),
|
|
121
|
+
loading: o.boolean().optional(),
|
|
122
|
+
icon: o.string().optional()
|
|
123
|
+
}),
|
|
124
|
+
events: ["press"],
|
|
125
|
+
description: "User action. Use one primary action per local task when possible. Icon is an optional host-registered icon name."
|
|
126
|
+
},
|
|
127
|
+
Input: {
|
|
128
|
+
props: o.object({
|
|
129
|
+
value: o.union([o.string(), o.number()]).optional(),
|
|
130
|
+
type: o.string().optional(),
|
|
131
|
+
placeholder: o.string().optional(),
|
|
132
|
+
disabled: o.boolean().optional(),
|
|
133
|
+
readonly: o.boolean().optional(),
|
|
134
|
+
invalid: o.boolean().optional(),
|
|
135
|
+
size: o.enum(["sm", "md", "lg"]).optional(),
|
|
136
|
+
ariaLabel: o.string().optional()
|
|
137
|
+
}),
|
|
138
|
+
events: ["change"],
|
|
139
|
+
description: "Single-line input. Use $bindState/$bindItem on value for editable state."
|
|
140
|
+
},
|
|
141
|
+
Select: {
|
|
142
|
+
props: o.object({
|
|
143
|
+
value: o.string().optional(),
|
|
144
|
+
options: o.array(C),
|
|
145
|
+
placeholder: o.string().optional(),
|
|
146
|
+
disabled: o.boolean().optional(),
|
|
147
|
+
search: o.boolean().optional()
|
|
148
|
+
}),
|
|
149
|
+
events: ["change"],
|
|
150
|
+
description: "Single-choice select. Use $bindState/$bindItem on value."
|
|
151
|
+
},
|
|
152
|
+
Checkbox: {
|
|
153
|
+
props: o.object({
|
|
154
|
+
checked: o.boolean().optional(),
|
|
155
|
+
label: o.string().optional(),
|
|
156
|
+
description: o.string().optional(),
|
|
157
|
+
disabled: o.boolean().optional()
|
|
158
|
+
}),
|
|
159
|
+
events: ["change"],
|
|
160
|
+
description: "Boolean choice. Use $bindState/$bindItem on checked."
|
|
161
|
+
},
|
|
162
|
+
Switch: {
|
|
163
|
+
props: o.object({
|
|
164
|
+
checked: o.boolean().optional(),
|
|
165
|
+
label: o.string().optional(),
|
|
166
|
+
disabled: o.boolean().optional()
|
|
167
|
+
}),
|
|
168
|
+
events: ["change"],
|
|
169
|
+
description: "Immediate on/off setting. Use $bindState/$bindItem on checked."
|
|
170
|
+
},
|
|
171
|
+
RadioGroup: {
|
|
172
|
+
props: o.object({
|
|
173
|
+
value: o.string().optional(),
|
|
174
|
+
options: o.array(Pe),
|
|
175
|
+
orientation: o.enum(["vertical", "horizontal"]).optional(),
|
|
176
|
+
disabled: o.boolean().optional()
|
|
177
|
+
}),
|
|
178
|
+
events: ["change"],
|
|
179
|
+
description: "Choose one of a small visible set of mutually-exclusive options."
|
|
180
|
+
},
|
|
181
|
+
Form: {
|
|
182
|
+
props: o.object({
|
|
183
|
+
direction: o.enum(["auto", "horizontal", "vertical"]).optional(),
|
|
184
|
+
fieldWidth: o.string().optional()
|
|
185
|
+
}),
|
|
186
|
+
slots: ["default"],
|
|
187
|
+
events: ["submit"],
|
|
188
|
+
description: "Form container. Put Field components inside and bind controls to state."
|
|
189
|
+
},
|
|
190
|
+
Field: {
|
|
191
|
+
props: o.object({
|
|
192
|
+
label: o.string(),
|
|
193
|
+
description: o.string().optional(),
|
|
194
|
+
help: o.string().optional(),
|
|
195
|
+
required: o.boolean().optional(),
|
|
196
|
+
fieldWidth: o.string().optional(),
|
|
197
|
+
direction: o.enum(["auto", "horizontal", "vertical"]).optional()
|
|
198
|
+
}),
|
|
199
|
+
slots: ["default"],
|
|
200
|
+
description: "Labeled form field for create/edit dialogs and forms."
|
|
201
|
+
},
|
|
202
|
+
SettingsSection: {
|
|
203
|
+
props: o.object({
|
|
204
|
+
title: o.string(),
|
|
205
|
+
collapsible: o.boolean().optional(),
|
|
206
|
+
open: o.boolean().optional()
|
|
207
|
+
}),
|
|
208
|
+
slots: ["default", "actions"],
|
|
209
|
+
events: ["openChange"],
|
|
210
|
+
description: "Section of related settings. Prefer this plus SettingsField for DSM settings pages."
|
|
211
|
+
},
|
|
212
|
+
SettingsField: {
|
|
213
|
+
props: o.object({
|
|
214
|
+
label: o.string(),
|
|
215
|
+
description: o.string().optional(),
|
|
216
|
+
required: o.boolean().optional(),
|
|
217
|
+
fieldWidth: o.string().optional(),
|
|
218
|
+
align: o.enum(["start", "center"]).optional()
|
|
219
|
+
}),
|
|
220
|
+
slots: ["default"],
|
|
221
|
+
description: "DSM settings row with label, description and one control/content region."
|
|
222
|
+
},
|
|
223
|
+
Surface: {
|
|
224
|
+
props: o.object({
|
|
225
|
+
padding: o.enum(["none", "sm", "md", "lg"]).optional(),
|
|
226
|
+
border: o.boolean().optional(),
|
|
227
|
+
raised: o.boolean().optional()
|
|
228
|
+
}),
|
|
229
|
+
slots: ["default"],
|
|
230
|
+
description: "Neutral bordered/raised content surface when Card semantics are not needed."
|
|
231
|
+
},
|
|
232
|
+
Card: {
|
|
233
|
+
props: o.object({
|
|
234
|
+
title: o.string().optional(),
|
|
235
|
+
layoutType: o.enum(["regular", "compact"]).optional(),
|
|
236
|
+
expandable: o.boolean().optional(),
|
|
237
|
+
expanded: o.boolean().optional(),
|
|
238
|
+
footerAlign: o.enum(["start", "end", "between"]).optional()
|
|
239
|
+
}),
|
|
240
|
+
slots: ["default", "header-actions", "footer"],
|
|
241
|
+
events: ["expandedChange"],
|
|
242
|
+
description: "Grouped content with optional header/actions/footer. Do not use a Card just to add spacing."
|
|
243
|
+
},
|
|
244
|
+
Callout: {
|
|
245
|
+
props: o.object({
|
|
246
|
+
tone: o.enum(["info", "success", "warning", "danger"]).optional(),
|
|
247
|
+
title: o.string().optional(),
|
|
248
|
+
closable: o.boolean().optional()
|
|
249
|
+
}),
|
|
250
|
+
slots: ["default", "actions"],
|
|
251
|
+
events: ["close"],
|
|
252
|
+
description: "Inline information, success, warning or error message."
|
|
253
|
+
},
|
|
254
|
+
EmptyState: {
|
|
255
|
+
props: o.object({
|
|
256
|
+
title: o.string(),
|
|
257
|
+
description: o.string().optional(),
|
|
258
|
+
icon: o.string().optional()
|
|
259
|
+
}),
|
|
260
|
+
slots: ["actions"],
|
|
261
|
+
description: "Empty/no-results state with optional recovery or create actions."
|
|
262
|
+
},
|
|
263
|
+
Progress: {
|
|
264
|
+
props: o.object({
|
|
265
|
+
value: o.number().optional(),
|
|
266
|
+
max: o.number().optional(),
|
|
267
|
+
label: o.string().optional(),
|
|
268
|
+
valueLabel: o.string().optional()
|
|
269
|
+
}),
|
|
270
|
+
description: "Linear determinate progress. Domain-specific progress visualizations should be custom components."
|
|
271
|
+
},
|
|
272
|
+
Badge: {
|
|
273
|
+
props: o.object({
|
|
274
|
+
text: o.string(),
|
|
275
|
+
tone: o.enum(["neutral", "info", "success", "warning", "danger"]).optional()
|
|
276
|
+
}),
|
|
277
|
+
description: "Short status/category badge."
|
|
278
|
+
},
|
|
279
|
+
Dialog: {
|
|
280
|
+
props: o.object({
|
|
281
|
+
open: o.boolean().optional(),
|
|
282
|
+
title: o.string().optional(),
|
|
283
|
+
description: o.string().optional(),
|
|
284
|
+
width: o.string().optional(),
|
|
285
|
+
showClose: o.boolean().optional()
|
|
286
|
+
}),
|
|
287
|
+
slots: ["default", "footer"],
|
|
288
|
+
events: ["openChange"],
|
|
289
|
+
description: "Modal task/dialog. Bind open to state. Put confirmation or form actions in footer."
|
|
290
|
+
},
|
|
291
|
+
DataTable: {
|
|
292
|
+
props: o.object({
|
|
293
|
+
columns: o.array(U),
|
|
294
|
+
rows: o.array(o.record(o.string(), o.unknown())),
|
|
295
|
+
rowKey: o.string().optional(),
|
|
296
|
+
emptyText: o.string().optional(),
|
|
297
|
+
compact: o.boolean().optional(),
|
|
298
|
+
mobileView: o.enum(["auto", "cards", "table"]).optional()
|
|
299
|
+
}),
|
|
300
|
+
description: "Simple resource table where cells display scalar row fields. Use an app extension component for rich/custom cells and row actions."
|
|
301
|
+
},
|
|
302
|
+
ResourceList: {
|
|
303
|
+
props: o.object({
|
|
304
|
+
columns: o.array(U),
|
|
305
|
+
rows: o.array(o.record(o.string(), o.unknown())),
|
|
306
|
+
rowKey: o.string().optional(),
|
|
307
|
+
emptyText: o.string().optional(),
|
|
308
|
+
compact: o.boolean().optional(),
|
|
309
|
+
mobileView: o.enum(["auto", "cards", "table"]).optional()
|
|
310
|
+
}),
|
|
311
|
+
slots: ["toolbar"],
|
|
312
|
+
description: "Common resource collection pattern: optional toolbar plus a simple table. For domain-specific row rendering, extend the catalog with a custom list/table component."
|
|
313
|
+
},
|
|
314
|
+
Loading: {
|
|
315
|
+
props: o.object({ text: o.string().optional() }),
|
|
316
|
+
description: "Centered loading state for a page or bounded region."
|
|
317
|
+
}
|
|
318
|
+
}, A = z(H, {
|
|
319
|
+
components: _,
|
|
320
|
+
actions: {}
|
|
321
|
+
}), Ce = [
|
|
322
|
+
"Prefer AppRoot > AppShell > Page as the normal DSM application hierarchy.",
|
|
323
|
+
"Prefer SettingsSection and SettingsField for settings; prefer ResourceList/DataTable for ordinary scalar resource collections.",
|
|
324
|
+
"Keep state in the spec and use $bindState/$bindItem for editable controls; do not invent JavaScript in props.",
|
|
325
|
+
"Use host-declared actions for side effects. Never encode fetch, timers, filesystem operations, or business logic as UI expressions.",
|
|
326
|
+
"If a domain view needs custom rendering or behavior (for example a treemap, terminal, rich download row, NAS path picker), use a host extension component instead of forcing it into generic components.",
|
|
327
|
+
"AppShell adapts to its actual DSM window width: phone (<=600px), desktop window (601–1199px), and wide web/Portal (>=1200px). Never assume browser viewport width.",
|
|
328
|
+
"For a phone-unfriendly custom view, use ResponsiveView slots: mobile alternative and default full view. Keep essential actions accessible on mobile.",
|
|
329
|
+
"Use low-level Stack/Text only when a semantic component does not already express the layout."
|
|
330
|
+
], ke = '## Self-check workflow\n\n1. List components with `npx syno-ui catalog`; read props with `npx syno-ui describe <Component>`.\n2. Write the spec as JSON: {"root", "elements", "state"}. Seed every state path you bind or read.\n3. Run `npx syno-ui validate ui.json --json` (add --actions, --functions, --state, --messages for host-provided names).\n Fix every error at its JSON Pointer `path` using its `hint`, then validate again until it passes.\n4. Run `npx syno-ui render ui.json --out shots` to screenshot phone, DSM window and wide widths and to\n report layout overflow; fix reported elements and repeat.\n5. Hand over the spec only when validate and render both exit 0.';
|
|
331
|
+
function rt() {
|
|
332
|
+
return `${A.prompt({ customRules: Ce })}
|
|
333
|
+
|
|
334
|
+
${ke}
|
|
335
|
+
`;
|
|
336
|
+
}
|
|
337
|
+
const Re = {
|
|
338
|
+
none: "0",
|
|
339
|
+
xs: "var(--ui-space-1)",
|
|
340
|
+
sm: "var(--ui-space-2)",
|
|
341
|
+
md: "var(--ui-space-3)",
|
|
342
|
+
lg: "var(--ui-space-4)"
|
|
343
|
+
}, Ke = { start: "flex-start", center: "center", end: "flex-end", stretch: "stretch" }, Ie = { start: "flex-start", center: "center", end: "flex-end", between: "space-between" };
|
|
344
|
+
function Ue(e) {
|
|
345
|
+
return { default: () => e };
|
|
346
|
+
}
|
|
347
|
+
function k(e) {
|
|
348
|
+
return e ? Ae(e) : void 0;
|
|
349
|
+
}
|
|
350
|
+
function T(e) {
|
|
351
|
+
return {
|
|
352
|
+
...e,
|
|
353
|
+
icon: k(e.icon),
|
|
354
|
+
children: e.children?.map(T)
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function De(e) {
|
|
358
|
+
return e.map((t) => ({ ...t, items: t.items.map(T) }));
|
|
359
|
+
}
|
|
360
|
+
const Le = {
|
|
361
|
+
AppRoot: ({ props: e, slots: t }) => d(xe, { modalScope: e.modalScope }, t),
|
|
362
|
+
AppShell: ({ props: e, slots: t }) => d(Oe, {
|
|
363
|
+
sidebarWidth: e.sidebarWidth,
|
|
364
|
+
minHeight: e.minHeight,
|
|
365
|
+
responsive: e.responsive,
|
|
366
|
+
wideLayout: e.wideLayout
|
|
367
|
+
}, t),
|
|
368
|
+
ResponsiveView: ({ props: e, slots: t }) => d(je, { mode: e.mode }, t),
|
|
369
|
+
Page: ({ props: e, slots: t }) => d(Se, { width: e.width ?? "fill", surface: e.surface }, t),
|
|
370
|
+
PageHeader: ({ props: e, slots: t }) => d($e, e, t),
|
|
371
|
+
Toolbar: ({ props: e, slots: t }) => d(ve, e, t),
|
|
372
|
+
SidebarNav: ({ props: e, bindings: t, emit: n }) => {
|
|
373
|
+
const [s, i] = b(e.value, t?.value);
|
|
374
|
+
return d(we, {
|
|
375
|
+
modelValue: s,
|
|
376
|
+
groups: De(e.groups),
|
|
377
|
+
searchable: e.searchable,
|
|
378
|
+
collapsible: e.collapsible,
|
|
379
|
+
searchPlaceholder: e.searchPlaceholder,
|
|
380
|
+
"onUpdate:modelValue": (a) => {
|
|
381
|
+
i(a), n("change");
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
},
|
|
385
|
+
Tabs: ({ props: e, bindings: t, emit: n }) => {
|
|
386
|
+
const [s, i] = b(e.value, t?.value);
|
|
387
|
+
return d(ye, {
|
|
388
|
+
modelValue: s ?? "",
|
|
389
|
+
items: e.items,
|
|
390
|
+
ariaLabel: e.ariaLabel,
|
|
391
|
+
"onUpdate:modelValue": (a) => {
|
|
392
|
+
i(a), n("change");
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
},
|
|
396
|
+
Stack: ({ props: e, children: t }) => d("div", {
|
|
397
|
+
"data-syno-ui": "",
|
|
398
|
+
style: {
|
|
399
|
+
display: "flex",
|
|
400
|
+
flexDirection: e.direction ?? "column",
|
|
401
|
+
gap: Re[e.gap ?? "md"],
|
|
402
|
+
alignItems: Ke[e.align ?? "stretch"],
|
|
403
|
+
justifyContent: Ie[e.justify ?? "start"],
|
|
404
|
+
flexWrap: e.wrap ? "wrap" : "nowrap"
|
|
405
|
+
}
|
|
406
|
+
}, t),
|
|
407
|
+
Text: ({ props: e }) => {
|
|
408
|
+
const t = e.variant ?? "body";
|
|
409
|
+
return d(t === "heading" ? "h2" : t === "mono" ? "code" : "span", { class: t === "muted" ? "text-[13px] text-[var(--ui-text-muted)]" : t === "caption" ? "text-[12px] text-[var(--ui-text-muted)]" : t === "heading" ? "text-[16px] font-semibold text-[var(--ui-text)]" : t === "mono" ? "font-mono text-[12px] text-[var(--ui-text)]" : "text-[13px] text-[var(--ui-text)]", "data-syno-ui": "" }, e.text);
|
|
410
|
+
},
|
|
411
|
+
Button: ({ props: e, emit: t }) => {
|
|
412
|
+
const n = k(e.icon);
|
|
413
|
+
return d(be, {
|
|
414
|
+
variant: e.variant,
|
|
415
|
+
size: e.size,
|
|
416
|
+
disabled: e.disabled,
|
|
417
|
+
loading: e.loading,
|
|
418
|
+
onClick: () => t("press")
|
|
419
|
+
}, {
|
|
420
|
+
default: () => [n ? d(n, { class: "size-4", "aria-hidden": "true" }) : null, e.label]
|
|
421
|
+
});
|
|
422
|
+
},
|
|
423
|
+
Input: ({ props: e, bindings: t, emit: n }) => {
|
|
424
|
+
const [s, i] = b(e.value, t?.value);
|
|
425
|
+
return d(ge, {
|
|
426
|
+
modelValue: s,
|
|
427
|
+
type: e.type,
|
|
428
|
+
placeholder: e.placeholder,
|
|
429
|
+
disabled: e.disabled,
|
|
430
|
+
readonly: e.readonly,
|
|
431
|
+
invalid: e.invalid,
|
|
432
|
+
size: e.size,
|
|
433
|
+
ariaLabel: e.ariaLabel,
|
|
434
|
+
"onUpdate:modelValue": (a) => {
|
|
435
|
+
i(e.type === "number" && a !== "" ? Number(a) : a), n("change");
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
},
|
|
439
|
+
Select: ({ props: e, bindings: t, emit: n }) => {
|
|
440
|
+
const [s, i] = b(e.value, t?.value);
|
|
441
|
+
return d(fe, {
|
|
442
|
+
modelValue: s,
|
|
443
|
+
options: e.options,
|
|
444
|
+
placeholder: e.placeholder,
|
|
445
|
+
disabled: e.disabled,
|
|
446
|
+
search: e.search,
|
|
447
|
+
"onUpdate:modelValue": (a) => {
|
|
448
|
+
i(a), n("change");
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
},
|
|
452
|
+
Checkbox: ({ props: e, bindings: t, emit: n }) => {
|
|
453
|
+
const [s, i] = b(e.checked, t?.checked);
|
|
454
|
+
return d(he, {
|
|
455
|
+
modelValue: s ?? !1,
|
|
456
|
+
label: e.label,
|
|
457
|
+
description: e.description,
|
|
458
|
+
disabled: e.disabled,
|
|
459
|
+
"onUpdate:modelValue": (a) => {
|
|
460
|
+
i(a), n("change");
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
},
|
|
464
|
+
Switch: ({ props: e, bindings: t, emit: n }) => {
|
|
465
|
+
const [s, i] = b(e.checked, t?.checked);
|
|
466
|
+
return d(me, {
|
|
467
|
+
modelValue: s ?? !1,
|
|
468
|
+
label: e.label,
|
|
469
|
+
disabled: e.disabled,
|
|
470
|
+
"onUpdate:modelValue": (a) => {
|
|
471
|
+
i(a), n("change");
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
},
|
|
475
|
+
RadioGroup: ({ props: e, bindings: t, emit: n }) => {
|
|
476
|
+
const [s, i] = b(e.value, t?.value);
|
|
477
|
+
return d(ue, {
|
|
478
|
+
modelValue: s,
|
|
479
|
+
options: e.options,
|
|
480
|
+
orientation: e.orientation,
|
|
481
|
+
disabled: e.disabled,
|
|
482
|
+
"onUpdate:modelValue": (a) => {
|
|
483
|
+
i(a), n("change");
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
},
|
|
487
|
+
Form: ({ props: e, slots: t, emit: n }) => d(pe, {
|
|
488
|
+
direction: e.direction,
|
|
489
|
+
fieldWidth: e.fieldWidth,
|
|
490
|
+
onSubmit: () => n("submit")
|
|
491
|
+
}, t),
|
|
492
|
+
Field: ({ props: e, slots: t }) => d(de, {
|
|
493
|
+
label: e.label,
|
|
494
|
+
description: e.description,
|
|
495
|
+
help: e.help,
|
|
496
|
+
requiredMark: e.required,
|
|
497
|
+
fieldWidth: e.fieldWidth,
|
|
498
|
+
direction: e.direction
|
|
499
|
+
}, t),
|
|
500
|
+
SettingsSection: ({ props: e, bindings: t, slots: n, emit: s }) => {
|
|
501
|
+
const [i, a] = b(e.open, t?.open);
|
|
502
|
+
return d(ce, {
|
|
503
|
+
title: e.title,
|
|
504
|
+
collapsible: e.collapsible,
|
|
505
|
+
open: i,
|
|
506
|
+
"onUpdate:open": (r) => {
|
|
507
|
+
a(r), s("openChange");
|
|
508
|
+
}
|
|
509
|
+
}, n);
|
|
510
|
+
},
|
|
511
|
+
SettingsField: ({ props: e, slots: t }) => d(le, {
|
|
512
|
+
label: e.label,
|
|
513
|
+
description: e.description,
|
|
514
|
+
required: e.required,
|
|
515
|
+
fieldWidth: e.fieldWidth,
|
|
516
|
+
align: e.align
|
|
517
|
+
}, t),
|
|
518
|
+
Surface: ({ props: e, slots: t }) => d(re, e, t),
|
|
519
|
+
Card: ({ props: e, bindings: t, slots: n, emit: s }) => {
|
|
520
|
+
const [i, a] = b(e.expanded, t?.expanded);
|
|
521
|
+
return d(ae, {
|
|
522
|
+
title: e.title,
|
|
523
|
+
layoutType: e.layoutType,
|
|
524
|
+
expandable: e.expandable,
|
|
525
|
+
expanded: i,
|
|
526
|
+
footerAlign: e.footerAlign,
|
|
527
|
+
"onUpdate:expanded": (r) => {
|
|
528
|
+
a(r), s("expandedChange");
|
|
529
|
+
}
|
|
530
|
+
}, n);
|
|
531
|
+
},
|
|
532
|
+
Callout: ({ props: e, slots: t, emit: n }) => d(se, {
|
|
533
|
+
tone: e.tone,
|
|
534
|
+
title: e.title,
|
|
535
|
+
closable: e.closable,
|
|
536
|
+
onClose: () => n("close")
|
|
537
|
+
}, t),
|
|
538
|
+
EmptyState: ({ props: e, slots: t }) => d(ie, {
|
|
539
|
+
title: e.title,
|
|
540
|
+
description: e.description,
|
|
541
|
+
icon: k(e.icon)
|
|
542
|
+
}, t),
|
|
543
|
+
Progress: ({ props: e }) => d(ne, e),
|
|
544
|
+
Badge: ({ props: e }) => d(oe, { tone: e.tone }, Ue(e.text)),
|
|
545
|
+
Dialog: ({ props: e, bindings: t, slots: n, emit: s }) => {
|
|
546
|
+
const [i, a] = b(e.open, t?.open);
|
|
547
|
+
return d(te, {
|
|
548
|
+
open: i ?? !1,
|
|
549
|
+
title: e.title,
|
|
550
|
+
description: e.description,
|
|
551
|
+
width: e.width,
|
|
552
|
+
showClose: e.showClose,
|
|
553
|
+
"onUpdate:open": (r) => {
|
|
554
|
+
a(r), s("openChange");
|
|
555
|
+
}
|
|
556
|
+
}, n);
|
|
557
|
+
},
|
|
558
|
+
DataTable: ({ props: e }) => d(I, e),
|
|
559
|
+
ResourceList: ({ props: e, slots: t }) => d("section", { class: "min-w-0", "data-syno-ui": "", "data-ui-resource-list": "" }, [
|
|
560
|
+
t.toolbar ? d("div", { class: "mb-3 flex flex-wrap items-center gap-2" }, t.toolbar()) : null,
|
|
561
|
+
d(I, e)
|
|
562
|
+
]),
|
|
563
|
+
Loading: ({ props: e }) => d(ee, { text: e.text })
|
|
564
|
+
}, { registry: D } = B(A, {
|
|
565
|
+
components: Le
|
|
566
|
+
}), lt = /* @__PURE__ */ G({
|
|
567
|
+
__name: "SynoJsonRenderer",
|
|
568
|
+
props: {
|
|
569
|
+
spec: {},
|
|
570
|
+
store: {},
|
|
571
|
+
state: {},
|
|
572
|
+
handlers: {},
|
|
573
|
+
navigate: { type: Function },
|
|
574
|
+
functions: {},
|
|
575
|
+
directives: {},
|
|
576
|
+
validationFunctions: {},
|
|
577
|
+
onStateChange: { type: Function },
|
|
578
|
+
loading: { type: Boolean },
|
|
579
|
+
fallback: {}
|
|
580
|
+
},
|
|
581
|
+
setup(e) {
|
|
582
|
+
return (t, n) => (X(), Y(j(M), {
|
|
583
|
+
registry: j(D),
|
|
584
|
+
store: e.store,
|
|
585
|
+
"initial-state": e.state ?? e.spec?.state ?? {},
|
|
586
|
+
handlers: e.handlers,
|
|
587
|
+
navigate: e.navigate,
|
|
588
|
+
functions: e.functions,
|
|
589
|
+
directives: e.directives,
|
|
590
|
+
"validation-functions": e.validationFunctions,
|
|
591
|
+
"on-state-change": e.onStateChange
|
|
592
|
+
}, {
|
|
593
|
+
default: Q(() => [
|
|
594
|
+
Z(j(W), {
|
|
595
|
+
spec: e.spec,
|
|
596
|
+
registry: j(D),
|
|
597
|
+
loading: e.loading,
|
|
598
|
+
fallback: e.fallback
|
|
599
|
+
}, null, 8, ["spec", "registry", "loading", "fallback"])
|
|
600
|
+
]),
|
|
601
|
+
_: 1
|
|
602
|
+
}, 8, ["registry", "store", "initial-state", "handlers", "navigate", "functions", "directives", "validation-functions", "on-state-change"]));
|
|
603
|
+
}
|
|
604
|
+
}), Ee = ["label", "title", "description", "text", "placeholder", "emptyText", "help", "ariaLabel", "valueLabel", "searchPlaceholder"];
|
|
605
|
+
function q(e, t = "") {
|
|
606
|
+
return Array.isArray(e) ? e : Object.entries(e).flatMap(([n, s]) => m(s) ? q(s, `${t}${n}.`) : [`${t}${n}`]);
|
|
607
|
+
}
|
|
608
|
+
const m = (e) => typeof e == "object" && e !== null && !Array.isArray(e), u = (...e) => e.map((t) => "/" + String(t).replaceAll("~", "~0").replaceAll("/", "~1")).join("");
|
|
609
|
+
function Ne(e, t) {
|
|
610
|
+
const n = Array.from({ length: t.length + 1 }, (s, i) => i);
|
|
611
|
+
for (let s = 1; s <= e.length; s++) {
|
|
612
|
+
let i = n[0];
|
|
613
|
+
n[0] = s;
|
|
614
|
+
for (let a = 1; a <= t.length; a++) {
|
|
615
|
+
const r = n[a];
|
|
616
|
+
n[a] = Math.min(n[a] + 1, n[a - 1] + 1, i + (e[s - 1].toLowerCase() === t[a - 1].toLowerCase() ? 0 : 1)), i = r;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return n[t.length];
|
|
620
|
+
}
|
|
621
|
+
function y(e, t) {
|
|
622
|
+
let n, s = 1 / 0;
|
|
623
|
+
for (const i of t) {
|
|
624
|
+
const a = Ne(e, i);
|
|
625
|
+
a < s && ([n, s] = [i, a]);
|
|
626
|
+
}
|
|
627
|
+
return n !== void 0 && s <= Math.max(2, Math.floor(e.length / 3)) ? n : void 0;
|
|
628
|
+
}
|
|
629
|
+
const Je = ["$state", "$item", "$index", "$bindState", "$bindItem", "$cond", "$computed", "$template"], Ve = (e) => m(e) && Je.some((t) => Object.hasOwn(e, t)), R = (e) => Ve(e) || (Array.isArray(e) ? e.some(R) : m(e) && Object.values(e).some(R));
|
|
630
|
+
function P(e) {
|
|
631
|
+
const t = e.props?.shape;
|
|
632
|
+
return m(t) ? t : void 0;
|
|
633
|
+
}
|
|
634
|
+
const K = (e) => e.safeParse(void 0).success;
|
|
635
|
+
function _e(e) {
|
|
636
|
+
if (e.code === "invalid_value") return `Use one of: ${e.values.map((t) => JSON.stringify(t)).join(", ")}.`;
|
|
637
|
+
if (e.code === "invalid_type") return `Expected ${e.expected}.`;
|
|
638
|
+
}
|
|
639
|
+
const v = (e, t) => `${e}: ${t.join(", ")}`;
|
|
640
|
+
class Te {
|
|
641
|
+
errors = [];
|
|
642
|
+
warnings = [];
|
|
643
|
+
error(t) {
|
|
644
|
+
this.errors.push({ severity: "error", ...t });
|
|
645
|
+
}
|
|
646
|
+
warn(t) {
|
|
647
|
+
this.warnings.push({ severity: "warning", ...t });
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function ct(e, t = {}) {
|
|
651
|
+
const n = t.catalog ?? A, s = n.data.components, i = new Te();
|
|
652
|
+
if (!m(e))
|
|
653
|
+
return i.error({ code: "invalid_spec", path: "", message: "A spec must be a JSON object.", hint: 'Return {"root": "<key>", "elements": {...}, "state": {...}}.' }), E(i);
|
|
654
|
+
const a = m(e.elements) ? e.elements : void 0;
|
|
655
|
+
a || i.error({ code: "invalid_elements", path: "/elements", message: '"elements" must be an object keyed by element id.', hint: 'Use a flat map: {"elements": {"root": {"type": "AppRoot", "props": {}, "children": []}}}.' });
|
|
656
|
+
const r = Object.keys(a ?? {});
|
|
657
|
+
typeof e.root != "string" || e.root === "" ? i.error({ code: "missing_root", path: "/root", message: '"root" must name the top element.', hint: r.length ? v('Set "root" to one of', r) : 'Add "root" and the element it names.' }) : a && !Object.hasOwn(a, e.root) && i.error({ code: "root_not_found", path: "/root", message: `Root "${e.root}" is not in "elements".`, hint: v("Existing element keys", r) });
|
|
658
|
+
for (const [p, f] of Object.entries(a ?? {})) {
|
|
659
|
+
const w = (...g) => u("elements", p, ...g);
|
|
660
|
+
if (!m(f)) {
|
|
661
|
+
i.error({ code: "invalid_element", path: w(), elementKey: p, message: 'An element must be an object with "type", "props" and "children".' });
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
const h = f.type;
|
|
665
|
+
if (typeof h != "string" || !Object.hasOwn(s, h)) {
|
|
666
|
+
const g = typeof h == "string" ? y(h, Object.keys(s)) : void 0;
|
|
667
|
+
i.error({
|
|
668
|
+
code: "unknown_component",
|
|
669
|
+
path: w("type"),
|
|
670
|
+
elementKey: p,
|
|
671
|
+
message: `Unknown component type ${JSON.stringify(h)}.`,
|
|
672
|
+
hint: g ? `Did you mean "${g}"? Run "syno-ui catalog" for every component.` : 'Run "syno-ui catalog" for every component, or register a host extension component.'
|
|
673
|
+
});
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
qe(i, p, h, s[h], f.props), Fe(i, p, h, s[h], f, a);
|
|
677
|
+
}
|
|
678
|
+
const c = t.messages && q(t.messages), l = {
|
|
679
|
+
issues: i,
|
|
680
|
+
components: s,
|
|
681
|
+
actions: [
|
|
682
|
+
...(n.schema?.builtInActions ?? []).map((p) => p.name),
|
|
683
|
+
...Object.keys(n.data.actions ?? {}),
|
|
684
|
+
...t.actions ?? []
|
|
685
|
+
],
|
|
686
|
+
state: { ...t.state, ...m(e.state) ? e.state : {} },
|
|
687
|
+
// A message catalog implies the host registers the `t` function.
|
|
688
|
+
functions: t.functions || c ? [...t.functions ?? [], ...c ? ["t"] : []] : void 0,
|
|
689
|
+
messageKeys: c
|
|
690
|
+
};
|
|
691
|
+
return a && typeof e.root == "string" && Object.hasOwn(a, e.root) && ze(l, e.root, a), E(i);
|
|
692
|
+
}
|
|
693
|
+
function qe(e, t, n, s, i) {
|
|
694
|
+
const a = (...l) => u("elements", t, "props", ...l);
|
|
695
|
+
if (!m(i)) {
|
|
696
|
+
e.error({ code: "invalid_props", path: a(), elementKey: t, message: '"props" must be an object.', hint: 'Use "props": {} when the component needs none.' });
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const r = P(s);
|
|
700
|
+
if (!r) return;
|
|
701
|
+
const c = Object.keys(r);
|
|
702
|
+
for (const [l, p] of Object.entries(i)) {
|
|
703
|
+
const f = r[l];
|
|
704
|
+
if (!f) {
|
|
705
|
+
const h = y(l, c);
|
|
706
|
+
e.error({
|
|
707
|
+
code: "unknown_prop",
|
|
708
|
+
path: a(l),
|
|
709
|
+
elementKey: t,
|
|
710
|
+
message: `${n} has no prop "${l}".`,
|
|
711
|
+
hint: h ? `Did you mean "${h}"? Run "syno-ui describe ${n}".` : v(`${n} props`, c)
|
|
712
|
+
});
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
if (R(p)) continue;
|
|
716
|
+
const w = f.safeParse(p);
|
|
717
|
+
if (!w.success)
|
|
718
|
+
for (const h of w.error.issues)
|
|
719
|
+
e.error({
|
|
720
|
+
code: "invalid_prop",
|
|
721
|
+
path: a(l, ...h.path.map((g) => String(g))),
|
|
722
|
+
elementKey: t,
|
|
723
|
+
message: `${n}.${l}: ${h.message}`,
|
|
724
|
+
hint: _e(h)
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
for (const l of c)
|
|
728
|
+
!Object.hasOwn(i, l) && !K(r[l]) && e.error({ code: "missing_prop", path: a(l), elementKey: t, message: `${n} requires prop "${l}".`, hint: `Run "syno-ui describe ${n}" for its type.` });
|
|
729
|
+
}
|
|
730
|
+
function F(e, t) {
|
|
731
|
+
const n = [];
|
|
732
|
+
if (Array.isArray(t.children) && t.children.forEach((s, i) => {
|
|
733
|
+
typeof s == "string" && n.push({ child: s, path: u("elements", e, "children", i) });
|
|
734
|
+
}), m(t.slots))
|
|
735
|
+
for (const [s, i] of Object.entries(t.slots))
|
|
736
|
+
Array.isArray(i) && i.forEach((a, r) => {
|
|
737
|
+
typeof a == "string" && n.push({ child: a, path: u("elements", e, "slots", s, r) });
|
|
738
|
+
});
|
|
739
|
+
return n;
|
|
740
|
+
}
|
|
741
|
+
function Fe(e, t, n, s, i, a) {
|
|
742
|
+
const r = s.slots ?? [], { children: c } = i;
|
|
743
|
+
if (c !== void 0 && !(Array.isArray(c) && c.every((l) => typeof l == "string")) ? e.error({ code: "invalid_children", path: u("elements", t, "children"), elementKey: t, message: '"children" must be an array of element keys.' }) : c?.length && !r.includes("default") && e.error({
|
|
744
|
+
code: "children_not_allowed",
|
|
745
|
+
path: u("elements", t, "children"),
|
|
746
|
+
elementKey: t,
|
|
747
|
+
message: `${n} does not render children.`,
|
|
748
|
+
hint: r.length ? v(`Put them in a named slot of ${n}`, r) : 'Use "children": [] and put the content in a sibling or parent container.'
|
|
749
|
+
}), i.slots !== void 0)
|
|
750
|
+
if (!m(i.slots))
|
|
751
|
+
e.error({ code: "invalid_slots", path: u("elements", t, "slots"), elementKey: t, message: '"slots" must map slot names to arrays of element keys.' });
|
|
752
|
+
else
|
|
753
|
+
for (const [l, p] of Object.entries(i.slots))
|
|
754
|
+
if (r.includes(l))
|
|
755
|
+
Array.isArray(p) || e.error({ code: "invalid_slots", path: u("elements", t, "slots", l), elementKey: t, message: `Slot "${l}" must be an array of element keys.` });
|
|
756
|
+
else {
|
|
757
|
+
const f = y(l, r);
|
|
758
|
+
e.error({
|
|
759
|
+
code: "unknown_slot",
|
|
760
|
+
path: u("elements", t, "slots", l),
|
|
761
|
+
elementKey: t,
|
|
762
|
+
message: `${n} has no slot "${l}".`,
|
|
763
|
+
hint: f ? `Did you mean "${f}"?` : r.length ? v(`${n} slots`, r) : `${n} has no slots.`
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
for (const l of F(t, i))
|
|
767
|
+
if (!Object.hasOwn(a, l.child)) {
|
|
768
|
+
const p = y(l.child, Object.keys(a));
|
|
769
|
+
e.error({
|
|
770
|
+
code: "missing_child",
|
|
771
|
+
path: l.path,
|
|
772
|
+
elementKey: t,
|
|
773
|
+
message: `"${l.child}" is not in "elements".`,
|
|
774
|
+
hint: p ? `Did you mean "${p}"? Otherwise add the element.` : "Add the element or remove the reference."
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function ze(e, t, n) {
|
|
779
|
+
const { issues: s } = e, i = /* @__PURE__ */ new Set(), a = (r, c, l, p) => {
|
|
780
|
+
const f = n[r];
|
|
781
|
+
if (!m(f)) return;
|
|
782
|
+
i.add(r);
|
|
783
|
+
const w = m(f.repeat);
|
|
784
|
+
He(e, r, f, p || w);
|
|
785
|
+
const h = f.type === "AppRoot";
|
|
786
|
+
h && l && s.error({ code: "nested_app_root", path: u("elements", r), elementKey: r, message: "AppRoot cannot be nested inside another AppRoot.", hint: "Use a single AppRoot at the top of the spec." });
|
|
787
|
+
for (const g of F(r, f))
|
|
788
|
+
if (Object.hasOwn(n, g.child)) {
|
|
789
|
+
if (c.includes(g.child)) {
|
|
790
|
+
s.error({ code: "cycle", path: g.path, elementKey: r, message: `"${g.child}" is its own ancestor.`, hint: "Element references must form a tree." });
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
a(g.child, [...c, g.child], l || h, p || w);
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
a(t, [t], !1, !1);
|
|
797
|
+
for (const r of Object.keys(n))
|
|
798
|
+
i.has(r) || s.warn({ code: "orphaned_element", path: u("elements", r), elementKey: r, message: `"${r}" is not reachable from the root.`, hint: "Reference it from a parent or delete it." });
|
|
799
|
+
}
|
|
800
|
+
function Be(e, t) {
|
|
801
|
+
let n = e;
|
|
802
|
+
for (const s of t.split("/").slice(1)) {
|
|
803
|
+
const i = s.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
804
|
+
if (Array.isArray(n) && /^\d+$/.test(i)) n = n[Number(i)];
|
|
805
|
+
else if (m(n) && Object.hasOwn(n, i)) n = n[i];
|
|
806
|
+
else return { found: !1 };
|
|
807
|
+
}
|
|
808
|
+
return { found: !0, value: n };
|
|
809
|
+
}
|
|
810
|
+
function $(e, t, n, s, { mustExist: i }) {
|
|
811
|
+
if (typeof n != "string" || !n.startsWith("/")) {
|
|
812
|
+
e.issues.error({
|
|
813
|
+
code: "invalid_state_path",
|
|
814
|
+
path: s,
|
|
815
|
+
elementKey: t,
|
|
816
|
+
message: `State paths are JSON Pointers; got ${JSON.stringify(n)}.`,
|
|
817
|
+
hint: typeof n == "string" ? `Use "/${n.replace(/^\.?\/?/, "").replaceAll(".", "/")}".` : 'Use a string such as "/settings/enabled".'
|
|
818
|
+
});
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
i && !Be(e.state, n).found && e.issues.warn({
|
|
822
|
+
code: "state_path_missing",
|
|
823
|
+
path: s,
|
|
824
|
+
elementKey: t,
|
|
825
|
+
message: `State "${n}" has no initial value.`,
|
|
826
|
+
hint: `Seed it in the spec's "state", or pass the host state with --state, so the first render has a value.`
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
function S(e, t, n, s, i) {
|
|
830
|
+
if (Array.isArray(n)) {
|
|
831
|
+
n.forEach((a, r) => S(e, t, a, s + u(r), i));
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
if (m(n)) {
|
|
835
|
+
for (const a of ["$state", "$bindState"])
|
|
836
|
+
Object.hasOwn(n, a) && $(e, t, n[a], s + u(a), { mustExist: !0 });
|
|
837
|
+
for (const a of ["$item", "$bindItem", "$index"])
|
|
838
|
+
Object.hasOwn(n, a) && !i && e.issues.error({
|
|
839
|
+
code: "item_outside_repeat",
|
|
840
|
+
path: s + u(a),
|
|
841
|
+
elementKey: t,
|
|
842
|
+
message: `${a} only works inside an element with "repeat" or its descendants.`,
|
|
843
|
+
hint: 'Add "repeat": {"statePath": "/list"} to an ancestor, or read the value with $state.'
|
|
844
|
+
});
|
|
845
|
+
if (typeof n.$template == "string")
|
|
846
|
+
for (const a of n.$template.matchAll(/\$\{([^}]*)\}/g))
|
|
847
|
+
$(e, t, a[1], s + u("$template"), { mustExist: !0 });
|
|
848
|
+
typeof n.$computed == "string" && Me(e, t, n, s);
|
|
849
|
+
for (const [a, r] of Object.entries(n))
|
|
850
|
+
["$state", "$bindState", "$item", "$bindItem", "$index", "$template"].includes(a) || S(e, t, r, s + u(a), i);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
function Me(e, t, n, s) {
|
|
854
|
+
const i = n.$computed;
|
|
855
|
+
if (!e.functions)
|
|
856
|
+
e.issues.warn({
|
|
857
|
+
code: "unverified_function",
|
|
858
|
+
path: s + u("$computed"),
|
|
859
|
+
elementKey: t,
|
|
860
|
+
message: `$computed "${i}" must be registered by the host.`,
|
|
861
|
+
hint: `Pass the host function names with --functions ${i} to verify it.`
|
|
862
|
+
});
|
|
863
|
+
else if (!e.functions.includes(i)) {
|
|
864
|
+
const r = y(i, e.functions);
|
|
865
|
+
e.issues.error({
|
|
866
|
+
code: "unknown_function",
|
|
867
|
+
path: s + u("$computed"),
|
|
868
|
+
elementKey: t,
|
|
869
|
+
message: `$computed "${i}" is not a host function.`,
|
|
870
|
+
hint: `${r ? `Did you mean "${r}"? ` : ""}${v("Host functions", e.functions)}`
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
if (i !== "t" || !e.messageKeys) return;
|
|
874
|
+
const a = m(n.args) ? n.args.key : void 0;
|
|
875
|
+
if (typeof a != "string" || !e.messageKeys.includes(a)) {
|
|
876
|
+
const r = typeof a == "string" ? y(a, e.messageKeys) : void 0;
|
|
877
|
+
e.issues.error({
|
|
878
|
+
code: "missing_i18n_key",
|
|
879
|
+
path: s + u("args", "key"),
|
|
880
|
+
elementKey: t,
|
|
881
|
+
message: `Message key ${JSON.stringify(a)} is not in the message catalog.`,
|
|
882
|
+
hint: r ? `Did you mean "${r}"? Otherwise add the key to every locale.` : "Add the key to every locale, or use an existing key."
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
const We = ["statePath", "clearStatePath"];
|
|
887
|
+
function He(e, t, n, s) {
|
|
888
|
+
const i = (...c) => u("elements", t, ...c);
|
|
889
|
+
if (S(e, t, n.props, i("props"), s), e.messageKeys && m(n.props))
|
|
890
|
+
for (const c of Ee) {
|
|
891
|
+
const l = n.props[c];
|
|
892
|
+
typeof l == "string" && l.trim() && e.issues.warn({
|
|
893
|
+
code: "literal_text",
|
|
894
|
+
path: i("props", c),
|
|
895
|
+
elementKey: t,
|
|
896
|
+
message: `Literal text ${JSON.stringify(l)} is not localized.`,
|
|
897
|
+
hint: 'Use {"$computed": "t", "args": {"key": "<message key>"}}.'
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
n.visible !== void 0 && S(e, t, n.visible, i("visible"), s), m(n.repeat) && $(e, t, n.repeat.statePath, i("repeat", "statePath"), { mustExist: !0 });
|
|
901
|
+
const a = typeof n.type == "string" ? n.type : "", r = e.components[a]?.events ?? [];
|
|
902
|
+
if (m(n.on))
|
|
903
|
+
for (const [c, l] of Object.entries(n.on)) {
|
|
904
|
+
if (!r.includes(c)) {
|
|
905
|
+
const p = y(c, r);
|
|
906
|
+
e.issues.error({
|
|
907
|
+
code: "unknown_event",
|
|
908
|
+
path: i("on", c),
|
|
909
|
+
elementKey: t,
|
|
910
|
+
message: `${a} does not emit "${c}".`,
|
|
911
|
+
hint: p ? `Did you mean "${p}"?` : r.length ? v(`${a} events`, r) : `${a} emits no events.`
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
L(e, t, l, i("on", c), s);
|
|
915
|
+
}
|
|
916
|
+
if (m(n.watch))
|
|
917
|
+
for (const [c, l] of Object.entries(n.watch))
|
|
918
|
+
$(e, t, c, i("watch", c), { mustExist: !1 }), L(e, t, l, i("watch", c), s);
|
|
919
|
+
}
|
|
920
|
+
function L(e, t, n, s, i) {
|
|
921
|
+
(Array.isArray(n) ? n : [n]).forEach((r, c) => {
|
|
922
|
+
const l = Array.isArray(n) ? s + u(c) : s;
|
|
923
|
+
if (!m(r) || typeof r.action != "string") {
|
|
924
|
+
e.issues.error({ code: "invalid_action_binding", path: l, elementKey: t, message: 'An action binding is {"action": "<name>", "params": {...}}.' });
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
if (!e.actions.includes(r.action)) {
|
|
928
|
+
const p = y(r.action, e.actions);
|
|
929
|
+
e.issues.error({
|
|
930
|
+
code: "unknown_action",
|
|
931
|
+
path: l + u("action"),
|
|
932
|
+
elementKey: t,
|
|
933
|
+
message: `Action "${r.action}" is not declared.`,
|
|
934
|
+
hint: `${p ? `Did you mean "${p}"? ` : ""}Declare it in the app catalog, or pass host handlers with --actions ${r.action}. ${v("Known actions", e.actions)}`
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
if (m(r.params)) {
|
|
938
|
+
for (const p of We)
|
|
939
|
+
Object.hasOwn(r.params, p) && $(e, t, r.params[p], l + u("params", p), { mustExist: !1 });
|
|
940
|
+
S(e, t, r.params, l + u("params"), i);
|
|
941
|
+
}
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
function E(e) {
|
|
945
|
+
return { valid: e.errors.length === 0, errors: e.errors, warnings: e.warnings };
|
|
946
|
+
}
|
|
947
|
+
const O = _;
|
|
948
|
+
function Ge(e) {
|
|
949
|
+
const t = o.toJSONSchema(e);
|
|
950
|
+
return delete t.$schema, t;
|
|
951
|
+
}
|
|
952
|
+
function Xe(e) {
|
|
953
|
+
if (Array.isArray(e.enum)) return e.enum[0];
|
|
954
|
+
switch (e.type) {
|
|
955
|
+
case "string":
|
|
956
|
+
return "…";
|
|
957
|
+
case "number":
|
|
958
|
+
case "integer":
|
|
959
|
+
return 0;
|
|
960
|
+
case "boolean":
|
|
961
|
+
return !1;
|
|
962
|
+
case "array":
|
|
963
|
+
return [];
|
|
964
|
+
default:
|
|
965
|
+
return {};
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function dt() {
|
|
969
|
+
return Object.entries(O).map(([e, t]) => ({
|
|
970
|
+
name: e,
|
|
971
|
+
description: t.description,
|
|
972
|
+
required: Object.entries(P(t) ?? {}).filter(([, n]) => !K(n)).map(([n]) => n),
|
|
973
|
+
slots: t.slots ?? [],
|
|
974
|
+
events: t.events ?? []
|
|
975
|
+
}));
|
|
976
|
+
}
|
|
977
|
+
function pt(e) {
|
|
978
|
+
return y(e, Object.keys(O));
|
|
979
|
+
}
|
|
980
|
+
function ut(e) {
|
|
981
|
+
if (!Object.hasOwn(O, e)) return;
|
|
982
|
+
const t = O[e], n = Object.fromEntries(Object.entries(P(t) ?? {}).map(([s, i]) => [
|
|
983
|
+
s,
|
|
984
|
+
{ required: !K(i), schema: Ge(i) }
|
|
985
|
+
]));
|
|
986
|
+
return {
|
|
987
|
+
name: e,
|
|
988
|
+
description: t.description,
|
|
989
|
+
props: n,
|
|
990
|
+
slots: t.slots ?? [],
|
|
991
|
+
events: t.events ?? [],
|
|
992
|
+
example: {
|
|
993
|
+
type: e,
|
|
994
|
+
props: Object.fromEntries(Object.entries(n).filter(([, s]) => s.required).map(([s, { schema: i }]) => [s, Xe(i)])),
|
|
995
|
+
children: []
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
const x = { type: "string", pattern: "^/" }, Ye = {
|
|
1000
|
+
description: "A json-render expression resolved at render time.",
|
|
1001
|
+
anyOf: [
|
|
1002
|
+
{ type: "object", required: ["$state"], properties: { $state: x } },
|
|
1003
|
+
{ type: "object", required: ["$bindState"], properties: { $bindState: x } },
|
|
1004
|
+
{ type: "object", required: ["$item"], properties: { $item: { type: "string" } } },
|
|
1005
|
+
{ type: "object", required: ["$bindItem"], properties: { $bindItem: { type: "string" } } },
|
|
1006
|
+
{ type: "object", required: ["$index"], properties: { $index: { const: !0 } } },
|
|
1007
|
+
{ type: "object", required: ["$template"], properties: { $template: { type: "string" } } },
|
|
1008
|
+
{ type: "object", required: ["$computed"], properties: { $computed: { type: "string" }, args: { type: "object" } } },
|
|
1009
|
+
{ type: "object", required: ["$cond", "$then", "$else"], properties: { $cond: {}, $then: {}, $else: {} } }
|
|
1010
|
+
]
|
|
1011
|
+
}, Qe = {
|
|
1012
|
+
type: "object",
|
|
1013
|
+
required: ["action"],
|
|
1014
|
+
properties: {
|
|
1015
|
+
action: { type: "string" },
|
|
1016
|
+
params: { type: "object" },
|
|
1017
|
+
confirm: { type: "object" },
|
|
1018
|
+
onSuccess: { type: "object" },
|
|
1019
|
+
onError: { type: "object" },
|
|
1020
|
+
preventDefault: { type: "boolean" }
|
|
1021
|
+
},
|
|
1022
|
+
additionalProperties: !1
|
|
1023
|
+
}, N = { anyOf: [{ $ref: "#/$defs/actionBinding" }, { type: "array", items: { $ref: "#/$defs/actionBinding" } }] }, J = { type: "array", items: { type: "string" } };
|
|
1024
|
+
function Ze(e, t, n) {
|
|
1025
|
+
const s = (r) => Array.isArray(r) ? r.map(s) : !r || typeof r != "object" ? r : Object.fromEntries(Object.entries(r).map(([c, l]) => [
|
|
1026
|
+
c,
|
|
1027
|
+
c === "$ref" && typeof l == "string" ? l.replace("#/$defs/", `#/$defs/${t}`) : s(l)
|
|
1028
|
+
])), { $defs: i, ...a } = e;
|
|
1029
|
+
delete a.$schema;
|
|
1030
|
+
for (const [r, c] of Object.entries(i ?? {})) n[t + r] = s(c);
|
|
1031
|
+
return s(a);
|
|
1032
|
+
}
|
|
1033
|
+
function et(e, t, n) {
|
|
1034
|
+
const s = P(t), i = t.slots ?? [];
|
|
1035
|
+
let a = { type: "object" };
|
|
1036
|
+
if (s) {
|
|
1037
|
+
const r = Ze(o.toJSONSchema(o.object(s)), `${e}__`, n), c = r.properties;
|
|
1038
|
+
a = {
|
|
1039
|
+
...r,
|
|
1040
|
+
properties: Object.fromEntries(Object.entries(c).map(([l, p]) => [
|
|
1041
|
+
l,
|
|
1042
|
+
{ anyOf: [p, { $ref: "#/$defs/expression" }] }
|
|
1043
|
+
]))
|
|
1044
|
+
}, r.required?.length || delete a.required;
|
|
1045
|
+
}
|
|
1046
|
+
return {
|
|
1047
|
+
if: { properties: { type: { const: e } } },
|
|
1048
|
+
then: {
|
|
1049
|
+
...t.description ? { description: t.description } : {},
|
|
1050
|
+
properties: {
|
|
1051
|
+
props: a,
|
|
1052
|
+
...i.includes("default") ? {} : { children: { maxItems: 0 } },
|
|
1053
|
+
slots: { propertyNames: { enum: i.filter((r) => r !== "default") } },
|
|
1054
|
+
on: { propertyNames: { enum: t.events ?? [] } }
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
function mt(e = A) {
|
|
1060
|
+
const t = e.data.components, n = { expression: Ye, actionBinding: Qe }, s = Object.entries(t).map(([i, a]) => et(i, a, n));
|
|
1061
|
+
return n.element = {
|
|
1062
|
+
type: "object",
|
|
1063
|
+
required: ["type", "props"],
|
|
1064
|
+
properties: {
|
|
1065
|
+
type: { enum: Object.keys(t) },
|
|
1066
|
+
props: { type: "object" },
|
|
1067
|
+
children: J,
|
|
1068
|
+
slots: { type: "object", additionalProperties: J },
|
|
1069
|
+
visible: {},
|
|
1070
|
+
on: { type: "object", additionalProperties: N },
|
|
1071
|
+
watch: { type: "object", propertyNames: x, additionalProperties: N },
|
|
1072
|
+
repeat: { type: "object", required: ["statePath"], properties: { statePath: x, key: { type: "string" } }, additionalProperties: !1 }
|
|
1073
|
+
},
|
|
1074
|
+
additionalProperties: !1,
|
|
1075
|
+
allOf: s
|
|
1076
|
+
}, {
|
|
1077
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1078
|
+
title: "Synology Package Land JSON UI spec",
|
|
1079
|
+
type: "object",
|
|
1080
|
+
required: ["root", "elements"],
|
|
1081
|
+
properties: {
|
|
1082
|
+
root: { type: "string", minLength: 1 },
|
|
1083
|
+
elements: { type: "object", additionalProperties: { $ref: "#/$defs/element" } },
|
|
1084
|
+
state: { type: "object" }
|
|
1085
|
+
},
|
|
1086
|
+
additionalProperties: !1,
|
|
1087
|
+
$defs: n
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
export {
|
|
1091
|
+
$t as ActionProvider,
|
|
1092
|
+
St as JSONUIProvider,
|
|
1093
|
+
jt as Renderer,
|
|
1094
|
+
Ot as StateProvider,
|
|
1095
|
+
lt as SynoJsonRenderer,
|
|
1096
|
+
xt as ValidationProvider,
|
|
1097
|
+
At as VisibilityProvider,
|
|
1098
|
+
pt as closestSynoJsonComponent,
|
|
1099
|
+
yt as createStateStore,
|
|
1100
|
+
wt as defineCatalog,
|
|
1101
|
+
Pt as defineRegistry,
|
|
1102
|
+
ut as describeSynoJsonComponent,
|
|
1103
|
+
It as jsonRenderVueSchema,
|
|
1104
|
+
dt as listSynoJsonComponents,
|
|
1105
|
+
rt as synoJsonAiPrompt,
|
|
1106
|
+
Ce as synoJsonAiRules,
|
|
1107
|
+
A as synoJsonCatalog,
|
|
1108
|
+
_ as synoJsonComponentDefinitions,
|
|
1109
|
+
D as synoJsonRegistry,
|
|
1110
|
+
Le as synoJsonRenderers,
|
|
1111
|
+
mt as synoJsonSchema,
|
|
1112
|
+
Ct as useActions,
|
|
1113
|
+
kt as useBoundProp,
|
|
1114
|
+
Rt as useStateStore,
|
|
1115
|
+
ct as validateSynoJsonSpec,
|
|
1116
|
+
gt as z
|
|
1117
|
+
};
|