@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.
- package/README.md +18 -266
- package/dist/client.cjs +1 -611
- package/dist/client.d.cts +37 -684
- package/dist/client.d.mts +37 -684
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{reference-Dxy9uQYz.mjs → reference-Cjcn21uW.mjs} +0 -1
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +2 -2
- package/src/client/index.ts +7 -9
- package/src/client/schema-form/Editor.tsx +25 -38
- package/src/client/schema-form/SchemaForm.tsx +6 -12
- package/src/client/schema-form/controller.ts +65 -123
- package/src/client/schema-form/draft.ts +55 -138
- package/src/client/schema-form/fields.tsx +5 -14
- package/src/client/schema-form/hints.ts +31 -131
- package/src/client/schema-form/index.ts +15 -20
- package/src/client/schema-form/labels.ts +7 -9
- package/src/client/schema-form/lines.ts +36 -60
- package/src/client/schema-form/locales.ts +6 -8
- package/src/client/schema-form/rows.ts +22 -41
- package/src/client/schema-form/schema-node.ts +87 -150
- package/src/client/schema-form/slot-contract.ts +55 -76
- package/src/client/schema-form/styles.ts +39 -51
- package/src/client/schema-form/testing/fake-describe.ts +3 -9
- package/src/client/schema-form/testing/fake-scope.ts +8 -10
- package/src/client/schema-form/value.tsx +14 -26
- package/src/client/settings-form/SettingsForm.tsx +13 -17
- package/src/client/settings-form/fields.tsx +25 -35
- package/src/client/settings-form/form-model.ts +63 -145
- package/src/client/styling/styled.tsx +3 -5
- package/src/client/theme.generated.ts +6 -7
- package/src/reference-markdown.tsx +1 -1
- package/src/reference.ts +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as parseReferenceToken, n as ReferenceSpan, r as findReferences, t as Reference } from "./reference-BX54bj6_.cjs";
|
|
2
2
|
//#region src/index.d.ts
|
|
3
|
-
declare function apply(): void;
|
|
3
|
+
export declare function apply(): void;
|
|
4
4
|
//#endregion
|
|
5
|
-
export { type Reference, type ReferenceSpan,
|
|
5
|
+
export { type Reference, type ReferenceSpan, findReferences, parseReferenceToken };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as parseReferenceToken, n as ReferenceSpan, r as findReferences, t as Reference } from "./reference-BX54bj6_.mjs";
|
|
2
2
|
//#region src/index.d.ts
|
|
3
|
-
declare function apply(): void;
|
|
3
|
+
export declare function apply(): void;
|
|
4
4
|
//#endregion
|
|
5
|
-
export { type Reference, type ReferenceSpan,
|
|
5
|
+
export { type Reference, type ReferenceSpan, findReferences, parseReferenceToken };
|
package/dist/index.mjs
CHANGED
|
@@ -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
|
|
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
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morlay/dsh-client-ui-primitives",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "css-in-js styling kit (styled / Token / Styling)
|
|
3
|
+
"version": "0.0.6",
|
|
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",
|
package/src/client/index.ts
CHANGED
|
@@ -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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
654
|
-
* 字典列的是业务注册的候选键(还能自己敲键名)。粘一整段 `{…}` / `[…]` 则整层覆盖。
|
|
655
|
-
*/
|
|
639
|
+
// 容器闭合行后面的添加入口。
|
|
640
|
+
//
|
|
641
|
+
// 数组是「追加一个空项」;对象/字典给出**候选**——对象列的是 schema 声明了、值里还没有的字段(带说明),
|
|
642
|
+
// 字典列的是业务注册的候选键(还能自己敲键名)。粘一整段 `{…}` / `[…]` 则整层覆盖。
|
|
656
643
|
function AddLine({
|
|
657
644
|
line,
|
|
658
645
|
state,
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
+
// 字段在状态表里的键(具体路径的 JSON)。
|
|
45
43
|
export function fieldKey(path: readonly string[]): string {
|
|
46
44
|
return JSON.stringify(path);
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
95
|
+
// 控制器要的外部面(`apply` 里从 cordis ctx 组装;测试直接给替身)。
|
|
122
96
|
export interface SchemaFormDeps {
|
|
123
|
-
|
|
97
|
+
// 本命名空间的共享配置表单(读 + 带栅栏的写)。
|
|
124
98
|
form: DraftScope;
|
|
125
|
-
|
|
99
|
+
// describe mirror 的读面:本行的 schema、autoGenerate 与 secret 槽。
|
|
126
100
|
describe: SettingsDescribeFace;
|
|
127
|
-
|
|
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
|
-
|
|
135
|
-
* 用户填了才算写。
|
|
136
|
-
*/
|
|
107
|
+
// dict 字段的候选键(业务注册,见 `hints.ts`):候选里有、值里没有的键会补成一行「未配置」,
|
|
108
|
+
// 用户填了才算写。
|
|
137
109
|
hints?: HintSources | undefined;
|
|
138
110
|
}
|
|
139
111
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
* 页面上不占行——它们出现在容器的闭合行那个「添加属性 / 添加键」输入框的候选里,选中才加。
|
|
144
|
-
*/
|
|
112
|
+
// 这一层「能加进来」的一项:schema 里声明、值里还没有的字段,或业务注册的候选键。
|
|
113
|
+
//
|
|
114
|
+
// 页面上不占行——它们出现在容器的闭合行那个「添加属性 / 添加键」输入框的候选里,选中才加。
|
|
145
115
|
export interface AddableProperty {
|
|
146
|
-
|
|
116
|
+
// 加进来的键名。
|
|
147
117
|
key: string;
|
|
148
|
-
|
|
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
|
-
|
|
154
|
+
// 这一行的 schema 是否可渲染(否则页面只显示一行说明)。
|
|
194
155
|
configured: boolean;
|
|
195
|
-
|
|
156
|
+
// 字段树;未 configured 时是空对象树。
|
|
196
157
|
root: FieldNode;
|
|
197
|
-
|
|
158
|
+
// 按草稿后的值展开的字段列表(渲染顺序):追加的数组项与新增的字典键立刻出现。
|
|
198
159
|
walked: readonly WalkedField[];
|
|
199
|
-
|
|
160
|
+
// 每个具体路径的草稿状态,键见 `fieldKey`。
|
|
200
161
|
fields: ReadonlyMap<string, DraftFieldState>;
|
|
201
|
-
|
|
162
|
+
// secret 槽是否已配置,键见 `fieldKey`。
|
|
202
163
|
secrets: ReadonlyMap<string, boolean>;
|
|
203
|
-
|
|
164
|
+
// 每个字段的文案(提示面注册的),键见 `fieldKey`;渲染层把它画成注释行。
|
|
204
165
|
texts: ReadonlyMap<string, FieldText>;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
* 候选依赖的兄弟字段一改,这里就跟着变(投影时按草稿后的值求一次)。
|
|
208
|
-
*/
|
|
166
|
+
// 每个字段的可选项,键见 `fieldKey`:字面量集合的 union 自带候选,其余来自提示面。
|
|
167
|
+
// 候选依赖的兄弟字段一改,这里就跟着变(投影时按草稿后的值求一次)。
|
|
209
168
|
options: ReadonlyMap<string, readonly SelectOption[]>;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
* 因此只在还有声明字段没配时出现。
|
|
213
|
-
*/
|
|
169
|
+
// 每个容器能加进来的项,键见 `fieldKey`(容器自己的路径)。没有条目就是加不了东西——对象层的输入框
|
|
170
|
+
// 因此只在还有声明字段没配时出现。
|
|
214
171
|
addable: ReadonlyMap<string, readonly AddableProperty[]>;
|
|
215
|
-
|
|
172
|
+
// 保存被 schema 挡下时的消息与它的位置。
|
|
216
173
|
violation: ValidationFailure | undefined;
|
|
217
|
-
|
|
174
|
+
// 每行的校验消息(键见 `fieldKey`):行内报错,与底部的整段提示同源。
|
|
218
175
|
invalidAt: ReadonlyMap<string, string>;
|
|
219
176
|
}
|
|
220
177
|
|
|
221
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|