@morlay/dsh-client-ui-primitives 0.0.2-alpha.8 → 0.0.2-alpha.9

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/dist/client.cjs CHANGED
@@ -16,10 +16,1813 @@ window.__ModuleLoader__.load({
16
16
  return target;
17
17
  };
18
18
  //#endregion
19
- let react = require("react");
20
19
  let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
21
- let react_jsx_runtime = require("react/jsx-runtime");
20
+ let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
21
+ let react = require("react");
22
22
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
23
+ let react_jsx_runtime = require("react/jsx-runtime");
24
+ /** 空的元数据:动态子节点(无声明 schema 的 dict 键)与空字段树用它。 */
25
+ function emptyMeta() {
26
+ return {
27
+ description: void 0,
28
+ comment: void 0,
29
+ link: void 0,
30
+ role: void 0,
31
+ secret: false,
32
+ required: false,
33
+ disabled: false,
34
+ collapse: false,
35
+ badges: [],
36
+ pattern: void 0,
37
+ min: void 0,
38
+ max: void 0,
39
+ step: void 0,
40
+ hasDefault: false,
41
+ defaultValue: void 0,
42
+ extra: void 0
43
+ };
44
+ }
45
+ /** `badges` 只留文本:配色与文案由字典决定。 */
46
+ function badgesOf(meta) {
47
+ return (meta.badges ?? []).map((badge) => badge.text);
48
+ }
49
+ /** 归一化一个节点的 meta。 */
50
+ function metaOf(schema) {
51
+ const meta = schema.meta ?? {};
52
+ return {
53
+ description: meta.description,
54
+ comment: meta.comment,
55
+ link: meta.link,
56
+ role: meta.role,
57
+ secret: meta.role === "secret",
58
+ required: meta.required === true,
59
+ disabled: meta.disabled === true,
60
+ collapse: meta.collapse === true,
61
+ badges: badgesOf(meta),
62
+ pattern: typeof meta.pattern === "string" ? meta.pattern : meta.pattern?.source ?? void 0,
63
+ min: meta.min,
64
+ max: meta.max,
65
+ step: meta.step,
66
+ hasDefault: meta.default !== void 0,
67
+ defaultValue: meta.default,
68
+ extra: meta.extra
69
+ };
70
+ }
71
+ /** 外层 meta 与内层 meta 合并:外层的 undefined 不覆盖内层(transform 的 inner 带着自己的角色)。 */
72
+ function mergeMeta(inner, outer) {
73
+ const merged = { ...inner };
74
+ for (const [key, value] of Object.entries(outer)) if (value !== void 0) merged[key] = value;
75
+ return merged;
76
+ }
77
+ /** 节点自己的路径末段:动态段保持占位,根节点为空串。 */
78
+ function keyOf(path) {
79
+ return path[path.length - 1] ?? "";
80
+ }
81
+ /** 一个纯字符串叶子(dict 没有声明键 schema 时用它)。 */
82
+ function stringLeaf(path, meta) {
83
+ return {
84
+ key: keyOf(path),
85
+ path,
86
+ label: void 0,
87
+ meta,
88
+ readOnly: null,
89
+ type: "string"
90
+ };
91
+ }
92
+ /** 把 schema 的类型名收口成渲染层认识的那几种。 */
93
+ function leafTypeOf(type) {
94
+ switch (type) {
95
+ case "string": return "string";
96
+ case "number": return "number";
97
+ case "boolean": return "boolean";
98
+ case "function": return "function";
99
+ case "never": return "never";
100
+ default: return "any";
101
+ }
102
+ }
103
+ /**
104
+ * 投影一个 schema 节点。
105
+ * @param schema - rehydrate 之后的 volatile 表单 schema(或它的子节点)。
106
+ * @param path - 该节点相对段根的路径;动态子节点由调用方给占位段。
107
+ * @returns 渲染层要的字段树。
108
+ */
109
+ function projectNode(schema, path = []) {
110
+ return project(schema, path, []);
111
+ }
112
+ function project(schema, path, ancestors) {
113
+ const base = {
114
+ key: keyOf(path),
115
+ path,
116
+ label: void 0,
117
+ meta: metaOf(schema),
118
+ readOnly: null
119
+ };
120
+ if (ancestors.includes(schema)) return {
121
+ ...base,
122
+ type: "recursive",
123
+ origin: schema.type
124
+ };
125
+ const chain = [...ancestors, schema];
126
+ switch (schema.type) {
127
+ case "object": {
128
+ const fields = Object.entries(schema.dict ?? {}).filter(([, child]) => child.meta?.hidden !== true).map(([key, child]) => project(child, [...path, key], chain));
129
+ return {
130
+ ...base,
131
+ type: "object",
132
+ fields
133
+ };
134
+ }
135
+ case "dict": {
136
+ const segment = [...path, "*"];
137
+ const keyNode = schema.sKey === void 0 ? stringLeaf(segment, emptyMeta()) : project(schema.sKey, segment, chain);
138
+ return {
139
+ ...base,
140
+ type: "dict",
141
+ keyNode,
142
+ valueNode: project(schema.inner ?? schema, segment, chain)
143
+ };
144
+ }
145
+ case "array": {
146
+ const item = project(schema.inner ?? schema, [...path, "*"], chain);
147
+ return {
148
+ ...base,
149
+ type: "array",
150
+ item,
151
+ mergeKey: mergeKeyOf(schema, item)
152
+ };
153
+ }
154
+ case "tuple": {
155
+ const items = (schema.list ?? []).map((child, index) => project(child, [...path, String(index)], chain));
156
+ return {
157
+ ...base,
158
+ type: "tuple",
159
+ items
160
+ };
161
+ }
162
+ case "union": {
163
+ const members = schema.list ?? [];
164
+ const variants = members.map((child) => project(child, path, chain));
165
+ const literals = members.every((child) => child.type === "const") ? members.map((child) => child.value) : void 0;
166
+ return {
167
+ ...base,
168
+ type: "union",
169
+ choices: literals,
170
+ variants,
171
+ tagKey: tagKeyOf(schema, variants)
172
+ };
173
+ }
174
+ case "intersect": return {
175
+ ...base,
176
+ type: "intersect",
177
+ members: (schema.list ?? []).map((child) => project(child, path, chain))
178
+ };
179
+ case "bitset": {
180
+ const bits = Object.entries(schema.bits ?? {}).map(([name, bit]) => ({
181
+ name,
182
+ bit
183
+ }));
184
+ return {
185
+ ...base,
186
+ type: "bitset",
187
+ bits
188
+ };
189
+ }
190
+ case "const": return {
191
+ ...base,
192
+ type: "const",
193
+ value: schema.value
194
+ };
195
+ case "transform": {
196
+ const projected = project(schema.inner ?? schema, path, chain);
197
+ return {
198
+ ...projected,
199
+ meta: mergeMeta(projected.meta, base.meta),
200
+ readOnly: "transform"
201
+ };
202
+ }
203
+ case "lazy": {
204
+ const inner = schema.inner;
205
+ return inner === void 0 ? {
206
+ ...base,
207
+ type: "any"
208
+ } : {
209
+ ...base,
210
+ type: "lazy",
211
+ inner: project(inner, path, chain)
212
+ };
213
+ }
214
+ case "is": return {
215
+ ...base,
216
+ type: "any",
217
+ readOnly: "constructor"
218
+ };
219
+ default: return {
220
+ ...base,
221
+ type: leafTypeOf(schema.type),
222
+ readOnly: schema.type === "function" ? "function" : null
223
+ };
224
+ }
225
+ }
226
+ /** 展开 `lazy` 包装:投影里 lazy 只是一层,值仍在同一路径上。 */
227
+ function unwrapLazy(node) {
228
+ let current = node;
229
+ while (current?.type === "lazy") current = current.inner;
230
+ return current;
231
+ }
232
+ /** 某一层可见的字段:object 直接给;union / intersect 把各变体的对象字段并起来。 */
233
+ function fieldsOf(node) {
234
+ const current = unwrapLazy(node);
235
+ switch (current?.type) {
236
+ case "object": return current.fields;
237
+ case "union": return current.variants.flatMap((variant) => fieldsOf(variant));
238
+ case "intersect": return current.members.flatMap((member) => fieldsOf(member));
239
+ default: return [];
240
+ }
241
+ }
242
+ /**
243
+ * 按具体路径找回投影节点(动态段匹配真实键或索引)。
244
+ * @param root - 段根的字段树。
245
+ * @param path - 具体路径。
246
+ * @returns 命中的节点,或 `undefined`。
247
+ */
248
+ function seekNode(root, path) {
249
+ let current = root;
250
+ for (const segment of path) {
251
+ const node = unwrapLazy(current);
252
+ if (node === void 0) return void 0;
253
+ switch (node.type) {
254
+ case "object":
255
+ case "union":
256
+ case "intersect":
257
+ current = fieldsOf(node).find((field) => field.key === segment);
258
+ break;
259
+ case "dict":
260
+ current = node.valueNode;
261
+ break;
262
+ case "array":
263
+ current = node.item;
264
+ break;
265
+ case "tuple":
266
+ current = node.items[Number(segment)];
267
+ break;
268
+ default: return;
269
+ }
270
+ }
271
+ return unwrapLazy(current);
272
+ }
273
+ /**
274
+ * 按当前值把字段树展开成渲染顺序的扁平列表(前序):容器在前、子项紧随其后。
275
+ *
276
+ * 出现的**只有值里有的东西,加上必填的**:对象的必填字段始终占行(值是空的也要让用户看见"必须填"),非必填的
277
+ * 按值出现——值里没有就不占行,它在这一层的「添加属性」候选里,选中才加。数组按索引、字典按键展开;字面量
278
+ * union 是叶子控件,对象 union 只展开与当前值匹配的那个变体,intersect 的各成员在同一层展开。
279
+ * @param root - 段根的字段树。
280
+ * @param value - 生效值(或草稿后的值)。
281
+ * @returns 渲染顺序的字段列表。
282
+ */
283
+ function walkFields(root, value) {
284
+ const out = [];
285
+ visit(root, root.path, value, out, true, void 0, /* @__PURE__ */ new Set(), void 0);
286
+ return out;
287
+ }
288
+ function visit(node, path, value, out, emit, label, emitted, member) {
289
+ const current = unwrapLazy(node);
290
+ if (current === void 0) return;
291
+ const positioned = {
292
+ ...current,
293
+ path,
294
+ ...member === void 0 ? {} : { key: member.key },
295
+ ...label === void 0 ? {} : { label }
296
+ };
297
+ const key = JSON.stringify(path);
298
+ if (emit && !emitted.has(key)) {
299
+ emitted.add(key);
300
+ out.push({
301
+ path,
302
+ node: positioned,
303
+ ...member === void 0 ? {} : { member }
304
+ });
305
+ }
306
+ switch (positioned.type) {
307
+ case "object":
308
+ for (const field of positioned.fields) {
309
+ const child = memberOf(value, field.key);
310
+ if (child === void 0 && field.meta.required !== true) continue;
311
+ visit(field, [...path, field.key], child, out, true, void 0, emitted, void 0);
312
+ }
313
+ return;
314
+ case "dict":
315
+ for (const [key, child] of entriesOf(value)) visit(positioned.valueNode, [...path, key], child, out, true, void 0, emitted, {
316
+ parent: path,
317
+ key,
318
+ index: void 0
319
+ });
320
+ return;
321
+ case "array":
322
+ for (const [key, child] of entriesOf(value)) {
323
+ const label = positioned.mergeKey === void 0 ? void 0 : memberLabel(child, positioned.mergeKey);
324
+ visit(positioned.item, [...path, key], child, out, true, label, emitted, {
325
+ parent: path,
326
+ key,
327
+ index: Number(key)
328
+ });
329
+ }
330
+ return;
331
+ case "tuple":
332
+ positioned.items.forEach((item, index) => {
333
+ visit(item, [...path, String(index)], memberOf(value, index), out, true, void 0, emitted, void 0);
334
+ });
335
+ return;
336
+ case "union": {
337
+ if (positioned.choices !== void 0) return;
338
+ const variant = pickVariant(positioned, value);
339
+ if (variant !== void 0) visit(variant, path, value, out, false, void 0, emitted, member);
340
+ return;
341
+ }
342
+ case "intersect":
343
+ for (const part of positioned.members) visit(part, path, value, out, false, void 0, emitted, member);
344
+ return;
345
+ default: return;
346
+ }
347
+ }
348
+ /** 数组的项身份:显式声明优先,否则 item 上的 `id` 字段。 */
349
+ function mergeKeyOf(schema, item) {
350
+ const declared = declaredExtra(schema, "mergeKey");
351
+ if (declared !== void 0) return declared;
352
+ const current = unwrapLazy(item);
353
+ return current?.type === "object" && current.fields.some((field) => field.key === "id") ? "id" : void 0;
354
+ }
355
+ /** union 的判别标签:显式声明优先,否则所有对象成员共有的唯一常量字段。 */
356
+ function tagKeyOf(schema, variants) {
357
+ const declared = declaredExtra(schema, "tag");
358
+ if (declared !== void 0) return declared;
359
+ const members = variants.map((variant) => unwrapLazy(variant));
360
+ if (members.length === 0 || members.some((member) => member?.type !== "object")) return void 0;
361
+ const objects = members;
362
+ const candidates = (objects[0]?.fields ?? []).map((field) => unwrapLazy(field)).filter((field) => field?.type === "const").map((field) => field.key).filter((key) => objects.every((object) => unwrapLazy(object.fields.find((field) => field.key === key))?.type === "const"));
363
+ return candidates.length === 1 ? candidates[0] : void 0;
364
+ }
365
+ /** `role(text, { … })` 里声明的字符串约定(项身份 `mergeKey`、判别标签 `tag`)。 */
366
+ function declaredExtra(schema, name) {
367
+ const extra = schema.meta?.extra;
368
+ if (typeof extra !== "object" || extra === null) return void 0;
369
+ const value = Reflect.get(extra, name);
370
+ return typeof value === "string" && value.length > 0 ? value : void 0;
371
+ }
372
+ /** 一项的项身份值(渲染与冲突检查共用的读法)。 */
373
+ function memberLabel(value, mergeKey) {
374
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
375
+ const member = Reflect.get(value, mergeKey);
376
+ if (typeof member === "string") return member;
377
+ if (typeof member === "number" && Number.isFinite(member)) return String(member);
378
+ }
379
+ /**
380
+ * 项身份冲突:同一层里两个成员同名,或某个成员没有名字。
381
+ *
382
+ * 名字是成员的「身份」,写盘仍按索引——所以这只是保存前的本地校验:重名的数组在页面上是非法草稿,
383
+ * 而不是悄悄写到 host 上。
384
+ * @param root - 段根的字段树。
385
+ * @param value - 草稿后的段值。
386
+ * @returns 第一处冲突,或 `undefined`。
387
+ */
388
+ function mergeKeyConflict(root, value) {
389
+ return scanConflict(root, root.path, value);
390
+ }
391
+ function scanConflict(node, path, value) {
392
+ const current = unwrapLazy(node);
393
+ if (current === void 0) return void 0;
394
+ switch (current.type) {
395
+ case "object":
396
+ for (const field of current.fields) {
397
+ const conflict = scanConflict(field, [...path, field.key], memberOf(value, field.key));
398
+ if (conflict !== void 0) return conflict;
399
+ }
400
+ return;
401
+ case "dict":
402
+ for (const [key, child] of entriesOf(value)) {
403
+ const conflict = scanConflict(current.valueNode, [...path, key], child);
404
+ if (conflict !== void 0) return conflict;
405
+ }
406
+ return;
407
+ case "array": {
408
+ const names = /* @__PURE__ */ new Set();
409
+ for (const [key, child] of entriesOf(value)) {
410
+ const itemPath = [...path, key];
411
+ if (current.mergeKey !== void 0) {
412
+ const label = memberLabel(child, current.mergeKey);
413
+ if (label === void 0 || label.trim() === "") return {
414
+ kind: "missing",
415
+ path: itemPath,
416
+ mergeKey: current.mergeKey,
417
+ value: ""
418
+ };
419
+ if (names.has(label)) return {
420
+ kind: "duplicate",
421
+ path: itemPath,
422
+ mergeKey: current.mergeKey,
423
+ value: label
424
+ };
425
+ names.add(label);
426
+ }
427
+ const conflict = scanConflict(current.item, itemPath, child);
428
+ if (conflict !== void 0) return conflict;
429
+ }
430
+ return;
431
+ }
432
+ case "tuple":
433
+ for (const [index, item] of current.items.entries()) {
434
+ const conflict = scanConflict(item, [...path, String(index)], memberOf(value, index));
435
+ if (conflict !== void 0) return conflict;
436
+ }
437
+ return;
438
+ case "union": {
439
+ if (current.choices !== void 0) return void 0;
440
+ const variant = pickVariant(current, value);
441
+ return variant === void 0 ? void 0 : scanConflict(variant, path, value);
442
+ }
443
+ case "intersect":
444
+ for (const member of current.members) {
445
+ const conflict = scanConflict(member, path, value);
446
+ if (conflict !== void 0) return conflict;
447
+ }
448
+ return;
449
+ default: return;
450
+ }
451
+ }
452
+ /** 一个容器值里的成员(对象键或数组索引)。 */
453
+ function entriesOf(value) {
454
+ if (Array.isArray(value)) return value.map((item, index) => [String(index), item]);
455
+ if (typeof value === "object" && value !== null) return Object.entries(value);
456
+ return [];
457
+ }
458
+ /** 读一个成员的值。 */
459
+ function memberOf(value, key) {
460
+ if (Array.isArray(value)) return value[Number(key)];
461
+ if (typeof value === "object" && value !== null) return value[String(key)];
462
+ }
463
+ /** 把节点收成 union:渲染层判形状与选支都从这里进。 */
464
+ function unionOf(node) {
465
+ const current = unwrapLazy(node);
466
+ return current?.type === "union" ? current : void 0;
467
+ }
468
+ /**
469
+ * 当前值落在哪一支:与 {@link walkFields} 的展开同一判据(标签值优先,其次值的形状,最后第一支)。
470
+ * @param node - 字段节点(不是 union 就原样返回)。
471
+ * @param value - 这一层的值。
472
+ * @returns 选中变体的节点。
473
+ */
474
+ function variantOf(node, value) {
475
+ const union = unionOf(node);
476
+ if (union === void 0) return unwrapLazy(node) ?? node;
477
+ return pickVariant(union, value) ?? union;
478
+ }
479
+ /** 变体里那个判别标签字段的常量值(不是 `const` 就没有)。 */
480
+ function tagValueOf(variant, tagKey) {
481
+ const current = unwrapLazy(variant);
482
+ if (current?.type !== "object" && current?.type !== "intersect") return {
483
+ found: false,
484
+ value: void 0
485
+ };
486
+ for (const field of fieldsOf(current)) {
487
+ if (field.key !== tagKey) continue;
488
+ const candidate = unwrapLazy(field);
489
+ if (candidate?.type === "const") return {
490
+ found: true,
491
+ value: candidate.value
492
+ };
493
+ }
494
+ return {
495
+ found: false,
496
+ value: void 0
497
+ };
498
+ }
499
+ /** 一个变体在页面上的名字:能看出"这一支是什么",而不是内部类型名。 */
500
+ function variantLabel(variant, tag) {
501
+ if (tag.found) return JSON.stringify(tag.value);
502
+ switch (unwrapLazy(variant)?.type) {
503
+ case "string": return "\"\"";
504
+ case "number": return "0";
505
+ case "boolean": return "true";
506
+ case "array":
507
+ case "tuple": return "[ ]";
508
+ case "object":
509
+ case "dict":
510
+ case "intersect": return "{ }";
511
+ default: return unwrapLazy(variant)?.type ?? "any";
512
+ }
513
+ }
514
+ /**
515
+ * 变体切换的选项。
516
+ *
517
+ * 带判别标签的 union 写**标签值**(`session-rdb` 的 `type: sqlite` ↔ `type: postgres`);没有标签的写目标变体的
518
+ * 空值(`access` 在「一段文本」与「一组文本」之间切)。
519
+ * @param union - 投影出来的 union 节点。
520
+ * @returns 每个变体一项,顺序即声明顺序。
521
+ */
522
+ function variantChoices(union) {
523
+ return union.variants.map((variant) => {
524
+ const tag = union.tagKey === void 0 ? {
525
+ found: false,
526
+ value: void 0
527
+ } : tagValueOf(variant, union.tagKey);
528
+ return {
529
+ label: variantLabel(variant, tag),
530
+ value: tag.found ? tag.value : emptyValueOf(variant)
531
+ };
532
+ });
533
+ }
534
+ /** 按路径读一个值:对象键与数组下标都认(值的读法只有这一处)。 */
535
+ function readPath(root, path) {
536
+ return path.reduce((node, segment) => {
537
+ if (Array.isArray(node)) return node[Number(segment)];
538
+ if (typeof node !== "object" || node === null) return void 0;
539
+ return node[segment];
540
+ }, root);
541
+ }
542
+ /**
543
+ * 这个"默认值"其实不是值——它不该用来当新加进来的那一项的初值。
544
+ *
545
+ * 两种:`null`("没有值"的判断位,例如 `defaultModel` 用 `.default(null)` 表达"没配就没有这个键"),以及
546
+ * schemastery 给容器类型自动塞的空壳(`{}`)。两种都该让容器把字段摆出来。
547
+ * @param node - 字段节点。
548
+ * @returns 是不是空壳。
549
+ */
550
+ function isBlankDefault(node) {
551
+ const value = node.meta.defaultValue;
552
+ if (value === null) return true;
553
+ if (node.type !== "object" && node.type !== "intersect") return false;
554
+ return typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0;
555
+ }
556
+ /**
557
+ * 一个新子节点的初值:声明的默认值优先;对象把字段摆出来(见下);字典/数组给空的容器;其余标量给 **`null`**。
558
+ *
559
+ * `null` 在这里是"还没有值"的判断位(`null` / `undefined` 都算),页面据此把它画成一个等着输入的位子——
560
+ * 而不是 `""`、`0` 这种看着像已经填过的值。host 侧也认这个语义:非必填字段拿到 `null` 回退到默认值,
561
+ * 必填字段拿到 `null` 就是"必须填"。
562
+ * @param node - 子节点的字段树。
563
+ * @returns 初值。
564
+ */
565
+ function emptyValueOf(node) {
566
+ if (node.meta.hasDefault && !isBlankDefault(node)) return structuredClone(node.meta.defaultValue);
567
+ switch (node.type) {
568
+ case "array": return [];
569
+ case "tuple": return node.items.map((item) => emptyValueOf(item));
570
+ case "dict": return {};
571
+ case "object":
572
+ case "intersect": return Object.fromEntries(fieldsOf(node).map((field) => [field.key, emptyValueOf(field)]));
573
+ case "const": return node.value;
574
+ case "bitset": return 0;
575
+ case "lazy": return emptyValueOf(node.inner);
576
+ default: return null;
577
+ }
578
+ }
579
+ /** 选中与当前值匹配的 union 变体:对象按判别标签的值判,叶子按值类型判。 */
580
+ function pickVariant(node, value) {
581
+ if (node.tagKey !== void 0 && typeof value === "object" && value !== null && !Array.isArray(value)) {
582
+ const tagValue = Reflect.get(value, node.tagKey);
583
+ for (const variant of node.variants) {
584
+ const current = unwrapLazy(variant);
585
+ if (current?.type !== "object") continue;
586
+ const field = unwrapLazy(current.fields.find((candidate) => candidate.key === node.tagKey));
587
+ if (field?.type === "const" && field.value === tagValue) return variant;
588
+ }
589
+ }
590
+ for (const variant of node.variants) {
591
+ const current = unwrapLazy(variant);
592
+ if (current?.type === "string" && typeof value === "string") return variant;
593
+ if (current?.type === "number" && typeof value === "number") return variant;
594
+ if (current?.type === "boolean" && typeof value === "boolean") return variant;
595
+ if (current?.type === "array" && Array.isArray(value)) return variant;
596
+ }
597
+ return node.variants[0];
598
+ }
599
+ //#endregion
600
+ //#region src/client/schema-form/draft.ts
601
+ /**
602
+ * 把校验器抛出的错误收成一条失败。
603
+ *
604
+ * schemastery 的 `ValidationError` 把路径留在 `options.path` 上(消息里的 `$…` 前缀是给命令行看的),所以这里
605
+ * 取路径、去前缀——行内报错据此知道挂在哪一行。
606
+ * @param error - 校验器抛出的错误。
607
+ * @returns 消息与路径。
608
+ */
609
+ function failureOf(error) {
610
+ const raw = error instanceof Error ? error.message : String(error);
611
+ const path = (error.options?.path ?? []).filter((segment) => typeof segment === "string" || typeof segment === "number").map(String);
612
+ const message = raw.replace(/^\$\S*\s/, "").trim();
613
+ return {
614
+ message: message === "" ? raw : message,
615
+ path
616
+ };
617
+ }
618
+ /**
619
+ * 一页配置的草稿。一个命名空间一个实例:它订阅共享配置表单,投影给渲染层读。
620
+ */
621
+ var SchemaDraftModel = class {
622
+ #scope;
623
+ #root;
624
+ #validate;
625
+ #staged = /* @__PURE__ */ new Map();
626
+ #listeners = /* @__PURE__ */ new Set();
627
+ #unsubscribe;
628
+ #baseline;
629
+ #saving = false;
630
+ #failed = false;
631
+ #violation;
632
+ /**
633
+ * @param options - 本页的共享配置表单、字段树与整段校验。
634
+ */
635
+ constructor(options) {
636
+ this.#scope = options.scope;
637
+ this.#root = options.root;
638
+ this.#validate = options.validate;
639
+ this.#unsubscribe = this.#scope.subscribe(() => {
640
+ this.#publish();
641
+ });
642
+ }
643
+ /**
644
+ * 换一份字段树。
645
+ *
646
+ * 增删项要按它取节点默认值,而 schema 可能随 describe 变(行的 Config 改了)——所以控制器每次投影都同步一次,
647
+ * 否则加进来的项会取不到默认值。
648
+ * @param root - 段根的字段树。
649
+ */
650
+ setRoot(root) {
651
+ this.#root = root;
652
+ }
653
+ /**
654
+ * 观察草稿与读数的变化。
655
+ * @param listener - 每次投影都调一次的回调。
656
+ * @returns 取消订阅。
657
+ */
658
+ subscribe(listener) {
659
+ this.#listeners.add(listener);
660
+ return () => {
661
+ this.#listeners.delete(listener);
662
+ };
663
+ }
664
+ /**
665
+ * 投影一份读数给渲染层。
666
+ * @param project - 用模型的当前读数造渲染要的状态。
667
+ * @returns 渲染层按选择器读的 store。
668
+ */
669
+ bind(project) {
670
+ const store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(project());
671
+ this.subscribe(() => {
672
+ store.set(project());
673
+ });
674
+ return store;
675
+ }
676
+ /**
677
+ * 表单级状态(与上游 `SettingsForm` 的壳同形)。
678
+ * @returns 可用性、可写性、脏、非法、保存中、上次保存失败。
679
+ */
680
+ shell() {
681
+ const { writes, blocked } = this.#plan();
682
+ return {
683
+ available: this.#scope.getSnapshot().status === "ready",
684
+ writable: this.#scope.getSnapshot().writable,
685
+ dirty: writes.some((item) => item.op !== void 0) || blocked,
686
+ invalid: blocked || this.#violation !== void 0,
687
+ saving: this.#saving,
688
+ failed: this.#failed
689
+ };
690
+ }
691
+ /**
692
+ * 保存前的整段校验(保存被 schema 挡下时非空)。
693
+ * @returns 失败消息与它的位置,或 `undefined`。
694
+ */
695
+ violation() {
696
+ return this.#violation;
697
+ }
698
+ /**
699
+ * 读一个字段的草稿状态。
700
+ * @param path - 具体路径(动态段已按真实键或索引替换)。
701
+ * @param preview - 本轮已经算好的读数(`preview()` 的结果)。
702
+ *
703
+ * **容器的值要跟着别处的草稿走**:判别式 union 的选支就是容器的值,若这里读生效层,`type` 的切换控件会显示旧
704
+ * 的那一支(字段树却已经换了)。所以有 `preview` 就用它——控制器一次投影里只算一份。
705
+ * @returns 显示值、文本草稿、非法消息、是否覆盖。
706
+ */
707
+ field(path, preview) {
708
+ const snapshot = this.#scope.getSnapshot();
709
+ const staged = this.#staged.get(pathKey(path));
710
+ const stored = hasPath(snapshot.user, path);
711
+ if (staged === void 0) return {
712
+ value: readPath(preview ?? snapshot.value, path),
713
+ text: void 0,
714
+ invalid: void 0,
715
+ overridden: stored,
716
+ staged: false
717
+ };
718
+ switch (staged.edit.kind) {
719
+ case "value": return {
720
+ value: staged.edit.value,
721
+ text: void 0,
722
+ invalid: void 0,
723
+ overridden: true,
724
+ staged: true
725
+ };
726
+ case "text": {
727
+ const parsed = staged.edit.parse(staged.edit.text);
728
+ return {
729
+ value: parsed.kind === "value" ? parsed.value : readPath(snapshot.value, path),
730
+ text: staged.edit.text,
731
+ invalid: parsed.kind === "invalid" ? parsed.message : void 0,
732
+ overridden: true,
733
+ staged: true
734
+ };
735
+ }
736
+ case "clear": return {
737
+ value: readPath(snapshot.base, path),
738
+ text: void 0,
739
+ invalid: void 0,
740
+ overridden: false,
741
+ staged: true
742
+ };
743
+ case "drop": return {
744
+ value: void 0,
745
+ text: void 0,
746
+ invalid: void 0,
747
+ overridden: false,
748
+ staged: true
749
+ };
750
+ }
751
+ }
752
+ /**
753
+ * 暂存一个合法值。
754
+ * @param path - 具体路径。
755
+ * @param value - 控件给出的值。
756
+ */
757
+ set(path, value) {
758
+ this.#stage(path, {
759
+ kind: "value",
760
+ value
761
+ });
762
+ }
763
+ /**
764
+ * 暂存一段文本,保存时才解析。
765
+ * @param path - 具体路径。
766
+ * @param text - 用户输入的原文。
767
+ * @param parse - 解析规则;失败时给出消息并挡住保存。
768
+ */
769
+ setText(path, text, parse) {
770
+ this.#stage(path, {
771
+ kind: "text",
772
+ text,
773
+ parse
774
+ });
775
+ }
776
+ /**
777
+ * 暂存「恢复默认」。
778
+ * @param path - 具体路径。
779
+ */
780
+ clear(path) {
781
+ this.#stage(path, { kind: "clear" });
782
+ }
783
+ /**
784
+ * 追加一个数组项,值取该项节点的默认值。
785
+ * @param path - 数组字段的具体路径。
786
+ */
787
+ appendItem(path) {
788
+ const node = seekNode(this.#root, path);
789
+ if (node?.type !== "array") return;
790
+ const current = readPath(this.preview(), path);
791
+ const index = Array.isArray(current) ? current.length : 0;
792
+ this.set([...path, String(index)], emptyValueOf(node.item));
793
+ }
794
+ /**
795
+ * 移除一个数组项。
796
+ * @param path - 数组字段的具体路径。
797
+ * @param index - 项在数组里的位置。
798
+ */
799
+ removeItem(path, index) {
800
+ this.#stage([...path, String(index)], { kind: "drop" });
801
+ }
802
+ /**
803
+ * 给一层加一项,值取它的默认值。
804
+ *
805
+ * 字典认任意键;对象只认 schema 声明过的字段(写一个没声明的键会被 host 挡下,所以这里直接不写)。
806
+ * @param path - 容器的具体路径。
807
+ * @param key - 要加的键。
808
+ */
809
+ addKey(path, key) {
810
+ const node = seekNode(this.#root, path);
811
+ if (node === void 0) return;
812
+ if (node.type === "dict") {
813
+ this.set([...path, key], emptyValueOf(node.valueNode));
814
+ return;
815
+ }
816
+ const field = fieldsOf(node).find((candidate) => candidate.key === key);
817
+ if (field === void 0) return;
818
+ this.set([...path, key], emptyValueOf(field));
819
+ }
820
+ /**
821
+ * 删一个字典键。
822
+ * @param path - 字典字段的具体路径。
823
+ * @param key - 要删的键。
824
+ */
825
+ removeKey(path, key) {
826
+ this.#stage([...path, key], { kind: "drop" });
827
+ }
828
+ /**
829
+ * 撤销一个字段的草稿(回到它当前的生效值)——行内编辑取消时用。
830
+ * @param path - 具体路径。
831
+ */
832
+ revert(path) {
833
+ if (!this.#staged.delete(pathKey(path))) return;
834
+ this.#failed = false;
835
+ this.#violation = void 0;
836
+ this.#publish();
837
+ }
838
+ /** 丢弃全部草稿,回到 host 的读数。 */
839
+ discard() {
840
+ if (this.#staged.size === 0 && !this.#failed && this.#violation === void 0) return;
841
+ this.#staged.clear();
842
+ this.#baseline = void 0;
843
+ this.#failed = false;
844
+ this.#violation = void 0;
845
+ this.#publish();
846
+ }
847
+ /**
848
+ * 把全部草稿写成一次带 revision 栅栏的提交。
849
+ *
850
+ * host 是「值是否被接受」的唯一权威(有些约束 schema 表达不出来),所以落盘与否读回来决定,而不是在这里
851
+ * 预测;没落盘的保存保留草稿,让用户改而不是重打。
852
+ * @returns 写完与读回之后的收束。
853
+ */
854
+ async save() {
855
+ const snapshot = this.#scope.getSnapshot();
856
+ if (this.#saving || !snapshot.writable) return;
857
+ const { writes, blocked } = this.#plan();
858
+ if (blocked) return;
859
+ const ops = writes.flatMap((item) => item.op === void 0 ? [] : [item.op]);
860
+ if (ops.length === 0) {
861
+ this.#violation = void 0;
862
+ this.#publish();
863
+ return;
864
+ }
865
+ const failure = this.#validate(applyOps(snapshot.value ?? {}, ops));
866
+ if (failure !== void 0) {
867
+ this.#violation = failure;
868
+ this.#publish();
869
+ return;
870
+ }
871
+ this.#violation = void 0;
872
+ this.#saving = true;
873
+ this.#failed = false;
874
+ this.#publish();
875
+ try {
876
+ const landed = await this.#scope.mutate(ops, this.#baseline?.revision);
877
+ if (landed) {
878
+ this.#staged.clear();
879
+ this.#baseline = void 0;
880
+ }
881
+ this.#failed = !landed;
882
+ } catch {
883
+ this.#failed = true;
884
+ } finally {
885
+ this.#saving = false;
886
+ this.#publish();
887
+ }
888
+ }
889
+ /** 释放对共享配置表单的订阅。 */
890
+ dispose() {
891
+ this.#unsubscribe();
892
+ this.#listeners.clear();
893
+ }
894
+ /** 每条草稿这一轮会写什么;被 schema 挡下的草稿没有 op,但仍然是脏的。 */
895
+ #plan() {
896
+ const snapshot = this.#scope.getSnapshot();
897
+ const writes = [];
898
+ let blocked = false;
899
+ for (const { path, edit } of this.#staged.values()) {
900
+ if (edit.kind === "drop") {
901
+ writes.push({ op: {
902
+ op: "unset",
903
+ path: [...path]
904
+ } });
905
+ continue;
906
+ }
907
+ if (edit.kind === "clear") {
908
+ if (hasPath(snapshot.user, path)) writes.push({ op: {
909
+ op: "unset",
910
+ path: [...path]
911
+ } });
912
+ continue;
913
+ }
914
+ const parsed = edit.kind === "value" ? {
915
+ kind: "value",
916
+ value: edit.value
917
+ } : edit.parse(edit.text);
918
+ if (parsed.kind === "invalid") {
919
+ blocked = true;
920
+ continue;
921
+ }
922
+ if (parsed.kind === "skip") continue;
923
+ if (sameValue$1(readPath(snapshot.value, path), parsed.value)) continue;
924
+ writes.push({ op: {
925
+ op: "set",
926
+ path: [...path],
927
+ value: parsed.value
928
+ } });
929
+ }
930
+ return {
931
+ writes,
932
+ blocked
933
+ };
934
+ }
935
+ /** 生效值套上本轮草稿后的段值:字段树按它展开(追加的数组项立刻出现),整段校验也看它。 */
936
+ preview() {
937
+ const ops = this.#plan().writes.flatMap((item) => item.op === void 0 ? [] : [item.op]);
938
+ return applyOps(this.#scope.getSnapshot().value ?? {}, ops);
939
+ }
940
+ #stage(path, edit) {
941
+ this.#baseline ??= this.#scope.getSnapshot();
942
+ this.#staged.set(pathKey(path), {
943
+ path: [...path],
944
+ edit
945
+ });
946
+ this.#failed = false;
947
+ this.#violation = void 0;
948
+ this.#publish();
949
+ }
950
+ #publish() {
951
+ for (const listener of this.#listeners) listener();
952
+ }
953
+ };
954
+ /** 深比较:值相等就不写(避免把「改回原值」也当成一次写)。 */
955
+ function sameValue$1(left, right) {
956
+ if (Object.is(left, right)) return true;
957
+ if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) return false;
958
+ return JSON.stringify(left) === JSON.stringify(right);
959
+ }
960
+ /** 路径的 map 键。 */
961
+ function pathKey(path) {
962
+ return JSON.stringify(path);
963
+ }
964
+ /** 路径末段是否在用户层里存在(覆盖判据)。 */
965
+ function hasPath(root, path) {
966
+ if (path.length === 0) return root !== void 0;
967
+ const parent = readPath(root, path.slice(0, -1));
968
+ const leaf = path[path.length - 1] ?? "";
969
+ if (Array.isArray(parent)) return Number(leaf) < parent.length;
970
+ if (typeof parent !== "object" || parent === null) return false;
971
+ return leaf in parent;
972
+ }
973
+ /** 把一组 op 套到一个段值上(本地读数用,不写 host)。 */
974
+ function applyOps(section, ops) {
975
+ const next = structuredClone(section);
976
+ for (const op of ops) {
977
+ const parent = readPath(next, op.path.slice(0, -1));
978
+ const leaf = op.path[op.path.length - 1] ?? "";
979
+ if (Array.isArray(parent)) {
980
+ if (op.op === "set") parent[Number(leaf)] = op.value;
981
+ else parent.splice(Number(leaf), 1);
982
+ continue;
983
+ }
984
+ if (typeof parent !== "object" || parent === null) continue;
985
+ if (op.op === "set") parent[leaf] = op.value;
986
+ else Reflect.deleteProperty(parent, leaf);
987
+ }
988
+ return next;
989
+ }
990
+ //#endregion
991
+ //#region src/client/schema-form/controller.ts
992
+ /** `role('select', { source })` 里声明的候选源名字。 */
993
+ function declaredSource(node) {
994
+ const extra = node?.meta.extra;
995
+ if (typeof extra !== "object" || extra === null) return void 0;
996
+ const name = Reflect.get(extra, "source");
997
+ return typeof name === "string" && name.length > 0 ? name : void 0;
998
+ }
999
+ /** 字段在状态表里的键(具体路径的 JSON)。 */
1000
+ function fieldKey(path) {
1001
+ return JSON.stringify(path);
1002
+ }
1003
+ /**
1004
+ * rehydrate 一段配置 schema,并判它能不能当页面的根。
1005
+ *
1006
+ * 能当根的形状有三种:对象段、**相交段**(共享判别字段 + 各分支的 `const` 标记,段值仍是对象)、以及带判别
1007
+ * 标签的 `union`(成员都是对象)。字面量 `union`(整段就是一个标量)不行:那一行的写盘会变成整段替换。
1008
+ * @param serialized - host 发来的 `schema.toJSON()`。
1009
+ * @param rehydrate - host schema 的 rehydrate。
1010
+ * @returns schema 与它的字段树,或 `undefined`(不能当页面根)。
1011
+ */
1012
+ function projectRoot(serialized, rehydrate) {
1013
+ try {
1014
+ const schema = rehydrate(serialized);
1015
+ const root = projectNode(schema);
1016
+ if (root.type === "object" || root.type === "intersect") return {
1017
+ schema,
1018
+ root
1019
+ };
1020
+ if (root.type === "union" && root.tagKey !== void 0 && root.choices === void 0) return {
1021
+ schema,
1022
+ root
1023
+ };
1024
+ return;
1025
+ } catch {
1026
+ return;
1027
+ }
1028
+ }
1029
+ /**
1030
+ * 一行能否自动生成配置页:它的 schema 能 rehydrate 且根是 {@link projectRoot} 认的形状。
1031
+ * @param view - host 发来的命名空间视图。
1032
+ * @param rehydrate - host schema 的 rehydrate。
1033
+ * @returns 是否可以渲染。
1034
+ */
1035
+ function canRender(view, rehydrate) {
1036
+ return view !== void 0 && projectRoot(view.schema, rehydrate) !== void 0;
1037
+ }
1038
+ /** 段根不是可渲染对象时的空字段树。 */
1039
+ const NO_FIELDS = {
1040
+ key: "",
1041
+ path: [],
1042
+ label: void 0,
1043
+ meta: emptyMeta(),
1044
+ readOnly: null,
1045
+ type: "object",
1046
+ fields: []
1047
+ };
1048
+ /** 值里已有的键(数组给索引)。 */
1049
+ function keysOf(value) {
1050
+ if (Array.isArray(value)) return value.map((_item, index) => String(index));
1051
+ if (typeof value === "object" && value !== null) return Object.keys(value);
1052
+ return [];
1053
+ }
1054
+ /**
1055
+ * 一个容器当前能加什么。
1056
+ *
1057
+ * 对象层(含相交与 union 的选中变体)是**非必填**、值里又没有的字段——必填的已经占着行了;字典层是业务注册的
1058
+ * 候选键(模式清单这类运行期数据)。数组层不在这里:它的「添加一项」是追加一个空项,没有键名可选。
1059
+ * @param node - 容器节点。
1060
+ * @param path - 该容器的具体路径。
1061
+ * @param value - 该容器自己的值(草稿后的)。
1062
+ * @param hints - 提示面(只用到候选键)。
1063
+ * @returns 可添加项,顺序即 schema / 读数的顺序;列表为空时页面不给输入框。
1064
+ */
1065
+ function addableAt(node, path, value, hints) {
1066
+ const current = variantOf(node, value);
1067
+ if (current.type === "dict") {
1068
+ const present = new Set(keysOf(value));
1069
+ return (hints?.keysFor(path) ?? []).flatMap((key) => present.has(key) ? [] : [{
1070
+ key,
1071
+ description: void 0
1072
+ }]);
1073
+ }
1074
+ if (current.type !== "object" && current.type !== "intersect") return [];
1075
+ return fieldsOf(current).flatMap((field) => field.meta.required !== true && readPath(value, [field.key]) === void 0 ? [{
1076
+ key: field.key,
1077
+ description: field.meta.description ?? field.meta.comment
1078
+ }] : []);
1079
+ }
1080
+ /**
1081
+ * 一个字段的可选项。
1082
+ *
1083
+ * 字面量集合的 union(`z.union(["wal", "delete"])`)自己就带着候选;其余看提示面注册的读数——读的时候把依赖的
1084
+ * 兄弟字段值喂进去,所以「选完服务商才列它有哪些模型」是投影的一部分,不是额外的一次订阅。
1085
+ * @param node - 字段节点。
1086
+ * @param path - 该字段的具体路径。
1087
+ * @param value - 段值(草稿后的)。
1088
+ * @param hints - 提示面。
1089
+ * @returns 可选项;没有候选就是 `undefined`。
1090
+ */
1091
+ function optionsFor(node, path, value, hints) {
1092
+ const current = unwrapLazy(node);
1093
+ if (current?.type === "union" && current.choices !== void 0) return current.choices.map((choice) => ({ value: choice }));
1094
+ const source = declaredSource(current);
1095
+ const spec = source === void 0 ? hints?.selectFor?.(path) : hints?.sourceFor?.(source);
1096
+ if (spec === void 0) return void 0;
1097
+ const parent = path.slice(0, -1);
1098
+ const options = spec.options((relative) => readPath(value, [...parent, ...relative]));
1099
+ return options.length === 0 ? void 0 : options;
1100
+ }
1101
+ /**
1102
+ * 一行配置页的控制器。
1103
+ */
1104
+ var SchemaFormController = class {
1105
+ #ns;
1106
+ #deps;
1107
+ #model;
1108
+ #store;
1109
+ #disposers = [];
1110
+ #view;
1111
+ #schema;
1112
+ #root;
1113
+ /**
1114
+ * @param ns - settings 命名空间(行 id)。
1115
+ * @param deps - 该行的共享表单与 schema 面。
1116
+ */
1117
+ constructor(ns, deps) {
1118
+ this.#ns = ns;
1119
+ this.#deps = deps;
1120
+ this.#model = new SchemaDraftModel({
1121
+ scope: deps.form,
1122
+ root: NO_FIELDS,
1123
+ validate: (value) => this.#validate(deps, value)
1124
+ });
1125
+ this.#store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(this.#project());
1126
+ this.#disposers.push(this.#model.subscribe(() => {
1127
+ this.#store.set(this.#project());
1128
+ }));
1129
+ this.#disposers.push(deps.describe.subscribe(() => {
1130
+ this.#sync();
1131
+ }));
1132
+ deps.describe.ensure();
1133
+ this.#sync();
1134
+ }
1135
+ /** 槽位渲染器要的动作与状态。 */
1136
+ face() {
1137
+ return {
1138
+ hooks: { schemaForm: this.#store },
1139
+ set: (path, value) => {
1140
+ this.#model.set(path, value);
1141
+ },
1142
+ setText: (path, text, parse) => {
1143
+ this.#model.setText(path, text, parse);
1144
+ },
1145
+ clear: (path) => {
1146
+ this.#model.clear(path);
1147
+ },
1148
+ appendItem: (path) => {
1149
+ this.#model.appendItem(path);
1150
+ },
1151
+ removeItem: (path, index) => {
1152
+ this.#model.removeItem(path, index);
1153
+ },
1154
+ addKey: (path, key) => {
1155
+ this.#model.addKey(path, key);
1156
+ },
1157
+ removeKey: (path, key) => {
1158
+ this.#model.removeKey(path, key);
1159
+ },
1160
+ save: () => {
1161
+ this.#model.save();
1162
+ },
1163
+ revert: (path) => {
1164
+ this.#model.revert(path);
1165
+ },
1166
+ discard: () => {
1167
+ this.#model.discard();
1168
+ }
1169
+ };
1170
+ }
1171
+ /** 本行的 settings 命名空间。 */
1172
+ namespace() {
1173
+ return this.#ns;
1174
+ }
1175
+ /** 外部提示(候选键这类)变了:重新投影一次,字段树与草稿都不动。 */
1176
+ refresh() {
1177
+ this.#store.set(this.#project());
1178
+ }
1179
+ /** 释放订阅。 */
1180
+ dispose() {
1181
+ for (const dispose of this.#disposers.splice(0)) dispose();
1182
+ this.#model.dispose();
1183
+ }
1184
+ /** 重新读 schema(describe 变了):只换字段树,草稿与 store 不动。 */
1185
+ #sync() {
1186
+ const view = this.#deps.describe.getSnapshot().view?.namespaces.find((row) => row.ns === this.#ns);
1187
+ this.#view = view;
1188
+ const projected = view === void 0 ? void 0 : projectRoot(view.schema, this.#deps.rehydrate);
1189
+ this.#schema = projected?.schema;
1190
+ this.#root = projected?.root;
1191
+ this.#model.setRoot(projected?.root ?? NO_FIELDS);
1192
+ this.#store.set(this.#project());
1193
+ }
1194
+ /** 整段校验:schema 先说话,再看数组的项身份有没有冲突。 */
1195
+ #validate(deps, value) {
1196
+ if (this.#schema !== void 0) {
1197
+ const failure = deps.validate(this.#schema, value);
1198
+ if (failure !== void 0) return failure;
1199
+ }
1200
+ if (this.#root === void 0) return void 0;
1201
+ const conflict = mergeKeyConflict(this.#root, value);
1202
+ if (conflict === void 0) return void 0;
1203
+ return {
1204
+ message: conflict.kind === "duplicate" ? deps.t("duplicateItem", {
1205
+ key: conflict.mergeKey,
1206
+ value: conflict.value
1207
+ }) : deps.t("missingItem", { key: conflict.mergeKey }),
1208
+ path: conflict.path
1209
+ };
1210
+ }
1211
+ /** 渲染层读的一份完整投影。 */
1212
+ #project() {
1213
+ const fields = /* @__PURE__ */ new Map();
1214
+ const root = this.#root;
1215
+ const preview = this.#model.preview();
1216
+ const walked = root === void 0 ? [] : walkFields(root, preview);
1217
+ for (const item of walked) fields.set(fieldKey(item.path), this.#model.field(item.path, preview));
1218
+ const secrets = /* @__PURE__ */ new Map();
1219
+ for (const secret of this.#view?.secrets ?? []) secrets.set(fieldKey(secret.path), secret.set);
1220
+ const texts = /* @__PURE__ */ new Map();
1221
+ const options = /* @__PURE__ */ new Map();
1222
+ const addable = /* @__PURE__ */ new Map();
1223
+ for (const item of walked) {
1224
+ const text = this.#deps.hints?.textFor(item.path);
1225
+ if (text !== void 0) texts.set(fieldKey(item.path), text);
1226
+ const candidates = optionsFor(item.node, item.path, preview, this.#deps.hints);
1227
+ if (candidates !== void 0) options.set(fieldKey(item.path), candidates);
1228
+ const addableHere = addableAt(item.node, item.path, readPath(preview, item.path), this.#deps.hints);
1229
+ if (addableHere.length > 0) addable.set(fieldKey(item.path), addableHere);
1230
+ }
1231
+ const violation = this.#model.violation();
1232
+ const invalidAt = /* @__PURE__ */ new Map();
1233
+ if (violation !== void 0 && violation.path.length > 0) invalidAt.set(fieldKey(violation.path), violation.message);
1234
+ return {
1235
+ ...this.#model.shell(),
1236
+ configured: root !== void 0,
1237
+ root: root ?? NO_FIELDS,
1238
+ walked,
1239
+ fields,
1240
+ secrets,
1241
+ texts,
1242
+ options,
1243
+ addable,
1244
+ violation,
1245
+ invalidAt
1246
+ };
1247
+ }
1248
+ };
1249
+ //#endregion
1250
+ //#region src/client/schema-form/hints.ts
1251
+ /**
1252
+ * 通用表单的提示面:业务知道、而 schema 表达不了的结构信息。
1253
+ *
1254
+ * 两件事:**dict 字段的候选键**(例如「会话模式」那一行,键是运行期清单里的模式 id,schema 里写不出来),以及
1255
+ * **字段的候选值**(选服务商、选模型这类——候选来自部署里的另一份配置,还经常互相依赖)。业务在客户端注册一个
1256
+ * 同步读数,表单一侧在投影时用它补出「未配置」行或把字段画成选择器。
1257
+ *
1258
+ * 读数必须是**同步**的:投影发生在渲染帧里。异步来源(HTTP / remote)由业务自己取好再注册;注册本身就当作一次
1259
+ * 变更通知(所以"取到清单后再注册"是推荐用法)。
1260
+ */
1261
+ /** 提示面的完整服务(业务用注册侧,表单一侧用读侧)。 */
1262
+ var SchemaFormHints = class extends _deepseek_ai_cordis.Service {
1263
+ #readers = /* @__PURE__ */ new Map();
1264
+ #texts = /* @__PURE__ */ new Map();
1265
+ #selects = /* @__PURE__ */ new Map();
1266
+ #sources = /* @__PURE__ */ new Map();
1267
+ #listeners = /* @__PURE__ */ new Set();
1268
+ /** @param ctx - 提供本服务的插件上下文。 */
1269
+ constructor(ctx) {
1270
+ super(ctx, "schemaFormHints");
1271
+ }
1272
+ /** @param ns - settings 命名空间。 @param path - 字段路径。 @returns 该处的键。 */
1273
+ keysFor(ns, path) {
1274
+ return lookup(this.#readers, ns, path)?.() ?? [];
1275
+ }
1276
+ /** @param ns - settings 命名空间。 @param path - 字段路径。 @returns 该处注册的文案。 */
1277
+ textFor(ns, path) {
1278
+ return lookup(this.#texts, ns, path)?.() ?? {};
1279
+ }
1280
+ /**
1281
+ * @param ns - settings 命名空间。
1282
+ * @param path - 字段路径。
1283
+ * @param spec - 候选取法与依赖。
1284
+ * @returns 注销函数。
1285
+ */
1286
+ select(ns, path, spec) {
1287
+ const key = locationKey(ns, path);
1288
+ this.#selects.set(key, spec);
1289
+ this.#publish();
1290
+ return () => {
1291
+ if (this.#selects.get(key) !== spec) return;
1292
+ this.#selects.delete(key);
1293
+ this.#publish();
1294
+ };
1295
+ }
1296
+ /** @param ns - settings 命名空间。 @param path - 字段路径。 @returns 该处注册的候选读数。 */
1297
+ selectFor(ns, path) {
1298
+ return lookup(this.#selects, ns, path);
1299
+ }
1300
+ /**
1301
+ * @param name - 源的名字。
1302
+ * @param spec - 候选取法与依赖。
1303
+ * @returns 注销函数。
1304
+ */
1305
+ source(name, spec) {
1306
+ this.#sources.set(name, spec);
1307
+ this.#publish();
1308
+ return () => {
1309
+ if (this.#sources.get(name) !== spec) return;
1310
+ this.#sources.delete(name);
1311
+ this.#publish();
1312
+ };
1313
+ }
1314
+ /** @param name - 源的名字。 @returns 该源注册的候选读数。 */
1315
+ sourceFor(name) {
1316
+ return this.#sources.get(name);
1317
+ }
1318
+ /**
1319
+ * @param ns - settings 命名空间。
1320
+ * @param path - 字段路径。
1321
+ * @param read - 同步读数。
1322
+ * @returns 注销函数。
1323
+ */
1324
+ describe(ns, path, read) {
1325
+ const key = locationKey(ns, path);
1326
+ this.#texts.set(key, read);
1327
+ this.#publish();
1328
+ return () => {
1329
+ if (this.#texts.get(key) !== read) return;
1330
+ this.#texts.delete(key);
1331
+ this.#publish();
1332
+ };
1333
+ }
1334
+ /**
1335
+ * @param ns - settings 命名空间。
1336
+ * @param path - 字段路径。
1337
+ * @param read - 同步读数。
1338
+ * @returns 注销函数。
1339
+ */
1340
+ suggestKeys(ns, path, read) {
1341
+ const key = locationKey(ns, path);
1342
+ this.#readers.set(key, read);
1343
+ this.#publish();
1344
+ return () => {
1345
+ if (this.#readers.get(key) !== read) return;
1346
+ this.#readers.delete(key);
1347
+ this.#publish();
1348
+ };
1349
+ }
1350
+ /**
1351
+ * 观察候选键的变化(注册与注销)。
1352
+ * @param listener - 变更回调。
1353
+ * @returns 取消订阅。
1354
+ */
1355
+ subscribe(listener) {
1356
+ this.#listeners.add(listener);
1357
+ return () => {
1358
+ this.#listeners.delete(listener);
1359
+ };
1360
+ }
1361
+ /**
1362
+ * 重新通知一次:读数背后的数据变了(业务自己知道),页面据此重算候选行。
1363
+ */
1364
+ refresh() {
1365
+ this.#publish();
1366
+ }
1367
+ #publish() {
1368
+ for (const listener of this.#listeners) listener();
1369
+ }
1370
+ };
1371
+ /** 一处字段的定位键(命名空间 + 路径)。 */
1372
+ function locationKey(ns, path) {
1373
+ return `${ns}\u0000${path.join("\0")}`;
1374
+ }
1375
+ /**
1376
+ * 按具体路径找一处注册:先精确匹配,再试**模板变体**(动态键 / 数组下标的段写成 `*`)。
1377
+ *
1378
+ * 业务因此可以注册一次 `models.*.provider` 就覆盖每个模式,不必知道运行期有哪些键。
1379
+ * @param map - 某类注册表。
1380
+ * @param ns - settings 命名空间。
1381
+ * @param path - 具体路径。
1382
+ * @returns 命中的注册,或 `undefined`。
1383
+ */
1384
+ function lookup(map, ns, path) {
1385
+ const exact = map.get(locationKey(ns, path));
1386
+ if (exact !== void 0) return exact;
1387
+ for (const masked of templates(path)) {
1388
+ const hit = map.get(locationKey(ns, masked));
1389
+ if (hit !== void 0) return hit;
1390
+ }
1391
+ }
1392
+ /** 具体路径的模板变体(首段是顶层字段名,保持原样):通配得多的先试。 */
1393
+ function* templates(path) {
1394
+ const wildcardable = Math.max(0, path.length - 1);
1395
+ for (let bits = (1 << wildcardable) - 1; bits > 0; bits--) yield path.map((segment, index) => index > 0 && (bits & 1 << index - 1) !== 0 ? "*" : segment);
1396
+ }
1397
+ //#endregion
1398
+ //#region src/client/schema-form/locales.ts
1399
+ /**
1400
+ * 本包字典(命名空间 `settings.schema-form`):表单壳、字段控件与只读呈现的文案。
1401
+ *
1402
+ * 字段自身的人话标签不在这里(那是业务方经字段槽或 schema `description` 给的),这里只放**机制**文案。
1403
+ */
1404
+ /** 中文文案(部署语言偏好是 zh)。 */
1405
+ const zh = {
1406
+ unavailable: "该插件当前未加载,暂时无法配置。",
1407
+ readOnly: "本部署的设置为只读。",
1408
+ save: "保存",
1409
+ confirmEdit: "确认",
1410
+ cancelEdit: "取消",
1411
+ saving: "保存中…",
1412
+ saveFailed: "本部署没有接受这些值,已保留供你修改。",
1413
+ notSaved: "没有保存:{message}",
1414
+ noSchema: "这一行没有可自动生成的配置项。",
1415
+ reset: "恢复默认",
1416
+ revert: "撤回",
1417
+ invalid: "这个值不被接受。",
1418
+ invalidNumber: "需要一个数字。",
1419
+ invalidInteger: "需要一个整数。",
1420
+ invalidJson: "需要一段合法的 JSON。",
1421
+ invalidBoolean: "需要 true 或 false。",
1422
+ invalidConst: "这个字段只接受固定值。",
1423
+ invalidPattern: "要匹配 {pattern}。",
1424
+ invalidRange: "要在 {min} 与 {max} 之间。",
1425
+ invalidMin: "不能小于 {min}。",
1426
+ invalidMax: "不能大于 {max}。",
1427
+ invalidChoice: "只能是 {choices} 之一。",
1428
+ unknownProperty: "这一层没有这个字段:从上面的候选里选,或换个键名。",
1429
+ empty: "(空)",
1430
+ toggleGroup: "展开 / 收起这一层",
1431
+ switchVariant: "切换这一层的形状或分支",
1432
+ copyValue: "复制值",
1433
+ copied: "已复制",
1434
+ addProperty: "添加属性",
1435
+ addItemPaste: "添加数组项(可粘贴 JSON 字符串)",
1436
+ addItem: "添加一项",
1437
+ removeItem: "移除",
1438
+ addKey: "添加键(可粘贴 JSON 字符串)",
1439
+ keyName: "键名",
1440
+ noKeys: "还没有键。",
1441
+ noItems: "还没有项。",
1442
+ deprecated: "已废弃",
1443
+ experimental: "试验中",
1444
+ secretSet: "已配置",
1445
+ secretUnset: "未配置",
1446
+ secretHint: "留空即保留已存的值。",
1447
+ constValue: "固定值",
1448
+ notEditable: "这一项由插件在运行时提供,不能在页面上编辑。",
1449
+ recursive: "递归结构:展开到这一层为止。",
1450
+ fixedLength: "固定长度",
1451
+ bitsetHint: "可多选。",
1452
+ unconfigured: "未配置",
1453
+ readOnlyField: "只读",
1454
+ noDescription: "(无说明)",
1455
+ required: "必填",
1456
+ duplicateItem: "重复的 {key}:{value}",
1457
+ missingItem: "每一项都需要一个 {key}"
1458
+ };
1459
+ /** 英文文案。 */
1460
+ const en = {
1461
+ unavailable: "This plugin is not loaded, so it cannot be configured right now.",
1462
+ readOnly: "This deployment stores settings read-only.",
1463
+ save: "Save",
1464
+ confirmEdit: "Confirm",
1465
+ cancelEdit: "Cancel",
1466
+ saving: "Saving…",
1467
+ saveFailed: "This deployment did not accept these values; they are kept for you to fix.",
1468
+ notSaved: "Not saved: {message}",
1469
+ noSchema: "This row has no configuration to generate.",
1470
+ reset: "Reset",
1471
+ revert: "Revert",
1472
+ invalid: "This value is not accepted.",
1473
+ invalidNumber: "Enter a number.",
1474
+ invalidInteger: "Enter a whole number.",
1475
+ invalidJson: "Enter valid JSON.",
1476
+ invalidBoolean: "Enter true or false.",
1477
+ invalidConst: "This field only accepts its fixed value.",
1478
+ invalidPattern: "Must match {pattern}.",
1479
+ invalidRange: "Must be between {min} and {max}.",
1480
+ invalidMin: "Must not be less than {min}.",
1481
+ invalidMax: "Must not be greater than {max}.",
1482
+ invalidChoice: "Must be one of {choices}.",
1483
+ unknownProperty: "No such field at this level: pick one of the candidates above.",
1484
+ empty: "(empty)",
1485
+ toggleGroup: "Expand or collapse this level",
1486
+ switchVariant: "Switch this level's shape or branch",
1487
+ copyValue: "Copy value",
1488
+ copied: "Copied",
1489
+ addProperty: "Add property",
1490
+ addItemPaste: "Add item (a JSON string can be pasted)",
1491
+ addItem: "Add item",
1492
+ removeItem: "Remove",
1493
+ addKey: "Add key (a JSON string can be pasted)",
1494
+ keyName: "Key",
1495
+ noKeys: "No keys yet.",
1496
+ noItems: "No items yet.",
1497
+ deprecated: "deprecated",
1498
+ experimental: "experimental",
1499
+ secretSet: "configured",
1500
+ secretUnset: "not set",
1501
+ secretHint: "Leave blank to keep the stored value.",
1502
+ constValue: "fixed",
1503
+ notEditable: "This entry is provided by the plugin at runtime and cannot be edited here.",
1504
+ recursive: "Recursive structure: expanded down to this level.",
1505
+ fixedLength: "fixed length",
1506
+ bitsetHint: "Multiple choices.",
1507
+ unconfigured: "not configured",
1508
+ readOnlyField: "read-only",
1509
+ noDescription: "(no description)",
1510
+ required: "required",
1511
+ duplicateItem: "Duplicate {key}: {value}",
1512
+ missingItem: "Every item needs a {key}"
1513
+ };
1514
+ //#endregion
1515
+ //#region src/client/schema-form/rows.ts
1516
+ /**
1517
+ * 配对命名空间与 bundle 行清单。
1518
+ * @param namespaces - host 发来的命名空间视图。
1519
+ * @param bundles - host 的 bundle 清单(含各 bundle 声明的行 id)。
1520
+ * @returns 该自动注册的行,按命名空间视图的顺序。
1521
+ */
1522
+ function rowRegistrations(namespaces, bundles) {
1523
+ const owners = /* @__PURE__ */ new Map();
1524
+ for (const bundle of bundles) for (const row of bundle.rows) owners.set(row.rowId, bundle.name);
1525
+ const plans = [];
1526
+ for (const view of namespaces) {
1527
+ if (view.autoGenerate !== true) continue;
1528
+ const owner = owners.get(view.ns);
1529
+ if (owner === void 0) continue;
1530
+ plans.push({
1531
+ key: `${owner}#${view.ns}`,
1532
+ ns: view.ns
1533
+ });
1534
+ }
1535
+ return plans;
1536
+ }
1537
+ /** 自动项的兜底优先级:手写卡片(默认 0)遮住它。 */
1538
+ const FALLBACK_PRIORITY = 100;
1539
+ /**
1540
+ * 自动注册的差分器:按 describe 与 bundle 清单把「该有的行」与「已在的行」对齐。
1541
+ */
1542
+ var RowRegistration = class {
1543
+ #deps;
1544
+ #live = /* @__PURE__ */ new Map();
1545
+ #pending;
1546
+ #disposed = false;
1547
+ /**
1548
+ * @param deps - 槽位、清单读面与每行的槽项工厂。
1549
+ */
1550
+ constructor(deps) {
1551
+ this.#deps = deps;
1552
+ }
1553
+ /**
1554
+ * 对齐一次:新增该有的行,注销不该有的行。
1555
+ *
1556
+ * 并发调用折叠成一次(bundle 清单只读一趟),后到的调用等同一趟结束。
1557
+ * @returns 对齐完成。
1558
+ */
1559
+ sync() {
1560
+ if (this.#disposed) return Promise.resolve();
1561
+ this.#pending ??= this.#run().finally(() => {
1562
+ this.#pending = void 0;
1563
+ });
1564
+ return this.#pending;
1565
+ }
1566
+ /** 收掉自己:注销还活着的全部注册并释放它们的控制器。 */
1567
+ dispose() {
1568
+ this.#disposed = true;
1569
+ this.#release();
1570
+ }
1571
+ async #run() {
1572
+ const bundles = await this.#deps.bundles();
1573
+ if (this.#disposed) return;
1574
+ const namespaces = this.#deps.describe.getSnapshot().view?.namespaces ?? [];
1575
+ const wanted = /* @__PURE__ */ new Map();
1576
+ for (const plan of rowRegistrations(namespaces, bundles)) {
1577
+ if (this.#deps.entryFor(plan.ns) === void 0) continue;
1578
+ wanted.set(plan.ns, plan);
1579
+ }
1580
+ for (const [ns, dispose] of this.#live) {
1581
+ if (wanted.has(ns)) continue;
1582
+ dispose();
1583
+ this.#live.delete(ns);
1584
+ this.#deps.release(ns);
1585
+ }
1586
+ for (const [ns, plan] of wanted) {
1587
+ if (this.#live.has(ns)) continue;
1588
+ const entry = this.#deps.entryFor(ns);
1589
+ if (entry === void 0) continue;
1590
+ const dispose = this.#deps.slots.register({
1591
+ name: "plugins.row.config",
1592
+ key: plan.key,
1593
+ priority: FALLBACK_PRIORITY,
1594
+ locale: this.#deps.locale
1595
+ }, entry.component);
1596
+ this.#live.set(ns, dispose);
1597
+ }
1598
+ }
1599
+ /** 注销全部并释放控制器。 */
1600
+ #release() {
1601
+ for (const [ns, dispose] of this.#live) {
1602
+ dispose();
1603
+ this.#live.delete(ns);
1604
+ this.#deps.release(ns);
1605
+ }
1606
+ }
1607
+ };
1608
+ //#endregion
1609
+ //#region src/client/schema-form/labels.ts
1610
+ /** 字段的说明(`description` 本地化后 + `comment`),没有就是 undefined。 */
1611
+ function fieldHint(node, resolveText) {
1612
+ const parts = [];
1613
+ if (node.meta.description !== void 0) parts.push(resolveText(node.meta.description));
1614
+ if (node.meta.comment !== void 0) parts.push(node.meta.comment);
1615
+ return parts.length === 0 ? void 0 : parts.join(" ");
1616
+ }
1617
+ //#endregion
1618
+ //#region src/client/schema-form/lines.ts
1619
+ /**
1620
+ * 行模型:把字段树(`walkFields` 的结果)排成**编辑器的行**——结构行(`{` / `[` / `}` / `]`)、字段行、注释行、
1621
+ * 以及容器尾部的添加行。
1622
+ *
1623
+ * 渲染层因此只按行画,不必自己递归:行序 = 视觉顺序,行号连续。折叠在这里生效(折叠的容器只留开启行)。
1624
+ */
1625
+ /**
1626
+ * 容器的形状:对象类用 `{}`,序列类用 `[]`。
1627
+ *
1628
+ * union 没有自己的形状——它跟着**当前选中那一支**走(`access` 是数组就在页面上画 `[`),这也是
1629
+ * `access: string | string[]` 这类字段以前被画成对象容器的原因。
1630
+ * @param node - 字段节点。
1631
+ * @param value - 这一层的值(选支判据)。
1632
+ * @returns 形状;叶子节点是 `undefined`。
1633
+ */
1634
+ function containerShape(node, value) {
1635
+ switch (variantOf(node, value).type) {
1636
+ case "object":
1637
+ case "dict":
1638
+ case "intersect": return "object";
1639
+ case "array":
1640
+ case "tuple": return "array";
1641
+ default: return;
1642
+ }
1643
+ }
1644
+ /** 渲染顺序里可见的字段:折叠的容器把整棵子树滤掉,只留它自己的开启行。 */
1645
+ function visibleFields(state, fold) {
1646
+ const containers = new Set(state.walked.filter((item) => containerShape(item.node, valueAt(state, item.path)) !== void 0).map((item) => fieldKey(item.path)));
1647
+ return state.walked.filter((item) => {
1648
+ for (let depth = 1; depth < item.path.length; depth++) {
1649
+ const ancestor = item.path.slice(0, depth);
1650
+ if (containers.has(fieldKey(ancestor)) && fold.collapsed(ancestor)) return false;
1651
+ }
1652
+ return true;
1653
+ });
1654
+ }
1655
+ /** 这一层的当前值:与字段行显示的是同一份读数(草稿优先)。 */
1656
+ function valueAt(state, path) {
1657
+ return state.fields.get(fieldKey(path))?.value;
1658
+ }
1659
+ /** 这一层能加什么:数组追加空项,字典敲键名(可来自候选),对象从还没配的声明字段里选。 */
1660
+ function addOf(node, shape, options) {
1661
+ if (shape === "array") return { kind: "item" };
1662
+ if (unwrapLazy(node)?.type === "dict") return {
1663
+ kind: "key",
1664
+ options
1665
+ };
1666
+ return options.length === 0 ? void 0 : {
1667
+ kind: "prop",
1668
+ options
1669
+ };
1670
+ }
1671
+ /** 一个节点上的变体切换控件:不是 union、或只有一支、或成员全是常量(字面量集合是选择器)时没有。 */
1672
+ function variantsOf(node, value) {
1673
+ const union = unionOf(node);
1674
+ if (union === void 0 || union.choices !== void 0 || union.variants.length < 2) return void 0;
1675
+ return {
1676
+ choices: variantChoices(union),
1677
+ selected: union.variants.indexOf(variantOf(union, value))
1678
+ };
1679
+ }
1680
+ /**
1681
+ * 排成行序列。
1682
+ * @param state - 页面读数。
1683
+ * @param fold - 折叠状态。
1684
+ * @param resolveText - `description` 的本地化。
1685
+ * @param t - 本包字典。
1686
+ * @returns 行序列(行号即下标)。
1687
+ */
1688
+ function editorLines(state, fold, resolveText, t) {
1689
+ const lines = [];
1690
+ const open = [];
1691
+ const closeOpenUntil = (path) => {
1692
+ while (open.length > 0) {
1693
+ const top = open[open.length - 1];
1694
+ if (isPrefix(top.path, path)) return;
1695
+ open.pop();
1696
+ lines.push({
1697
+ kind: "close",
1698
+ path: top.path,
1699
+ node: top.node,
1700
+ shape: top.shape,
1701
+ depth: top.depth,
1702
+ add: top.add,
1703
+ member: top.member
1704
+ });
1705
+ }
1706
+ };
1707
+ for (const item of visibleFields(state, fold)) {
1708
+ closeOpenUntil(item.path);
1709
+ const node = unwrapLazy(item.node) ?? item.node;
1710
+ const value = valueAt(state, item.path);
1711
+ const depth = item.path.length;
1712
+ const comment = commentFor(state, item, resolveText, t);
1713
+ if (comment !== void 0) lines.push({
1714
+ kind: "comment",
1715
+ path: item.path,
1716
+ depth,
1717
+ text: comment.text,
1718
+ invalid: comment.invalid
1719
+ });
1720
+ const variants = variantsOf(node, value);
1721
+ const shape = containerShape(node, value);
1722
+ if (shape !== void 0) {
1723
+ const collapsed = fold.collapsed(item.path);
1724
+ const add = addOf(node, shape, state.addable.get(fieldKey(item.path)) ?? []);
1725
+ lines.push({
1726
+ kind: "open",
1727
+ path: item.path,
1728
+ node,
1729
+ shape,
1730
+ depth,
1731
+ collapsed,
1732
+ member: item.member,
1733
+ variants
1734
+ });
1735
+ if (!collapsed) open.push({
1736
+ path: item.path,
1737
+ node,
1738
+ shape,
1739
+ depth,
1740
+ add,
1741
+ member: item.member
1742
+ });
1743
+ continue;
1744
+ }
1745
+ lines.push({
1746
+ kind: "field",
1747
+ path: item.path,
1748
+ node,
1749
+ depth,
1750
+ field: state.fields.get(fieldKey(item.path)) ?? {
1751
+ value: void 0,
1752
+ text: void 0,
1753
+ invalid: void 0,
1754
+ overridden: false,
1755
+ staged: false
1756
+ },
1757
+ member: item.member,
1758
+ variants,
1759
+ variantsStandIn: false
1760
+ });
1761
+ }
1762
+ while (open.length > 0) {
1763
+ const top = open.pop();
1764
+ lines.push({
1765
+ kind: "close",
1766
+ path: top.path,
1767
+ node: top.node,
1768
+ shape: top.shape,
1769
+ depth: top.depth,
1770
+ add: top.add,
1771
+ member: top.member
1772
+ });
1773
+ }
1774
+ return moveVariantControlToTag(lines);
1775
+ }
1776
+ /**
1777
+ * 判别式 union 的切换控件挪到**标签字段那一行**上:`session-rdb` 的页面上用户改的就是 `type`,把它放在容器
1778
+ * 行上等于让人在两处看同一件事。没有标签字段(`access` 这种按值形状选的)就留在 union 自己那一行。
1779
+ */
1780
+ function moveVariantControlToTag(lines) {
1781
+ for (const line of lines) {
1782
+ if (line.kind === "comment" || line.kind === "close") continue;
1783
+ if (line.variants === void 0) continue;
1784
+ const tagKey = unionOf(line.node)?.tagKey;
1785
+ if (tagKey === void 0) continue;
1786
+ const tagPath = [...line.path, tagKey];
1787
+ const tagLine = lines.find((candidate) => candidate.kind === "field" && candidate.path.length === tagPath.length && candidate.path.every((segment, index) => segment === tagPath[index]));
1788
+ if (tagLine === void 0 || tagLine.kind !== "field") continue;
1789
+ tagLine.variants = line.variants;
1790
+ tagLine.variantsStandIn = true;
1791
+ line.variants = void 0;
1792
+ }
1793
+ return lines;
1794
+ }
1795
+ /**
1796
+ * 一行的注释:业务给的文案(字段槽的 label/hint)优先,其次 schema 的说明。
1797
+ *
1798
+ * 这一行有问题时**注释位让给那条消息**(参考实现也是把错误画在行尾那一段的位置上)——说明换成"哪里不对",
1799
+ * 错误因此不必挤在值后面。
1800
+ */
1801
+ function commentFor(state, item, resolveText, t) {
1802
+ const key = fieldKey(item.path);
1803
+ const failure = state.fields.get(key)?.invalid ?? state.invalidAt.get(key);
1804
+ if (failure !== void 0) return {
1805
+ text: failure,
1806
+ invalid: true
1807
+ };
1808
+ const node = unwrapLazy(item.node) ?? item.node;
1809
+ const parts = [];
1810
+ const text = state.texts.get(fieldKey(item.path));
1811
+ const hint = [text?.label, text?.hint].filter((part) => part !== void 0 && part !== "").join(":") || fieldHint(node, resolveText);
1812
+ if (hint !== void 0 && hint !== "") parts.push(hint);
1813
+ if (node.meta.disabled) parts.push(t("readOnlyField"));
1814
+ if (node.meta.badges.length > 0) parts.push(node.meta.badges.join(" / "));
1815
+ return parts.length === 0 ? void 0 : {
1816
+ text: parts.join(" · "),
1817
+ invalid: false
1818
+ };
1819
+ }
1820
+ /** `path` 是不是 `prefix` 的后代(含自身)。 */
1821
+ function isPrefix(prefix, path) {
1822
+ if (prefix.length > path.length) return false;
1823
+ return prefix.every((segment, index) => path[index] === segment);
1824
+ }
1825
+ //#endregion
23
1826
  //#region src/client/styling/toolkit.ts
24
1827
  function isUndefined(value) {
25
1828
  return value === void 0;
@@ -324,7 +2127,7 @@ window.__ModuleLoader__.load({
324
2127
  //#endregion
325
2128
  //#region src/client/theme.generated.ts
326
2129
  /**
327
- * 官方主题的 `--dsw-*` token 全集(373 个),由 packages/client/ui-primitives/scripts/gen-design-tokens.mts 生成,勿手改。
2130
+ * 官方主题的 `--dsw-*` token 全集(395 个),由 packages/client/ui-primitives/scripts/gen-design-tokens.mts 生成,勿手改。
328
2131
  *
329
2132
  * 叶子是该变量的**默认值**(官方 light 主题里首次出现的定义):既是类型推导的来源,也可作 fallback。
330
2133
  * `"$"` 键出现在「自身也是 token 的分支」上(命名体系里有 40 个短名同时是长名前缀的 token)。
@@ -334,7 +2137,7 @@ window.__ModuleLoader__.load({
334
2137
  alias: {
335
2138
  bg: {
336
2139
  base: "var(--dsw-static-neutral-bluish-00)",
337
- document: { preview: "var(--dsw-static-neutral-bluish-750)" },
2140
+ document: { preview: "var(--dsw-static-neutral-bluish-100)" },
338
2141
  layer: {
339
2142
  "1": "var(--dsw-static-neutral-bluish-00)",
340
2143
  "2": "var(--dsw-static-neutral-bluish-00)",
@@ -428,7 +2231,7 @@ window.__ModuleLoader__.load({
428
2231
  label: {
429
2232
  caption: "var(--dsw-static-neutral-bluish-400)",
430
2233
  dimmed: "var(--dsw-static-neutral-bluish-200)",
431
- document: { preview: "var(--dsw-static-neutral-bluish-200)" },
2234
+ document: { preview: "var(--dsw-static-neutral-bluish-700)" },
432
2235
  primary: {
433
2236
  $: "var(--dsw-static-neutral-bluish-1000)",
434
2237
  bluish: "var(--dsw-static-blue-900)",
@@ -456,6 +2259,13 @@ window.__ModuleLoader__.load({
456
2259
  placeholder: "var(--dsw-static-neutral-bluish-60)",
457
2260
  tag: "var(--dsw-static-neutral-bluish-75)"
458
2261
  },
2262
+ menu: { icon: "var(--dsw-static-neutral-bluish-800)" },
2263
+ onboarding: {
2264
+ accent: "rgb(57, 100, 254)",
2265
+ card: { fill: "color-mix(in srgb, var(--dsw-static-neutral-bluish-00) 80%, transparent)" },
2266
+ checkbox: { border: "color-mix(in srgb, var(--dsw-static-neutral-bluish-1000) 20%, transparent)" },
2267
+ secondary: { fill: "var(--dsw-static-neutral-bluish-00)" }
2268
+ },
459
2269
  scrollbar: {
460
2270
  bg: {
461
2271
  l1: "var(--dsw-static-neutral-200)",
@@ -466,6 +2276,10 @@ window.__ModuleLoader__.load({
466
2276
  l2: "var(--dsw-static-neutral-300)"
467
2277
  }
468
2278
  },
2279
+ settings: { card: {
2280
+ fill: "var(--dsw-alias-bg-layer-2)",
2281
+ stroke: "var(--dsw-alias-border-l4)"
2282
+ } },
469
2283
  state: {
470
2284
  business: {
471
2285
  primary: "var(--dsw-static-deepseek-500)",
@@ -488,8 +2302,14 @@ window.__ModuleLoader__.load({
488
2302
  tertiary: "var(--dsw-static-amber-100)"
489
2303
  }
490
2304
  },
491
- toast: { bg: "var(--dsw-static-neutral-bluish-800)" },
492
- tooltip: { bg: "var(--dsw-static-neutral-bluish-850)" }
2305
+ toast: {
2306
+ bg: "var(--dsw-static-neutral-bluish-800)",
2307
+ label: "var(--dsw-static-neutral-bluish-00)"
2308
+ },
2309
+ tooltip: {
2310
+ bg: "var(--dsw-static-neutral-bluish-850)",
2311
+ key: { bg: "color-mix(in srgb, var(--dsw-alias-tooltip-bg), white 18%)" }
2312
+ }
493
2313
  },
494
2314
  corner: { shape: "superellipse(1.5)" },
495
2315
  elevation: {
@@ -501,6 +2321,10 @@ window.__ModuleLoader__.load({
501
2321
  color: "var(--dsw-alias-border-l4)"
502
2322
  }
503
2323
  },
2324
+ focus: { ring: {
2325
+ color: "transparent",
2326
+ width: "2px"
2327
+ } },
504
2328
  font: {
505
2329
  base: {
506
2330
  "16": {
@@ -524,7 +2348,10 @@ window.__ModuleLoader__.load({
524
2348
  line: { height: "24px" }
525
2349
  } }
526
2350
  },
527
- family: "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',\n 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif",
2351
+ family: {
2352
+ $: "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',\n 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif",
2353
+ brand: "'Montserrat', var(--dsw-font-family)"
2354
+ },
528
2355
  l: { "20": {
529
2356
  $: "500 20px/28px var(--dsw-font-family)",
530
2357
  font: {
@@ -816,12 +2643,28 @@ window.__ModuleLoader__.load({
816
2643
  } }
817
2644
  }
818
2645
  },
2646
+ gradient: { onboarding: {
2647
+ blue: { stops: "#3964fe 18.75%, #398efe 51.78%, #6dccff 86.252%, #3964fe" },
2648
+ cyan: { stops: "#0293b4 21.154%, #2dc8eb 50.954%, #aff0ff 85.326%, #0293b4" },
2649
+ violet: { stops: "#2a2fb6 33.102%, #8b76f6 50.954%, #c5c0ff 85.326%, #2a2fb6" }
2650
+ } },
819
2651
  linear: {
820
2652
  gradient: { think: "linear-gradient(180deg, #fff 20.19%, rgba(255, 255, 255, 0) 100%)" },
821
2653
  think: { select: "linear-gradient(180deg, #f5f6f7 20.19%, rgba(245, 246, 247, 0) 100%)" }
822
2654
  },
823
- mask: { blur: "blur(2px)" },
824
- menu: { backdrop: { filter: "blur(40px) saturate(150%)" } },
2655
+ mask: { blur: "none" },
2656
+ menu: {
2657
+ backdrop: { filter: "blur(40px) saturate(150%)" },
2658
+ surface: { fill: "rgba(248, 249, 250, 0.58)" }
2659
+ },
2660
+ radius: {
2661
+ lg: "16px",
2662
+ md: "12px",
2663
+ panel: "28px",
2664
+ sm: "8px",
2665
+ xl: "20px",
2666
+ xs: "4px"
2667
+ },
825
2668
  shadow: {
826
2669
  lv1: {
827
2670
  $: "0 2px 4px 0 rgba(0, 0, 0, 0.05)",
@@ -837,7 +2680,7 @@ window.__ModuleLoader__.load({
837
2680
  },
838
2681
  input: { major: "var(--dsw-static-neutral-bluish-00)" },
839
2682
  login: { input: "var(--dsw-static-neutral-bluish-50)" },
840
- menu: "rgba(248, 249, 250, 0.58)",
2683
+ menu: "var(--dsw-menu-surface-fill)",
841
2684
  selector: "var(--dsw-static-neutral-bluish-60)",
842
2685
  sidebar: {
843
2686
  fill: "var(--dsw-static-neutral-bluish-50)",
@@ -894,66 +2737,1277 @@ window.__ModuleLoader__.load({
894
2737
  a08: "rgb(34 197 94 / 8%)",
895
2738
  a12: "rgb(34 197 94 / 12%)"
896
2739
  },
897
- "900": "rgb(35, 60, 44)"
898
- },
899
- neutral: {
900
- "50": "rgb(250, 250, 250)",
901
- "100": "rgb(245, 245, 245)",
902
- "150": "rgb(237, 237, 237)",
903
- "200": "rgb(229, 229, 229)",
904
- "250": "rgb(220, 220, 220)",
905
- "300": "rgb(212, 212, 212)",
906
- "400": "rgb(162, 164, 166)",
907
- "500": "rgb(127, 130, 135)",
908
- "550": "rgb(101, 103, 107)",
909
- "600": "rgb(84, 85, 87)",
910
- "700": "rgb(60, 60, 61)",
911
- "800": "rgb(41, 41, 41)",
912
- "850": "rgb(33, 33, 35)",
913
- "900": "rgb(15, 15, 15)",
914
- "1000": "rgb(0, 0, 0)",
915
- "00": "rgb(255, 255, 255)",
916
- bluish: {
917
- "50": "rgb(249, 250, 251)",
918
- "60": "rgb(245, 246, 247)",
919
- "75": "rgb(241, 243, 245)",
920
- "100": "rgb(235, 238, 242)",
921
- "150": "rgb(233, 236, 242)",
922
- "200": "rgb(225, 229, 238)",
923
- "300": "rgb(207, 211, 214)",
924
- "400": "rgb(173, 178, 184)",
925
- "500": "rgb(151, 157, 166)",
926
- "600": "rgb(129, 133, 140)",
927
- "700": "rgb(97, 102, 107)",
928
- "750": "rgb(67, 69, 74)",
929
- "800": "rgb(53, 54, 56)",
930
- "850": "rgb(44, 44, 46)",
931
- "875": "rgb(35, 35, 36)",
932
- "900": "rgb(27, 27, 28)",
933
- "950": "rgb(21, 21, 23)",
934
- "1000": "rgb(15, 17, 21)",
935
- "00": "rgb(255, 255, 255)"
2740
+ "900": "rgb(35, 60, 44)"
2741
+ },
2742
+ neutral: {
2743
+ "50": "rgb(250, 250, 250)",
2744
+ "100": "rgb(245, 245, 245)",
2745
+ "150": "rgb(237, 237, 237)",
2746
+ "200": "rgb(229, 229, 229)",
2747
+ "250": "rgb(220, 220, 220)",
2748
+ "300": "rgb(212, 212, 212)",
2749
+ "400": "rgb(162, 164, 166)",
2750
+ "500": "rgb(127, 130, 135)",
2751
+ "550": "rgb(101, 103, 107)",
2752
+ "600": "rgb(84, 85, 87)",
2753
+ "700": "rgb(60, 60, 61)",
2754
+ "800": "rgb(41, 41, 41)",
2755
+ "850": "rgb(33, 33, 35)",
2756
+ "900": "rgb(15, 15, 15)",
2757
+ "1000": "rgb(0, 0, 0)",
2758
+ "00": "rgb(255, 255, 255)",
2759
+ bluish: {
2760
+ "50": "rgb(249, 250, 251)",
2761
+ "60": "rgb(245, 246, 247)",
2762
+ "75": "rgb(241, 243, 245)",
2763
+ "100": "rgb(235, 238, 242)",
2764
+ "150": "rgb(233, 236, 242)",
2765
+ "200": "rgb(225, 229, 238)",
2766
+ "300": "rgb(207, 211, 214)",
2767
+ "400": "rgb(173, 178, 184)",
2768
+ "500": "rgb(151, 157, 166)",
2769
+ "600": "rgb(129, 133, 140)",
2770
+ "700": "rgb(97, 102, 107)",
2771
+ "750": "rgb(67, 69, 74)",
2772
+ "800": "rgb(53, 54, 56)",
2773
+ "850": "rgb(44, 44, 46)",
2774
+ "875": "rgb(35, 35, 36)",
2775
+ "900": "rgb(27, 27, 28)",
2776
+ "950": "rgb(21, 21, 23)",
2777
+ "1000": "rgb(15, 17, 21)",
2778
+ "00": "rgb(255, 255, 255)"
2779
+ }
2780
+ },
2781
+ red: {
2782
+ "50": "rgb(254, 242, 242)",
2783
+ "100": "rgb(254, 226, 226)",
2784
+ "400": {
2785
+ $: "rgb(242, 90, 90)",
2786
+ a12: "rgb(242 90 90 / 12%)"
2787
+ },
2788
+ "500": "rgb(239, 68, 68)",
2789
+ "600": {
2790
+ $: "rgb(236, 19, 19)",
2791
+ a08: "rgb(236 19 19 / 8%)"
2792
+ },
2793
+ "900": "rgb(87, 12, 12)"
2794
+ }
2795
+ }
2796
+ };
2797
+ //#endregion
2798
+ //#region src/client/theme.ts
2799
+ const dsw = Token.vars(designTokens, { prefix: "dsw" });
2800
+ //#endregion
2801
+ //#region src/client/schema-form/styles.ts
2802
+ /**
2803
+ * 行式编辑器的样式:等宽、行号、语法色、悬停行为——全部消费官方 `--dsw-*` token。
2804
+ *
2805
+ * 几何只有一套:行高、行内间距、动作按钮尺寸都由下面几个常量给,行与行之间不各自决定。
2806
+ */
2807
+ /** 等宽字体栈:配置值的观感与代码一致。 */
2808
+ const MONO = "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace";
2809
+ /** 行高:行内容、行内输入、折叠箭头、按钮排都在这一档上对齐。 */
2810
+ const ROW = "22px";
2811
+ /** 行内元素之间的间距。 */
2812
+ const GAP = "6px";
2813
+ /** 动作按钮:方形图标键,尺寸与行高同一档的紧凑版。 */
2814
+ const ACTION = "20px";
2815
+ /** 动作按钮之间的间距(比行内间距紧一档)。 */
2816
+ const ACTION_GAP = "4px";
2817
+ /** 折叠列(chevron 与它的占位)宽度。 */
2818
+ const FOLD = "14px";
2819
+ /** 行号列宽度。 */
2820
+ const NUMBER = "32px";
2821
+ /** 行尾留白。 */
2822
+ const PAD = "8px";
2823
+ /** 细线:theme 给的 0.5px 描边(`0 0 0 0.5px var(--dsw-elevation-stroke-color)`)。 */
2824
+ const HAIRLINE = dsw.elevation.stroke;
2825
+ /** 描边色:theme 里给描边用的那一档(与官方 `Menu` 面板同源)。 */
2826
+ const LINE = dsw.elevation.stroke.color;
2827
+ /** 行左侧那道「改过」的标记宽度。 */
2828
+ const EDGE = "2px";
2829
+ /** 出错那一行的底色:theme 的红掺进透明里(半透明红),比整行实红轻、又比只有一道条明显。 */
2830
+ const ERROR_WASH = `color-mix(in srgb, ${String(dsw.alias.state.error.secondary)} 12%, transparent)`;
2831
+ /** 编辑器的根:纵向排列的行。 */
2832
+ const EditorRoot = styled("div")({
2833
+ display: "flex",
2834
+ flexDirection: "column",
2835
+ width: "100%",
2836
+ fontFamily: MONO,
2837
+ fontSize: "14px",
2838
+ lineHeight: ROW,
2839
+ color: dsw.alias.label.primary
2840
+ });
2841
+ /** 一行:行号 + 折进 + 行内容。悬停给一层浅底。 */
2842
+ const LineRow = styled("div")({
2843
+ display: "flex",
2844
+ alignItems: "flex-start",
2845
+ gap: GAP,
2846
+ minHeight: ROW,
2847
+ paddingRight: PAD,
2848
+ whiteSpace: "nowrap",
2849
+ "&:hover": { background: dsw.alias.bg.layer["2"] },
2850
+ "&[data-selected='true']": { background: dsw.alias.bg.layer["3"] },
2851
+ "&[data-dirty='true']": { boxShadow: `inset ${EDGE} 0 0 ${String(dsw.alias.brand.primary)}` },
2852
+ "&[data-overridden='true']": { boxShadow: `inset ${EDGE} 0 0 ${String(LINE)}` },
2853
+ "&[data-dirty='true'] [data-role='key']": { color: dsw.alias.brand.primary },
2854
+ "&[data-overridden='true'] [data-role='key']": { color: dsw.alias.brand.primary },
2855
+ "&[data-invalid='true']": {
2856
+ background: ERROR_WASH,
2857
+ boxShadow: `inset ${EDGE} 0 0 ${String(dsw.alias.state.error.primary)}`
2858
+ },
2859
+ "&[data-invalid='true'] [data-role='key']": { color: dsw.alias.state.error.primary },
2860
+ "&:hover [data-role='actions']": { opacity: 1 }
2861
+ });
2862
+ /** 一行的内容区:**缩进只作用在这里**——行号列固定在左边,缩进不推它。 */
2863
+ const LineBody = styled("div")({
2864
+ display: "flex",
2865
+ alignItems: "center",
2866
+ gap: GAP,
2867
+ flex: 1,
2868
+ minWidth: 0,
2869
+ minHeight: ROW
2870
+ });
2871
+ /** 行号列:右对齐、不可选、淡色。 */
2872
+ const LineNumber = styled("span")({
2873
+ flex: "none",
2874
+ width: NUMBER,
2875
+ lineHeight: ROW,
2876
+ textAlign: "right",
2877
+ color: dsw.alias.label.caption,
2878
+ userSelect: "none",
2879
+ cursor: "pointer"
2880
+ });
2881
+ /** 折叠 chevron(官方箭头图标,尺寸与别的行内图标同源)。 */
2882
+ const LineFold = styled("button")({
2883
+ display: "inline-flex",
2884
+ alignItems: "center",
2885
+ justifyContent: "center",
2886
+ flex: "none",
2887
+ width: FOLD,
2888
+ height: ROW,
2889
+ padding: 0,
2890
+ border: 0,
2891
+ background: "none",
2892
+ color: dsw.alias.label.caption,
2893
+ cursor: "pointer",
2894
+ "&:hover": { color: dsw.alias.label.secondary }
2895
+ });
2896
+ /** 叶子行的折叠占位(保持列对齐)。 */
2897
+ const LineFoldSpacer = styled("span")({
2898
+ flex: "none",
2899
+ width: FOLD
2900
+ });
2901
+ /** 键名(配置键原样;数组下标淡一些)。 */
2902
+ const LineKey = styled("span")({
2903
+ flex: "none",
2904
+ color: dsw.alias.label.primary,
2905
+ "&[data-index='true']": { color: dsw.alias.label.caption }
2906
+ });
2907
+ /** 结构符与标点(`{` `}` `[` `]` `:`)。 */
2908
+ const LineToken = styled("span")({
2909
+ flex: "none",
2910
+ color: dsw.alias.label.caption
2911
+ });
2912
+ /** 值(token):字符串 / 数字 / 布尔各有语法色。 */
2913
+ const LineValue = styled("span")({
2914
+ minWidth: 0,
2915
+ overflow: "hidden",
2916
+ textOverflow: "ellipsis",
2917
+ cursor: "text",
2918
+ color: dsw.alias.state.business.primary,
2919
+ "&[data-tone='number']": { color: dsw.alias.state.warn.primary },
2920
+ "&[data-tone='boolean']": { color: dsw.alias.state.success.primary },
2921
+ "&[data-tone='empty']": { color: dsw.alias.label.caption }
2922
+ });
2923
+ /**
2924
+ * 值上的下拉触发:**它自己就是值的呈现**(不再是「值 + 一个下拉」两遍),点开才出菜单。
2925
+ */
2926
+ const ValueTrigger = styled("button")({
2927
+ display: "inline-flex",
2928
+ alignItems: "center",
2929
+ gap: "2px",
2930
+ minWidth: 0,
2931
+ padding: 0,
2932
+ border: 0,
2933
+ background: "none",
2934
+ font: "inherit",
2935
+ textAlign: "left",
2936
+ cursor: "pointer",
2937
+ color: dsw.alias.label.caption,
2938
+ "&:disabled": { cursor: "default" },
2939
+ "&:focus-visible": { outline: "none" }
2940
+ });
2941
+ /**
2942
+ * 行内输入的外壳:官方 `Input` 压到与值同一行高,并**吃掉剩下的宽度**。
2943
+ *
2944
+ * 行内编辑与容器闭合行的添加输入共用它——两处的输入框因此长得一模一样。
2945
+ */
2946
+ const CompactInput = styled("span")({
2947
+ display: "inline-flex",
2948
+ flex: "1 1 auto",
2949
+ minWidth: 0,
2950
+ "& > span": {
2951
+ width: "100%",
2952
+ height: ROW,
2953
+ padding: "0 6px",
2954
+ borderRadius: "4px"
2955
+ },
2956
+ "& input": {
2957
+ fontSize: "14px",
2958
+ lineHeight: ROW
2959
+ }
2960
+ });
2961
+ /**
2962
+ * 多行值的行内输入:官方没有多行原子,所以这里用同一套观感的字段壳(同边框、同圆角、同焦点色),
2963
+ * 高度跟着内容长、宽度同样撑满剩下的位置。
2964
+ */
2965
+ const CompactTextField = styled("span")({
2966
+ display: "inline-flex",
2967
+ flex: "1 1 auto",
2968
+ minWidth: 0,
2969
+ padding: "0 6px",
2970
+ borderRadius: "4px",
2971
+ background: dsw.alias.bg.layer["1"],
2972
+ border: 0,
2973
+ boxShadow: HAIRLINE,
2974
+ "&:focus-within": { "--dsw-elevation-stroke-color": dsw.alias.brand.primary },
2975
+ "& > textarea": {
2976
+ flex: 1,
2977
+ width: "100%",
2978
+ minHeight: ACTION,
2979
+ maxHeight: "240px",
2980
+ padding: 0,
2981
+ border: 0,
2982
+ outline: "none",
2983
+ background: "transparent",
2984
+ resize: "none",
2985
+ fieldSizing: "content",
2986
+ fontFamily: "inherit",
2987
+ fontSize: "14px",
2988
+ lineHeight: ROW,
2989
+ color: dsw.alias.label.primary
2990
+ }
2991
+ });
2992
+ /** 行内注释(schema 说明与业务文案):比配置值小一档,占满这一行剩下的宽度,长了就截断(hover 看全文)。 */
2993
+ const LineComment = styled("span")({
2994
+ flex: "1 1 auto",
2995
+ minWidth: 0,
2996
+ overflow: "hidden",
2997
+ textOverflow: "ellipsis",
2998
+ color: dsw.alias.label.tertiary,
2999
+ fontFamily: "inherit",
3000
+ fontSize: "12px"
3001
+ });
3002
+ /** 行内的校验消息(与注释放同一档字号)。 */
3003
+ const LineInvalid = styled("span")({
3004
+ flex: "none",
3005
+ color: dsw.alias.state.error.primary,
3006
+ fontFamily: "inherit",
3007
+ fontSize: "12px"
3008
+ });
3009
+ /**
3010
+ * 悬停行时才显示这一行的动作按钮:显形规则写在行上(`[data-role='actions']`),组件只需要带这个属性。
3011
+ */
3012
+ const HoverActions = styled(styled("span")({
3013
+ display: "inline-flex",
3014
+ alignItems: "center",
3015
+ gap: ACTION_GAP,
3016
+ flex: "none",
3017
+ opacity: 0,
3018
+ "& > button": {
3019
+ display: "inline-flex",
3020
+ alignItems: "center",
3021
+ justifyContent: "center",
3022
+ width: ACTION,
3023
+ height: ACTION,
3024
+ padding: 0,
3025
+ border: 0,
3026
+ borderRadius: "4px",
3027
+ background: "none",
3028
+ color: dsw.alias.label.secondary,
3029
+ cursor: "pointer",
3030
+ "&:hover": {
3031
+ background: dsw.alias.bg.layer["3"],
3032
+ color: dsw.alias.label.primary
3033
+ },
3034
+ "&:disabled": {
3035
+ cursor: "not-allowed",
3036
+ color: dsw.alias.label.dimmed
3037
+ }
3038
+ }
3039
+ }))({
3040
+ /**
3041
+ * 编辑态的两个按钮常显,而且**要有底色与描边**:行底色上只有淡色图标是看不见的,
3042
+ * 所以这里给实体按钮(theme 的描边 + 一层底),确认用成功档、取消用中性档。
3043
+ */
3044
+ "&[data-editing='true']": {
3045
+ opacity: 1,
3046
+ gap: GAP,
3047
+ "& > button": {
3048
+ boxShadow: HAIRLINE,
3049
+ background: dsw.alias.bg.layer["1"],
3050
+ color: dsw.alias.label.primary,
3051
+ "&:first-of-type": { color: dsw.alias.state.success.primary }
3052
+ }
3053
+ } });
3054
+ /** 添加输入:**跟在容器的闭合括号同一行**(菜单锚在它上面),宽度吃掉剩下的位置。 */
3055
+ const AddWrap = styled("span")({
3056
+ position: "relative",
3057
+ display: "inline-flex",
3058
+ alignItems: "center",
3059
+ gap: GAP,
3060
+ flex: "1 1 auto",
3061
+ minWidth: 0,
3062
+ marginLeft: GAP,
3063
+ "&[data-invalid='true'] input": { borderColor: dsw.alias.state.error.primary }
3064
+ });
3065
+ /** 一行说明(不可用 / 没有可生成项时用)。 */
3066
+ const Hint$1 = styled("p")({
3067
+ margin: 0,
3068
+ fontSize: "12px",
3069
+ lineHeight: 1.5,
3070
+ color: dsw.alias.label.tertiary
3071
+ });
3072
+ //#endregion
3073
+ //#region src/client/schema-form/value.tsx
3074
+ /**
3075
+ * 行内值的呈现与编辑文本规则,以及字段槽注册方复用的默认值组件。
3076
+ *
3077
+ * 值本身仍走草稿:这个文件只决定"怎么显示"(语法色 token、下拉触发的呈现)与"怎么编辑"(一行输入框里的文本
3078
+ * 形状——字符串把换行写成 `\n`,与一行的输入框相配)。
3079
+ */
3080
+ /** 编辑态文本:字符串就是原文(换行照旧),其余按 JSON 写。 */
3081
+ function valueText(value) {
3082
+ if (value === void 0 || value === null) return "";
3083
+ if (typeof value === "string") return value;
3084
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
3085
+ return JSON.stringify(value);
3086
+ }
3087
+ /** 这个值要在多行输入里编辑吗(字符串带换行)。 */
3088
+ function isMultiline(value) {
3089
+ return typeof value === "string" && /[\r\n]/.test(value);
3090
+ }
3091
+ /** 显示态文本:字符串带引号;多行与特殊字符都按 JSON 的写法收在一行里。 */
3092
+ function tokenText(value) {
3093
+ if (value === void 0 || value === null) return "null";
3094
+ if (typeof value === "string") return JSON.stringify(value);
3095
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
3096
+ return JSON.stringify(value);
3097
+ }
3098
+ /** 值的语法色分组。 */
3099
+ function tokenTone(value) {
3100
+ if (value === void 0 || value === null) return "empty";
3101
+ if (typeof value === "number") return "number";
3102
+ if (typeof value === "boolean") return "boolean";
3103
+ return "string";
3104
+ }
3105
+ /**
3106
+ * 行内值的默认呈现(显示态 token)。
3107
+ * @param props.owner - 字段上下文(值、是否 secret、只读原因)。
3108
+ * @returns 一行里的值节点。
3109
+ */
3110
+ function InlineValue({ owner }) {
3111
+ if (owner.options !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionSelect, {
3112
+ owner,
3113
+ options: owner.options
3114
+ });
3115
+ if (owner.node.meta.secret) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineValue, {
3116
+ "data-tone": "empty",
3117
+ title: owner.hint,
3118
+ children: owner.secretConfigured ? "••••••" : ""
3119
+ });
3120
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineValue, {
3121
+ "data-tone": owner.node.readOnly === null ? tokenTone(owner.value) : "empty",
3122
+ title: tokenText(owner.value),
3123
+ children: tokenText(owner.value)
3124
+ });
3125
+ }
3126
+ /**
3127
+ * 选择器:**值本身**就是下拉的触发(不再另画一个 `select` 与它并列),点开是官方菜单。
3128
+ *
3129
+ * 当前值不在候选里时(例如目录里已经没有那个服务商)照原样显示,选中才换成新值。
3130
+ */
3131
+ function OptionSelect({ owner, options }) {
3132
+ const [open, setOpen] = (0, react.useState)(false);
3133
+ const selected = options.findIndex((option) => sameValue(option.value, owner.value));
3134
+ const current = selected < 0 ? void 0 : options[selected];
3135
+ const label = current?.label ?? (owner.value === void 0 ? owner.t("unconfigured") : tokenText(current?.value ?? owner.value));
3136
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
3137
+ open,
3138
+ dense: true,
3139
+ compact: true,
3140
+ portal: true,
3141
+ align: "start",
3142
+ items: options.map((option, index) => ({
3143
+ id: String(index),
3144
+ label: option.label ?? tokenText(option.value)
3145
+ })),
3146
+ selectedId: selected < 0 ? void 0 : String(selected),
3147
+ onSelect: (id) => {
3148
+ setOpen(false);
3149
+ const option = options[Number(id)];
3150
+ if (option !== void 0) owner.onChange(option.value);
3151
+ },
3152
+ onClose: () => {
3153
+ setOpen(false);
3154
+ },
3155
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ValueTrigger, {
3156
+ type: "button",
3157
+ disabled: owner.disabled,
3158
+ "aria-label": owner.label,
3159
+ onClick: () => {
3160
+ setOpen(true);
3161
+ },
3162
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineValue, {
3163
+ "data-tone": current === void 0 ? tokenTone(owner.value) : tokenTone(current.value),
3164
+ children: label
3165
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { size: 14 })]
3166
+ })
3167
+ });
3168
+ }
3169
+ /** 值是否就是这一项(菜单的高亮判据)。 */
3170
+ function sameValue(left, right) {
3171
+ if (Object.is(left, right)) return true;
3172
+ if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) return false;
3173
+ return JSON.stringify(left) === JSON.stringify(right);
3174
+ }
3175
+ /**
3176
+ * 字段槽注册方复用的默认值:只改文案时拿它包一层即可(`owner` 上覆盖 `label`/`hint`)。
3177
+ *
3178
+ * 注意行式视图里键名来自配置键本身、注释来自 `hint`,所以 `label` 只在业务自写控件时才需要。
3179
+ */
3180
+ function SchemaFieldDefault({ owner }) {
3181
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineValue, { owner });
3182
+ }
3183
+ //#endregion
3184
+ //#region src/client/schema-form/fields.tsx
3185
+ /**
3186
+ * 各类型的文本解析规则(消息来自本包字典)。
3187
+ *
3188
+ * 解析不只看类型:schema 上声明的界限(`pattern` / `min` / `max` / `step`)与字面量集合都在这里说话——
3189
+ * 用户敲完当场就知道对不对,而不是等保存时被整段校验退回。
3190
+ * @param node - 字段节点(`meta` 是约束的来源)。
3191
+ * @param t - 本包字典。
3192
+ * @returns 这段文本的解析规则。
3193
+ */
3194
+ function parseFor(node, t) {
3195
+ switch (node.type) {
3196
+ case "number": return (text) => {
3197
+ const trimmed = text.trim();
3198
+ const value = Number(trimmed);
3199
+ if (trimmed === "" || !Number.isFinite(value)) return {
3200
+ kind: "invalid",
3201
+ message: t("invalidNumber")
3202
+ };
3203
+ if (node.meta.step === 1 && !Number.isInteger(value)) return {
3204
+ kind: "invalid",
3205
+ message: t("invalidInteger")
3206
+ };
3207
+ const { min, max } = node.meta;
3208
+ if (min !== void 0 && max !== void 0 && (value < min || value > max)) return {
3209
+ kind: "invalid",
3210
+ message: t("invalidRange", {
3211
+ min,
3212
+ max
3213
+ })
3214
+ };
3215
+ if (min !== void 0 && value < min) return {
3216
+ kind: "invalid",
3217
+ message: t("invalidMin", { min })
3218
+ };
3219
+ if (max !== void 0 && value > max) return {
3220
+ kind: "invalid",
3221
+ message: t("invalidMax", { max })
3222
+ };
3223
+ return {
3224
+ kind: "value",
3225
+ value
3226
+ };
3227
+ };
3228
+ case "string": {
3229
+ const pattern = node.meta.pattern;
3230
+ if (pattern === void 0) return (text) => ({
3231
+ kind: "value",
3232
+ value: text
3233
+ });
3234
+ return (text) => new RegExp(pattern).test(text) ? {
3235
+ kind: "value",
3236
+ value: text
3237
+ } : {
3238
+ kind: "invalid",
3239
+ message: t("invalidPattern", { pattern })
3240
+ };
3241
+ }
3242
+ case "boolean": return (text) => {
3243
+ const trimmed = text.trim();
3244
+ if (trimmed === "true") return {
3245
+ kind: "value",
3246
+ value: true
3247
+ };
3248
+ if (trimmed === "false") return {
3249
+ kind: "value",
3250
+ value: false
3251
+ };
3252
+ return {
3253
+ kind: "invalid",
3254
+ message: t("invalidBoolean")
3255
+ };
3256
+ };
3257
+ case "const": return (text) => text === String(node.type === "const" ? node.value : "") || text.trim() === JSON.stringify(node.value) ? {
3258
+ kind: "value",
3259
+ value: node.value
3260
+ } : {
3261
+ kind: "invalid",
3262
+ message: t("invalidConst")
3263
+ };
3264
+ case "bitset": return (text) => {
3265
+ const value = Number(text.trim());
3266
+ return Number.isFinite(value) ? {
3267
+ kind: "value",
3268
+ value
3269
+ } : {
3270
+ kind: "invalid",
3271
+ message: t("invalidNumber")
3272
+ };
3273
+ };
3274
+ case "any": return (text) => {
3275
+ try {
3276
+ return {
3277
+ kind: "value",
3278
+ value: JSON.parse(text)
3279
+ };
3280
+ } catch {
3281
+ return {
3282
+ kind: "invalid",
3283
+ message: t("invalidJson")
3284
+ };
3285
+ }
3286
+ };
3287
+ default:
3288
+ if (node.type === "union" && node.choices !== void 0) {
3289
+ const choices = node.choices;
3290
+ const options = choices.map((choice) => String(choice)).join(" / ");
3291
+ return (text) => choices.some((choice) => String(choice) === text) ? {
3292
+ kind: "value",
3293
+ value: text
3294
+ } : {
3295
+ kind: "invalid",
3296
+ message: t("invalidChoice", { choices: options })
3297
+ };
3298
+ }
3299
+ return (text) => ({
3300
+ kind: "value",
3301
+ value: text
3302
+ });
3303
+ }
3304
+ }
3305
+ //#endregion
3306
+ //#region src/client/schema-form/Editor.tsx
3307
+ /**
3308
+ * 行式编辑器:按 `editorLines` 排出的行画 JSON 结构视图——行号、折进、`key: value`、结构行、注释行、容器闭合行
3309
+ * 后面的添加入口;值在行内点开编辑(Enter 提交 / Esc 撤销)。
3310
+ *
3311
+ * 缩进只作用在**行内容**上(行号列固定),添加输入与闭合括号同一行;行内控件用官方的 `Input` / `Menu` / `Button`,
3312
+ * 与设置页其余部分同一套视觉与键盘行为。
3313
+ *
3314
+ * 字段槽仍然生效:命中时该字段**值**的位置换成注册方的组件,键名、注释、行号与行为按钮仍由这里画。
3315
+ */
3316
+ /**
3317
+ * 画一行的配置。
3318
+ * @param props - 读数、动作、字典与字段槽入口。
3319
+ * @returns 行式编辑器。
3320
+ */
3321
+ function Editor(props) {
3322
+ const { ns, state, face, t, resolveText, renderField } = props;
3323
+ const [toggled, setToggled] = (0, react.useState)(() => /* @__PURE__ */ new Set());
3324
+ const [editing, setEditing] = (0, react.useState)(void 0);
3325
+ const [selected, setSelected] = (0, react.useState)(void 0);
3326
+ const [dismissed, setDismissed] = (0, react.useState)(() => /* @__PURE__ */ new Set());
3327
+ const fold = useFold(state, toggled, setToggled);
3328
+ const lines = (0, react.useMemo)(() => editorLines(state, fold, resolveText, t), [
3329
+ state,
3330
+ fold,
3331
+ resolveText,
3332
+ t
3333
+ ]);
3334
+ const disabled = !state.writable || !state.available;
3335
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditorRoot, {
3336
+ "data-editor": "schema-form",
3337
+ children: lines.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
3338
+ line,
3339
+ n: index + 1,
3340
+ ns,
3341
+ state,
3342
+ face,
3343
+ fold,
3344
+ t,
3345
+ resolveText,
3346
+ disabled,
3347
+ editing,
3348
+ selected,
3349
+ onEdit: (key) => {
3350
+ setEditing(key);
3351
+ if (key !== void 0) setDismissed((previous) => {
3352
+ const next = new Set(previous);
3353
+ next.delete(key);
3354
+ return next;
3355
+ });
3356
+ },
3357
+ onAdded: (path) => {
3358
+ setEditing(fieldKey(path));
3359
+ },
3360
+ dismissed,
3361
+ onDismiss: (key) => {
3362
+ setDismissed((previous) => new Set(previous).add(key));
3363
+ },
3364
+ onSelect: (path) => {
3365
+ setSelected(fieldKey(path));
3366
+ },
3367
+ renderField
3368
+ }, `${line.kind}:${fieldKey(line.path)}:${String(index)}`))
3369
+ });
3370
+ }
3371
+ /** 折叠状态:记「用户切换过的路径」,开合 = 切换过就反转 schema 的默认值。 */
3372
+ function useFold(state, toggled, setToggled) {
3373
+ const defaults = (0, react.useMemo)(() => {
3374
+ const collapsed = /* @__PURE__ */ new Set();
3375
+ for (const item of state.walked) {
3376
+ const value = state.fields.get(fieldKey(item.path))?.value;
3377
+ if (item.node.meta.collapse && containerShape(item.node, value) !== void 0) collapsed.add(fieldKey(item.path));
3378
+ }
3379
+ return collapsed;
3380
+ }, [state.walked, state.fields]);
3381
+ return (0, react.useMemo)(() => ({
3382
+ collapsed: (path) => {
3383
+ const key = fieldKey(path);
3384
+ return toggled.has(key) !== defaults.has(key);
3385
+ },
3386
+ toggle: (path) => {
3387
+ const key = fieldKey(path);
3388
+ const next = new Set(toggled);
3389
+ if (next.has(key)) next.delete(key);
3390
+ else next.add(key);
3391
+ setToggled(next);
3392
+ }
3393
+ }), [
3394
+ defaults,
3395
+ setToggled,
3396
+ toggled
3397
+ ]);
3398
+ }
3399
+ /** 一行的渲染分派。 */
3400
+ function Row({ line, n, ns, state, face, fold, t, resolveText, disabled, editing, selected, dismissed, onEdit, onAdded, onDismiss, onSelect, renderField }) {
3401
+ const key = fieldKey(line.path);
3402
+ const indent = { paddingLeft: `${String(line.depth * 14)}px` };
3403
+ const body = line.kind === "comment" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineFoldSpacer, { "data-role": "fold" }), line.invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineInvalid, {
3404
+ title: line.text,
3405
+ children: `// ${line.text}`
3406
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineComment, {
3407
+ title: line.text,
3408
+ children: `// ${line.text}`
3409
+ })] }) : line.kind === "open" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3410
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineFold, {
3411
+ "data-role": "fold",
3412
+ type: "button",
3413
+ "aria-expanded": !line.collapsed,
3414
+ "aria-label": t("toggleGroup"),
3415
+ onClick: () => {
3416
+ fold.toggle(line.path);
3417
+ },
3418
+ children: line.collapsed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { size: 14 })
3419
+ }),
3420
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinePrefix, {
3421
+ line,
3422
+ face,
3423
+ t,
3424
+ disabled
3425
+ }),
3426
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineToken, { children: line.shape === "object" ? line.collapsed ? "{…}" : "{" : line.collapsed ? "[…]" : "[" }),
3427
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VariantSelect, {
3428
+ control: line.variants,
3429
+ path: line.path,
3430
+ face,
3431
+ disabled,
3432
+ t
3433
+ })
3434
+ ] }) : line.kind === "close" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3435
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineFoldSpacer, { "data-role": "fold" }),
3436
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineToken, { children: line.shape === "object" ? "}" : "]" }),
3437
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddLine, {
3438
+ line,
3439
+ state,
3440
+ face,
3441
+ t,
3442
+ resolveText,
3443
+ disabled,
3444
+ onAdded
3445
+ })
3446
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldLine, {
3447
+ line,
3448
+ ns,
3449
+ state,
3450
+ face,
3451
+ t,
3452
+ disabled,
3453
+ editing,
3454
+ dismissed,
3455
+ onEdit,
3456
+ onDismiss,
3457
+ renderField
3458
+ });
3459
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(LineRow, {
3460
+ "data-line": line.kind,
3461
+ "data-field-path": line.path.join("."),
3462
+ "data-selected": selected === key ? "true" : void 0,
3463
+ "data-dirty": line.kind === "field" && line.field.staged ? "true" : void 0,
3464
+ "data-overridden": line.kind === "field" && line.field.overridden && !line.field.staged ? "true" : void 0,
3465
+ "data-invalid": line.kind === "field" && (line.field.invalid !== void 0 || state.invalidAt.has(key)) ? "true" : void 0,
3466
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineNumber, {
3467
+ "data-role": "number",
3468
+ onClick: () => {
3469
+ onSelect(line.path);
3470
+ },
3471
+ children: n
3472
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineBody, {
3473
+ "data-role": "body",
3474
+ style: indent,
3475
+ children: body
3476
+ })]
3477
+ });
3478
+ }
3479
+ /** 字段行:`key: value`(数组成员的键名是下标,淡一些)。 */
3480
+ function FieldLine({ line, ns, state, face, t, disabled, editing, dismissed, onEdit, onDismiss, renderField }) {
3481
+ const key = fieldKey(line.path);
3482
+ const member = line.member;
3483
+ const owner = {
3484
+ ns,
3485
+ path: line.path,
3486
+ node: line.node,
3487
+ label: line.node.label ?? member?.key ?? line.node.key,
3488
+ hint: void 0,
3489
+ value: line.field.value,
3490
+ options: state.options.get(key),
3491
+ text: line.field.text,
3492
+ secretConfigured: state.secrets.get(key) === true,
3493
+ depth: line.depth,
3494
+ overridden: line.field.overridden,
3495
+ invalid: line.field.invalid,
3496
+ disabled,
3497
+ t,
3498
+ actions: face,
3499
+ group: void 0,
3500
+ member,
3501
+ onChange: (value) => {
3502
+ face.set(line.path, value);
3503
+ },
3504
+ onEditText: (text, parse) => {
3505
+ face.setText(line.path, text, parse ?? parseFor(line.node, t));
3506
+ },
3507
+ onReset: () => {
3508
+ face.clear(line.path);
3509
+ }
3510
+ };
3511
+ const readOnly = line.node.meta.disabled || line.node.readOnly !== null;
3512
+ const editable = !disabled && !readOnly && line.node.type !== "const" && owner.options === void 0 && !line.variantsStandIn;
3513
+ const missing = line.field.value === null || line.field.value === void 0;
3514
+ const isEditing = editable && (editing === key || missing && !dismissed.has(key));
3515
+ const text = line.field.text ?? valueText(line.field.value);
3516
+ const multiline = isMultiline(line.field.value) || /[\r\n]/.test(text);
3517
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3518
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineFoldSpacer, { "data-role": "fold" }),
3519
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineKey, {
3520
+ "data-role": "key",
3521
+ "data-index": member?.index === void 0 ? void 0 : "true",
3522
+ title: member?.key ?? line.node.key,
3523
+ children: member?.key ?? line.node.key
3524
+ }),
3525
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineToken, { children: member?.index === void 0 ? ": " : " " }),
3526
+ isEditing ? multiline ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactTextField, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3527
+ autoFocus: !missing,
3528
+ rows: 1,
3529
+ "aria-label": owner.label,
3530
+ value: text,
3531
+ onChange: (event) => {
3532
+ owner.onEditText(event.target.value);
3533
+ },
3534
+ onKeyDown: (event) => {
3535
+ if (event.key === "Escape") {
3536
+ face.revert(line.path);
3537
+ onEdit(void 0);
3538
+ }
3539
+ }
3540
+ }) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactInput, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
3541
+ autoFocus: !missing,
3542
+ type: "text",
3543
+ "aria-label": owner.label,
3544
+ placeholder: missing ? t("empty") : "",
3545
+ value: text,
3546
+ onBlur: () => {
3547
+ if (!missing) onEdit(void 0);
3548
+ },
3549
+ onChange: (event) => {
3550
+ owner.onEditText(event.target.value);
3551
+ },
3552
+ onKeyDown: (event) => {
3553
+ if (event.key === "Enter" && !missing) onEdit(void 0);
3554
+ if (event.key === "Escape") {
3555
+ face.revert(line.path);
3556
+ onEdit(void 0);
3557
+ }
936
3558
  }
