@morlay/dsh-client-ui-primitives 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +18 -266
  2. package/dist/client.cjs +11 -612
  3. package/dist/client.d.cts +33 -658
  4. package/dist/client.d.mts +33 -658
  5. package/dist/index.mjs +1 -1
  6. package/dist/{reference-Dxy9uQYz.mjs → reference-Cjcn21uW.mjs} +0 -1
  7. package/locale/en.json +1 -1
  8. package/locale/zh.json +1 -1
  9. package/package.json +18 -18
  10. package/src/client/index.ts +7 -9
  11. package/src/client/schema-form/Editor.tsx +25 -38
  12. package/src/client/schema-form/SchemaForm.tsx +6 -12
  13. package/src/client/schema-form/controller.ts +65 -123
  14. package/src/client/schema-form/draft.ts +55 -138
  15. package/src/client/schema-form/fields.tsx +5 -14
  16. package/src/client/schema-form/hints.ts +31 -131
  17. package/src/client/schema-form/index.ts +15 -20
  18. package/src/client/schema-form/labels.ts +7 -9
  19. package/src/client/schema-form/lines.ts +36 -60
  20. package/src/client/schema-form/locales.ts +6 -8
  21. package/src/client/schema-form/rows.ts +22 -41
  22. package/src/client/schema-form/schema-node.ts +87 -150
  23. package/src/client/schema-form/slot-contract.ts +55 -76
  24. package/src/client/schema-form/styles.ts +39 -51
  25. package/src/client/schema-form/testing/fake-describe.ts +3 -9
  26. package/src/client/schema-form/testing/fake-scope.ts +8 -10
  27. package/src/client/schema-form/value.tsx +14 -26
  28. package/src/client/settings-form/SettingsForm.tsx +13 -17
  29. package/src/client/settings-form/fields.tsx +25 -35
  30. package/src/client/settings-form/form-model.ts +63 -145
  31. package/src/client/styling/styled.tsx +3 -5
  32. package/src/client/theme.generated.ts +18 -7
  33. package/src/reference-markdown.tsx +1 -1
  34. package/src/reference.ts +1 -1
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { o as parseReferenceToken, t as findReferences } from "./reference-Dxy9uQYz.mjs";
1
+ import { o as parseReferenceToken, t as findReferences } from "./reference-Cjcn21uW.mjs";
2
2
  //#region src/index.ts
3
3
  function apply() {}
4
4
  //#endregion
@@ -368,7 +368,6 @@ function parseBareReference(raw) {
368
368
  ...column === void 0 ? {} : { column: Number(column) }
369
369
  };
370
370
  }
371
- /** 引号 mention 的起手字符:`@"path with spaces"`。 */
372
371
  const QUOTED_PATH = "\"";
373
372
  function parseQuotedReference(body) {
374
373
  const end = body.indexOf(QUOTED_PATH, 1);
package/locale/en.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
3
  "title": "UI Primitives",
4
- "description": "css-in-js styling layer plus the shared reference parser and renderer for the forked conversation UI. It also carries the schema-driven row config pages (field-slot injection)."
4
+ "description": "css-in-js styling layer, the shared reference parser and renderer for the forked conversation UI, and the settings-form primitives forked from upstream. It also carries the schema-driven row config pages (field-slot injection)."
5
5
  }
6
6
  }
package/locale/zh.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
3
  "title": "UI 基础层",
4
- "description": "css-in-js 样式层,以及被 fork 的对话 UI 共用的引用解析与渲染转换。 另含按行 schema 自动生成的行配置页(字段槽注入)。"
4
+ "description": "css-in-js 样式层、被 fork 的对话 UI 共用的引用解析与渲染转换,以及从上游 fork 过来的设置表单原语。另含按行 schema 自动生成的行配置页(字段槽注入)。"
5
5
  }
6
6
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@morlay/dsh-client-ui-primitives",
3
- "version": "0.0.3",
4
- "description": "css-in-js styling kit (styled / Token / Styling) plus the shared reference parser and renderer (findReferences / parseReferenceToken) for the forked conversation UI: consumes the official --dsw-* design tokens, so no CSS Modules precompile step is needed.",
3
+ "version": "0.0.5",
4
+ "description": "css-in-js styling kit (styled / Token / Styling), the shared reference parser and renderer (findReferences / parseReferenceToken) for the forked conversation UI, and the settings-form primitives forked from upstream (SettingsFormModel / field controls / form shell): consumes the official --dsw-* design tokens, so no CSS Modules precompile step is needed.",
5
5
  "keywords": [
6
6
  "client",
7
7
  "css-in-js",
@@ -40,15 +40,15 @@
40
40
  "micromark-util-types": "^2.0.2"
41
41
  },
