@morlay/dsh-client-ui-primitives 0.0.4 → 0.0.6

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 (36) hide show
  1. package/README.md +18 -266
  2. package/dist/client.cjs +1 -611
  3. package/dist/client.d.cts +37 -684
  4. package/dist/client.d.mts +37 -684
  5. package/dist/index.d.cts +2 -2
  6. package/dist/index.d.mts +2 -2
  7. package/dist/index.mjs +1 -1
  8. package/dist/{reference-Dxy9uQYz.mjs → reference-Cjcn21uW.mjs} +0 -1
  9. package/locale/en.json +1 -1
  10. package/locale/zh.json +1 -1
  11. package/package.json +2 -2
  12. package/src/client/index.ts +7 -9
  13. package/src/client/schema-form/Editor.tsx +25 -38
  14. package/src/client/schema-form/SchemaForm.tsx +6 -12
  15. package/src/client/schema-form/controller.ts +65 -123
  16. package/src/client/schema-form/draft.ts +55 -138
  17. package/src/client/schema-form/fields.tsx +5 -14
  18. package/src/client/schema-form/hints.ts +31 -131
  19. package/src/client/schema-form/index.ts +15 -20
  20. package/src/client/schema-form/labels.ts +7 -9
  21. package/src/client/schema-form/lines.ts +36 -60
  22. package/src/client/schema-form/locales.ts +6 -8
  23. package/src/client/schema-form/rows.ts +22 -41
  24. package/src/client/schema-form/schema-node.ts +87 -150
  25. package/src/client/schema-form/slot-contract.ts +55 -76
  26. package/src/client/schema-form/styles.ts +39 -51
  27. package/src/client/schema-form/testing/fake-describe.ts +3 -9
  28. package/src/client/schema-form/testing/fake-scope.ts +8 -10
  29. package/src/client/schema-form/value.tsx +14 -26
  30. package/src/client/settings-form/SettingsForm.tsx +13 -17
  31. package/src/client/settings-form/fields.tsx +25 -35
  32. package/src/client/settings-form/form-model.ts +63 -145
  33. package/src/client/styling/styled.tsx +3 -5
  34. package/src/client/theme.generated.ts +6 -7
  35. package/src/reference-markdown.tsx +1 -1
  36. package/src/reference.ts +1 -1
@@ -1,15 +1,12 @@
1
- /**
2
- * 槽契约:字段级自定义输入与文案的注入点,以及承载它的 Factory 契约。
3
- *
4
- * 字段槽是 **chain**:注册项在 `select` 里按 `node.meta.role` 或 `ns + path` 决定认领哪个字段,命中即渲染自己的
5
- * 组件(收到 `matched`),未命中落到本包的默认控件。链式槽的注册项还可以只声明 `label`/`hint`(不改控件)——
6
- * 默认控件仍会渲染,文案以它为准。
7
- *
8
- * 字段槽声明在 Factory 上(`children`),而不是每个行注册项上:slots 的 children 声明 per key 只能一次,而本包
9
- * 会给每个可渲染的行各注册一个 `plugins.row.config` 项——那些项经 `renderFactorySlot` 渲染同一个 Factory,
10
- * 于是字段槽只需声明一次,所有行共用。
11
- */
12
-
1
+ // 槽契约:字段级自定义输入与文案的注入点,以及承载它的 Factory 契约。
2
+ //
3
+ // 字段槽是 **chain**:注册项在 `select` 里按 `node.meta.role` 或 `ns + path` 决定认领哪个字段,命中即渲染自己的
4
+ // 组件(收到 `matched`),未命中落到本包的默认控件。链式槽的注册项还可以只声明 `label`/`hint`(不改控件)——
5
+ // 默认控件仍会渲染,文案以它为准。
6
+ //
7
+ // 字段槽声明在 Factory 上(`children`),而不是每个行注册项上:slots 的 children 声明 per key 只能一次,而本包
8
+ // 会给每个可渲染的行各注册一个 `plugins.row.config` 项——那些项经 `renderFactorySlot` 渲染同一个 Factory,
9
+ // 于是字段槽只需声明一次,所有行共用。
13
10
  import type {} from "@deepseek-ai/dsh-client-ui-slots";