937
- },
938
- red: {
939
- "50": "rgb(254, 242, 242)",
940
- "100": "rgb(254, 226, 226)",
941
- "400": {
942
- $: "rgb(242, 90, 90)",
943
- a12: "rgb(242 90 90 / 12%)"
3559
+ }) }) : null,
3560
+ isEditing || line.variantsStandIn ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3561
+ onClick: () => {
3562
+ if (editable) onEdit(key);
944
3563
  },
945
- "500": "rgb(239, 68, 68)",
946
- "600": {
947
- $: "rgb(236, 19, 19)",
948
- a08: "rgb(236 19 19 / 8%)"
3564
+ children: renderField(owner)
3565
+ }),
3566
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VariantSelect, {
3567
+ control: line.variants,
3568
+ path: line.path,
3569
+ face,
3570
+ disabled,
3571
+ t
3572
+ }),
3573
+ isEditing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(HoverActions, {
3574
+ "data-role": "actions",
3575
+ "data-editing": "true",
3576
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3577
+ type: "button",
3578
+ "aria-label": t("confirmEdit"),
3579
+ title: t("confirmEdit"),
3580
+ onClick: () => {
3581
+ onEdit(void 0);
3582
+ },
3583
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 14 })
3584
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3585
+ type: "button",
3586
+ "aria-label": t("cancelEdit"),
3587
+ title: t("cancelEdit"),
3588
+ onClick: () => {
3589
+ face.revert(line.path);
3590
+ onEdit(void 0);
3591
+ onDismiss(key);
3592
+ },
3593
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 14 })
3594
+ })]
3595
+ }) : null,
3596
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(HoverActions, {
3597
+ "data-role": "actions",
3598
+ children: [
3599
+ line.field.staged && !disabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3600
+ type: "button",
3601
+ "aria-label": t("revert"),
3602
+ title: t("revert"),
3603
+ onClick: () => {
3604
+ face.revert(line.path);
3605
+ },
3606
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, { size: 14 })
3607
+ }) : null,
3608
+ line.field.overridden && !line.field.staged && !disabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3609
+ type: "button",
3610
+ "aria-label": t("reset"),
3611
+ title: t("reset"),
3612
+ onClick: () => {
3613
+ face.clear(line.path);
3614
+ },
3615
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, { size: 14 })
3616
+ }) : null,
3617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3618
+ type: "button",
3619
+ "aria-label": t("copyValue"),
3620
+ title: t("copyValue"),
3621
+ onClick: () => {
3622
+ navigator.clipboard?.writeText(line.field.text ?? tokenFor(line.field.value)).catch(() => {});
3623
+ },
3624
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutlineRegular, { size: 14 })
3625
+ }),
3626
+ member === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RemoveLineButton, {
3627
+ member,
3628
+ face,
3629
+ t,
3630
+ disabled
3631
+ })
3632
+ ]
3633
+ })
3634
+ ] });
3635
+ }
3636
+ /** 复制用的文本(字符串带引号,粘到哪里都是合法片段)。 */
3637
+ function tokenFor(value) {
3638
+ return typeof value === "string" ? JSON.stringify(value) : JSON.stringify(value ?? null);
3639
+ }
3640
+ /** 变体切换:判别式 union 挂在标签行上,按形状选的挂在字段行上。触发元素就是当前那一支。 */
3641
+ function VariantSelect({ control, path, face, disabled, t }) {
3642
+ const [open, setOpen] = (0, react.useState)(false);
3643
+ if (control === void 0) return null;
3644
+ const current = control.choices[control.selected];
3645
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
3646
+ open,
3647
+ dense: true,
3648
+ compact: true,
3649
+ portal: true,
3650
+ align: "start",
3651
+ items: control.choices.map((choice, index) => ({
3652
+ id: String(index),
3653
+ label: choice.label
3654
+ })),
3655
+ selectedId: String(control.selected),
3656
+ onSelect: (id) => {
3657
+ setOpen(false);
3658
+ const choice = control.choices[Number(id)];
3659
+ if (choice !== void 0) face.set(path, choice.value);
3660
+ },
3661
+ onClose: () => {
3662
+ setOpen(false);
3663
+ },
3664
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ValueTrigger, {
3665
+ type: "button",
3666
+ "data-role": "variant",
3667
+ disabled,
3668
+ "aria-label": t("switchVariant"),
3669
+ onClick: () => {
3670
+ setOpen(true);
949
3671
  },
950
- "900": "rgb(87, 12, 12)"
3672
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineValue, {
3673
+ "data-tone": "empty",
3674
+ children: current?.label ?? ""
3675
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { size: 14 })]
3676
+ })
3677
+ });
3678
+ }
3679
+ /** 开启行的前缀:字段键名(对象里的一层)或数组成员的下标;成员行另外带移除。 */
3680
+ function LinePrefix({ line, face, t, disabled }) {
3681
+ const member = line.member;
3682
+ const key = member?.key ?? line.node.key;
3683
+ if (key === "") return null;
3684
+ const indexed = member !== void 0 && member.index !== void 0;
3685
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3686
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineKey, {
3687
+ "data-role": "key",
3688
+ "data-index": indexed ? "true" : void 0,
3689
+ title: key,
3690
+ children: key
3691
+ }),
3692
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineToken, { children: indexed ? " " : ": " }),
3693
+ member === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HoverActions, {
3694
+ "data-role": "actions",
3695
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RemoveLineButton, {
3696
+ member,
3697
+ face,
3698
+ t,
3699
+ disabled
3700
+ })
3701
+ })
3702
+ ] });
3703
+ }
3704
+ /** 成员行的移除(悬停时出现):数组项按索引、字典键按键名。 */
3705
+ function RemoveLineButton({ member, face, t, disabled }) {
3706
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3707
+ type: "button",
3708
+ "aria-label": t("removeItem"),
3709
+ title: t("removeItem"),
3710
+ disabled,
3711
+ onClick: () => {
3712
+ if (member.index === void 0) face.removeKey(member.parent, member.key);
3713
+ else face.removeItem(member.parent, member.index);
3714
+ },
3715
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutlineRegular, { size: 14 })
3716
+ });
3717
+ }
3718
+ /**
3719
+ * 容器闭合行后面的添加入口。
3720
+ *
3721
+ * 数组是「追加一个空项」;对象/字典给出**候选**——对象列的是 schema 声明了、值里还没有的字段(带说明),
3722
+ * 字典列的是业务注册的候选键(还能自己敲键名)。粘一整段 `{…}` / `[…]` 则整层覆盖。
3723
+ */
3724
+ function AddLine({ line, state, face, t, resolveText, disabled, onAdded }) {
3725
+ const spec = line.add;
3726
+ const [text, setText] = (0, react.useState)("");
3727
+ const [open, setOpen] = (0, react.useState)(false);
3728
+ const [invalid, setInvalid] = (0, react.useState)(false);
3729
+ if (spec === void 0 || disabled) return null;
3730
+ const options = spec.kind === "item" ? [] : spec.options;
3731
+ const query = text.trim().toLowerCase();
3732
+ const matched = options.filter((option) => query === "" || option.key.toLowerCase().includes(query));
3733
+ const label = spec.kind === "item" ? t("addItemPaste") : spec.kind === "key" ? t("addKey") : t("addProperty");
3734
+ const commit = (picked) => {
3735
+ const raw = (picked ?? text).trim();
3736
+ if (raw === "") return;
3737
+ setInvalid(false);
3738
+ if (raw.startsWith("{") || raw.startsWith("[")) try {
3739
+ face.set(line.path, JSON.parse(raw));
3740
+ setText("");
3741
+ setOpen(false);
3742
+ return;
3743
+ } catch {}
3744
+ if (spec.kind === "key") {
3745
+ face.addKey(line.path, raw);
3746
+ onAdded([...line.path, raw]);
3747
+ } else if (spec.kind === "item") {
3748
+ const current = state.fields.get(fieldKey(line.path))?.value;
3749
+ const index = Array.isArray(current) ? current.length : 0;
3750
+ face.appendItem(line.path);
3751
+ onAdded([...line.path, String(index)]);
3752
+ } else {
3753
+ if (!spec.options.some((option) => option.key === raw)) {
3754
+ setInvalid(true);
3755
+ return;
3756
+ }
3757
+ face.addKey(line.path, raw);
3758
+ onAdded([...line.path, raw]);
951
3759
  }
952
- }
953
- };
3760
+ setText("");
3761
+ setOpen(false);
3762
+ };
3763
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(AddWrap, {
3764
+ "data-add": spec.kind,
3765
+ "data-invalid": invalid ? "true" : void 0,
3766
+ children: [
3767
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
3768
+ open: open && spec.kind !== "item" && matched.length > 0,
3769
+ dense: true,
3770
+ compact: true,
3771
+ portal: true,
3772
+ align: "start",
3773
+ items: matched.map((option) => ({
3774
+ id: option.key,
3775
+ label: option.key
3776
+ })),
3777
+ onSelect: (key) => {
3778
+ commit(key);
3779
+ },
3780
+ onClose: () => {
3781
+ setOpen(false);
3782
+ },
3783
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactInput, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
3784
+ value: text,
3785
+ placeholder: label,
3786
+ "aria-label": label,
3787
+ disabled,
3788
+ onFocus: () => {
3789
+ setOpen(true);
3790
+ },
3791
+ onChange: (event) => {
3792
+ setText(event.target.value);
3793
+ setInvalid(false);
3794
+ setOpen(true);
3795
+ },
3796
+ onKeyDown: (event) => {
3797
+ if (event.key === "Enter") {
3798
+ event.preventDefault();
3799
+ commit(matched.length === 1 && spec.kind === "prop" ? matched[0]?.key : void 0);
3800
+ }
3801
+ if (event.key === "Escape") {
3802
+ setText("");
3803
+ setOpen(false);
3804
+ }
3805
+ }
3806
+ }) })
3807
+ }),
3808
+ invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineInvalid, { children: t("unknownProperty") }) : null,
3809
+ !invalid && matched.length === 1 && matched[0]?.description !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineComment, {
3810
+ title: resolveText(matched[0].description),
3811
+ children: resolveText(matched[0].description)
3812
+ }) : null
3813
+ ]
3814
+ });
3815
+ }
954
3816
  //#endregion