42
42
  "devDependencies": {
43
- "@deepseek-ai/dsh-api-remotes": "0.1.7-rc.2",
44
- "@deepseek-ai/dsh-client-locale": "0.1.7-rc.2",
45
- "@deepseek-ai/dsh-client-store": "0.1.7-rc.2",
46
- "@deepseek-ai/dsh-client-ui-plugin-manager": "0.1.7-rc.2",
47
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2",
48
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.2",
49
- "@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.2",
50
- "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2",
51
- "@deepseek-ai/dsh-util-values": "0.1.7-rc.2",
43
+ "@deepseek-ai/dsh-api-remotes": "0.2.0-rc.1",
44
+ "@deepseek-ai/dsh-client-locale": "0.2.0-rc.1",
45
+ "@deepseek-ai/dsh-client-store": "0.2.0-rc.1",
46
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.0-rc.1",
47
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.1",
48
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.1",
49
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.1",
50
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1",
51
+ "@deepseek-ai/dsh-util-values": "0.2.0-rc.1",
52
52
  "@deepseek-ai/schemastery": "3.18.4",
53
53
  "@testing-library/react": "^16.3.0",
54
54
  "@types/mdast": "^4.0.4",
@@ -59,13 +59,13 @@
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@deepseek-ai/cordis": "^4.0.4",
62
- "@deepseek-ai/dsh-api-remotes": "^0.1.7-rc.2",
63
- "@deepseek-ai/dsh-client-locale": "^0.1.7-rc.2",
64
- "@deepseek-ai/dsh-client-store": "^0.1.7-rc.2",
65
- "@deepseek-ai/dsh-client-ui-plugin-manager": "^0.1.7-rc.2",
66
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-rc.2",
67
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.7-rc.2",
68
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.2"
62
+ "@deepseek-ai/dsh-api-remotes": "^0.2.0-rc.1",
63
+ "@deepseek-ai/dsh-client-locale": "^0.2.0-rc.1",
64
+ "@deepseek-ai/dsh-client-store": "^0.2.0-rc.1",
65
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "^0.2.0-rc.1",
66
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.2.0-rc.1",
67
+ "@deepseek-ai/dsh-client-ui-settings": "^0.2.0-rc.1",
68
+ "@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.1"
69
69
  },