14
11
  import type {
15
12
  FactoryComponentPropsOf,
@@ -23,129 +20,111 @@ import type { ResolveText } from "./labels.ts";
23
20
  import type { SchemaFormLocaleKey } from "./locales.ts";
24
21
  import type { FieldNode } from "./schema-node.ts";
25
22
 
26
- /** `plugins.row.config` 的 key:`<bundle 包名>#<行 id>`(与页面 `rowConfigKey` 同拼法)。 */
23
+ // `plugins.row.config` 的 key:`<bundle 包名>#<行 id>`(与页面 `rowConfigKey` 同拼法)。
27
24
  export type RowConfigKey = string;
28
25
 
29
- /** 字段槽的 owner props:路由输入 + 显示状态 + 受控动作(默认控件要的一切都在这里)。 */
26
+ // 字段槽的 owner props:路由输入 + 显示状态 + 受控动作(默认控件要的一切都在这里)。
30
27
  export interface SchemaFieldOwnerProps {
31
- /** settings 命名空间(行 id)。 */
28
+ // settings 命名空间(行 id)。
32
29
  ns: string;
33
- /** 字段的具体路径(动态段已按真实键或索引替换)。 */
30
+ // 字段的具体路径(动态段已按真实键或索引替换)。
34
31
  path: readonly string[];
35
- /** 字段节点(类型、`role`、`extra`、界限、只读原因)。 */
32
+ // 字段节点(类型、`role`、`extra`、界限、只读原因)。
36
33
  node: FieldNode;
37
- /** 显示标签:默认是字段名拆词,业务方经槽给时会覆盖。 */
34
+ // 显示标签:默认是字段名拆词,业务方经槽给时会覆盖。
38
35
  label: string;
39
- /** 显示说明(`description` 本地化 + `comment`)。 */
36
+ // 显示说明(`description` 本地化 + `comment`)。
40
37
  hint: string | undefined;
41
- /** 当前生效值(草稿优先)。 */
38
+ // 当前生效值(草稿优先)。
42
39
  value: unknown;
43
- /**
44
- * 这个字段的可选项:给了就是选择器(点值不再进文本编辑)。来源是字面量集合的 union,或业务经提示面注册的
45
- * 候选——候选依赖的兄弟字段一变,这里就换一批。
46
- */
40
+ // 这个字段的可选项:给了就是选择器(点值不再进文本编辑)。来源是字面量集合的 union,或业务经提示面注册的
41
+ // 候选——候选依赖的兄弟字段一变,这里就换一批。
47
42
  options: readonly SelectOption[] | undefined;
48
- /** 文本草稿:文本控件显示它,而不是重新格式化生效值。 */
43
+ // 文本草稿:文本控件显示它,而不是重新格式化生效值。
49
44
  text: string | undefined;
50
- /** secret 槽是否已配置(值本身不回传)。 */
45
+ // secret 槽是否已配置(值本身不回传)。
51
46
  secretConfigured: boolean;
52
- /** 嵌套深度:默认控件按它缩进。 */
47
+ // 嵌套深度:默认控件按它缩进。
53
48
  depth: number;
54
- /** 保存后这一字段是否会留下用户层条目。 */
49
+ // 保存后这一字段是否会留下用户层条目。
55
50
  overridden: boolean;
56
- /** 草稿不合法时的消息。 */
51
+ // 草稿不合法时的消息。
57
52
  invalid: string | undefined;
58
- /** 只读部署或不可用命名空间时为 true。 */
53
+ // 只读部署或不可用命名空间时为 true。
59
54
  disabled: boolean;
60
- /** 本包字典(复刻或包装默认控件时用)。 */
55
+ // 本包字典(复刻或包装默认控件时用)。
61
56
  t: SchemaFormTranslate;
62
- /** 容器动作(增删数组项、字典键、丢弃、保存)。 */
57
+ // 容器动作(增删数组项、字典键、丢弃、保存)。
63
58
  actions: SchemaFormActions;
64
- /**
65
- * 分组节点的折叠交互(object / dict / array / tuple);叶子节点是 `undefined`。
66
- * 默认折叠与否由 schema 的 `collapse()` 决定,用户点一下就是反转。
67
- */
59
+ // 分组节点的折叠交互(object / dict / array / tuple);叶子节点是 `undefined`。
60
+ // 默认折叠与否由 schema 的 `collapse()` 决定,用户点一下就是反转。
68
61
  group: { collapsed: boolean; toggle: () => void } | undefined;
69
- /**
70
- * 这一行是动态容器的成员本身(dict 的键 / array 的项)时的身份:容器路径、键、数组项的位置。容器成员把它画进
71
- * 标题行(名字 + 移除),叶子成员由渲染层另起一行——所以「哪个字段属于哪个键/哪一项」在页面上一眼可见。
72
- */
62
+ // 这一行是动态容器的成员本身(dict 的键 / array 的项)时的身份:容器路径、键、数组项的位置。容器成员把它画进
63
+ // 标题行(名字 + 移除),叶子成员由渲染层另起一行——所以「哪个字段属于哪个键/哪一项」在页面上一眼可见。
73
64
  member:
74
65
  | { parent: readonly string[]; key: string; index: number | undefined; pending?: boolean }
75
66
  | undefined;
76
- /** 暂存一个合法值。 */
67
+ // 暂存一个合法值。
77
68
  onChange: (value: unknown) => void;
78
- /**
79
- * 暂存一段文本:按字段类型的默认规则解析;给了 `parse` 就用它(例如 secret 留空=不写)。
80
- * @param text - 用户输入。
81
- * @param parse - 覆盖默认解析规则。
82
- */
69
+ // 暂存一段文本:按字段类型的默认规则解析;给了 `parse` 就用它(例如 secret 留空=不写)。
83
70
  onEditText: (text: string, parse?: TextParse) => void;
84
- /** 暂存「恢复默认」。 */
71
+ // 暂存「恢复默认」。
85
72
  onReset: () => void;
86
73
  }
87
74
 
88
- /**
89
- * 字段槽注册项的完整 props:框架注入的 owner props({@link SchemaFieldOwnerProps})+它的 `select` 结果。
90
- *
91
- * 注册方的组件按这个类型声明参数(**不要**自己重写 owner props 的类型:注册时框架会把它读成 inject 面)。
92
- */
75
+ // 字段槽注册项的完整 props:框架注入的 owner props(`SchemaFieldOwnerProps`)+它的 `select` 结果。
76
+ //
77
+ // 注册方的组件按这个类型声明参数(**不要**自己重写 owner props 的类型:注册时框架会把它读成 inject 面)。
93
78
  export type SchemaFieldComponentProps<M extends SchemaFieldMatch = SchemaFieldMatch> =
94
79
  PropsRuntime<"settings.schema-form.field"> & { matched: M };
95
80
 
96
- /**
97
- * 字段槽注册项的 `select`:返回认领结果,或 `null` 表示不认领(chain 槽的约定是 `null` 放行给下一项,
98
- * `undefined` 不会被当成"不认领")。
99
- */
81
+ // 字段槽注册项的 `select`:返回认领结果,或 `null` 表示不认领(chain 槽的约定是 `null` 放行给下一项,
82
+ // `undefined` 不会被当成"不认领")。
100
83
  export type SchemaFieldSelect = (field: SchemaFieldOwnerProps) => SchemaFieldMatch | null;
101
84
 
102
- /** 字段槽 `select` 可以只给文案:控件仍由注册方渲染(拿本包的默认控件包一层即可)。 */
85
+ // 字段槽 `select` 可以只给文案:控件仍由注册方渲染(拿本包的默认控件包一层即可)。
103
86
  export interface SchemaFieldMatch {
104
- /** 覆盖显示标签。 */
87
+ // 覆盖显示标签。
105
88
  label?: string;
106
- /** 覆盖显示说明。 */
89
+ // 覆盖显示说明。
107
90
  hint?: string;
108
91
  }
109
92
 
110
- /** Factory 的输入 props:一行的配置页。 */
93
+ // Factory 的输入 props:一行的配置页。
111
94
  export interface SchemaFormProps {
112
- /** settings 命名空间(行 id)。 */
95
+ // settings 命名空间(行 id)。
113
96
  ns: string;
114
- /** 该行的控制器动作与状态。 */
97
+ // 该行的控制器动作与状态。
115
98
  face: SchemaFormFace;
116
- /** schema 里的本地化文本(`description`)按当前语言解析。 */
99
+ // schema 里的本地化文本(`description`)按当前语言解析。
117
100
  resolveText: ResolveText;
118
101
  }
119
102
 
120
- /** Factory 组件收到的完整 props。 */
103
+ // Factory 组件收到的完整 props。
121
104
  export type SchemaFormComponentProps = FactoryComponentPropsOf<"settings.schema-form.form">;
122
105
 
123
- /** 本包字典的翻译函数(默认控件与表单壳使用)。 */
106
+ // 本包字典的翻译函数(默认控件与表单壳使用)。
124
107
  export type SchemaFormTranslate = TranslateNS<"settings.schema-form">;
125
108
 
126
- /** 渲染层读一行状态的便捷别名。 */
109
+ // 渲染层读一行状态的便捷别名。
127
110
  export type { SchemaFormState };
128
111
 
129
112
  declare module "@deepseek-ai/dsh-client-ui-slots" {
130
113
  interface LocaleNamespaceMap {
131
- /** 表单壳、字段控件与只读呈现的文案。 */
114
+ // 表单壳、字段控件与只读呈现的文案。
132
115
  "settings.schema-form": SchemaFormLocaleKey;
133
116
  }
134
117
 
135
118
  interface SlotMap {
136
- /**
137
- * 字段级自定义输入与文案:按 `role` 或 `ns + path` 认领一个字段,命中即渲染注册方的组件(未命中落到默认
138
- * 控件)。注册项由业务插件用 `ctx.slots.inject('settings.schema-form.field', …)` 注册,声明由本包的
139
- * Factory 给出。
140
- */
119
+ // 字段级自定义输入与文案:按 `role` 或 `ns + path` 认领一个字段,命中即渲染注册方的组件(未命中落到默认
120
+ // 控件)。注册项由业务插件用 `ctx.slots.inject('settings.schema-form.field', …)` 注册,声明由本包的
121
+ // Factory 给出。
141
122
  "settings.schema-form.field": { kind: "chain"; scope: "root"; owner: SchemaFieldOwnerProps };
142
123
  }
143
124
 
144
125
  interface SlotFactoryMap {
145
- /**
146
- * 一行配置页的渲染体:每个可渲染的行注册项经 `renderFactorySlot` 渲染同一个 Factory,字段槽因此只需声明
147
- * 一次(slots 的 children 声明 per key 唯一)。
148
- */
126
+ // 一行配置页的渲染体:每个可渲染的行注册项经 `renderFactorySlot` 渲染同一个 Factory,字段槽因此只需声明
127
+ // 一次(slots 的 children 声明 per key 唯一)。
149
128
  "settings.schema-form.form": {
150
129
  scope: "root";
151
130
  props: SchemaFormProps;
@@ -1,52 +1,50 @@
1
- /**
2
- * 行式编辑器的样式:等宽、行号、语法色、悬停行为——全部消费官方 `--dsw-*` token。
3
- *
4
- * 几何只有一套:行高、行内间距、动作按钮尺寸都由下面几个常量给,行与行之间不各自决定。
5
- */
1
+ // 行式编辑器的样式:等宽、行号、语法色、悬停行为——全部消费官方 `--dsw-*` token。
2
+ //
3
+ // 几何只有一套:行高、行内间距、动作按钮尺寸都由下面几个常量给,行与行之间不各自决定。
6
4
 
7
5
  import { styled } from "../styling/index.ts";
8
6
  import { dsw } from "../theme.ts";
9
7
 
10
- /** 等宽字体栈:配置值的观感与代码一致。 */
8
+ // 等宽字体栈:配置值的观感与代码一致。
11
9
  const MONO = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace';
12
10
 
13
- /** 行高:行内容、行内输入、折叠箭头、按钮排都在这一档上对齐。 */
11
+ // 行高:行内容、行内输入、折叠箭头、按钮排都在这一档上对齐。
14
12
  const ROW = "22px";
15
13
 
16
- /** 行内元素之间的间距。 */
14
+ // 行内元素之间的间距。
17
15
  const GAP = "6px";
18
16
 
19
- /** 动作按钮:方形图标键,尺寸与行高同一档的紧凑版。 */
17
+ // 动作按钮:方形图标键,尺寸与行高同一档的紧凑版。
20
18
  const ACTION = "20px";
21
19
 
22
- /** 动作按钮之间的间距(比行内间距紧一档)。 */
20
+ // 动作按钮之间的间距(比行内间距紧一档)。
23
21
  const ACTION_GAP = "4px";
24
22
 
25
- /** 图标边长(行内图标与动作键同一档)。 */
23
+ // 图标边长(行内图标与动作键同一档)。
26
24
  export const ICON_SIZE = 14;
27
25
 
28
- /** 折叠列(chevron 与它的占位)宽度。 */
26
+ // 折叠列(chevron 与它的占位)宽度。
29
27
  const FOLD = "14px";
30
28
 
31
- /** 行号列宽度。 */
29
+ // 行号列宽度。
32
30
  const NUMBER = "32px";
33
31
 
34
- /** 行尾留白。 */
32
+ // 行尾留白。
35
33
  const PAD = "8px";
36
34
 
37
- /** 细线:theme 给的 0.5px 描边(`0 0 0 0.5px var(--dsw-elevation-stroke-color)`)。 */
35
+ // 细线:theme 给的 0.5px 描边(`0 0 0 0.5px var(--dsw-elevation-stroke-color)`)。
38
36
  const HAIRLINE = dsw.elevation.stroke;
39
37
 
40
- /** 描边色:theme 里给描边用的那一档(与官方 `Menu` 面板同源)。 */
38
+ // 描边色:theme 里给描边用的那一档(与官方 `Menu` 面板同源)。
41
39
  const LINE = dsw.elevation.stroke.color;
42
40
 
43
- /** 行左侧那道「改过」的标记宽度。 */
41
+ // 行左侧那道「改过」的标记宽度。
44
42
  const EDGE = "2px";
45
43
 
46
- /** 出错那一行的底色:theme 的红掺进透明里(半透明红),比整行实红轻、又比只有一道条明显。 */
44
+ // 出错那一行的底色:theme 的红掺进透明里(半透明红),比整行实红轻、又比只有一道条明显。
47
45
  const ERROR_WASH = `color-mix(in srgb, ${String(dsw.alias.state.error.secondary)} 12%, transparent)`;
48
46
 
49
- /** 编辑器的根:纵向排列的行。 */
47
+ // 编辑器的根:纵向排列的行。
50
48
  export const EditorRoot = styled("div")({
51
49
  display: "flex",
52
50
  flexDirection: "column",
@@ -57,7 +55,7 @@ export const EditorRoot = styled("div")({
57
55
  color: dsw.alias.label.primary,
58
56
  });
59
57
 
60
- /** 一行:行号 + 折进 + 行内容。悬停给一层浅底。 */
58
+ // 一行:行号 + 折进 + 行内容。悬停给一层浅底。
61
59
  export const LineRow = styled("div")({
62
60
  display: "flex",
63
61
  // 顶部对齐:行里出现输入框或多行值时,内容不跟着上下跳。
@@ -85,7 +83,7 @@ export const LineRow = styled("div")({
85
83
  "&:hover [data-role='actions']": { opacity: 1 },
86
84
  });
87
85
 
88
- /** 一行的内容区:**缩进只作用在这里**——行号列固定在左边,缩进不推它。 */
86
+ // 一行的内容区:**缩进只作用在这里**——行号列固定在左边,缩进不推它。
89
87
  export const LineBody = styled("div")({
90
88
  display: "flex",
91
89
  alignItems: "center",
@@ -95,7 +93,7 @@ export const LineBody = styled("div")({
95
93
  minHeight: ROW,
96
94
  });
97
95
 
98
- /** 行号列:右对齐、不可选、淡色。 */
96
+ // 行号列:右对齐、不可选、淡色。
99
97
  export const LineNumber = styled("span")({
100
98
  flex: "none",
101
99
  width: NUMBER,
@@ -106,7 +104,7 @@ export const LineNumber = styled("span")({
106
104
  cursor: "pointer",
107
105
  });
108
106
 
109
- /** 折叠 chevron(官方箭头图标,尺寸与别的行内图标同源)。 */
107
+ // 折叠 chevron(官方箭头图标,尺寸与别的行内图标同源)。
110
108
  export const LineFold = styled("button")({
111
109
  display: "inline-flex",
112
110
  alignItems: "center",
@@ -122,26 +120,26 @@ export const LineFold = styled("button")({
122
120
  "&:hover": { color: dsw.alias.label.secondary },
123
121
  });
124
122
 
125
- /** 叶子行的折叠占位(保持列对齐)。 */
123
+ // 叶子行的折叠占位(保持列对齐)。
126
124
  export const LineFoldSpacer = styled("span")({
127
125
  flex: "none",
128
126
  width: FOLD,
129
127
  });
130
128
 
131
- /** 键名(配置键原样;数组下标淡一些)。 */
129
+ // 键名(配置键原样;数组下标淡一些)。
132
130
  export const LineKey = styled("span")({
133
131
  flex: "none",
134
132
  color: dsw.alias.label.primary,
135
133
  "&[data-index='true']": { color: dsw.alias.label.caption },
136
134
  });
137
135
 
138
- /** 结构符与标点(`{` `}` `[` `]` `:`)。 */
136
+ // 结构符与标点(`{` `}` `[` `]` `:`)。
139
137
  export const LineToken = styled("span")({
140
138
  flex: "none",
141
139
  color: dsw.alias.label.caption,
142
140
  });
143
141
 
144
- /** 值(token):字符串 / 数字 / 布尔各有语法色。 */
142
+ // 值(token):字符串 / 数字 / 布尔各有语法色。
145
143
  export const LineValue = styled("span")({
146
144
  minWidth: 0,
147
145
  overflow: "hidden",
@@ -153,9 +151,7 @@ export const LineValue = styled("span")({
153
151
  "&[data-tone='empty']": { color: dsw.alias.label.caption },
154
152
  });
155
153
 
156
- /**
157
- * 值上的下拉触发:**它自己就是值的呈现**(不再是「值 + 一个下拉」两遍),点开才出菜单。
158
- */
154
+ // 值上的下拉触发:**它自己就是值的呈现**(不再是「值 + 一个下拉」两遍),点开才出菜单。
159
155
  export const ValueTrigger = styled("button")({
160
156
  display: "inline-flex",
161
157
  alignItems: "center",
@@ -172,11 +168,9 @@ export const ValueTrigger = styled("button")({
172
168
  "&:focus-visible": { outline: "none" },
173
169
  });
174
170
 
175
- /**
176
- * 行内输入的外壳:官方 `Input` 压到与值同一行高,并**吃掉剩下的宽度**。
177
- *
178
- * 行内编辑与容器闭合行的添加输入共用它——两处的输入框因此长得一模一样。
179
- */
171
+ // 行内输入的外壳:官方 `Input` 压到与值同一行高,并**吃掉剩下的宽度**。
172
+ //
173
+ // 行内编辑与容器闭合行的添加输入共用它——两处的输入框因此长得一模一样。
180
174
  export const CompactInput = styled("span")({
181
175
  display: "inline-flex",
182
176
  flex: "1 1 auto",
@@ -193,10 +187,8 @@ export const CompactInput = styled("span")({
193
187
  },
194
188
  });
195
189
 
196
- /**
197
- * 多行值的行内输入:官方没有多行原子,所以这里用同一套观感的字段壳(同边框、同圆角、同焦点色),
198
- * 高度跟着内容长、宽度同样撑满剩下的位置。
199
- */
190
+ // 多行值的行内输入:官方没有多行原子,所以这里用同一套观感的字段壳(同边框、同圆角、同焦点色),
191
+ // 高度跟着内容长、宽度同样撑满剩下的位置。
200
192
  export const CompactTextField = styled("span")({
201
193
  display: "inline-flex",
202
194
  flex: "1 1 auto",
@@ -226,7 +218,7 @@ export const CompactTextField = styled("span")({
226
218
  },
227
219
  });
228
220
 
229
- /** 行内注释(schema 说明与业务文案):比配置值小一档,占满这一行剩下的宽度,长了就截断(hover 看全文)。 */
221
+ // 行内注释(schema 说明与业务文案):比配置值小一档,占满这一行剩下的宽度,长了就截断(hover 看全文)。
230
222
  export const LineComment = styled("span")({
231
223
  flex: "1 1 auto",
232
224
  minWidth: 0,
@@ -237,7 +229,7 @@ export const LineComment = styled("span")({
237
229
  fontSize: "12px",
238
230
  });
239
231
 
240
- /** 行内的校验消息(与注释放同一档字号)。 */
232
+ // 行内的校验消息(与注释放同一档字号)。
241
233
  export const LineInvalid = styled("span")({
242
234
  flex: "none",
243
235
  color: dsw.alias.state.error.primary,
@@ -245,7 +237,7 @@ export const LineInvalid = styled("span")({
245
237
  fontSize: "12px",
246
238
  });
247
239
 
248
- /** 行尾的动作组:方形图标键(默认透明,悬停时显形)。 */
240
+ // 行尾的动作组:方形图标键(默认透明,悬停时显形)。
249
241
  export const LineActions = styled("span")({
250
242
  display: "inline-flex",
251
243
  alignItems: "center",
@@ -269,14 +261,10 @@ export const LineActions = styled("span")({
269
261
  },
270
262
  });
271
263
 
272
- /**
273
- * 悬停行时才显示这一行的动作按钮:显形规则写在行上(`[data-role='actions']`),组件只需要带这个属性。
274
- */
264
+ // 悬停行时才显示这一行的动作按钮:显形规则写在行上(`[data-role='actions']`),组件只需要带这个属性。
275
265
  export const HoverActions = styled(LineActions)({
276
- /**
277
- * 编辑态的两个按钮常显,而且**要有底色与描边**:行底色上只有淡色图标是看不见的,
278
- * 所以这里给实体按钮(theme 的描边 + 一层底),确认用成功档、取消用中性档。
279
- */
266
+ // 编辑态的两个按钮常显,而且**要有底色与描边**:行底色上只有淡色图标是看不见的,
267
+ // 所以这里给实体按钮(theme 的描边 + 一层底),确认用成功档、取消用中性档。
280
268
  "&[data-editing='true']": {
281
269
  opacity: 1,
282
270
  gap: GAP,
@@ -289,7 +277,7 @@ export const HoverActions = styled(LineActions)({
289
277
  },
290
278
  });
291
279
 
292
- /** 添加输入:**跟在容器的闭合括号同一行**(菜单锚在它上面),宽度吃掉剩下的位置。 */
280
+ // 添加输入:**跟在容器的闭合括号同一行**(菜单锚在它上面),宽度吃掉剩下的位置。
293
281
  export const AddWrap = styled("span")({
294
282
  position: "relative",
295
283
  display: "inline-flex",
@@ -301,7 +289,7 @@ export const AddWrap = styled("span")({
301
289
  "&[data-invalid='true'] input": { borderColor: dsw.alias.state.error.primary },
302
290
  });
303
291
 
304
- /** 一行说明(不可用 / 没有可生成项时用)。 */
292
+ // 一行说明(不可用 / 没有可生成项时用)。
305
293
  export const Hint = styled("p")({
306
294
  margin: 0,
307
295
  fontSize: "12px",
@@ -1,20 +1,14 @@
1
- /**
2
- * 测试用的 describe 读面替身:可推动命名空间清单变化,形状与上游 mirror 的读面一致。
3
- */
1
+ // 测试用的 describe 读面替身:可推动命名空间清单变化,形状与上游 mirror 的读面一致。
4
2
 
5
3
  import type { SettingsNamespaceView } from "@deepseek-ai/dsh-api-remotes/client";
6
4
  import type { SettingsDescribeFace } from "@deepseek-ai/dsh-client-ui-settings/client";
7
5
 
8
- /** 一个可以推动变更的 describe 读面。 */
6
+ // 一个可以推动变更的 describe 读面。
9
7
  export interface FakeDescribe extends SettingsDescribeFace {
10
- /** 换掉命名空间清单并通知订阅者。 */
8
+ // 换掉命名空间清单并通知订阅者。
11
9
  publish(namespaces: readonly SettingsNamespaceView[]): void;
12
10
  }
13
11
 
14
- /**
15
- * @param initial - 初始命名空间清单。
16
- * @returns 可读可推的 describe 替身。
17
- */
18
12
  export function fakeDescribe(initial: readonly SettingsNamespaceView[] = []): FakeDescribe {
19
13
  const listeners = new Set<() => void>();
20
14
  let namespaces = initial;
@@ -1,21 +1,19 @@
1
- /**
2
- * 测试用的共享配置表单:内存里模拟 host 的读写两层(快照 / 订阅 / 一次带 revision 栅栏的写)。
3
- *
4
- * 它让控制器与草稿模型的用例不碰远程面、不碰装配,只盯「草稿怎么变成写」与「状态怎么投影」。
5
- */
1
+ // 测试用的共享配置表单:内存里模拟 host 的读写两层(快照 / 订阅 / 一次带 revision 栅栏的写)。
2
+ //
3
+ // 它让控制器与草稿模型的用例不碰远程面、不碰装配,只盯「草稿怎么变成写」与「状态怎么投影」。
6
4
 
7
5
  import type { SettingsPathOpView } from "@deepseek-ai/dsh-api-remotes/client";
8
6
  import type { ConfigForm, ConfigFormSnapshot } from "@deepseek-ai/dsh-client-ui-settings/client";
9
7
 
10
- /** 命名空间段的值。 */
8
+ // 命名空间段的值。
11
9
  export type Section = Record<string, unknown>;
12
10
 
13
11
  export class FakeScope implements ConfigForm<Section> {
14
- /** 每次保存发出的 path op(按保存顺序)。 */
12
+ // 每次保存发出的 path op(按保存顺序)。
15
13
  readonly writes: (readonly SettingsPathOpView[])[] = [];
16
- /** 每次保存带的 revision 栅栏。 */
14
+ // 每次保存带的 revision 栅栏。
17
15
  readonly fences: (number | undefined)[] = [];
18
- /** 让 host 拒绝下一次写。 */
16
+ // 让 host 拒绝下一次写。
19
17
  accepted = true;
20
18
 
21
19
  #snapshot: ConfigFormSnapshot<Section>;
@@ -85,7 +83,7 @@ export class FakeScope implements ConfigForm<Section> {
85
83
  return Promise.resolve(true);
86
84
  }
87
85
 
88
- /** 从外部改一次读数(模拟另一个界面或 host 的改动)。 */
86
+ // 从外部改一次读数(模拟另一个界面或 host 的改动)。
89
87
  publish(overrides: Partial<ConfigFormSnapshot<Section>> = {}): void {
90
88
  this.#snapshot = { ...this.#snapshot, ...overrides };
91
89
  this.#publish();
@@ -1,9 +1,7 @@
1
- /**
2
- * 行内值的呈现与编辑文本规则,以及字段槽注册方复用的默认值组件。
3
- *
4
- * 值本身仍走草稿:这个文件只决定"怎么显示"(语法色 token、下拉触发的呈现)与"怎么编辑"(一行输入框里的文本
5
- * 形状——字符串把换行写成 `\n`,与一行的输入框相配)。
6
- */
1
+ // 行内值的呈现与编辑文本规则,以及字段槽注册方复用的默认值组件。
2
+ //
3
+ // 值本身仍走草稿:这个文件只决定"怎么显示"(语法色 token、下拉触发的呈现)与"怎么编辑"(一行输入框里的文本
4
+ // 形状——字符串把换行写成 `\n`,与一行的输入框相配)。
7
5
 
8
6
  import { useState, type ReactNode } from "react";
9
7
  import { IconChevronDownOutlineRegular, Menu } from "@deepseek-ai/dsh-client-ui-primitives";
@@ -11,7 +9,7 @@ import type { SelectOption } from "./hints.ts";
11
9
  import type { SchemaFieldOwnerProps } from "./slot-contract.ts";
12
10
  import { ICON_SIZE, LineValue, ValueTrigger } from "./styles.ts";
13
11
 
14
- /** 编辑态文本:字符串就是原文(换行照旧),其余按 JSON 写。 */
12
+ // 编辑态文本:字符串就是原文(换行照旧),其余按 JSON 写。
15
13
  export function valueText(value: unknown): string {
16
14
  if (value === undefined || value === null) return "";
17
15
  if (typeof value === "string") return value;
@@ -19,12 +17,12 @@ export function valueText(value: unknown): string {
19
17
  return JSON.stringify(value);
20
18
  }
21
19
 
22
- /** 这个值要在多行输入里编辑吗(字符串带换行)。 */
20
+ // 这个值要在多行输入里编辑吗(字符串带换行)。
23
21
  export function isMultiline(value: unknown): boolean {
24
22
  return typeof value === "string" && /[\r\n]/.test(value);
25
23
  }
26
24
 
27
- /** 显示态文本:字符串带引号;多行与特殊字符都按 JSON 的写法收在一行里。 */
25
+ // 显示态文本:字符串带引号;多行与特殊字符都按 JSON 的写法收在一行里。
28
26
  export function tokenText(value: unknown): string {
29
27
  if (value === undefined || value === null) return "null";
30
28
  if (typeof value === "string") return JSON.stringify(value);
@@ -32,7 +30,7 @@ export function tokenText(value: unknown): string {
32
30
  return JSON.stringify(value);
33
31
  }
34
32
 
35
- /** 值的语法色分组。 */
33
+ // 值的语法色分组。
36
34
  export function tokenTone(value: unknown): "string" | "number" | "boolean" | "empty" {
37
35
  if (value === undefined || value === null) return "empty";
38
36
  if (typeof value === "number") return "number";
@@ -40,11 +38,7 @@ export function tokenTone(value: unknown): "string" | "number" | "boolean" | "em
40
38
  return "string";
41
39
  }
42
40
 
43
- /**
44
- * 行内值的默认呈现(显示态 token)。
45
- * @param props.owner - 字段上下文(值、是否 secret、只读原因)。
46
- * @returns 一行里的值节点。
47
- */
41
+ // 行内值的默认呈现(显示态 token)。
48
42
  export function InlineValue({ owner }: { owner: SchemaFieldOwnerProps }): ReactNode {
49
43
  if (owner.options !== undefined) return <OptionSelect owner={owner} options={owner.options} />;
50
44
  if (owner.node.meta.secret) {
@@ -66,11 +60,8 @@ export function InlineValue({ owner }: { owner: SchemaFieldOwnerProps }): ReactN
66
60
  );
67
61
  }
68
62
 
69
- /**
70
- * 选择器:**值本身**就是下拉的触发(不再另画一个 `select` 与它并列),点开是官方菜单。
71
- *
72
- * 当前值不在候选里时(例如目录里已经没有那个服务商)照原样显示,选中才换成新值。
73
- */
63
+ // 选择器:**值本身**就是下拉的触发(不再另画一个 `select` 与它并列),点开是官方菜单。
64
+ // 当前值不在候选里时(例如目录里已经没有那个服务商)照原样显示,选中才换成新值。
74
65
  function OptionSelect({
75
66
  owner,
76
67
  options,
@@ -128,7 +119,7 @@ function OptionSelect({
128
119
  );
129
120
  }
130
121
 
131
- /** 值是否就是这一项(菜单的高亮判据)。 */
122
+ // 值是否就是这一项(菜单的高亮判据)。
132
123
  function sameValue(left: unknown, right: unknown): boolean {
133
124
  if (Object.is(left, right)) return true;
134
125
  if (typeof left !== "object" || typeof right !== "object" || left === null || right === null)
@@ -136,11 +127,8 @@ function sameValue(left: unknown, right: unknown): boolean {
136
127
  return JSON.stringify(left) === JSON.stringify(right);
137
128
  }
138
129
 
139
- /**
140
- * 字段槽注册方复用的默认值:只改文案时拿它包一层即可(`owner` 上覆盖 `label`/`hint`)。
141
- *
142
- * 注意行式视图里键名来自配置键本身、注释来自 `hint`,所以 `label` 只在业务自写控件时才需要。
143
- */
130
+ // 字段槽注册方复用的默认值:只改文案时拿它包一层即可(`owner` 上覆盖 `label`/`hint`)。
131
+ // 行式视图里键名来自配置键本身、注释来自 `hint`,所以 `label` 只在业务自写控件时才需要。
144
132
  export function SchemaFieldDefault({ owner }: { owner: SchemaFieldOwnerProps }): ReactNode {
145
133
  return <InlineValue owner={owner} />;
146
134
  }
@@ -12,39 +12,35 @@ const Footer = styled("div")(styles.footer);
12
12
  const Failed = styled("p")(styles.failed);
13
13
  const Save = styled("button")(styles.save);
14
14
 
15
- /** The copy the form frame renders, from the owning plugin's dictionary. */
15
+ // The copy the form frame renders, from the owning plugin's dictionary.
16
16
  export interface SettingsFormLabels {
17
- /** Shown in place of the controls while the namespace is not served. */
17
+ // Shown in place of the controls while the namespace is not served.
18
18
  unavailable: string;
19
- /** Shown over the controls while the document is read-only. */
19
+ // Shown over the controls while the document is read-only.
20
20
  readOnly: string;
21
- /** Shown beside the save after a save the Host did not accept. */
21
+ // Shown beside the save after a save the Host did not accept.
22
22
  saveFailed: string;
23
- /** The save control. */
23
+ // The save control.
24
24
  save: string;
25
- /** The save control while a save is crossing the wire. */
25
+ // The save control while a save is crossing the wire.
26
26
  saving: string;
27
27
  }
28
28
 
29
- /** Form chrome shared by every settings page. */
29
+ // Form chrome shared by every settings page.
30
30
  export interface SettingsFormProps {
31
- /** The frame's copy. */
31
+ // The frame's copy.
32
32
  labels: SettingsFormLabels;
33
- /** The form state: availability, writability, and what a save would do. */
33
+ // The form state: availability, writability, and what a save would do.
34
34
  state: SettingsFormShell;
35
- /** Write every staged edit. */
35
+ // Write every staged edit.
36
36
  onSave: () => void;
37
- /** Drop every staged edit; the form calls it when it leaves the page. */
37
+ // Drop every staged edit; the form calls it when it leaves the page.
38
38
  onDiscard: () => void;
39
- /** The plugin's controls. */
39
+ // The plugin's controls.
40
40
  children: ReactNode;
41
41
  }
42
42
 
43
- /**
44
- * Render one plugin's settings form.
45
- * @param props - the form's copy and state, its controls, and the save and discard actions.
46
- * @returns the form, or the unavailable line while the namespace is not served.
47
- */
43
+ // Render one plugin's settings form.
48
44
  export function SettingsForm(props: SettingsFormProps) {
49
45
  const { state, labels } = props;
50
46
  const discard = useRef(props.onDiscard);