955
- //#region src/client/theme.ts
956
- const dsw = Token.vars(designTokens, { prefix: "dsw" });
3817
+ //#region src/client/schema-form/SchemaForm.tsx
3818
+ /**
3819
+ * 一行的配置页:官方表单壳(保存 / 丢弃 / 只读 / 不可用)+ 行式 JSON 结构编辑器 + 字段槽。
3820
+ *
3821
+ * 每个可渲染的行注册项经 `renderFactorySlot('settings.schema-form.form', …)` 渲染这个 Factory,字段槽因此只需在
3822
+ * Factory 的 `children` 里声明一次(slots 的 children 声明 per key 唯一,而本包给每个行都注册了一项)。
3823
+ */
3824
+ /**
3825
+ * 渲染一行的配置页。
3826
+ * @param props - 行 id、控制器面、本地化解析与框架注入的槽位渲染座位。
3827
+ * @returns 表单壳里的行式编辑器,或「没有可自动生成的配置项」一行。
3828
+ */
3829
+ function SchemaForm(props) {
3830
+ const { ns, face, t, resolveText, renderSlotChain } = props;
3831
+ const store = face.hooks.schemaForm;
3832
+ const state = (0, react.useSyncExternalStore)((listener) => store.subscribe(listener), () => store.getSnapshot());
3833
+ const labels = {
3834
+ unavailable: t("unavailable"),
3835
+ readOnly: t("readOnly"),
3836
+ saveFailed: t("saveFailed"),
3837
+ save: t("save"),
3838
+ saving: t("saving")
3839
+ };
3840
+ if (!state.configured) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Hint$1, {
3841
+ role: "status",
3842
+ children: t("noSchema")
3843
+ });
3844
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.SettingsForm, {
3845
+ labels,
3846
+ state,
3847
+ onSave: () => {
3848
+ face.save();
3849
+ },
3850
+ onDiscard: () => {
3851
+ face.discard();
3852
+ },
3853
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Editor, {
3854
+ ns,
3855
+ state,
3856
+ face,
3857
+ t,
3858
+ resolveText,
3859
+ renderField: (owner) => renderSlotChain("settings.schema-form.field", owner, { fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SchemaFieldDefault, { owner: {
3860
+ ...owner,
3861
+ onEditText: withParse(owner, t)
3862
+ } }) })
3863
+ }), state.violation === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Hint$1, {
3864
+ role: "alert",
3865
+ "data-violation": "true",
3866
+ children: t("notSaved", { message: state.violation.message })
3867
+ })]
3868
+ });
3869
+ }
3870
+ /** 文本草稿的解析规则按字段类型给:行内编辑只上报原文,值在保存那一刻成形。 */
3871
+ function withParse(owner, t) {
3872
+ return (text, parse) => {
3873
+ owner.onEditText(text, parse ?? parseFor(owner.node, t));
3874
+ };
3875
+ }
3876
+ //#endregion
3877
+ //#region src/client/schema-form/index.ts
3878
+ var schema_form_exports = /* @__PURE__ */ __exportAll({
3879
+ InlineValue: () => InlineValue,
3880
+ NS: () => NS,
3881
+ SchemaFieldDefault: () => SchemaFieldDefault,
3882
+ SchemaFormController: () => SchemaFormController,
3883
+ SchemaFormHints: () => SchemaFormHints,
3884
+ apply: () => apply$1,
3885
+ canRender: () => canRender,
3886
+ containerShape: () => containerShape,
3887
+ editorLines: () => editorLines,
3888
+ failureOf: () => failureOf,
3889
+ inject: () => inject$1,
3890
+ optionsFor: () => optionsFor,
3891
+ parseFor: () => parseFor,
3892
+ projectNode: () => projectNode,
3893
+ projectRoot: () => projectRoot,
3894
+ rowRegistrations: () => rowRegistrations,
3895
+ tokenText: () => tokenText,
3896
+ tokenTone: () => tokenTone,
3897
+ valueText: () => valueText,
3898
+ visibleFields: () => visibleFields,
3899
+ walkFields: () => walkFields
3900
+ });
3901
+ /** 本包字典与槽位文案的命名空间。 */
3902
+ const NS = "settings.schema-form";
3903
+ /** 需要的服务:槽位、字典、共享配置表单、schema 服务与插件管理(bundle 行清单)。 */
3904
+ const inject$1 = [
3905
+ "slots",
3906
+ "locale",
3907
+ "configForms",
3908
+ "settingsSchema",
3909
+ "remote",
3910
+ "remote.pluginManager"
3911
+ ];
3912
+ /**
3913
+ * 装上本包:字典、Factory(字段槽的声明者)与按行自动注册。
3914
+ * @param ctx - 浏览器插件上下文。
3915
+ */
3916
+ function apply$1(ctx) {
3917
+ ctx.effect(() => ctx.locale.register(NS, {
3918
+ zh,
3919
+ en
3920
+ }), "ui-schema-form: dictionaries");
3921
+ const hints = new SchemaFormHints(ctx);
3922
+ ctx.effect(() => ctx.slots.registerFactory({
3923
+ name: "settings.schema-form.form",
3924
+ scope: "root",
3925
+ children: { "settings.schema-form.field": {
3926
+ kind: "chain",
3927
+ scope: "root"
3928
+ } },
3929
+ locale: NS
3930
+ }, SchemaForm), "ui-schema-form: form factory");
3931
+ const rehydrate = (serialized) => ctx.settingsSchema.rehydrate(serialized);
3932
+ const validate = (schema, value) => {
3933
+ try {
3934
+ schema(value);
3935
+ return;
3936
+ } catch (error) {
3937
+ return failureOf(error);
3938
+ }
3939
+ };
3940
+ const resolveText = (text) => typeof text === "string" ? text : ctx.locale.resolveText(text);
3941
+ const entries = /* @__PURE__ */ new Map();
3942
+ const entryFor = (ns) => {
3943
+ const describe = ctx.configForms.describe();
3944
+ const view = describe.getSnapshot().view?.namespaces.find((row) => row.ns === ns);
3945
+ if (!canRender(view, rehydrate)) return void 0;
3946
+ const existing = entries.get(ns);
3947
+ if (existing !== void 0) return existing;
3948
+ const controller = new SchemaFormController(ns, {
3949
+ form: ctx.configForms.get(ns),
3950
+ describe,
3951
+ rehydrate,
3952
+ validate,
3953
+ t: ctx.locale.bind(NS),
3954
+ hints: {
3955
+ keysFor: (path) => hints.keysFor(ns, path),
3956
+ textFor: (path) => hints.textFor(ns, path),
3957
+ selectFor: (path) => hints.selectFor(ns, path),
3958
+ sourceFor: (name) => hints.sourceFor(name)
3959
+ }
3960
+ });
3961
+ const face = controller.face();
3962
+ const props = {
3963
+ ns,
3964
+ face,
3965
+ resolveText
3966
+ };
3967
+ const component = (entryProps) => entryProps.view === "page" ? entryProps.renderFactorySlot("settings.schema-form.form", props) : null;
3968
+ const entry = {
3969
+ controller,
3970
+ face,
3971
+ component
3972
+ };
3973
+ entries.set(ns, entry);
3974
+ return entry;
3975
+ };
3976
+ const registration = new RowRegistration({
3977
+ slots: ctx.slots,
3978
+ describe: ctx.configForms.describe(),
3979
+ bundles: async () => {
3980
+ const response = await ctx.remote.pluginManager.listBundles();
3981
+ return response.ok ? response.value : [];
3982
+ },
3983
+ subscribeBundles: (listener) => ctx.remote.$on("plugin-manager/changed", listener),
3984
+ entryFor,
3985
+ locale: NS,
3986
+ release: (ns) => {
3987
+ const entry = entries.get(ns);
3988
+ entries.delete(ns);
3989
+ entry?.controller.dispose();
3990
+ }
3991
+ });
3992
+ ctx.effect(() => {
3993
+ const offDescribe = ctx.configForms.describe().subscribe(() => {
3994
+ registration.sync();
3995
+ });
3996
+ const offHints = hints.subscribe(() => {
3997
+ for (const entry of entries.values()) entry.controller.refresh();
3998
+ });
3999
+ registration.sync();
4000
+ return () => {
4001
+ offDescribe();
4002
+ offHints();
4003
+ registration.dispose();
4004
+ for (const [ns, entry] of entries) {
4005
+ entries.delete(ns);
4006
+ entry.controller.dispose();
4007
+ }
4008
+ };
4009
+ }, "ui-schema-form: row registrations");
4010
+ }
957
4011
  //#endregion