70
70
  "dsh": {
71
71
  "client": {
@@ -44,15 +44,13 @@ export type { Reference, ReferenceSpan } from "../reference.ts";
44
44
  export { ReferenceMarkdown, referenceMentions } from "../reference-markdown.tsx";
45
45
  export type { ReferenceActions, ReferenceMarkdownProps } from "../reference-markdown.tsx";
46
46
 
47
- /**
48
- * 需要的**服务名**:样式与引用那半不要服务,schema 表单那半要槽位、字典与配置表单。
49
- *
50
- * 与包清单里的 `dsh.client.inject` 不是一回事:那份是**装配行 id**(谁先到),交给模块系统排 arrival 顺序;
51
- * 这里读的是 cordis 服务名(谁已 provide),Loader 拿它决定这一行何时才算 active。写错成包名,这一行就永远
52
- * pending(`web boot: ... waiting for services`)。
53
- *
54
- * 值直接取自 schema 表单那一面,不另抄一份清单。
55
- */
47
+ // 需要的**服务名**:样式与引用那半不要服务,schema 表单那半要槽位、字典与配置表单。
48
+ //
49
+ // 与包清单里的 `dsh.client.inject` 不是一回事:那份是**装配行 id**(谁先到),交给模块系统排 arrival 顺序;
50
+ // 这里读的是 cordis 服务名(谁已 provide),Loader 拿它决定这一行何时才算 active。写错成包名,这一行就永远
51
+ // pending(`web boot: ... waiting for services`)。
52
+ //
53
+ // 值直接取自 schema 表单那一面,不另抄一份清单。
56
54
  export const inject: readonly string[] = [...schemaForm.inject];
57
55
 
58
56
  export function apply(ctx: Context): void {
@@ -1,12 +1,10 @@
1
- /**
2
- * 行式编辑器:按 `editorLines` 排出的行画 JSON 结构视图——行号、折进、`key: value`、结构行、注释行、容器闭合行
3
- * 后面的添加入口;值在行内点开编辑(Enter 提交 / Esc 撤销)。
4
- *
5
- * 缩进只作用在**行内容**上(行号列固定),添加输入与闭合括号同一行;行内控件用官方的 `Input` / `Menu` / `Button`,
6
- * 与设置页其余部分同一套视觉与键盘行为。
7
- *
8
- * 字段槽仍然生效:命中时该字段**值**的位置换成注册方的组件,键名、注释、行号与行为按钮仍由这里画。
9
- */
1
+ // 行式编辑器:按 `editorLines` 排出的行画 JSON 结构视图——行号、折进、`key: value`、结构行、注释行、容器闭合行
2
+ // 后面的添加入口;值在行内点开编辑(Enter 提交 / Esc 撤销)。
3
+ //
4
+ // 缩进只作用在**行内容**上(行号列固定),添加输入与闭合括号同一行;行内控件用官方的 `Input` / `Menu` / `Button`,
5
+ // 与设置页其余部分同一套视觉与键盘行为。
6
+ //
7
+ // 字段槽仍然生效:命中时该字段**值**的位置换成注册方的组件,键名、注释、行号与行为按钮仍由这里画。
10
8
 
11
9
  import { useMemo, useState, type ReactNode } from "react";
12
10
  import {
@@ -53,31 +51,22 @@ import {
53
51
  ValueTrigger,
54
52
  } from "./styles.ts";
55
53
 
56
- /** 编辑器要的一切。 */
54
+ // 编辑器要的一切。
57
55
  export interface EditorProps {
58
- /** settings 命名空间(行 id)。 */
56
+ // settings 命名空间(行 id)。
59
57
  ns: string;
60
- /** 页面读数。 */
58
+ // 页面读数。
61
59
  state: SchemaFormState;
62
- /** 动作面(写草稿、保存、丢弃)。 */
60
+ // 动作面(写草稿、保存、丢弃)。
63
61
  face: SchemaFormFace;
64
- /** 本包字典。 */
62
+ // 本包字典。
65
63
  t: SchemaFormTranslate;
66
- /** `description` 的本地化。 */
64
+ // `description` 的本地化。
67
65
  resolveText: ResolveText;
68
- /**
69
- * 一个字段的值位置怎么画(字段槽入口:命中用注册方的组件,否则用本包的行内值)。
70
- * @param owner - 字段上下文与动作。
71
- * @returns 行内值的节点。
72
- */
66
+ // 一个字段的值位置怎么画(字段槽入口:命中用注册方的组件,否则用本包的行内值)。
73
67
  renderField: (owner: SchemaFieldOwnerProps) => ReactNode;
74
68
  }
75
69
 
76
- /**
77
- * 画一行的配置。
78
- * @param props - 读数、动作、字典与字段槽入口。
79
- * @returns 行式编辑器。
80
- */
81
70
  export function Editor(props: EditorProps): ReactNode {
82
71
  const { ns, state, face, t, resolveText, renderField } = props;
83
72
  const [toggled, setToggled] = useState<ReadonlySet<string>>(() => new Set());
@@ -135,7 +124,7 @@ export function Editor(props: EditorProps): ReactNode {
135
124
  );
136
125
  }
137
126
 
138
- /** 折叠状态:记「用户切换过的路径」,开合 = 切换过就反转 schema 的默认值。 */
127
+ // 折叠状态:记「用户切换过的路径」,开合 = 切换过就反转 schema 的默认值。
139
128
  function useFold(
140
129
  state: SchemaFormState,
141
130
  toggled: ReadonlySet<string>,
@@ -169,7 +158,7 @@ function useFold(
169
158
  );
170
159
  }
171
160
 
172
- /** 一行的渲染分派。 */
161
+ // 一行的渲染分派。
173
162
  function Row({
174
163
  line,
175
164
  n,
@@ -308,7 +297,7 @@ function Row({
308
297
  );
309
298
  }
310
299
 
311
- /** 字段行:`key: value`(数组成员的键名是下标,淡一些)。 */
300
+ // 字段行:`key: value`(数组成员的键名是下标,淡一些)。
312
301
  function FieldLine({
313
302
  line,
314
303
  ns,
@@ -526,12 +515,12 @@ function FieldLine({
526
515
  );
527
516
  }
528
517
 
529
- /** 复制用的文本(字符串带引号,粘到哪里都是合法片段)。 */
518
+ // 复制用的文本(字符串带引号,粘到哪里都是合法片段)。
530
519
  function tokenFor(value: unknown): string {
531
520
  return typeof value === "string" ? JSON.stringify(value) : JSON.stringify(value ?? null);
532
521
  }
533
522
 
534
- /** 变体切换:判别式 union 挂在标签行上,按形状选的挂在字段行上。触发元素就是当前那一支。 */
523
+ // 变体切换:判别式 union 挂在标签行上,按形状选的挂在字段行上。触发元素就是当前那一支。
535
524
  function VariantSelect({
536
525
  control,
537
526
  path,
@@ -586,7 +575,7 @@ function VariantSelect({
586
575
  );
587
576
  }
588
577
 
589
- /** 开启行的前缀:字段键名(对象里的一层)或数组成员的下标;成员行另外带移除。 */
578
+ // 开启行的前缀:字段键名(对象里的一层)或数组成员的下标;成员行另外带移除。
590
579
  function LinePrefix({
591
580
  line,
592
581
  face,
@@ -619,7 +608,7 @@ function LinePrefix({
619
608
  );
620
609
  }
621
610
 
622
- /** 成员行的移除(悬停时出现):数组项按索引、字典键按键名。 */
611
+ // 成员行的移除(悬停时出现):数组项按索引、字典键按键名。
623
612
  function RemoveLineButton({
624
613
  member,
625
614
  face,
@@ -647,12 +636,10 @@ function RemoveLineButton({
647
636
  );
648
637
  }
649
638
 
650
- /**
651
- * 容器闭合行后面的添加入口。
652
- *
653
- * 数组是「追加一个空项」;对象/字典给出**候选**——对象列的是 schema 声明了、值里还没有的字段(带说明),
654
- * 字典列的是业务注册的候选键(还能自己敲键名)。粘一整段 `{…}` / `[…]` 则整层覆盖。
655
- */
639
+ // 容器闭合行后面的添加入口。
640
+ //
641
+ // 数组是「追加一个空项」;对象/字典给出**候选**——对象列的是 schema 声明了、值里还没有的字段(带说明),
642
+ // 字典列的是业务注册的候选键(还能自己敲键名)。粘一整段 `{…}` / `[…]` 则整层覆盖。
656
643
  function AddLine({
657
644
  line,
658
645
  state,
@@ -1,9 +1,7 @@
1
- /**
2
- * 一行的配置页:官方表单壳(保存 / 丢弃 / 只读 / 不可用)+ 行式 JSON 结构编辑器 + 字段槽。
3
- *
4
- * 每个可渲染的行注册项经 `renderFactorySlot('settings.schema-form.form', …)` 渲染这个 Factory,字段槽因此只需在
5
- * Factory 的 `children` 里声明一次(slots 的 children 声明 per key 唯一,而本包给每个行都注册了一项)。
6
- */
1
+ // 一行的配置页:官方表单壳(保存 / 丢弃 / 只读 / 不可用)+ 行式 JSON 结构编辑器 + 字段槽。
2
+ //
3
+ // 每个可渲染的行注册项经 `renderFactorySlot('settings.schema-form.form', …)` 渲染这个 Factory,字段槽因此只需在
4
+ // Factory 的 `children` 里声明一次(slots 的 children 声明 per key 唯一,而本包给每个行都注册了一项)。
7
5
 
8
6
  import { useSyncExternalStore, type ReactNode } from "react";
9
7
  import { SettingsForm, type SettingsFormLabels } from "@deepseek-ai/dsh-client-ui-primitives";
@@ -13,11 +11,7 @@ import type { SchemaFieldOwnerProps, SchemaFormComponentProps } from "./slot-con
13
11
  import { Hint } from "./styles.ts";
14
12
  import { SchemaFieldDefault } from "./value.tsx";
15
13
 
16
- /**
17
- * 渲染一行的配置页。
18
- * @param props - 行 id、控制器面、本地化解析与框架注入的槽位渲染座位。
19
- * @returns 表单壳里的行式编辑器,或「没有可自动生成的配置项」一行。
20
- */
14
+ // 渲染一行的配置页。
21
15
  export function SchemaForm(props: SchemaFormComponentProps): ReactNode {
22
16
  const { ns, face, t, resolveText, renderSlotChain } = props;
23
17
  const store = face.hooks.schemaForm;
@@ -66,7 +60,7 @@ export function SchemaForm(props: SchemaFormComponentProps): ReactNode {
66
60
  );
67
61
  }
68
62
 
69
- /** 文本草稿的解析规则按字段类型给:行内编辑只上报原文,值在保存那一刻成形。 */
63
+ // 文本草稿的解析规则按字段类型给:行内编辑只上报原文,值在保存那一刻成形。
70
64
  function withParse(
71
65
  owner: SchemaFieldOwnerProps,
72
66
  t: SchemaFormComponentProps["t"],
@@ -1,9 +1,7 @@
1
- /**
2
- * 一行的配置页控制器:把 describe 里的 volatile schema 投影成字段树,绑上该命名空间的共享配置表单与草稿模型。
3
- *
4
- * 一个命名空间一个实例(行 id 就是 settings 命名空间)。行被 describe 出来之后 `configured` 才是 true:
5
- * schema 变了就重新投影字段树(模型不重建,草稿仍在),行消失时页面只显示一行说明。
6
- */
1
+ // 一行的配置页控制器:把 describe 里的 volatile schema 投影成字段树,绑上该命名空间的共享配置表单与草稿模型。
2
+ //
3
+ // 一个命名空间一个实例(行 id 就是 settings 命名空间)。行被 describe 出来之后 `configured` 才是 true:
4
+ // schema 变了就重新投影字段树(模型不重建,草稿仍在),行消失时页面只显示一行说明。
7
5
 
8
6
  import type { SettingsNamespaceView } from "@deepseek-ai/dsh-api-remotes/client";
9
7
  import { createSnapshotStore, type SnapshotStore } from "@deepseek-ai/dsh-client-store";
@@ -33,7 +31,7 @@ import {
33
31
  import type { FieldText, SelectOption, SelectSpec } from "./hints.ts";
34
32
  import type { SchemaFormTranslate } from "./slot-contract.ts";
35
33
 
36
- /** `role('select', { source })` 里声明的候选源名字。 */
34
+ // `role('select', { source })` 里声明的候选源名字。
37
35
  function declaredSource(node: FieldNode | undefined): string | undefined {
38
36
  const extra = node?.meta.extra;
39
37
  if (typeof extra !== "object" || extra === null) return undefined;
@@ -41,20 +39,13 @@ function declaredSource(node: FieldNode | undefined): string | undefined {
41
39
  return typeof name === "string" && name.length > 0 ? name : undefined;
42
40
  }
43
41
 
44
- /** 字段在状态表里的键(具体路径的 JSON)。 */
42
+ // 字段在状态表里的键(具体路径的 JSON)。
45
43
  export function fieldKey(path: readonly string[]): string {
46
44
  return JSON.stringify(path);
47
45
  }
48
46
 
49
- /**
50
- * rehydrate 一段配置 schema,并判它能不能当页面的根。
51
- *
52
- * 能当根的形状有三种:对象段、**相交段**(共享判别字段 + 各分支的 `const` 标记,段值仍是对象)、以及带判别
53
- * 标签的 `union`(成员都是对象)。字面量 `union`(整段就是一个标量)不行:那一行的写盘会变成整段替换。
54
- * @param serialized - host 发来的 `schema.toJSON()`。
55
- * @param rehydrate - host schema 的 rehydrate。
56
- * @returns schema 与它的字段树,或 `undefined`(不能当页面根)。
57
- */
47
+ // rehydrate 一段配置 schema,判它能不能当页面根:对象段、相交段,或带判别标签的 union
48
+ // (字面量 union 会让那一行的写盘变成整段替换)。
58
49
  export function projectRoot(
59
50
  serialized: unknown,
60
51
  rehydrate: (serialized: unknown) => SchemaNode,
@@ -72,12 +63,7 @@ export function projectRoot(
72
63
  }
73
64
  }
74
65
 
75
- /**
76
- * 一行能否自动生成配置页:它的 schema 能 rehydrate 且根是 {@link projectRoot} 认的形状。
77
- * @param view - host 发来的命名空间视图。
78
- * @param rehydrate - host schema 的 rehydrate。
79
- * @returns 是否可以渲染。
80
- */
66
+ // 一行能否自动生成配置页:它的 schema 能 rehydrate 且根是 `projectRoot` 认的形状。
81
67
  export function canRender(
82
68
  view: SettingsNamespaceView | undefined,
83
69
  rehydrate: (serialized: unknown) => SchemaNode,
@@ -85,7 +71,7 @@ export function canRender(
85
71
  return view !== undefined && projectRoot(view.schema, rehydrate) !== undefined;
86
72
  }
87
73
 
88
- /** 段根不是可渲染对象时的空字段树。 */
74
+ // 段根不是可渲染对象时的空字段树。
89
75
  const NO_FIELDS: FieldNode = {
90
76
  key: "",
91
77
  path: [],
@@ -96,77 +82,52 @@ const NO_FIELDS: FieldNode = {
96
82
  fields: [],
97
83
  };
98
84
 
99
- /** 提示面在控制器里的读法:候选键 + 字段文案 + 候选值。 */
85
+ // 提示面在控制器里的读法:候选键 + 字段文案 + 候选值。
100
86
  export interface HintSources extends SuggestedKeys {
101
- /**
102
- * 读某个字段的文案。
103
- * @param path - 该字段的具体路径。
104
- * @returns 业务注册的文案;没有就是空对象。
105
- */
87
+ // 读某个字段的文案;没有就是空对象。
106
88
  textFor: (path: readonly string[]) => FieldText;
107
- /**
108
- * 读某个字段的候选值读数;不接提示面的装配可以不给(字段就还是文本编辑)。
109
- * @param path - 该字段的具体路径。
110
- * @returns 业务注册的候选读数;没有就是 `undefined`。
111
- */
89
+ // 读某个字段的候选值读数;不接提示面的装配可以不给(字段就还是文本编辑)。
112
90
  selectFor?: ((path: readonly string[]) => SelectSpec | undefined) | undefined;
113
- /**
114
- * 读一个**具名候选源**(schema 的 `role('select', { source })` 认领它)。
115
- * @param name - 源的名字。
116
- * @returns 该源的读数;没注册过就是 `undefined`。
117
- */
91
+ // 读一个具名候选源(schema 的 `role('select', { source })` 认领它)。
118
92
  sourceFor?: ((name: string) => SelectSpec | undefined) | undefined;
119
93
  }
120
94
 
121
- /** 控制器要的外部面(`apply` 里从 cordis ctx 组装;测试直接给替身)。 */
95
+ // 控制器要的外部面(`apply` 里从 cordis ctx 组装;测试直接给替身)。
122
96
  export interface SchemaFormDeps {
123
- /** 本命名空间的共享配置表单(读 + 带栅栏的写)。 */
97
+ // 本命名空间的共享配置表单(读 + 带栅栏的写)。
124
98
  form: DraftScope;
125
- /** describe mirror 的读面:本行的 schema、autoGenerate 与 secret 槽。 */
99
+ // describe mirror 的读面:本行的 schema、autoGenerate 与 secret 槽。
126
100
  describe: SettingsDescribeFace;
127
- /** rehydrate host 发来的 `schema.toJSON()`。 */
101
+ // rehydrate host 发来的 `schema.toJSON()`。
128
102
  rehydrate: (serialized: unknown) => SchemaNode;
129
- /** 整段校验;失败时连路径一起给(页面把消息画在那一行)。 */
103
+ // 整段校验;失败时连路径一起给(页面把消息画在那一行)。
130
104
  validate: (schema: SchemaNode, value: unknown) => ValidationFailure | undefined;
131
- /** 本包字典(项身份冲突这类本地校验的消息)。 */
105
+ // 本包字典(项身份冲突这类本地校验的消息)。
132
106
  t: SchemaFormTranslate;
133
- /**
134
- * dict 字段的候选键(业务注册,见 `hints.ts`):候选里有、值里没有的键会补成一行「未配置」,
135
- * 用户填了才算写。
136
- */
107
+ // dict 字段的候选键(业务注册,见 `hints.ts`):候选里有、值里没有的键会补成一行「未配置」,
108
+ // 用户填了才算写。
137
109
  hints?: HintSources | undefined;
138
110
  }
139
111
 
140
- /**
141
- * 这一层「能加进来」的一项:schema 里声明、值里还没有的字段,或业务注册的候选键。
142
- *
143
- * 页面上不占行——它们出现在容器的闭合行那个「添加属性 / 添加键」输入框的候选里,选中才加。
144
- */
112
+ // 这一层「能加进来」的一项:schema 里声明、值里还没有的字段,或业务注册的候选键。
113
+ //
114
+ // 页面上不占行——它们出现在容器的闭合行那个「添加属性 / 添加键」输入框的候选里,选中才加。
145
115
  export interface AddableProperty {
146
- /** 加进来的键名。 */
116
+ // 加进来的键名。
147
117
  key: string;
148
- /** 说明(schema 的 `description` / `comment`,可能是本地化字典):渲染层解析后当候选的副标题。 */
118
+ // 说明(schema 的 `description` / `comment`,可能是本地化字典):渲染层解析后当候选的副标题。
149
119
  description: string | Readonly<Record<string, string>> | undefined;
150
120
  }
151
121
 
152
- /** 值里已有的键(数组给索引)。 */
122
+ // 值里已有的键(数组给索引)。
153
123
  function keysOf(value: unknown): string[] {
154
124
  if (Array.isArray(value)) return value.map((_item, index) => String(index));
155
125
  if (typeof value === "object" && value !== null) return Object.keys(value);
156
126
  return [];
157
127
  }
158
128
 
159
- /**
160
- * 一个容器当前能加什么。
161
- *
162
- * 对象层(含相交与 union 的选中变体)是**非必填**、值里又没有的字段——必填的已经占着行了;字典层是业务注册的
163
- * 候选键(模式清单这类运行期数据)。数组层不在这里:它的「添加一项」是追加一个空项,没有键名可选。
164
- * @param node - 容器节点。
165
- * @param path - 该容器的具体路径。
166
- * @param value - 该容器自己的值(草稿后的)。
167
- * @param hints - 提示面(只用到候选键)。
168
- * @returns 可添加项,顺序即 schema / 读数的顺序;列表为空时页面不给输入框。
169
- */
129
+ // 一个容器当前能加什么:对象层给非必填且值里没有的字段(必填的已占着行),字典层给业务注册的候选键;
130
+ // 数组层的「添加一项」是追加空项,没有键名可选。
170
131
  export function addableAt(
171
132
  node: FieldNode,
172
133
  path: readonly string[],
@@ -188,78 +149,65 @@ export function addableAt(
188
149
  );
189
150
  }
190
151
 
191
- /** 页面级状态:表单壳 + 字段树 + 每字段草稿状态。 */
152
+ // 页面级状态:表单壳 + 字段树 + 每字段草稿状态。
192
153
  export interface SchemaFormState extends SettingsFormShell {
193
- /** 这一行的 schema 是否可渲染(否则页面只显示一行说明)。 */
154
+ // 这一行的 schema 是否可渲染(否则页面只显示一行说明)。
194
155
  configured: boolean;
195
- /** 字段树;未 configured 时是空对象树。 */
156
+ // 字段树;未 configured 时是空对象树。
196
157
  root: FieldNode;
197
- /** 按草稿后的值展开的字段列表(渲染顺序):追加的数组项与新增的字典键立刻出现。 */
158
+ // 按草稿后的值展开的字段列表(渲染顺序):追加的数组项与新增的字典键立刻出现。
198
159
  walked: readonly WalkedField[];
199
- /** 每个具体路径的草稿状态,键见 {@link fieldKey}。 */
160
+ // 每个具体路径的草稿状态,键见 `fieldKey`。
200
161
  fields: ReadonlyMap<string, DraftFieldState>;
201
- /** secret 槽是否已配置,键见 {@link fieldKey}。 */
162
+ // secret 槽是否已配置,键见 `fieldKey`。
202
163
  secrets: ReadonlyMap<string, boolean>;
203
- /** 每个字段的文案(提示面注册的),键见 {@link fieldKey};渲染层把它画成注释行。 */
164
+ // 每个字段的文案(提示面注册的),键见 `fieldKey`;渲染层把它画成注释行。
204
165
  texts: ReadonlyMap<string, FieldText>;
205
- /**
206
- * 每个字段的可选项,键见 {@link fieldKey}:字面量集合的 union 自带候选,其余来自提示面。
207
- * 候选依赖的兄弟字段一改,这里就跟着变(投影时按草稿后的值求一次)。
208
- */
166
+ // 每个字段的可选项,键见 `fieldKey`:字面量集合的 union 自带候选,其余来自提示面。
167
+ // 候选依赖的兄弟字段一改,这里就跟着变(投影时按草稿后的值求一次)。
209
168
  options: ReadonlyMap<string, readonly SelectOption[]>;
210
- /**
211
- * 每个容器能加进来的项,键见 {@link fieldKey}(容器自己的路径)。没有条目就是加不了东西——对象层的输入框
212
- * 因此只在还有声明字段没配时出现。
213
- */
169
+ // 每个容器能加进来的项,键见 `fieldKey`(容器自己的路径)。没有条目就是加不了东西——对象层的输入框
170
+ // 因此只在还有声明字段没配时出现。
214
171
  addable: ReadonlyMap<string, readonly AddableProperty[]>;
215
- /** 保存被 schema 挡下时的消息与它的位置。 */
172
+ // 保存被 schema 挡下时的消息与它的位置。
216
173
  violation: ValidationFailure | undefined;
217
- /** 每行的校验消息(键见 {@link fieldKey}):行内报错,与底部的整段提示同源。 */
174
+ // 每行的校验消息(键见 `fieldKey`):行内报错,与底部的整段提示同源。
218
175
  invalidAt: ReadonlyMap<string, string>;
219
176
  }
220
177
 
221
- /** 控件的写动作:全部落在草稿上,`save` 是唯一的写盘点。 */
178
+ // 控件的写动作:全部落在草稿上,`save` 是唯一的写盘点。
222
179
  export interface SchemaFormActions {
223
- /** 暂存一个合法值。 */
180
+ // 暂存一个合法值。
224
181
  set: (path: readonly string[], value: unknown) => void;
225
- /** 暂存一段文本(保存时解析)。 */
182
+ // 暂存一段文本(保存时解析)。
226
183
  setText: (path: readonly string[], text: string, parse: TextParse) => void;
227
- /** 暂存「恢复默认」。 */
184
+ // 暂存「恢复默认」。
228
185
  clear: (path: readonly string[]) => void;
229
- /** 追加一个数组项。 */
186
+ // 追加一个数组项。
230
187
  appendItem: (path: readonly string[]) => void;
231
- /** 移除一个数组项。 */
188
+ // 移除一个数组项。
232
189
  removeItem: (path: readonly string[], index: number) => void;
233
- /** 加一个字典键。 */
190
+ // 加一个字典键。
234
191
  addKey: (path: readonly string[], key: string) => void;
235
- /** 删一个字典键。 */
192
+ // 删一个字典键。
236
193
  removeKey: (path: readonly string[], key: string) => void;
237
- /** 写盘。 */
194
+ // 写盘。
238
195
  save: () => void;
239
- /** 撤销一个字段的草稿(行内编辑取消)。 */
196
+ // 撤销一个字段的草稿(行内编辑取消)。
240
197
  revert: (path: readonly string[]) => void;
241
- /** 丢弃草稿。 */
198
+ // 丢弃草稿。
242
199
  discard: () => void;
243
200
  }
244
201
 
245
- /** 槽位渲染器绑定的动作与可观察状态。 */
202
+ // 槽位渲染器绑定的动作与可观察状态。
246
203
  export interface SchemaFormFace extends SchemaFormActions {
247
204
  hooks: {
248
205
  schemaForm: SnapshotStore<SchemaFormState>;
249
206
  };
250
207
  }
251
208
 
252
- /**
253
- * 一个字段的可选项。
254
- *
255
- * 字面量集合的 union(`z.union(["wal", "delete"])`)自己就带着候选;其余看提示面注册的读数——读的时候把依赖的
256
- * 兄弟字段值喂进去,所以「选完服务商才列它有哪些模型」是投影的一部分,不是额外的一次订阅。
257
- * @param node - 字段节点。
258
- * @param path - 该字段的具体路径。
259
- * @param value - 段值(草稿后的)。
260
- * @param hints - 提示面。
261
- * @returns 可选项;没有候选就是 `undefined`。
262
- */
209
+ // 一个字段的可选项:字面量集合的 union 自带候选,其余看提示面注册的读数(求值时把依赖的兄弟字段值喂进去,
210
+ // 所以「选完服务商才列它有哪些模型」是投影的一部分,不是额外订阅)。
263
211
  export function optionsFor(
264
212
  node: FieldNode,
265
213
  path: readonly string[],
@@ -280,9 +228,7 @@ export function optionsFor(
280
228
  return options.length === 0 ? undefined : options;
281
229
  }
282
230
 
283
- /**
284
- * 一行配置页的控制器。
285
- */
231
+ // 一行配置页的控制器。
286
232
  export class SchemaFormController {
287
233
  readonly #ns: string;
288
234
  readonly #deps: SchemaFormDeps;
@@ -293,10 +239,6 @@ export class SchemaFormController {
293
239
  #schema: SchemaNode | undefined;
294
240
  #root: FieldNode | undefined;
295
241
 
296
- /**
297
- * @param ns - settings 命名空间(行 id)。
298
- * @param deps - 该行的共享表单与 schema 面。
299
- */
300
242
  constructor(ns: string, deps: SchemaFormDeps) {
301
243
  this.#ns = ns;
302
244
  this.#deps = deps;
@@ -320,7 +262,7 @@ export class SchemaFormController {
320
262
  this.#sync();
321
263
  }
322
264
 
323
- /** 槽位渲染器要的动作与状态。 */
265
+ // 槽位渲染器要的动作与状态。
324
266
  face(): SchemaFormFace {
325
267
  return {
326
268
  hooks: { schemaForm: this.#store },
@@ -357,23 +299,23 @@ export class SchemaFormController {
357
299
  };
358
300
  }
359
301
 
360
- /** 本行的 settings 命名空间。 */
302
+ // 本行的 settings 命名空间。
361
303
  namespace(): string {
362
304
  return this.#ns;
363
305
  }
364
306
 
365
- /** 外部提示(候选键这类)变了:重新投影一次,字段树与草稿都不动。 */
307
+ // 外部提示(候选键这类)变了:重新投影一次,字段树与草稿都不动。
366
308
  refresh(): void {
367
309
  this.#store.set(this.#project());
368
310
  }
369
311
 
370
- /** 释放订阅。 */
312
+ // 释放订阅。
371
313
  dispose(): void {
372
314
  for (const dispose of this.#disposers.splice(0)) dispose();
373
315
  this.#model.dispose();
374
316
  }
375
317
 
376
- /** 重新读 schema(describe 变了):只换字段树,草稿与 store 不动。 */
318
+ // 重新读 schema(describe 变了):只换字段树,草稿与 store 不动。
377
319
  #sync(): void {
378
320
  const view = this.#deps.describe
379
321
  .getSnapshot()
@@ -388,7 +330,7 @@ export class SchemaFormController {
388
330
  this.#store.set(this.#project());
389
331
  }
390
332
 
391
- /** 整段校验:schema 先说话,再看数组的项身份有没有冲突。 */
333
+ // 整段校验:schema 先说话,再看数组的项身份有没有冲突。
392
334
  #validate(deps: SchemaFormDeps, value: Section): ValidationFailure | undefined {
393
335
  if (this.#schema !== undefined) {
394
336
  const failure = deps.validate(this.#schema, value);
@@ -406,7 +348,7 @@ export class SchemaFormController {
406
348
  };
407
349
  }
408
350
 
409
- /** 渲染层读的一份完整投影。 */
351
+ // 渲染层读的一份完整投影。
410
352
  #project(): SchemaFormState {
411
353
  const fields = new Map<string, DraftFieldState>();
412
354
  const root = this.#root;