@morlay/dsh-client-ui-primitives 0.0.4 → 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.
- package/README.md +18 -266
- package/dist/client.cjs +1 -611
- package/dist/client.d.cts +11 -658
- package/dist/client.d.mts +11 -658
- 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
|
@@ -21,7 +21,7 @@ const Input = styled("input")(styles.input);
|
|
|
21
21
|
const Invalid = styled("p")(styles.invalid);
|
|
22
22
|
const Hint = styled("p")(styles.hint);
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
// 上游 icons 里的 IconInfoOutlineRegular(fill-only 几何,整份图标表不搬)。
|
|
25
25
|
function InfoGlyph({ size }: { size: number }) {
|
|
26
26
|
return (
|
|
27
27
|
<svg
|
|
@@ -42,50 +42,45 @@ function InfoGlyph({ size }: { size: number }) {
|
|
|
42
42
|
);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
// What every settings field control needs regardless of its value type.
|
|
46
46
|
export interface SettingsFieldProps {
|
|
47
|
-
|
|
47
|
+
// Stable id associating the label with its control.
|
|
48
48
|
id: string;
|
|
49
|
-
|
|
49
|
+
// Visible label.
|
|
50
50
|
label: string;
|
|
51
|
-
|
|
51
|
+
// One-line explanation rendered under the control.
|
|
52
52
|
hint: string;
|
|
53
|
-
|
|
53
|
+
// Draft text this control renders.
|
|
54
54
|
text: string;
|
|
55
|
-
|
|
55
|
+
// True when saving would leave a user-layer entry for this field.
|
|
56
56
|
overridden: boolean;
|
|
57
|
-
|
|
57
|
+
// True when the draft is not a value this field accepts.
|
|
58
58
|
invalid: boolean;
|
|
59
|
-
|
|
59
|
+
// Copy for the overridden badge.
|
|
60
60
|
overriddenLabel: string;
|
|
61
|
-
|
|
61
|
+
// Copy for the reset control.
|
|
62
62
|
resetLabel: string;
|
|
63
|
-
|
|
63
|
+
// Copy shown in place of the hint while the draft is invalid.
|
|
64
64
|
invalidLabel: string;
|
|
65
|
-
|
|
65
|
+
// Disables every control (read-only document, or an unavailable namespace).
|
|
66
66
|
disabled: boolean;
|
|
67
|
-
|
|
67
|
+
// Stage draft text.
|
|
68
68
|
onEdit: (text: string) => void;
|
|
69
|
-
|
|
69
|
+
// Stage a clear so the field re-inherits the composition layer.
|
|
70
70
|
onReset: () => void;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
* accepts is decided by its spec, so the control never silently rewrites what
|
|
76
|
-
* the user typed.
|
|
77
|
-
* @param props - the field's copy, its staged text, and the edit actions.
|
|
78
|
-
* @returns the labelled control.
|
|
79
|
-
*/
|
|
73
|
+
// A staged value field. `numeric` only hints the keypad: which drafts a field accepts is decided by its spec, so the
|
|
74
|
+
// control never silently rewrites what the user typed.
|
|
80
75
|
export function SettingsValueField(
|
|
81
76
|
props: Omit<SettingsFieldProps, "hint"> & {
|
|
82
|
-
|
|
77
|
+
// Optional explanation shown below the input.
|
|
83
78
|
hint?: string;
|
|
84
|
-
|
|
79
|
+
// Rules disclosed by the information button beside the label.
|
|
85
80
|
help?: { label: string; content: ReactNode };
|
|
86
|
-
|
|
81
|
+
// Hints a numeric keypad without narrowing what the control accepts.
|
|
87
82
|
numeric?: boolean;
|
|
88
|
-
|
|
83
|
+
// Placeholder shown while the draft is empty.
|
|
89
84
|
placeholder?: string;
|
|
90
85
|
},
|
|
91
86
|
) {
|
|
@@ -153,19 +148,14 @@ export function SettingsValueField(
|
|
|
153
148
|
);
|
|
154
149
|
}
|
|
155
150
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
* draft writes nothing, which keeps the stored key rather than clearing it.
|
|
160
|
-
* 控件要求浏览器不要把已存的登录密码自动填进来(上游 11c0511271)。
|
|
161
|
-
* @param props - the field's copy, its staged text, and the configured state.
|
|
162
|
-
* @returns the labelled control.
|
|
163
|
-
*/
|
|
151
|
+
// A write-only credential control. The value never rides a response, so the control reports only whether one is
|
|
152
|
+
// configured and starts blank; a blank draft writes nothing, which keeps the stored key rather than clearing it.
|
|
153
|
+
// 控件要求浏览器不要把已存的登录密码自动填进来(上游 11c0511271)。
|
|
164
154
|
export function SettingsSecretField(
|
|
165
155
|
props: Pick<SettingsFieldProps, "id" | "label" | "hint" | "text" | "disabled" | "onEdit"> & {
|
|
166
|
-
|
|
156
|
+
// Whether the Host reports a configured credential for this reference.
|
|
167
157
|
configured: boolean;
|
|
168
|
-
|
|
158
|
+
// Copy describing the configured state.
|
|
169
159
|
stateLabel: string;
|
|
170
160
|
},
|
|
171
161
|
) {
|
|
@@ -1,165 +1,123 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* The staged form model behind a plugin's settings page.
|
|
7
|
-
*
|
|
8
|
-
* A card stages what the user types and writes it only when they save. Each
|
|
9
|
-
* settings write is a durable, revision-fenced document mutation, so a control
|
|
10
|
-
* that committed as it settled turned one edit into a write the user never
|
|
11
|
-
* asked for and could not preview; staged text makes what is on screen exactly
|
|
12
|
-
* what a save would store.
|
|
13
|
-
*
|
|
14
|
-
* A field shows its effective value — the user layer over the composition
|
|
15
|
-
* layer over the schema default — and whether the user layer carries it. That
|
|
16
|
-
* presence, not a value comparison, is what marks a field overridden: an
|
|
17
|
-
* override equal to the composition default is still an override.
|
|
18
|
-
*/
|
|
1
|
+
// 与 vendor/deepseek-harness/packages/client/ui-primitives/src/settings-form/form-model.ts 逐行对应;
|
|
2
|
+
// 改这里之前先看上游是否也改了同一处(上游只读)。
|
|
3
|
+
|
|
4
|
+
// The staged form model behind a plugin's settings page: a card stages what the user types and writes it only on
|
|
5
|
+
// save, and a field is marked overridden by its user-layer presence rather than by a value comparison.
|
|
19
6
|
|
|
20
7
|
import { createSnapshotStore, type SnapshotStore } from "@deepseek-ai/dsh-client-store";
|
|
21
8
|
|
|
22
|
-
|
|
9
|
+
// What the model reads of one Host entry's form.
|
|
23
10
|
export interface SettingsFormScopeSnapshot<T> {
|
|
24
|
-
|
|
11
|
+
// `ready` while the Host serves the entry to this client; the form renders nothing otherwise.
|
|
25
12
|
status: "loading" | "ready" | "unavailable";
|
|
26
|
-
|
|
13
|
+
// Last accepted schema-resolved section; undefined before the first acceptance.
|
|
27
14
|
value: T | undefined;
|
|
28
|
-
|
|
15
|
+
// Composition layer the value resolves over: what a field reverts to once cleared.
|
|
29
16
|
base: unknown;
|
|
30
|
-
|
|
17
|
+
// Raw user layer as stored; a field's PRESENCE here is what marks it overridden.
|
|
31
18
|
user: unknown;
|
|
32
|
-
|
|
19
|
+
// Whether the Host document accepts writes.
|
|
33
20
|
writable: boolean;
|
|
34
|
-
|
|
21
|
+
// Revision the snapshot was read at; a save fences its mutation with the revision its drafts started from.
|
|
35
22
|
revision: number | undefined;
|
|
36
23
|
}
|
|
37
24
|
|
|
38
|
-
|
|
25
|
+
// One path edit a save sends, as the shared configuration form's `mutate` accepts it.
|
|
39
26
|
export type SettingsFormPathOp =
|
|
40
27
|
| { op: "set"; path: readonly string[]; value: unknown }
|
|
41
28
|
| { op: "unset"; path: readonly string[] };
|
|
42
29
|
|
|
43
|
-
|
|
30
|
+
// The entry form the model stages over: the reads and the atomic write of the form `ui-settings` shares per Host entry.
|
|
44
31
|
export interface SettingsFormScope<T> {
|
|
45
|
-
/** @returns the current sync snapshot. */
|
|
46
32
|
getSnapshot(): SettingsFormScopeSnapshot<T>;
|
|
47
|
-
|
|
48
|
-
* Observe snapshot replacements.
|
|
49
|
-
* @param listener - invoked after each snapshot change.
|
|
50
|
-
* @returns the disposer removing this listener.
|
|
51
|
-
*/
|
|
33
|
+
// Observe snapshot replacements.
|
|
52
34
|
subscribe(listener: () => void): () => void;
|
|
53
|
-
|
|
54
|
-
* Apply ordered field edits in one revision-fenced write.
|
|
55
|
-
* @param ops - the edits, in staging order.
|
|
56
|
-
* @param expectedRevision - the revision the drafts were staged against, when known.
|
|
57
|
-
* @returns true for Host acceptance, false for refusal, after any recovery read.
|
|
58
|
-
*/
|
|
35
|
+
// Apply ordered field edits in one revision-fenced write; resolves after any recovery read.
|
|
59
36
|
mutate(ops: readonly SettingsFormPathOp[], expectedRevision?: number): Promise<boolean>;
|
|
60
37
|
}
|
|
61
38
|
|
|
62
|
-
|
|
39
|
+
// The write one field's staged text performs when the card is saved.
|
|
63
40
|
export type SettingsFieldWrite = { kind: "set"; value: unknown } | { kind: "clear" };
|
|
64
41
|
|
|
65
|
-
|
|
42
|
+
// How one section field converts between its stored value and its draft text.
|
|
66
43
|
export interface SettingsFieldSpec {
|
|
67
|
-
|
|
44
|
+
// Field name inside the namespace section.
|
|
68
45
|
field: string;
|
|
69
|
-
|
|
46
|
+
// Render a stored value as draft text; the empty string when the section carries none.
|
|
70
47
|
format: (value: unknown) => string;
|
|
71
|
-
|
|
72
|
-
* The write this draft text stages, or undefined when the text is not a
|
|
73
|
-
* value this field accepts — which blocks the save rather than discarding it.
|
|
74
|
-
*/
|
|
48
|
+
// The write this draft text stages, or undefined when the text is not a value this field accepts (blocks the save).
|
|
75
49
|
parse: (text: string) => SettingsFieldWrite | undefined;
|
|
76
50
|
}
|
|
77
51
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
* literal never rides a response, so its draft has nothing to seed from: it is
|
|
81
|
-
* blank until typed, and a blank draft writes nothing.
|
|
82
|
-
*/
|
|
52
|
+
// A control whose value is written outside the settings section: a credential literal never rides a response, so its
|
|
53
|
+
// draft is blank until typed and a blank draft writes nothing.
|
|
83
54
|
export interface SettingsSecretSpec {
|
|
84
|
-
|
|
55
|
+
// Field name addressing this control inside the card's form.
|
|
85
56
|
field: string;
|
|
86
|
-
|
|
57
|
+
// Write the staged text; resolves to whether the Host accepted it.
|
|
87
58
|
write: (text: string) => Promise<boolean>;
|
|
88
59
|
}
|
|
89
60
|
|
|
90
|
-
|
|
61
|
+
// One field as a card's control renders it.
|
|
91
62
|
export interface SettingsFieldState {
|
|
92
|
-
|
|
63
|
+
// Draft text the control renders.
|
|
93
64
|
text: string;
|
|
94
|
-
|
|
95
|
-
* Whether saving would leave a user-layer entry for this field. A staged
|
|
96
|
-
* edit answers for itself, so the badge previews the save rather than
|
|
97
|
-
* reporting a state the pending edit already contradicts.
|
|
98
|
-
*/
|
|
65
|
+
// Whether saving would leave a user-layer entry for this field; a staged edit answers for itself.
|
|
99
66
|
overridden: boolean;
|
|
100
|
-
|
|
67
|
+
// Whether the draft is not a value this field accepts, which blocks saving.
|
|
101
68
|
invalid: boolean;
|
|
102
69
|
}
|
|
103
70
|
|
|
104
|
-
|
|
71
|
+
// Form state every plugin card shares.
|
|
105
72
|
export interface SettingsFormShell {
|
|
106
|
-
|
|
73
|
+
// False while the namespace is not served to this client; the card renders nothing.
|
|
107
74
|
available: boolean;
|
|
108
|
-
|
|
75
|
+
// Whether the Host document accepts writes.
|
|
109
76
|
writable: boolean;
|
|
110
|
-
|
|
77
|
+
// Whether the form holds edits that a save would write.
|
|
111
78
|
dirty: boolean;
|
|
112
|
-
|
|
79
|
+
// Whether any staged draft is invalid, which blocks the save.
|
|
113
80
|
invalid: boolean;
|
|
114
|
-
|
|
81
|
+
// Whether a save is crossing the wire.
|
|
115
82
|
saving: boolean;
|
|
116
|
-
|
|
83
|
+
// Whether the last save did not land as staged; cleared by the next edit or save.
|
|
117
84
|
failed: boolean;
|
|
118
85
|
}
|
|
119
86
|
|
|
120
|
-
|
|
87
|
+
// The write actions every plugin card's slot entry injects.
|
|
121
88
|
export interface SettingsFormActions {
|
|
122
|
-
|
|
89
|
+
// Stage draft text for one field.
|
|
123
90
|
edit: (field: string, text: string) => void;
|
|
124
|
-
|
|
91
|
+
// Stage a clear, so saving lets the field re-inherit the composition layer.
|
|
125
92
|
resetField: (field: string) => void;
|
|
126
|
-
|
|
93
|
+
// Write every staged edit, then re-seed from what the Host accepted.
|
|
127
94
|
save: () => void;
|
|
128
|
-
|
|
95
|
+
// Drop every staged edit.
|
|
129
96
|
discard: () => void;
|
|
130
97
|
}
|
|
131
98
|
|
|
132
|
-
|
|
99
|
+
// One field's staged edit.
|
|
133
100
|
interface StagedEdit {
|
|
134
|
-
|
|
101
|
+
// Draft text the control renders.
|
|
135
102
|
text: string;
|
|
136
|
-
|
|
103
|
+
// True when this edit clears the field whatever text it shows.
|
|
137
104
|
clear: boolean;
|
|
138
105
|
}
|
|
139
106
|
|
|
140
|
-
|
|
107
|
+
// One staged edit resolved into the write a save performs.
|
|
141
108
|
interface PlannedWrite {
|
|
142
|
-
|
|
109
|
+
// Field this entry writes.
|
|
143
110
|
field: string;
|
|
144
|
-
|
|
145
|
-
* Perform the write and report whether the Host holds the staged value
|
|
146
|
-
* afterwards; undefined when the draft is not a value the field accepts.
|
|
147
|
-
*/
|
|
111
|
+
// Perform the write and report whether the Host holds the staged value afterwards.
|
|
148
112
|
run?: () => Promise<boolean>;
|
|
149
113
|
op?: SettingsFormPathOp;
|
|
150
114
|
}
|
|
151
115
|
|
|
152
|
-
|
|
153
|
-
* A whole-number field. An empty draft clears the field; any other draft that
|
|
154
|
-
* is not a finite number blocks the save.
|
|
155
|
-
* @param field - field name inside the namespace section.
|
|
156
|
-
* @returns the field's conversion spec.
|
|
157
|
-
*/
|
|
116
|
+
// A whole-number field: an empty draft clears the field, any other draft that is not a finite number blocks the save.
|
|
158
117
|
export function settingsNumberField(field: string): SettingsFieldSpec {
|
|
159
118
|
return {
|
|
160
119
|
field,
|
|
161
|
-
// A section that carries no number for this field renders empty rather
|
|
162
|
-
// than as a value nobody chose.
|
|
120
|
+
// A section that carries no number for this field renders empty rather than as a value nobody chose.
|
|
163
121
|
format: (value) => (typeof value === "number" ? String(value) : ""),
|
|
164
122
|
parse: (text) => {
|
|
165
123
|
const trimmed = text.trim();
|
|
@@ -170,12 +128,7 @@ export function settingsNumberField(field: string): SettingsFieldSpec {
|
|
|
170
128
|
};
|
|
171
129
|
}
|
|
172
130
|
|
|
173
|
-
|
|
174
|
-
* A free-text field. An empty draft clears the field, so emptying the control
|
|
175
|
-
* and saving is the same gesture as resetting it.
|
|
176
|
-
* @param field - field name inside the namespace section.
|
|
177
|
-
* @returns the field's conversion spec.
|
|
178
|
-
*/
|
|
131
|
+
// A free-text field: an empty draft clears the field, so emptying the control and saving is the same as resetting it.
|
|
179
132
|
export function settingsTextField(field: string): SettingsFieldSpec {
|
|
180
133
|
return {
|
|
181
134
|
field,
|
|
@@ -187,13 +140,8 @@ export function settingsTextField(field: string): SettingsFieldSpec {
|
|
|
187
140
|
};
|
|
188
141
|
}
|
|
189
142
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
*
|
|
193
|
-
* The form publishes through a snapshot store because slot components read
|
|
194
|
-
* through a snapshot selector, while both the scope and the local drafts
|
|
195
|
-
* change underneath; every projection is rebuilt from the two together.
|
|
196
|
-
*/
|
|
143
|
+
// Stages one card's edits over one settings namespace and writes them on save. The form publishes through a snapshot
|
|
144
|
+
// store because slot components read through a snapshot selector while both the scope and the drafts change underneath.
|
|
197
145
|
export class SettingsFormModel<T> {
|
|
198
146
|
private readonly specs: Map<string, SettingsFieldSpec>;
|
|
199
147
|
private readonly secretSpecs: Map<string, SettingsSecretSpec>;
|
|
@@ -204,11 +152,6 @@ export class SettingsFormModel<T> {
|
|
|
204
152
|
private saving = false;
|
|
205
153
|
private failed = false;
|
|
206
154
|
|
|
207
|
-
/**
|
|
208
|
-
* @param scope - the shared configuration form for this card's namespace.
|
|
209
|
-
* @param specs - the section fields this card edits.
|
|
210
|
-
* @param secrets - the card's write-only controls, written outside the section.
|
|
211
|
-
*/
|
|
212
155
|
constructor(
|
|
213
156
|
private readonly scope: SettingsFormScope<T>,
|
|
214
157
|
specs: SettingsFieldSpec[],
|
|
@@ -221,11 +164,7 @@ export class SettingsFormModel<T> {
|
|
|
221
164
|
});
|
|
222
165
|
}
|
|
223
166
|
|
|
224
|
-
|
|
225
|
-
* Publish a projection of this form, rebuilt whenever the scope or a draft changes.
|
|
226
|
-
* @param project - build the card's state from the form's current reads.
|
|
227
|
-
* @returns the store the card's component reads through its bound selector.
|
|
228
|
-
*/
|
|
167
|
+
// Publish a projection of this form, rebuilt whenever the scope or a draft changes.
|
|
229
168
|
bind<S>(project: () => S): SnapshotStore<S> {
|
|
230
169
|
const store = createSnapshotStore(project());
|
|
231
170
|
this.listeners.add(() => {
|
|
@@ -234,10 +173,7 @@ export class SettingsFormModel<T> {
|
|
|
234
173
|
return store;
|
|
235
174
|
}
|
|
236
175
|
|
|
237
|
-
|
|
238
|
-
* Read the card-level state: what the Host serves, and what a save would do.
|
|
239
|
-
* @returns the form state every card shares.
|
|
240
|
-
*/
|
|
176
|
+
// Read the card-level state: what the Host serves, and what a save would do.
|
|
241
177
|
shell(): SettingsFormShell {
|
|
242
178
|
const snapshot = this.scope.getSnapshot();
|
|
243
179
|
const plan = this.plan();
|
|
@@ -251,11 +187,7 @@ export class SettingsFormModel<T> {
|
|
|
251
187
|
};
|
|
252
188
|
}
|
|
253
189
|
|
|
254
|
-
|
|
255
|
-
* Read one control's state.
|
|
256
|
-
* @param field - field name of a section field or of a write-only control.
|
|
257
|
-
* @returns the draft text, whether a save would leave an override, and whether it is invalid.
|
|
258
|
-
*/
|
|
190
|
+
// Read one control's state: draft text, whether a save would leave an override, and whether it is invalid.
|
|
259
191
|
field(field: string): SettingsFieldState {
|
|
260
192
|
const staged = this.staged.get(field);
|
|
261
193
|
if (this.secretSpecs.has(field)) {
|
|
@@ -277,10 +209,7 @@ export class SettingsFormModel<T> {
|
|
|
277
209
|
};
|
|
278
210
|
}
|
|
279
211
|
|
|
280
|
-
|
|
281
|
-
* Build the edit, reset, save, and discard actions bound to this form.
|
|
282
|
-
* @returns the actions a card's slot entry injects.
|
|
283
|
-
*/
|
|
212
|
+
// Build the edit, reset, save, and discard actions bound to this form.
|
|
284
213
|
actions(): SettingsFormActions {
|
|
285
214
|
return {
|
|
286
215
|
edit: (field, text) => {
|
|
@@ -302,15 +231,8 @@ export class SettingsFormModel<T> {
|
|
|
302
231
|
};
|
|
303
232
|
}
|
|
304
233
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
*
|
|
308
|
-
* The Host is the only authority on whether a value was accepted — its
|
|
309
|
-
* validators own the constraints no schema can express — so the outcome is
|
|
310
|
-
* read back from the section rather than predicted here. A save that did not
|
|
311
|
-
* land keeps its drafts, so the user can correct them instead of retyping.
|
|
312
|
-
* @returns settlement after every write and the read-back.
|
|
313
|
-
*/
|
|
234
|
+
// Write every staged edit, then re-seed from what the Host accepted: the outcome is read back rather than predicted
|
|
235
|
+
// (the Host is the only authority), and a save that did not land keeps its drafts so the user can correct them.
|
|
314
236
|
async save(): Promise<void> {
|
|
315
237
|
const plan = this.plan();
|
|
316
238
|
if (
|
|
@@ -344,18 +266,14 @@ export class SettingsFormModel<T> {
|
|
|
344
266
|
}
|
|
345
267
|
}
|
|
346
268
|
|
|
347
|
-
|
|
269
|
+
// Release the form's accepted-value subscription.
|
|
348
270
|
dispose(): void {
|
|
349
271
|
this.unsubscribe();
|
|
350
272
|
this.listeners.clear();
|
|
351
273
|
}
|
|
352
274
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
* its field accepts carries no write: the form is still dirty, and the save
|
|
356
|
-
* refuses rather than dropping the edit.
|
|
357
|
-
* @returns the planned writes, in the order the fields were staged.
|
|
358
|
-
*/
|
|
275
|
+
// Every staged edit a save would write; an entry whose draft its field rejects carries no write (the form stays
|
|
276
|
+
// dirty, and the save refuses rather than dropping the edit).
|
|
359
277
|
private plan(): PlannedWrite[] {
|
|
360
278
|
const plan: PlannedWrite[] = [];
|
|
361
279
|
for (const [field, staged] of this.staged) {
|
|
@@ -388,8 +306,8 @@ export class SettingsFormModel<T> {
|
|
|
388
306
|
|
|
389
307
|
private spec(field: string): SettingsFieldSpec {
|
|
390
308
|
const spec = this.specs.get(field);
|
|
391
|
-
// Every call site names a field this card declared; a missing one is a
|
|
392
|
-
//
|
|
309
|
+
// Every call site names a field this card declared; a missing one is a wiring mistake that must not become a
|
|
310
|
+
// silently inert control.
|
|
393
311
|
if (spec === undefined) throw new Error(`plugin card has no field ${field}`);
|
|
394
312
|
return spec;
|
|
395
313
|
}
|
|
@@ -24,10 +24,8 @@ export type StyledComponent<T extends ElementType> = ForwardRefExoticComponent<
|
|
|
24
24
|
StyledProps<T> & RefAttributes<StyledRef<T>>
|
|
25
25
|
> & { readonly [sxSymbol]: CSSProps };
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* 多个样式对象按顺序深合并(后者覆盖前者),便于把变体叠在基样式上。
|
|
30
|
-
*/
|
|
27
|
+
// `styled('div')({ padding: '4px' }, { '&:hover': { … } })` → 带 `data-css-*` 的组件。
|
|
28
|
+
// 多个样式对象按顺序深合并(后者覆盖前者),便于把变体叠在基样式上。
|
|
31
29
|
export function styled<T extends ElementType>(
|
|
32
30
|
Component: T,
|
|
33
31
|
defaultProps: Partial<ComponentProps<T>> = {},
|
|
@@ -50,7 +48,7 @@ export function styled<T extends ElementType>(
|
|
|
50
48
|
};
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
|
|
51
|
+
// 取回 styled 组件携带的样式对象(需要在其之上继续组合时用)。
|
|
54
52
|
export function styleOf(component: StyledComponent<ElementType>): CSSProps {
|
|
55
53
|
return component[sxSymbol];
|
|
56
54
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* 漂移、往返与默认值完整性由 src/__tests__/design-tokens.spec.ts 守卫。 */
|
|
1
|
+
// 官方主题的 `--dsw-*` token 全集(400 个),由 packages/client/ui-primitives/scripts/gen-design-tokens.mts 生成,勿手改。
|
|
2
|
+
//
|
|
3
|
+
// 叶子是该变量的**默认值**(官方 light 主题里首次出现的定义):既是类型推导的来源,也可作 fallback。
|
|
4
|
+
// `"$"` 键出现在「自身也是 token 的分支」上(命名体系里有 40 个短名同时是长名前缀的 token)。
|
|
5
|
+
// 消费时只需树形状——`Token.vars()` 生成 `var(--dsw-…)`,不重新定义变量。
|
|
6
|
+
// 漂移、往返与默认值完整性由 src/__tests__/design-tokens.spec.ts 守卫。
|
|
8
7
|
export const designTokens = {
|
|
9
8
|
alias: {
|
|
10
9
|
bg: {
|
|
@@ -106,7 +106,7 @@ function rangeOf(reference: Reference): string {
|
|
|
106
106
|
// 本地引用的原文切片 → inline code;外部地址原样留下。引用不会嵌套(解析器保证),
|
|
107
107
|
// 这里仍按 offset 顺序跳过被前一个切片覆盖的引用。
|
|
108
108
|
// 边界:引用紧贴相邻 inline code 的反引号(`` `x`file:y ``)时两个 code span 会并成一个,
|
|
109
|
-
// 这种写法退化成普通 code
|
|
109
|
+
// 这种写法退化成普通 code。
|
|
110
110
|
function tokenizeReferences(text: string): {
|
|
111
111
|
readonly source: string;
|
|
112
112
|
readonly tokens: Map<string, Reference>;
|
package/src/reference.ts
CHANGED
|
@@ -522,7 +522,7 @@ function parseBareReference(raw: string): Reference | undefined {
|
|
|
522
522
|
};
|
|
523
523
|
}
|
|
524
524
|
|
|
525
|
-
|
|
525
|
+
// 引号 mention 的起手字符:`@"path with spaces"`。
|
|
526
526
|
const QUOTED_PATH = '"';
|
|
527
527
|
|
|
528
528
|
function parseQuotedReference(body: string): Reference | undefined {
|