958
4012
  //#region src/client/markdown-labels.ts
959
4013
  function markdownLabels(t) {
@@ -1256,6 +4310,9 @@ window.__ModuleLoader__.load({
1256
4310
  }
1257
4311
  };
1258
4312
  //#endregion
4313
+ //#region src/client/styling/focus.ts
4314
+ const focusRing = `${String(dsw.focus.ring.width)} solid ${Token.collect(Token.fallbackVar(dsw.focus.ring.color, String(dsw.alias.state.business.primary)))}`;
4315
+ //#endregion
1259
4316
  //#region src/client/settings-form/fields.styles.ts
1260
4317
  const helpButtonHoverBackground = "var(--dsw-alias-bg-layer-4)";
1261
4318
  const styles$1 = {
@@ -1309,7 +4366,7 @@ window.__ModuleLoader__.load({
1309
4366
  color: dsw.alias.label.secondary
1310
4367
  },
1311
4368
  "&:focus-visible": {
1312
- outline: `2px solid ${String(dsw.alias.brand.primary)}`,
4369
+ outline: focusRing,
1313
4370
  outlineOffset: "1px"
1314
4371
  }
1315
4372
  },
@@ -1366,7 +4423,7 @@ window.__ModuleLoader__.load({
1366
4423
  color: dsw.alias.label.primary,
1367
4424
  "&:focus-visible": {
1368
4425
  outline: "none",
1369
- borderColor: dsw.alias.brand.primary
4426
+ borderColor: dsw.alias.state.business.primary
1370
4427
  },
1371
4428
  "&:disabled": {
1372
4429
  color: dsw.alias.label.tertiary,
@@ -1492,6 +4549,7 @@ window.__ModuleLoader__.load({
1492
4549
  * A write-only credential control. The value never rides a response, so the
1493
4550
  * control reports only whether one is configured and starts blank; a blank
1494
4551
  * draft writes nothing, which keeps the stored key rather than clearing it.
4552
+ * 控件要求浏览器不要把已存的登录密码自动填进来(上游 11c0511271)。
1495
4553
  * @param props - the field's copy, its staged text, and the configured state.
1496
4554
  * @returns the labelled control.
1497
4555
  */
@@ -1505,7 +4563,7 @@ window.__ModuleLoader__.load({
1505
4563
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
1506
4564
  id: props.id,
1507
4565
  type: "password",
1508
- autoComplete: "off",
4566
+ autoComplete: "new-password",
1509
4567
  value: props.text,
1510
4568
  disabled: props.disabled,
1511
4569
  onChange: (event) => {
@@ -1556,7 +4614,7 @@ window.__ModuleLoader__.load({
1556
4614
  cursor: "default"
1557
4615
  },
1558
4616
  "&:focus-visible": {
1559
- outline: `2px solid ${String(dsw.alias.brand.primary)}`,
4617
+ outline: focusRing,
1560
4618
  outlineOffset: "1px"
1561
4619
  }
1562
4620
  }
@@ -2113,7 +5171,7 @@ window.__ModuleLoader__.load({
2113
5171
  }
2114
5172
  }
2115
5173
  //#endregion
2116
- //#region ../../../node_modules/.pnpm/micromark-factory-space@2.0.1/node_modules/micromark-factory-space/index.js
5174
+ //#region ../../../node_modules/.pnpm/micromark-factory-space@2.1.0/node_modules/micromark-factory-space/index.js
2117
5175
  /**
2118
5176
  * @import {Effects, State, TokenType} from 'micromark-util-types'
2119
5177
  */
@@ -2143,14 +5201,14 @@ window.__ModuleLoader__.load({
2143
5201
  * @param {State} ok
2144
5202
  * State switched to when successful.
2145
5203
  * @param {TokenType} type
2146
- * Type (`' \t'`).
5204
+ * Type of the whole whitespace.
2147
5205
  * @param {number | undefined} [max=Infinity]
2148
5206
  * Max (exclusive).
2149
5207
  * @returns {State}
2150
5208
  * Start state.
2151
5209
  */
2152
5210
  function factorySpace(effects, ok, type, max) {
2153
- const limit = max ? max - 1 : Number.POSITIVE_INFINITY;
5211
+ const limit = max ? max - 1 : Infinity;
2154
5212
  let size = 0;
2155
5213
  return start;
2156
5214
  /** @type {State} */
@@ -2171,8 +5229,56 @@ window.__ModuleLoader__.load({
2171
5229
  return ok(code);
2172
5230
  }
2173
5231
  }
5232
+ /**
5233
+ * Parse spaces and tabs, with a required minimum and maximum, matching
5234
+ * `markdown-rs`’s `space_or_tab_min_max`.
5235
+ *
5236
+ * Unlike `factorySpace`, this can fail: `nok` is used when fewer than
5237
+ * `min` spaces or tabs are found.
5238
+ *
5239
+ * @param {Effects} effects
5240
+ * Context.
5241
+ * @param {State} ok
5242
+ * State switched to when successful.
5243
+ * @param {State} nok
5244
+ * State switched to when unsuccessful.
5245
+ * @param {TokenType} type
5246
+ * Type of the whole whitespace.
5247
+ * @param {number} min
5248
+ * Minimum allowed characters (inclusive).
5249
+ * @param {number} max
5250
+ * Maximum allowed characters (inclusive).
5251
+ * @returns {State}
5252
+ * Start state.
5253
+ */
5254
+ function factorySpaceMinMax(effects, ok, nok, type, min, max) {
5255
+ let size = 0;
5256
+ return start;
5257
+ /** @type {State} */
5258
+ function start(code) {
5259
+ if (max > 0 && markdownSpace(code)) {
5260
+ effects.enter(type);
5261
+ return prefix(code);
5262
+ }
5263
+ return after(code);
5264
+ }
5265
+ /** @type {State} */
5266
+ function prefix(code) {
5267
+ if (markdownSpace(code) && size < max) {
5268
+ effects.consume(code);
5269
+ size++;
5270
+ return prefix;
5271
+ }
5272
+ effects.exit(type);
5273
+ return after(code);
5274
+ }
5275
+ /** @type {State} */
5276
+ function after(code) {
5277
+ return size >= min ? ok(code) : nok(code);
5278
+ }
5279
+ }
2174
5280
  //#endregion
2175
- //#region ../../../node_modules/.pnpm/micromark@4.0.2_supports-color@9.4.0/node_modules/micromark/lib/initialize/content.js
5281
+ //#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/initialize/content.js
2176
5282
  /**
2177
5283
  * @import {
2178
5284
  * InitialConstruct,
@@ -2234,12 +5340,158 @@ window.__ModuleLoader__.load({
2234
5340
  effects.exit("chunkText");
2235
5341
  return lineStart;
2236
5342
  }
2237
- effects.consume(code);
2238
- return data;
5343
+ effects.consume(code);
5344
+ return data;
5345
+ }
5346
+ }
5347
+ //#endregion
5348
+ //#region ../../../node_modules/.pnpm/micromark-util-edit-map@1.0.0/node_modules/micromark-util-edit-map/index.js
5349
+ /**
5350
+ * @import {Event} from 'micromark-util-types'
5351
+ */
5352
+ /**
5353
+ * @typedef {[number, number, Array<Event>]} Change
5354
+ * @typedef {[number, number, number]} Jump
5355
+ */
5356
+ /**
5357
+ * Tracks a bunch of edits.
5358
+ *
5359
+ * Port of `edit_map.rs` from `markdown-rs`:
5360
+ * <https://github.com/wooorm/markdown-rs/blob/1506572f/src/util/edit_map.rs>.
5361
+ *
5362
+ * Deal with several changes in events, batching them together.
5363
+ *
5364
+ * Preferably, changes should be kept to a minimum.
5365
+ * Sometimes, it’s needed to change the list of events, because parsing can be
5366
+ * messy, and it helps to expose a cleaner interface of events to the compiler
5367
+ * and other users.
5368
+ * It can also help to merge many adjacent similar events.
5369
+ * And, in other cases, it’s needed to parse subcontent: pass some events
5370
+ * through another tokenizer and inject the result.
5371
+ */
5372
+ var EditMap$1 = class {
5373
+ /**
5374
+ * Create a new edit map.
5375
+ *
5376
+ * @returns
5377
+ * New instance.
5378
+ */
5379
+ constructor() {
5380
+ /**
5381
+ * Changes by index, so `add` does not need to scan `map` (which is
5382
+ * quadratic for documents with many edits at different places).
5383
+ *
5384
+ * @type {Map<number, Change>}
5385
+ */
5386
+ this.index = /* @__PURE__ */ new Map();
5387
+ /**
5388
+ * Record of changes.
5389
+ *
5390
+ * @type {Array<Change>}
5391
+ */
5392
+ this.map = [];
5393
+ }
5394
+ /**
5395
+ * Create an edit: a remove and/or add at a certain place.
5396
+ *
5397
+ * @param {number} index
5398
+ * Index at which to apply the edit.
5399
+ * @param {number} remove
5400
+ * Count of items to remove at the index.
5401
+ * @param {Array<Event>} add
5402
+ * Items to add at the index.
5403
+ * @returns {undefined}
5404
+ * Nothing.
5405
+ */
5406
+ add(index, remove, add) {
5407
+ addImplementation$1(this, index, remove, add, false);
5408
+ }
5409
+ /**
5410
+ * Create an edit: but insert `add` before existing additions, instead of
5411
+ * after them.
5412
+ *
5413
+ * @param {number} index
5414
+ * Index at which to apply the edit.
5415
+ * @param {number} remove
5416
+ * Count of items to remove at the index.
5417
+ * @param {Array<Event>} add
5418
+ * Items to add at the index.
5419
+ * @returns {undefined}
5420
+ * Nothing.
5421
+ */
5422
+ addBefore(index, remove, add) {
5423
+ addImplementation$1(this, index, remove, add, true);
5424
+ }
5425
+ /**
5426
+ * Done, change the events.
5427
+ *
5428
+ * @param {Array<Event>} events
5429
+ * List of events to apply the edits to.
5430
+ * @returns {undefined}
5431
+ * Nothing.
5432
+ */
5433
+ consume(events) {
5434
+ this.map.sort(function(a, b) {
5435
+ return a[0] - b[0];
5436
+ });
5437
+ if (this.map.length === 0) return;
5438
+ let index = this.map.length;
5439
+ /** @type {Array<Array<Event>>} */
5440
+ const vecs = [];
5441
+ while (index > 0) {
5442
+ index -= 1;
5443
+ vecs.push(events.slice(this.map[index][0] + this.map[index][1]), this.map[index][2]);
5444
+ events.length = this.map[index][0];
5445
+ }
5446
+ vecs.push(events.slice());
5447
+ events.length = 0;
5448
+ let slice = vecs.pop();
5449
+ while (slice) {
5450
+ for (const element of slice) events.push(element);
5451
+ slice = vecs.pop();
5452
+ }
5453
+ this.map.length = 0;
5454
+ this.index.clear();
5455
+ }
5456
+ };
5457
+ /**
5458
+ * Create an edit.
5459
+ *
5460
+ * @param {EditMap} editMap
5461
+ * Edit map to apply to.
5462
+ * @param {number} at
5463
+ * Index at which to apply the edit.
5464
+ * @param {number} remove
5465
+ * Count of items to remove at the index.
5466
+ * @param {Array<Event>} add
5467
+ * Items to add at the index.
5468
+ * @param {boolean} before
5469
+ * Insert `add` before existing additions at `at`, instead of after them.
5470
+ * @returns {undefined}
5471
+ * Nothing.
5472
+ */
5473
+ function addImplementation$1(editMap, at, remove, add, before) {
5474
+ if (remove === 0 && add.length === 0) return;
5475
+ const existing = editMap.index.get(at);
5476
+ if (existing) {
5477
+ existing[1] += remove;
5478
+ if (before) {
5479
+ add.push(...existing[2]);
5480
+ existing[2] = add;
5481
+ } else existing[2].push(...add);
5482
+ return;
2239
5483
  }
5484
+ /** @type {Change} */
5485
+ const change = [
5486
+ at,
5487
+ remove,
5488
+ add
5489
+ ];
5490
+ editMap.map.push(change);
5491
+ editMap.index.set(at, change);
2240
5492
  }
2241
5493
  //#endregion
2242
- //#region ../../../node_modules/.pnpm/micromark@4.0.2_supports-color@9.4.0/node_modules/micromark/lib/initialize/document.js
5494
+ //#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/initialize/document.js
2243
5495
  /**
2244
5496
  * @import {
2245
5497
  * Construct,
@@ -2308,8 +5560,10 @@ window.__ModuleLoader__.load({
2308
5560
  self.events[index][1].end = { ...point };
2309
5561
  index++;
2310
5562
  }
2311
- splice(self.events, indexBeforeFlow + 1, 0, self.events.slice(indexBeforeExits));
2312
- self.events.length = index;
5563
+ const editMap = new EditMap$1();
5564
+ editMap.add(indexBeforeFlow + 1, 0, self.events.slice(indexBeforeExits));
5565
+ editMap.add(indexBeforeExits, index - indexBeforeExits, []);
5566
+ editMap.consume(self.events);
2313
5567
  return checkNewContainers(code);
2314
5568
  }
2315
5569
  return start(code);
@@ -2419,8 +5673,10 @@ window.__ModuleLoader__.load({
2419
5673
  self.events[index][1].end = { ...point };
2420
5674
  index++;
2421
5675
  }
2422
- splice(self.events, indexBeforeFlow + 1, 0, self.events.slice(indexBeforeExits));
2423
- self.events.length = index;
5676
+ const editMap = new EditMap$1();
5677
+ editMap.add(indexBeforeFlow + 1, 0, self.events.slice(indexBeforeExits));
5678
+ editMap.add(indexBeforeExits, index - indexBeforeExits, []);
5679
+ editMap.consume(self.events);
2424
5680
  }
2425
5681
  }
2426
5682
  /**
@@ -2508,7 +5764,7 @@ window.__ModuleLoader__.load({
2508
5764
  return events;
2509
5765
  }
2510
5766
  //#endregion
2511
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/attention.js
5767
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/attention.js
2512
5768
  /**
2513
5769
  * @import {
2514
5770
  * Code,
@@ -2535,47 +5791,33 @@ window.__ModuleLoader__.load({
2535
5791
  */
2536
5792
  function resolveAllAttention(events, context) {
2537
5793
  let index = -1;
2538
- /** @type {number} */
2539
- let open;
2540
- /** @type {Token} */
2541
- let group;
2542
- /** @type {Token} */
2543
- let text;
2544
- /** @type {Token} */
2545
- let openingSequence;
2546
- /** @type {Token} */
2547
- let closingSequence;
2548
- /** @type {number} */
2549
- let use;
2550
5794
  /** @type {Array<Event>} */
2551
5795
  let nextEvents;
2552
- /** @type {number} */
2553
- let offset;
2554
5796
  while (++index < events.length) if (events[index][0] === "enter" && events[index][1].type === "attentionSequence" && events[index][1]._close) {
2555
- open = index;
5797
+ let open = index;
2556
5798
  while (open--) if (events[open][0] === "exit" && events[open][1].type === "attentionSequence" && events[open][1]._open && context.sliceSerialize(events[open][1]).charCodeAt(0) === context.sliceSerialize(events[index][1]).charCodeAt(0)) {
2557
5799
  if ((events[open][1]._close || events[index][1]._open) && (events[index][1].end.offset - events[index][1].start.offset) % 3 && !((events[open][1].end.offset - events[open][1].start.offset + events[index][1].end.offset - events[index][1].start.offset) % 3)) continue;
2558
- use = events[open][1].end.offset - events[open][1].start.offset > 1 && events[index][1].end.offset - events[index][1].start.offset > 1 ? 2 : 1;
5800
+ const use = events[open][1].end.offset - events[open][1].start.offset > 1 && events[index][1].end.offset - events[index][1].start.offset > 1 ? 2 : 1;
2559
5801
  const start = { ...events[open][1].end };
2560
5802
  const end = { ...events[index][1].start };
2561
5803
  movePoint(start, -use);
2562
5804
  movePoint(end, use);
2563
- openingSequence = {
5805
+ const openingSequence = {
2564
5806
  type: use > 1 ? "strongSequence" : "emphasisSequence",
2565
5807
  start,
2566
5808
  end: { ...events[open][1].end }
2567
5809
  };
2568
- closingSequence = {
5810
+ const closingSequence = {
2569
5811
  type: use > 1 ? "strongSequence" : "emphasisSequence",
2570
5812
  start: { ...events[index][1].start },
2571
5813
  end
2572
5814
  };
2573
- text = {
5815
+ const text = {
2574
5816
  type: use > 1 ? "strongText" : "emphasisText",
2575
5817
  start: { ...events[open][1].end },
2576
5818
  end: { ...events[index][1].start }
2577
5819
  };
2578
- group = {
5820
+ const group = {
2579
5821
  type: use > 1 ? "strong" : "emphasis",
2580
5822
  start: { ...openingSequence.start },
2581
5823
  end: { ...closingSequence.end }
@@ -2637,6 +5879,8 @@ window.__ModuleLoader__.load({
2637
5879
  context
2638
5880
  ]
2639
5881
  ]);
5882
+ /** @type {number} */
5883
+ let offset = 0;
2640
5884
  if (events[index][1].end.offset - events[index][1].start.offset) {
2641
5885
  offset = 2;
2642
5886
  nextEvents = push(nextEvents, [[
@@ -2648,7 +5892,7 @@ window.__ModuleLoader__.load({
2648
5892
  events[index][1],
2649
5893
  context
2650
5894
  ]]);
2651
- } else offset = 0;
5895
+ }
2652
5896
  splice(events, open - 1, index - open + 3, nextEvents);
2653
5897
  index = open + nextEvents.length - offset - 2;
2654
5898
  break;
@@ -2702,8 +5946,8 @@ window.__ModuleLoader__.load({
2702
5946
  }
2703
5947
  const token = effects.exit("attentionSequence");
2704
5948
  const after = classifyCharacter(code);
2705
- const open = !after || after === 2 && before || attentionMarkers.includes(code);
2706
- const close = !before || before === 2 && after || attentionMarkers.includes(previous);
5949
+ const open = !after || after === 2 && before || attentionMarkers.includes(code) && code !== 42 && code !== 95;
5950
+ const close = !before || before === 2 && after || attentionMarkers.includes(previous) && previous !== 42 && previous !== 95;
2707
5951
  token._open = Boolean(marker === 42 ? open : open && (before || !close));
2708
5952
  token._close = Boolean(marker === 42 ? close : close && (after || !open));
2709
5953
  return ok(code);
@@ -2728,7 +5972,7 @@ window.__ModuleLoader__.load({
2728
5972
  point._bufferIndex += offset;
2729
5973
  }
2730
5974
  //#endregion
2731
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/autolink.js
5975
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/autolink.js
2732
5976
  /**
2733
5977
  * @import {
2734
5978
  * Construct,
@@ -2939,7 +6183,7 @@ window.__ModuleLoader__.load({
2939
6183
  }
2940
6184
  }
2941
6185
  //#endregion
2942
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/blank-line.js
6186
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/blank-line.js
2943
6187
  /**
2944
6188
  * @import {
2945
6189
  * Construct,
@@ -2996,7 +6240,7 @@ window.__ModuleLoader__.load({
2996
6240
  }
2997
6241
  }
2998
6242
  //#endregion
2999
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/block-quote.js
6243
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/block-quote.js
3000
6244
  /**
3001
6245
  * @import {
3002
6246
  * Construct,
@@ -3123,7 +6367,7 @@ window.__ModuleLoader__.load({
3123
6367
  effects.exit("blockQuote");
3124
6368
  }
3125
6369
  //#endregion
3126
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/character-escape.js
6370
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/character-escape.js
3127
6371
  /**
3128
6372
  * @import {
3129
6373
  * Construct,
@@ -3183,7 +6427,7 @@ window.__ModuleLoader__.load({
3183
6427
  }
3184
6428
  }
3185
6429
  //#endregion
3186
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/character-reference.js
6430
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/character-reference.js
3187
6431
  /**
3188
6432
  * @import {
3189
6433
  * Code,
@@ -3321,7 +6565,55 @@ window.__ModuleLoader__.load({
3321
6565
  }
3322
6566
  }
3323
6567
  //#endregion
3324
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/code-fenced.js
6568
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/partial-non-lazy-continuation.js
6569
+ /** @type {Construct} */
6570
+ const nonLazyContinuation = {
6571
+ partial: true,
6572
+ tokenize: tokenizeNonLazyContinuation
6573
+ };
6574
+ /**
6575
+ * @this {TokenizeContext}
6576
+ * Context.
6577
+ * @type {Tokenizer}
6578
+ */
6579
+ function tokenizeNonLazyContinuation(effects, ok, nok) {
6580
+ const self = this;
6581
+ return start;
6582
+ /**
6583
+ * At eol, before continuation.
6584
+ *
6585
+ * ```markdown
6586
+ * > | * ```js
6587
+ * ^
6588
+ * | b
6589
+ * ```
6590
+ *
6591
+ * @type {State}
6592
+ */
6593
+ function start(code) {
6594
+ if (code === null) return nok(code);
6595
+ effects.enter("lineEnding");
6596
+ effects.consume(code);
6597
+ effects.exit("lineEnding");
6598
+ return after;
6599
+ }
6600
+ /**
6601
+ * A continuation.
6602
+ *
6603
+ * ```markdown
6604
+ * | * ```js
6605
+ * > | b
6606
+ * ^
6607
+ * ```
6608
+ *
6609
+ * @type {State}
6610
+ */
6611
+ function after(code) {
6612
+ return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
6613
+ }
6614
+ }
6615
+ //#endregion
6616
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/code-fenced.js
3325
6617
  /**
3326
6618
  * @import {
3327
6619
  * Code,
@@ -3332,11 +6624,6 @@ window.__ModuleLoader__.load({
3332
6624
  * } from 'micromark-util-types'
3333
6625
  */
3334
6626
  /** @type {Construct} */
3335
- const nonLazyContinuation = {
3336
- partial: true,
3337
- tokenize: tokenizeNonLazyContinuation
3338
- };
3339
- /** @type {Construct} */
3340
6627
  const codeFenced = {
3341
6628
  concrete: true,
3342
6629
  name: "codeFenced",
@@ -3706,37 +6993,8 @@ window.__ModuleLoader__.load({
3706
6993
  }
3707
6994
  }
3708
6995
  }
3709
- /**
3710
- * @this {TokenizeContext}
3711
- * Context.
3712
- * @type {Tokenizer}
3713
- */
3714
- function tokenizeNonLazyContinuation(effects, ok, nok) {
3715
- const self = this;
3716
- return start;
3717
- /**
3718
- *
3719
- *
3720
- * @type {State}
3721
- */
3722
- function start(code) {
3723
- if (code === null) return nok(code);
3724
- effects.enter("lineEnding");
3725
- effects.consume(code);
3726
- effects.exit("lineEnding");
3727
- return lineStart;
3728
- }
3729
- /**
3730
- *
3731
- *
3732
- * @type {State}
3733
- */
3734
- function lineStart(code) {
3735
- return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
3736
- }
3737
- }
3738
6996
  //#endregion
3739
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/code-indented.js
6997
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/code-indented.js
3740
6998
  /**
3741
6999
  * @import {
3742
7000
  * Construct,
@@ -3761,7 +7019,6 @@ window.__ModuleLoader__.load({
3761
7019
  * @type {Tokenizer}
3762
7020
  */
3763
7021
  function tokenizeCodeIndented(effects, ok, nok) {
3764
- const self = this;
3765
7022
  return start;
3766
7023
  /**
3767
7024
  * Start of code (indented).
@@ -3779,21 +7036,7 @@ window.__ModuleLoader__.load({
3779
7036
  */
3780
7037
  function start(code) {
3781
7038
  effects.enter("codeIndented");
3782
- return factorySpace(effects, afterPrefix, "linePrefix", 5)(code);
3783
- }
3784
- /**
3785
- * At start, after 1 or 4 spaces.
3786
- *
3787
- * ```markdown
3788
- * > | aaa
3789
- * ^
3790
- * ```
3791
- *
3792
- * @type {State}
3793
- */
3794
- function afterPrefix(code) {
3795
- const tail = self.events[self.events.length - 1];
3796
- return tail && tail[1].type === "linePrefix" && tail[2].sliceSerialize(tail[1], true).length >= 4 ? atBreak(code) : nok(code);
7039
+ return factorySpaceMinMax(effects, atBreak, nok, "linePrefix", 4, 4)(code);
3797
7040
  }
3798
7041
  /**
3799
7042
  * At a break.
@@ -3862,25 +7105,28 @@ window.__ModuleLoader__.load({
3862
7105
  effects.exit("lineEnding");
3863
7106
  return furtherStart;
3864
7107
  }
3865
- return factorySpace(effects, afterPrefix, "linePrefix", 5)(code);
7108
+ return factorySpaceMinMax(effects, ok, onNotEnoughPrefix, "linePrefix", 4, 4)(code);
3866
7109
  }
3867
7110
  /**
3868
- * At start, after 1 or 4 spaces.
7111
+ * After not enough of a prefix.
7112
+ *
7113
+ * A following line ending is another (potentially blank) line to try,
7114
+ * anything else means this isn’t a continuation.
3869
7115
  *
3870
7116
  * ```markdown
3871
7117
  * > | aaa
3872
- * ^
7118
+ *
7119
+ * | bbb
3873
7120
  * ```
3874
7121
  *
3875
7122
  * @type {State}
3876
7123
  */
3877
- function afterPrefix(code) {
3878
- const tail = self.events[self.events.length - 1];
3879
- return tail && tail[1].type === "linePrefix" && tail[2].sliceSerialize(tail[1], true).length >= 4 ? ok(code) : markdownLineEnding(code) ? furtherStart(code) : nok(code);
7124
+ function onNotEnoughPrefix(code) {
7125
+ return markdownLineEnding(code) ? furtherStart(code) : nok(code);
3880
7126
  }
3881
7127
  }
3882
7128
  //#endregion
3883
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/code-text.js
7129
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/code-text.js
3884
7130
  /**
3885
7131
  * @import {
3886
7132
  * Construct,
@@ -4450,7 +7696,7 @@ window.__ModuleLoader__.load({
4450
7696
  return gaps;
4451
7697
  }
4452
7698
  //#endregion
4453
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/content.js
7699
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/content.js
4454
7700
  /**
4455
7701
  * @import {
4456
7702
  * Construct,
@@ -5086,7 +8332,7 @@ window.__ModuleLoader__.load({
5086
8332
  }
5087
8333
  }
5088
8334
  //#endregion
5089
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/definition.js
8335
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/definition.js
5090
8336
  /**
5091
8337
  * @import {
5092
8338
  * Construct,
@@ -5301,7 +8547,7 @@ window.__ModuleLoader__.load({
5301
8547
  }
5302
8548
  }
5303
8549
  //#endregion
5304
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/hard-break-escape.js
8550
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/hard-break-escape.js
5305
8551
  /**
5306
8552
  * @import {
5307
8553
  * Construct,
@@ -5358,7 +8604,7 @@ window.__ModuleLoader__.load({
5358
8604
  }
5359
8605
  }
5360
8606
  //#endregion
5361
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/heading-atx.js
8607
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/heading-atx.js
5362
8608
  /**
5363
8609
  * @import {
5364
8610
  * Construct,
@@ -5379,20 +8625,16 @@ window.__ModuleLoader__.load({
5379
8625
  function resolveHeadingAtx(events, context) {
5380
8626
  let contentEnd = events.length - 2;
5381
8627
  let contentStart = 3;
5382
- /** @type {Token} */
5383
- let content;
5384
- /** @type {Token} */
5385
- let text;
5386
8628
  if (events[contentStart][1].type === "whitespace") contentStart += 2;
5387
8629
  if (contentEnd - 2 > contentStart && events[contentEnd][1].type === "whitespace") contentEnd -= 2;
5388
8630
  if (events[contentEnd][1].type === "atxHeadingSequence" && (contentStart === contentEnd - 1 || contentEnd - 4 > contentStart && events[contentEnd - 2][1].type === "whitespace")) contentEnd -= contentStart + 1 === contentEnd ? 2 : 4;
5389
8631
  if (contentEnd > contentStart) {
5390
- content = {
8632
+ const content = {
5391
8633
  type: "atxHeadingText",
5392
8634
  start: events[contentStart][1].start,
5393
8635
  end: events[contentEnd][1].end
5394
8636
  };
5395
- text = {
8637
+ const text = {
5396
8638
  type: "chunkText",
5397
8639
  start: events[contentStart][1].start,
5398
8640
  end: events[contentEnd][1].end,
@@ -5642,7 +8884,7 @@ window.__ModuleLoader__.load({
5642
8884
  "textarea"
5643
8885
  ];
5644
8886
  //#endregion
5645
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/html-flow.js
8887
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/html-flow.js
5646
8888
  /**
5647
8889
  * @import {
5648
8890
  * Code,
@@ -5665,10 +8907,6 @@ window.__ModuleLoader__.load({
5665
8907
  partial: true,
5666
8908
  tokenize: tokenizeBlankLineBefore
5667
8909
  };
5668
- const nonLazyContinuationStart = {
5669
- partial: true,
5670
- tokenize: tokenizeNonLazyContinuationStart
5671
- };
5672
8910
  /** @type {Resolver} */
5673
8911
  function resolveToHtmlFlow(events) {
5674
8912
  let index = events.length;
@@ -6171,7 +9409,7 @@ window.__ModuleLoader__.load({
6171
9409
  * @type {State}
6172
9410
  */
6173
9411
  function continuationStart(code) {
6174
- return effects.check(nonLazyContinuationStart, continuationStartNonLazy, continuationAfter)(code);
9412
+ return effects.check(nonLazyContinuation, continuationStartNonLazy, continuationAfter)(code);
6175
9413
  }
6176
9414
  /**
6177
9415
  * In continuation, at eol, before non-lazy content.
@@ -6351,49 +9589,6 @@ window.__ModuleLoader__.load({
6351
9589
  * Context.
6352
9590
  * @type {Tokenizer}
6353
9591
  */
6354
- function tokenizeNonLazyContinuationStart(effects, ok, nok) {
6355
- const self = this;
6356
- return start;
6357
- /**
6358
- * At eol, before continuation.
6359
- *
6360
- * ```markdown
6361
- * > | * ```js
6362
- * ^
6363
- * | b
6364
- * ```
6365
- *
6366
- * @type {State}
6367
- */
6368
- function start(code) {
6369
- if (markdownLineEnding(code)) {
6370
- effects.enter("lineEnding");
6371
- effects.consume(code);
6372
- effects.exit("lineEnding");
6373
- return after;
6374
- }
6375
- return nok(code);
6376
- }
6377
- /**
6378
- * A continuation.
6379
- *
6380
- * ```markdown
6381
- * | * ```js
6382
- * > | b
6383
- * ^
6384
- * ```
6385
- *
6386
- * @type {State}
6387
- */
6388
- function after(code) {
6389
- return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
6390
- }
6391
- }
6392
- /**
6393
- * @this {TokenizeContext}
6394
- * Context.
6395
- * @type {Tokenizer}
6396
- */
6397
9592
  function tokenizeBlankLineBefore(effects, ok, nok) {
6398
9593
  return start;
6399
9594
  /**
@@ -6415,7 +9610,7 @@ window.__ModuleLoader__.load({
6415
9610
  }
6416
9611
  }
6417
9612
  //#endregion
6418
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/html-text.js
9613
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/html-text.js
6419
9614
  /**
6420
9615
  * @import {
6421
9616
  * Code,
@@ -7029,7 +10224,7 @@ window.__ModuleLoader__.load({
7029
10224
  }
7030
10225
  }
7031
10226
  //#endregion
7032
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/label-end.js
10227
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/label-end.js
7033
10228
  /**
7034
10229
  * @import {
7035
10230
  * Construct,
@@ -7075,8 +10270,6 @@ window.__ModuleLoader__.load({
7075
10270
  function resolveToLabelEnd(events, context) {
7076
10271
  let index = events.length;
7077
10272
  let offset = 0;
7078
- /** @type {Token} */
7079
- let token;
7080
10273
  /** @type {number | undefined} */
7081
10274
  let open;
7082
10275
  /** @type {number | undefined} */
@@ -7084,7 +10277,7 @@ window.__ModuleLoader__.load({
7084
10277
  /** @type {Array<Event>} */
7085
10278
  let media;
7086
10279
  while (index--) {
7087
- token = events[index][1];
10280
+ const token = events[index][1];
7088
10281
  if (open) {
7089
10282
  if (token.type === "link" || token.type === "labelLink" && token._inactive) break;
7090
10283
  if (events[index][0] === "enter" && token.type === "labelLink") token._inactive = true;
@@ -7159,14 +10352,14 @@ window.__ModuleLoader__.load({
7159
10352
  */
7160
10353
  function tokenizeLabelEnd(effects, ok, nok) {
7161
10354
  const self = this;
7162
- let index = self.events.length;
10355
+ const labelStarts = self._labelStarts;
7163
10356
  /** @type {Token} */
7164
10357
  let labelStart;
7165
10358
  /** @type {boolean} */
7166
10359
  let defined;
7167
- while (index--) if ((self.events[index][1].type === "labelImage" || self.events[index][1].type === "labelLink") && !self.events[index][1]._balanced) {
7168
- labelStart = self.events[index][1];
7169
- break;
10360
+ if (labelStarts) {
10361
+ while (labelStarts.length > 0 && labelStarts[labelStarts.length - 1]._balanced) labelStarts.pop();
10362
+ labelStart = labelStarts[labelStarts.length - 1];
7170
10363
  }
7171
10364
  return start;
7172
10365
  /**
@@ -7253,6 +10446,7 @@ window.__ModuleLoader__.load({
7253
10446
  * @type {State}
7254
10447
  */
7255
10448
  function labelEndOk(code) {
10449
+ labelStarts.pop();
7256
10450
  return ok(code);
7257
10451
  }
7258
10452
  /**
@@ -7499,11 +10693,12 @@ window.__ModuleLoader__.load({
7499
10693
  }
7500
10694
  }
7501
10695
  //#endregion
7502
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/label-start-image.js
10696
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/label-start-image.js
7503
10697
  /**
7504
10698
  * @import {
7505
10699
  * Construct,
7506
10700
  * State,
10701
+ * Token,
7507
10702
  * TokenizeContext,
7508
10703
  * Tokenizer
7509
10704
  * } from 'micromark-util-types'
@@ -7521,6 +10716,8 @@ window.__ModuleLoader__.load({
7521
10716
  */
7522
10717
  function tokenizeLabelStartImage(effects, ok, nok) {
7523
10718
  const self = this;
10719
+ /** @type {Token} */
10720
+ let labelImage;
7524
10721
  return start;
7525
10722
  /**
7526
10723
  * Start of label (image) start.
@@ -7554,7 +10751,7 @@ window.__ModuleLoader__.load({
7554
10751
  effects.enter("labelMarker");
7555
10752
  effects.consume(code);
7556
10753
  effects.exit("labelMarker");
7557
- effects.exit("labelImage");
10754
+ labelImage = effects.exit("labelImage");
7558
10755
  return after;
7559
10756
  }
7560
10757
  return nok(code);
@@ -7587,16 +10784,20 @@ window.__ModuleLoader__.load({
7587
10784
  * @type {State}
7588
10785
  */
7589
10786
  function after(code) {
7590
- /* c8 ignore next 3 */
7591
- return code === 94 && "_hiddenFootnoteSupport" in self.parser.constructs ? nok(code) : ok(code);
10787
+ /* c8 ignore next 6 */
10788
+ if (code === 94 && "_hiddenFootnoteSupport" in self.parser.constructs) return nok(code);
10789
+ self._labelStarts = self._labelStarts || [];
10790
+ self._labelStarts.push(labelImage);
10791
+ return ok(code);
7592
10792
  }
7593
10793
  }
7594
10794
  //#endregion
7595
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/label-start-link.js
10795
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/label-start-link.js
7596
10796
  /**
7597
10797
  * @import {
7598
10798
  * Construct,
7599
10799
  * State,
10800
+ * Token,
7600
10801
  * TokenizeContext,
7601
10802
  * Tokenizer
7602
10803
  * } from 'micromark-util-types'
@@ -7614,6 +10815,8 @@ window.__ModuleLoader__.load({
7614
10815
  */
7615
10816
  function tokenizeLabelStartLink(effects, ok, nok) {
7616
10817
  const self = this;
10818
+ /** @type {Token} */
10819
+ let labelLink;
7617
10820
  return start;
7618
10821
  /**
7619
10822
  * Start of label (link) start.
@@ -7630,17 +10833,20 @@ window.__ModuleLoader__.load({
7630
10833
  effects.enter("labelMarker");
7631
10834
  effects.consume(code);
7632
10835
  effects.exit("labelMarker");
7633
- effects.exit("labelLink");
10836
+ labelLink = effects.exit("labelLink");
7634
10837
  return after;
7635
10838
  }
7636
10839
  /** @type {State} */
7637
10840
  function after(code) {
7638
- /* c8 ignore next 3 */
7639
- return code === 94 && "_hiddenFootnoteSupport" in self.parser.constructs ? nok(code) : ok(code);
10841
+ /* c8 ignore next 6 */
10842
+ if (code === 94 && "_hiddenFootnoteSupport" in self.parser.constructs) return nok(code);
10843
+ self._labelStarts = self._labelStarts || [];
10844
+ self._labelStarts.push(labelLink);
10845
+ return ok(code);
7640
10846
  }
7641
10847
  }
7642
10848
  //#endregion
7643
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/line-ending.js
10849
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/line-ending.js
7644
10850
  /**
7645
10851
  * @import {
7646
10852
  * Construct,
@@ -7670,7 +10876,7 @@ window.__ModuleLoader__.load({
7670
10876
  }
7671
10877
  }
7672
10878
  //#endregion
7673
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/thematic-break.js
10879
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/thematic-break.js
7674
10880
  /**
7675
10881
  * @import {
7676
10882
  * Code,
@@ -7765,7 +10971,7 @@ window.__ModuleLoader__.load({
7765
10971
  }
7766
10972
  }
7767
10973
  //#endregion
7768
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/list.js
10974
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/list.js
7769
10975
  /**
7770
10976
  * @import {
7771
10977
  * Code,
@@ -7937,7 +11143,7 @@ window.__ModuleLoader__.load({
7937
11143
  }
7938
11144
  }
7939
11145
  //#endregion
7940
- //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/lib/setext-underline.js
11146
+ //#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/setext-underline.js
7941
11147
  /**
7942
11148
  * @import {
7943
11149
  * Code,
@@ -7956,6 +11162,7 @@ window.__ModuleLoader__.load({
7956
11162
  };
7957
11163
  /** @type {Resolver} */
7958
11164
  function resolveToSetextUnderline(events, context) {
11165
+ const editMap = new EditMap$1();
7959
11166
  let index = events.length;
7960
11167
  /** @type {number | undefined} */
7961
11168
  let content;
@@ -7970,7 +11177,7 @@ window.__ModuleLoader__.load({
7970
11177
  }
7971
11178
  if (events[index][1].type === "paragraph") text = index;
7972
11179
  } else {
7973
- if (events[index][1].type === "content") events.splice(index, 1);
11180
+ if (events[index][1].type === "content") editMap.add(index, 1, []);
7974
11181
  if (!definition && events[index][1].type === "definition") definition = index;
7975
11182
  }
7976
11183
  const heading = {
@@ -7980,23 +11187,24 @@ window.__ModuleLoader__.load({
7980
11187
  };
7981
11188
  events[text][1].type = "setextHeadingText";
7982
11189
  if (definition) {
7983
- events.splice(text, 0, [
11190
+ editMap.add(text, 0, [[
7984
11191
  "enter",
7985
11192
  heading,
7986
11193
  context
7987
- ]);
7988
- events.splice(definition + 1, 0, [
11194
+ ]]);
11195
+ editMap.add(definition + 1, 0, [[
7989
11196
  "exit",
7990
11197
  events[content][1],
7991
11198
  context
7992
- ]);
11199
+ ]]);
7993
11200
  events[content][1].end = { ...events[definition][1].end };
7994
11201
  } else events[content][1] = heading;
7995
- events.push([
11202
+ editMap.add(events.length, 0, [[
7996
11203
  "exit",
7997
11204
  heading,
7998
11205
  context
7999
- ]);
11206
+ ]]);
11207
+ editMap.consume(events);
8000
11208
  return events;
8001
11209
  }
8002
11210
  /**
@@ -8089,7 +11297,7 @@ window.__ModuleLoader__.load({
8089
11297
  }
8090
11298
  }
8091
11299
  //#endregion
8092
- //#region ../../../node_modules/.pnpm/micromark@4.0.2_supports-color@9.4.0/node_modules/micromark/lib/initialize/flow.js
11300
+ //#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/initialize/flow.js
8093
11301
  /**
8094
11302
  * @import {
8095
11303
  * InitialConstruct,
@@ -8136,7 +11344,7 @@ window.__ModuleLoader__.load({
8136
11344
  }
8137
11345
  }
8138
11346
  //#endregion
8139
- //#region ../../../node_modules/.pnpm/micromark@4.0.2_supports-color@9.4.0/node_modules/micromark/lib/initialize/text.js
11347
+ //#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/initialize/text.js
8140
11348
  /**
8141
11349
  * @import {
8142
11350
  * Code,
@@ -8253,6 +11461,7 @@ window.__ModuleLoader__.load({
8253
11461
  * @type {Resolver}
8254
11462
  */
8255
11463
  function resolveAllLineSuffixes(events, context) {
11464
+ const editMap = new EditMap$1();
8256
11465
  let eventIndex = 0;
8257
11466
  while (++eventIndex <= events.length) if ((eventIndex === events.length || events[eventIndex][1].type === "lineEnding") && events[eventIndex - 1][1].type === "data") {
8258
11467
  const data = events[eventIndex - 1][1];
@@ -8295,25 +11504,23 @@ window.__ModuleLoader__.load({
8295
11504
  };
8296
11505
  data.end = { ...token.start };
8297
11506
  if (data.start.offset === data.end.offset) Object.assign(data, token);
8298
- else {
8299
- events.splice(eventIndex, 0, [
8300
- "enter",
8301
- token,
8302
- context
8303
- ], [
8304
- "exit",
8305
- token,
8306
- context
8307
- ]);
8308
- eventIndex += 2;
8309
- }
11507
+ else editMap.add(eventIndex, 0, [[
11508
+ "enter",
11509
+ token,
11510
+ context
11511
+ ], [
11512
+ "exit",
11513
+ token,
11514
+ context
11515
+ ]]);
8310
11516
  }
8311
11517
  eventIndex++;
8312
11518
  }
11519
+ editMap.consume(events);
8313
11520
  return events;
8314
11521
  }
8315
11522
  //#endregion
8316
- //#region ../../../node_modules/.pnpm/micromark@4.0.2_supports-color@9.4.0/node_modules/micromark/lib/constructs.js
11523
+ //#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/constructs.js
8317
11524
  /**
8318
11525
  * @import {Extension} from 'micromark-util-types'
8319
11526
  */
@@ -8391,7 +11598,7 @@ window.__ModuleLoader__.load({
8391
11598
  /** @satisfies {Extension['disable']} */
8392
11599
  const disable = { null: [] };
8393
11600
  //#endregion
8394
- //#region ../../../node_modules/.pnpm/micromark@4.0.2_supports-color@9.4.0/node_modules/micromark/lib/create-tokenizer.js
11601
+ //#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/create-tokenizer.js
8395
11602
  /**
8396
11603
  * @import {
8397
11604
  * Chunk,
@@ -8547,12 +11754,10 @@ window.__ModuleLoader__.load({
8547
11754
  * Nothing.
8548
11755
  */
8549
11756
  function main() {
8550
- /** @type {number} */
8551
- let chunkIndex;
8552
11757
  while (point._index < chunks.length) {
8553
11758
  const chunk = chunks[point._index];
8554
11759
  if (typeof chunk === "string") {
8555
- chunkIndex = point._index;
11760
+ const chunkIndex = point._index;
8556
11761
  if (point._bufferIndex < 0) point._bufferIndex = 0;
8557
11762
  while (point._index === chunkIndex && point._bufferIndex < chunk.length) go(chunk.charCodeAt(point._bufferIndex));
8558
11763
  } else go(chunk);
@@ -8860,7 +12065,7 @@ window.__ModuleLoader__.load({
8860
12065
  return result.join("");
8861
12066
  }
8862
12067
  //#endregion
8863
- //#region ../../../node_modules/.pnpm/micromark@4.0.2_supports-color@9.4.0/node_modules/micromark/lib/parse.js
12068
+ //#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/parse.js
8864
12069
  /**
8865
12070
  * @import {
8866
12071
  * Create,
@@ -8904,7 +12109,7 @@ window.__ModuleLoader__.load({
8904
12109
  }
8905
12110
  }
8906
12111
  //#endregion
8907
- //#region ../../../node_modules/.pnpm/micromark@4.0.2_supports-color@9.4.0/node_modules/micromark/lib/postprocess.js
12112
+ //#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/postprocess.js
8908
12113
  /**
8909
12114
  * @import {Event} from 'micromark-util-types'
8910
12115
  */
@@ -8919,7 +12124,7 @@ window.__ModuleLoader__.load({
8919
12124
  return events;
8920
12125
  }
8921
12126
  //#endregion
8922
- //#region ../../../node_modules/.pnpm/micromark@4.0.2_supports-color@9.4.0/node_modules/micromark/lib/preprocess.js
12127
+ //#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/preprocess.js
8923
12128
  /**
8924
12129
  * @import {Chunk, Code, Encoding, Value} from 'micromark-util-types'
8925
12130
  */
@@ -8950,20 +12155,10 @@ window.__ModuleLoader__.load({
8950
12155
  return preprocessor;
8951
12156
  /** @type {Preprocessor} */
8952
12157
  function preprocessor(value, encoding, end) {
12158
+ value = buffer + (typeof value === "string" ? value.toString() : new TextDecoder(encoding || void 0).decode(value));
8953
12159
  /** @type {Array<Chunk>} */
8954
12160
  const chunks = [];
8955
- /** @type {RegExpMatchArray | null} */
8956
- let match;
8957
- /** @type {number} */
8958
- let next;
8959
- /** @type {number} */
8960
- let startPosition;
8961
- /** @type {number} */
8962
- let endPosition;
8963
- /** @type {Code} */
8964
- let code;
8965
- value = buffer + (typeof value === "string" ? value.toString() : new TextDecoder(encoding || void 0).decode(value));
8966
- startPosition = 0;
12161
+ let startPosition = 0;
8967
12162
  buffer = "";
8968
12163
  if (start) {
8969
12164
  if (value.charCodeAt(0) === 65279) startPosition++;
@@ -8971,9 +12166,9 @@ window.__ModuleLoader__.load({
8971
12166
  }
8972
12167
  while (startPosition < value.length) {
8973
12168
  search.lastIndex = startPosition;
8974
- match = search.exec(value);
8975
- endPosition = match && match.index !== void 0 ? match.index : value.length;
8976
- code = value.charCodeAt(endPosition);
12169
+ const match = search.exec(value);
12170
+ const endPosition = match && match.index !== void 0 ? match.index : value.length;
12171
+ const code = value.charCodeAt(endPosition);
8977
12172
  if (!match) {
8978
12173
  buffer = value.slice(startPosition);
8979
12174
  break;
@@ -8995,11 +12190,12 @@ window.__ModuleLoader__.load({
8995
12190
  chunks.push(65533);
8996
12191
  column++;
8997
12192
  break;
8998
- case 9:
8999
- next = Math.ceil(column / 4) * 4;
12193
+ case 9: {
12194
+ const next = Math.ceil(column / 4) * 4;
9000
12195
  chunks.push(-2);
9001
12196
  while (column++ < next) chunks.push(-1);
9002
12197
  break;
12198
+ }
9003
12199
  case 10:
9004
12200
  chunks.push(-4);
9005
12201
  column = 1;
@@ -11074,7 +14270,8 @@ window.__ModuleLoader__.load({
11074
14270
  };
11075
14271
  }
11076
14272
  //#endregion
11077
- //#region ../../../node_modules/.pnpm/mdast-util-gfm-strikethrough@2.0.0_supports-color@9.4.0/node_modules/mdast-util-gfm-strikethrough/lib/index.js
14273
+ //#region ../../../node_modules/.pnpm/mdast-util-gfm-strikethrough@2.0.1_supports-color@9.4.0/node_modules/mdast-util-gfm-strikethrough/lib/index.js
14274
+ handleDelete.attention = attentionDelete;
11078
14275
  handleDelete.peek = peekDelete;
11079
14276
  /**
11080
14277
  * Create an extension for `mdast-util-from-markdown` to enable GFM
@@ -11091,16 +14288,21 @@ window.__ModuleLoader__.load({
11091
14288
  };
11092
14289
  }
11093
14290
  /**
14291
+ * Enter strikethrough.
14292
+ *
11094
14293
  * @this {CompileContext}
11095
14294
  * @type {FromMarkdownHandle}
11096
14295
  */
11097
14296
  function enterStrikethrough(token) {
11098
14297
  this.enter({
11099
14298
  type: "delete",
11100
- children: []
14299
+ children: [],
14300
+ position: void 0
11101
14301
  }, token);
11102
14302
  }
11103
14303
  /**
14304
+ * Exit strikethrough.
14305
+ *
11104
14306
  * @this {CompileContext}
11105
14307
  * @type {FromMarkdownHandle}
11106
14308
  */
@@ -11108,23 +14310,43 @@ window.__ModuleLoader__.load({
11108
14310
  this.exit(token);
11109
14311
  }
11110
14312
  /**
14313
+ * Serialize a delete node.
14314
+ *
11111
14315
  * @type {ToMarkdownHandle}
11112
14316
  * @param {Delete} node
14317
+ * Node to serialize.
11113
14318
  */
11114
14319
  function handleDelete(node, _, state, info) {
11115
14320
  const tracker = state.createTracker(info);
14321
+ const sequence = state.stack.includes("strikethrough") ? "~" : "~~";
11116
14322
  const exit = state.enter("strikethrough");
11117
- let value = tracker.move("~~");
14323
+ let value = tracker.move(sequence);
11118
14324
  value += state.containerPhrasing(node, {
11119
14325
  ...tracker.current(),
11120
14326
  before: value,
11121
14327
  after: "~"
11122
14328
  });
11123
- value += tracker.move("~~");
14329
+ value += tracker.move(sequence);
11124
14330
  exit();
11125
14331
  return value;
11126
14332
  }
11127
- /** @type {ToMarkdownHandle} */
14333
+ /**
14334
+ * Serialize a delete node as attention.
14335
+ *
14336
+ * @type {Attention}
14337
+ */
14338
+ function attentionDelete() {
14339
+ return {
14340
+ construct: "strikethrough",
14341
+ markers: ["~"],
14342
+ sizes: [2, 1]
14343
+ };
14344
+ }
14345
+ /**
14346
+ * Peek the first character of a delete node.
14347
+ *
14348
+ * @type {ToMarkdownHandle}
14349
+ */
11128
14350
  function peekDelete() {
11129
14351
  return "~";
11130
14352
  }
@@ -14398,10 +17620,24 @@ window.__ModuleLoader__.load({
14398
17620
  }
14399
17621
  //#endregion
14400
17622
  //#region src/client/index.ts
14401
- const inject = [];
14402
- function apply(_ctx) {}
17623
+ /**
17624
+ * 需要的**服务名**:样式与引用那半不要服务,schema 表单那半要槽位、字典与配置表单。
17625
+ *
17626
+ * 与包清单里的 `dsh.client.inject` 不是一回事:那份是**装配行 id**(谁先到),交给模块系统排 arrival 顺序;
17627
+ * 这里读的是 cordis 服务名(谁已 provide),Loader 拿它决定这一行何时才算 active。写错成包名,这一行就永远
17628
+ * pending(`web boot: ... waiting for services`)。
17629
+ *
17630
+ * 值直接取自 schema 表单那一面,不另抄一份清单。
17631
+ */
17632
+ const inject = [...inject$1];
17633
+ function apply(ctx) {
17634
+ apply$1(ctx);
17635
+ }
14403
17636
  //#endregion
14404
17637
  exports.ReferenceMarkdown = ReferenceMarkdown;
17638
+ exports.SCHEMA_FORM_NS = NS;
17639
+ exports.SchemaFormController = SchemaFormController;
17640
+ exports.SchemaFormHints = SchemaFormHints;
14405
17641
  exports.SettingsForm = SettingsForm;
14406
17642
  exports.SettingsFormModel = SettingsFormModel;
14407
17643
  exports.SettingsSecretField = SettingsSecretField;
@@ -14411,6 +17647,7 @@ window.__ModuleLoader__.load({
14411
17647
  exports.apply = apply;
14412
17648
  exports.designTokens = designTokens;
14413
17649
  exports.dsw = dsw;
17650
+ exports.failureOf = failureOf;
14414
17651
  exports.findReferences = findReferences;
14415
17652
  exports.formatReference = formatReference;
14416
17653
  exports.formatReferenceMention = formatReferenceMention;
@@ -14419,12 +17656,21 @@ window.__ModuleLoader__.load({
14419
17656
  exports.markdownLabels = markdownLabels;
14420
17657
  exports.parseReference = parseReference;
14421
17658
  exports.parseReferenceToken = parseReferenceToken;
17659
+ exports.projectRoot = projectRoot;
14422
17660
  exports.referenceMentions = referenceMentions;
17661
+ exports.rowRegistrations = rowRegistrations;
17662
+ Object.defineProperty(exports, "schemaForm", {
17663
+ enumerable: true,
17664
+ get: function() {
17665
+ return schema_form_exports;
17666
+ }
17667
+ });
14423
17668
  exports.settingsNumberField = settingsNumberField;
14424
17669
  exports.settingsTextField = settingsTextField;
14425
17670
  exports.styleOf = styleOf;
14426
17671
  exports.styled = styled;
14427
17672
  exports.styling = styling;
17673
+ exports.walkFields = walkFields;
14428
17674
  return module.exports;
14429
17675
  }
14430
17676
  });