@shwfed/config 3.0.3 → 3.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 (57) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/actions/buttons/2026-04-21/com.shwfed.actions.button.navigation/runtime.vue +4 -10
  3. package/dist/runtime/components/actions/buttons/2026-05-21/com.shwfed.actions.button.http.download/schema.d.ts +1 -1
  4. package/dist/runtime/components/actions/buttons/2026-05-21/com.shwfed.actions.button.http.download/schema.js +2 -2
  5. package/dist/runtime/components/actions/buttons/2026-07-13/com.shwfed.actions.button.state.write/runtime.vue +22 -2
  6. package/dist/runtime/components/actions/buttons/2026-07-13/com.shwfed.actions.button.state.write/schema.d.ts +4 -2
  7. package/dist/runtime/components/actions/buttons/2026-07-13/com.shwfed.actions.button.state.write/schema.js +1 -1
  8. package/dist/runtime/components/actions/utils/steps.d.ts +1 -1
  9. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/config.d.vue.ts +6 -6
  10. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/config.vue.d.ts +6 -6
  11. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/runtime.d.vue.ts +6 -6
  12. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/runtime.vue.d.ts +6 -6
  13. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/schema.d.ts +3 -3
  14. package/dist/runtime/components/config/index.vue +25 -0
  15. package/dist/runtime/components/config/prune-dialog.d.vue.ts +14 -0
  16. package/dist/runtime/components/config/prune-dialog.vue +72 -0
  17. package/dist/runtime/components/config/prune-dialog.vue.d.ts +14 -0
  18. package/dist/runtime/components/config/schema.js +2 -2
  19. package/dist/runtime/components/config/use-editor.d.ts +9 -0
  20. package/dist/runtime/components/config/use-editor.js +37 -9
  21. package/dist/runtime/components/config/utils/linkage.d.ts +8 -0
  22. package/dist/runtime/components/config/utils/linkage.js +7 -2
  23. package/dist/runtime/components/config/utils/prune.d.ts +68 -0
  24. package/dist/runtime/components/config/utils/prune.js +92 -0
  25. package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/config.d.vue.ts +31 -0
  26. package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/config.vue +191 -0
  27. package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/config.vue.d.ts +31 -0
  28. package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/expand.d.ts +49 -0
  29. package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/expand.js +87 -0
  30. package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/runtime.d.vue.ts +8 -0
  31. package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/runtime.vue +72 -0
  32. package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/runtime.vue.d.ts +8 -0
  33. package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/schema.d.ts +59 -0
  34. package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/schema.js +139 -0
  35. package/dist/runtime/components/form/utils/form-vars.d.ts +5 -15
  36. package/dist/runtime/components/form/utils/form-vars.js +12 -6
  37. package/dist/runtime/components/operations/2026-06-11/com.shwfed.operation.http.request.json/schema.js +2 -2
  38. package/dist/runtime/components/operations/2026-06-15/com.shwfed.operation.http.download/schema.js +2 -2
  39. package/dist/runtime/components/operations/2026-06-15/com.shwfed.operation.navigation/runtime.js +5 -11
  40. package/dist/runtime/components/operations/2026-06-15/com.shwfed.operation.navigation/schema.js +1 -1
  41. package/dist/runtime/components/operations/utils/resolve.d.ts +1 -1
  42. package/dist/runtime/components/operations/utils/step-vars.d.ts +4 -2
  43. package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.select/runtime.vue +1 -21
  44. package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.select/schema.d.ts +2 -0
  45. package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.select/schema.js +16 -1
  46. package/dist/runtime/components/table/index.vue +15 -0
  47. package/dist/runtime/components/table/schema.d.ts +6 -6
  48. package/dist/runtime/components/table/utils/resolve.d.ts +26 -0
  49. package/dist/runtime/components/table/utils/resolve.js +2 -1
  50. package/dist/runtime/components/ui/expression-editor/picker-entries.js +1 -1
  51. package/dist/runtime/components/ui/expression-editor/step-chip-extension.js +1 -1
  52. package/dist/runtime/components/ui/expression-editor/step-refs.js +1 -1
  53. package/dist/runtime/share/event-bus.js +2 -2
  54. package/dist/runtime/share/navigation.d.ts +1 -0
  55. package/dist/runtime/share/navigation.js +4 -0
  56. package/dist/runtime/utils/cel-context.d.ts +2 -2
  57. package/package.json +1 -1
@@ -0,0 +1,59 @@
1
+ import { Schema } from 'effect';
2
+ import type { Environment } from '../../../../../vendor/cel-js/lib/index.js';
3
+ export declare const type: "com.shwfed.form.field.dynamic";
4
+ export declare const compatibilityDate: "2026-07-13";
5
+ export declare const metadata: {
6
+ readonly name: "动态字段";
7
+ readonly icon: "fluent:form-multiple-20-regular";
8
+ readonly w: {
9
+ readonly initial: 12;
10
+ readonly min: 4;
11
+ readonly max: number;
12
+ };
13
+ readonly h: {
14
+ readonly initial: 4;
15
+ readonly min: 2;
16
+ readonly max: number;
17
+ readonly grow: true;
18
+ };
19
+ };
20
+ declare const DynFieldKind: Schema.Literal<["text", "number", "date", "daterange", "dropdown"]>;
21
+ export type DynFieldKindValue = Schema.Schema.Type<typeof DynFieldKind>;
22
+ export declare const KIND_TO_FIELD: Record<DynFieldKindValue, {
23
+ type: string;
24
+ compatibilityDate: string;
25
+ }>;
26
+ export declare const CHOICE_KINDS: ReadonlySet<DynFieldKindValue>;
27
+ /**
28
+ * The narrow, module-owned DTO the `transform` must emit per field. Wider than
29
+ * the column DTO because these are *edit* definitions — they carry requiredness,
30
+ * options, and default values. Each list element is decoded against this at
31
+ * runtime (partial-drop, loud); the strongest static guarantee is only that
32
+ * `transform` returns `list<dyn>`.
33
+ */
34
+ export declare const DynFieldDescriptor: Schema.Struct<{
35
+ binding: Schema.filter<typeof Schema.String>;
36
+ label: typeof Schema.String;
37
+ kind: Schema.Literal<["text", "number", "date", "daterange", "dropdown"]>;
38
+ required: Schema.optional<typeof Schema.Boolean>;
39
+ options: Schema.optional<Schema.Array$<Schema.Struct<{
40
+ value: Schema.Union<[typeof Schema.String, typeof Schema.Number]>;
41
+ label: typeof Schema.String;
42
+ }>>>;
43
+ default: Schema.optional<typeof Schema.Unknown>;
44
+ }>;
45
+ export type DynFieldDescriptorValue = Schema.Schema.Type<typeof DynFieldDescriptor>;
46
+ export declare function schema(configure: (env: Environment) => void): Schema.Struct<{
47
+ type: Schema.Literal<["com.shwfed.form.field.dynamic"]>;
48
+ compatibilityDate: Schema.Literal<["2026-07-13"]>;
49
+ id: Schema.refine<string, typeof Schema.String>;
50
+ displayName: Schema.optional<Schema.SchemaClass<string, string, never>>;
51
+ hidden: Schema.optional<Schema.Schema<string, string, never>>;
52
+ request: Schema.Schema<string, string, never>;
53
+ transform: Schema.Schema<string, string, never>;
54
+ columns: Schema.optional<Schema.refine<number, Schema.filter<typeof Schema.Number>>>;
55
+ orientation: Schema.optional<Schema.Literal<["vertical", "floating"]>>;
56
+ }>;
57
+ export type Value = Schema.Schema.Type<ReturnType<typeof schema>>;
58
+ export declare function defaults(): Partial<Value>;
59
+ export {};
@@ -0,0 +1,139 @@
1
+ import { Schema } from "effect";
2
+ import { Expression, HttpRequestResult } from "../../../../../share/expression.js";
3
+ import { md } from "../../../../../share/markdown.js";
4
+ import { FieldOrientationSchema } from "../../../utils/common.js";
5
+ import { FORM_VARIABLE_NAMES } from "../../../utils/form-vars.js";
6
+ export const type = "com.shwfed.form.field.dynamic";
7
+ export const compatibilityDate = "2026-07-13";
8
+ export const metadata = {
9
+ name: "\u52A8\u6001\u5B57\u6BB5",
10
+ icon: "fluent:form-multiple-20-regular",
11
+ w: { initial: 12, min: 4, max: Infinity },
12
+ h: { initial: 4, min: 2, max: Infinity, grow: true }
13
+ // Hosts a runtime-generated sub-form; its config (two expressions + two
14
+ // inputs) is small enough for the docked inline pane, so leave `inlineConfig`
15
+ // at its default (supported).
16
+ };
17
+ const DynFieldKind = Schema.Literal(
18
+ "text",
19
+ "number",
20
+ "date",
21
+ "daterange",
22
+ "dropdown"
23
+ );
24
+ export const KIND_TO_FIELD = {
25
+ text: { type: "com.shwfed.form.field.text", compatibilityDate: "2026-04-22" },
26
+ number: { type: "com.shwfed.form.field.number", compatibilityDate: "2026-04-28" },
27
+ date: { type: "com.shwfed.form.field.date", compatibilityDate: "2026-04-27" },
28
+ daterange: { type: "com.shwfed.form.field.daterange", compatibilityDate: "2026-04-27" },
29
+ dropdown: { type: "com.shwfed.form.field.combobox.single", compatibilityDate: "2026-06-14" }
30
+ };
31
+ export const CHOICE_KINDS = /* @__PURE__ */ new Set(["dropdown"]);
32
+ const DynFieldOption = Schema.Struct({
33
+ // The value bound when this option is chosen. A number maps to the leaves'
34
+ // `{ kind: 'number', value }` item shape, a string to `{ kind: 'text', value }`.
35
+ value: Schema.Union(Schema.String, Schema.Number),
36
+ // Plain string, no i18n (confirmed requirement); `expand` wraps it into a zh
37
+ // `Locale` via the dyn-cols `wrapLocale` idiom.
38
+ label: Schema.String
39
+ });
40
+ export const DynFieldDescriptor = Schema.Struct({
41
+ // dot-prop path like any field's binding; for the legacy app = `parameter_name`.
42
+ // Child `id` is namespaced `<blockId>::<binding>`, so it can never collide
43
+ // with a static field or another block in the form-wide validation /
44
+ // selections registries. First-wins on duplicate bindings within one block.
45
+ binding: Schema.String.pipe(Schema.minLength(1)),
46
+ // Plain string, no i18n (confirmed requirement); wrapped into a zh `Locale`.
47
+ label: Schema.String,
48
+ // Selects the pinned leaf (closed vocabulary).
49
+ kind: DynFieldKind,
50
+ // `true` → the child gets `required: 'true'` (the CEL literal), which is both
51
+ // the required star and the non-empty gate.
52
+ required: Schema.optional(Schema.Boolean),
53
+ // Choice kinds' static options. A choice-kind descriptor without `options` is
54
+ // dropped (loud). Legacy idiom:
55
+ // `f.optional_values.split('\n').map(v, {'value': v, 'label': v})`.
56
+ options: Schema.optional(Schema.Array(DynFieldOption)),
57
+ // Initial-value seed: written into form state at `binding` iff that path
58
+ // currently resolves to `undefined` (never clobbering the form-level `initial`
59
+ // seed or user input).
60
+ default: Schema.optional(Schema.Unknown)
61
+ });
62
+ function withoutFormVariables(configure) {
63
+ return (env) => {
64
+ const proxy = new Proxy(env, {
65
+ get(target, prop) {
66
+ if (prop === "registerVariable") {
67
+ return (name, ...rest) => {
68
+ if (FORM_VARIABLE_NAMES.has(name)) return;
69
+ return target.registerVariable(name, ...rest);
70
+ };
71
+ }
72
+ const v = Reflect.get(target, prop, target);
73
+ return typeof v === "function" ? v.bind(target) : v;
74
+ }
75
+ });
76
+ configure(proxy);
77
+ };
78
+ }
79
+ export function schema(configure) {
80
+ const frozenConfigure = withoutFormVariables(configure);
81
+ const CelRequest = Expression({ configure: frozenConfigure, resultType: HttpRequestResult });
82
+ const CelTransform = Expression({
83
+ configure: (env) => {
84
+ frozenConfigure(env);
85
+ const declared = new Set(env.getDefinitions().variables.map((v) => v.name));
86
+ if (!declared.has("json")) {
87
+ env.registerVariable("json", "optional<dyn>", {
88
+ label: "HTTP \u54CD\u5E94\u4F53",
89
+ description: "\u5B57\u6BB5\u5B9A\u4E49\u63A5\u53E3\u7684\u54CD\u5E94\u4F53\uFF08\u5DF2\u89E3\u6790 JSON\uFF09\uFF1B\u672A\u83B7\u53D6\u5230\u65F6\u4E3A none\uFF0C\u901A\u8FC7 `json.orValue(...)` \u89E3\u5305"
90
+ });
91
+ }
92
+ },
93
+ resultType: (t) => t.startsWith("list") || t === "dyn" || t.startsWith("optional")
94
+ });
95
+ const CelBool = Expression({ configure, resultType: "bool" });
96
+ return Schema.Struct({
97
+ type: Schema.Literal(type),
98
+ compatibilityDate: Schema.Literal(compatibilityDate),
99
+ // Design-time identity; placements reference it. The block carries NO
100
+ // `binding` / `required` / `validations` of its own — it has no value.
101
+ id: Schema.UUID.annotations({ description: "\u5B57\u6BB5\u552F\u4E00\u6807\u8BC6\uFF1B\u5E03\u5C40\u901A\u8FC7\u8BE5 id \u5F15\u7528\u8BE5\u52A8\u6001\u533A\u57DF" }),
102
+ displayName: Schema.optional(Schema.String.annotations({
103
+ title: "\u5185\u90E8\u540D\u79F0",
104
+ description: "\u4EC5\u5728\u7F16\u8F91\u5668\u5185\u53EF\u89C1\u7684\u540D\u79F0\uFF0C\u7528\u4E8E\u5728\u4FA7\u8FB9\u680F\u548C\u5E03\u5C40\u7F16\u8F91\u5668\u4E2D\u8BC6\u522B\u8BE5\u52A8\u6001\u533A\u57DF\uFF1B\u8FD0\u884C\u65F6\u4E0D\u5C55\u793A"
105
+ })),
106
+ hidden: Schema.optional(CelBool.annotations({
107
+ title: "\u9690\u85CF\u6761\u4EF6",
108
+ description: "\u8FD4\u56DE `true` \u65F6\u6574\u4E2A\u52A8\u6001\u533A\u57DF\u5728\u6240\u6709\u5E03\u5C40\u4E2D\u90FD\u4E0D\u6E32\u67D3\uFF08\u5E76\u89E6\u53D1\u8F68\u9053\u538B\u7F29\u6536\u8D77\u8BE5\u533A\u57DF\uFF09"
109
+ })),
110
+ request: CelRequest.annotations({
111
+ title: "\u5B57\u6BB5\u5B9A\u4E49\u8BF7\u6C42",
112
+ description: md`
113
+ 获取字段定义列表的 HTTP 请求表达式(与数据源 \`request\` 同为 \`HttpRequest\` 形状)。
114
+ 字段集合在挂载时**一次性**获取,**不随表单状态变化** —— 作用域内没有 \`form\` 等表单运行时变量。
115
+ `
116
+ }),
117
+ transform: CelTransform.annotations({
118
+ title: "\u5B57\u6BB5\u5B9A\u4E49\u6620\u5C04",
119
+ description: md`
120
+ 将 \`json\`(字段定义响应体,用 \`json.orValue([])\` 解包)映射为一组字段描述(\`list\`)。
121
+ 每个元素需符合固定字段(注意 CEL 映射字面量的键要加引号):\`binding\`(写入路径 / 唯一标识)、
122
+ \`label\`(标签,纯文本无 i18n)、\`kind\`(\`text\`/\`number\`/\`date\`/\`daterange\`/\`dropdown\`);
123
+ 可选 \`required\`(布尔)、\`options\`(\`list<{value, label}>\`,\`dropdown\` 必填)、
124
+ \`default\`(初始值)。**所有与后端数据契约相关的知识都在这里**。
125
+ `
126
+ }),
127
+ columns: Schema.optional(Schema.Number.pipe(Schema.int(), Schema.positive()).annotations({
128
+ title: "\u5217\u6570",
129
+ description: "\u6BCF\u884C\u6392\u5E03\u7684\u5B50\u5B57\u6BB5\u6570\uFF1A\u6BCF\u4E2A\u5B50\u5B57\u6BB5\u6070\u597D\u5360\u4E00\u683C\uFF0C\u6309\u5B9A\u4E49\u987A\u5E8F\u884C\u4F18\u5148\u6392\u5E03\u3001\u6EE1\u884C\u6362\u884C\u3002\u9ED8\u8BA4 1\uFF08\u6BCF\u4E2A\u5B57\u6BB5\u5360\u6EE1\u6574\u884C\uFF09"
130
+ })),
131
+ orientation: Schema.optional(FieldOrientationSchema.annotations({
132
+ title: "\u65B9\u5411",
133
+ description: "\u7EDF\u4E00\u65BD\u52A0\u5230\u6BCF\u4E2A\u52A8\u6001\u5B50\u5B57\u6BB5\u7684\u6807\u7B7E\u6392\u5217\u65B9\u5411\uFF08`orientation` \u662F\u914D\u7F6E\u6570\u636E\uFF0C\u8FD0\u884C\u65F6\u6CA1\u6709\u65B9\u5411\u63D0\u4F9B\u8005\uFF09\uFF1B\u641C\u7D22\u8868\u5355\u573A\u666F\u901A\u5E38\u7528 `floating`"
134
+ }))
135
+ }).annotations({ title: "DynamicFields", description: "\u8FD0\u884C\u65F6\u52A8\u6001\u5B57\u6BB5\uFF08\u7531\u670D\u52A1\u7AEF\u5B57\u6BB5\u5B9A\u4E49\u751F\u6210\uFF09" });
136
+ }
137
+ export function defaults() {
138
+ return { request: "", transform: "" };
139
+ }
@@ -1,19 +1,9 @@
1
1
  import type { Environment } from '../../../vendor/cel-js/lib/index.js';
2
2
  /**
3
- * Register the form-wide `now` / `form` live variables on `env`, skipping any
4
- * already declared. Idempotent so it composes safely from either side: a
5
- * `FormConfig` nested inside another (the `table` form field embeds a table
6
- * whose `query` is its own sub-form, and the embedding `configure` advertises
7
- * the same pair) would otherwise register `form` twice on one env and the CEL
8
- * registry throws `'form' is already registered`. Mirrors
9
- * `registerRowVariablesIfAbsent`.
10
- *
11
- * This is the single source of truth for the `form` / `now` metadata: the
12
- * schema's expression validators register it here, the designer-time picker
13
- * probes it (`buildFormCelScope`), and the runtime scope derives its
14
- * type/label/description from it too (`buildFormRuntimeScope`). Lives in this
15
- * dependency-light module — not `schema.ts` — so the scope helpers can import it
16
- * without pulling `utils/resolve` (and its `import.meta.glob`) into per-folder
17
- * unit tests. `schema.ts` re-exports it for existing call sites.
3
+ * The names the form registrar owns the form-runtime state a schema can
4
+ * subtract from an expression scope when its semantics forbid depending on the
5
+ * live form (the dynamic-fields block's `request` / `transform`: the field set
6
+ * is frozen at mount and must not read `form`).
18
7
  */
8
+ export declare const FORM_VARIABLE_NAMES: ReadonlySet<string>;
19
9
  export declare function registerFormVariablesIfAbsent(env: Environment): void;
@@ -1,10 +1,16 @@
1
1
  import { OPTIONS_VAR, SELECTIONS_VAR } from "../../../utils/cel-context.js";
2
+ const FORM_VARIABLE_DEFS = [
3
+ { name: "now", type: "Date", meta: { label: "\u5F53\u524D\u65F6\u95F4", description: "\u5F53\u524D\u65E5\u671F/\u65F6\u95F4" } },
4
+ { name: "form", type: "dyn", meta: { label: "\u8868\u5355\u503C", description: "\u5F53\u524D\u8868\u5355\u72B6\u6001" } },
5
+ { name: "undoable", type: "bool", meta: { label: "\u53EF\u64A4\u9500", description: "\u5F53\u524D\u662F\u5426\u6709\u53EF\u64A4\u9500\u7684\u4FEE\u6539" } },
6
+ { name: "redoable", type: "bool", meta: { label: "\u53EF\u91CD\u505A", description: "\u5F53\u524D\u662F\u5426\u6709\u53EF\u91CD\u505A\u7684\u4FEE\u6539" } },
7
+ { name: SELECTIONS_VAR, type: "dyn", meta: { label: "\u9009\u4E2D\u9879", description: "\u5404\u9009\u62E9\u5B57\u6BB5\u5F53\u524D\u9009\u4E2D\u9879\u7684\u5B8C\u6574\u5BF9\u8C61\uFF08\u6309\u5B57\u6BB5\u6807\u8BC6\uFF09" } },
8
+ { name: OPTIONS_VAR, type: "dyn", meta: { label: "\u5168\u90E8\u9009\u9879", description: "\u5404\u9009\u62E9\u5B57\u6BB5\u5F53\u524D\u5DF2\u52A0\u8F7D\u7684\u5168\u90E8\u9009\u9879\uFF08\u6309\u5B57\u6BB5\u6807\u8BC6\uFF0C\u5143\u7D20\u4E3A\u9009\u9879\u5B8C\u6574\u5BF9\u8C61\uFF09" } }
9
+ ];
10
+ export const FORM_VARIABLE_NAMES = new Set(FORM_VARIABLE_DEFS.map((d) => d.name));
2
11
  export function registerFormVariablesIfAbsent(env) {
3
12
  const declared = new Set(env.getDefinitions().variables.map((v) => v.name));
4
- if (!declared.has("now")) env.registerVariable("now", "Date", { label: "\u5F53\u524D\u65F6\u95F4", description: "\u5F53\u524D\u65E5\u671F/\u65F6\u95F4" });
5
- if (!declared.has("form")) env.registerVariable("form", "dyn", { label: "\u8868\u5355\u503C", description: "\u5F53\u524D\u8868\u5355\u72B6\u6001" });
6
- if (!declared.has("undoable")) env.registerVariable("undoable", "bool", { label: "\u53EF\u64A4\u9500", description: "\u5F53\u524D\u662F\u5426\u6709\u53EF\u64A4\u9500\u7684\u4FEE\u6539" });
7
- if (!declared.has("redoable")) env.registerVariable("redoable", "bool", { label: "\u53EF\u91CD\u505A", description: "\u5F53\u524D\u662F\u5426\u6709\u53EF\u91CD\u505A\u7684\u4FEE\u6539" });
8
- if (!declared.has(SELECTIONS_VAR)) env.registerVariable(SELECTIONS_VAR, "dyn", { label: "\u9009\u4E2D\u9879", description: "\u5404\u9009\u62E9\u5B57\u6BB5\u5F53\u524D\u9009\u4E2D\u9879\u7684\u5B8C\u6574\u5BF9\u8C61\uFF08\u6309\u5B57\u6BB5\u6807\u8BC6\uFF09" });
9
- if (!declared.has(OPTIONS_VAR)) env.registerVariable(OPTIONS_VAR, "dyn", { label: "\u5168\u90E8\u9009\u9879", description: "\u5404\u9009\u62E9\u5B57\u6BB5\u5F53\u524D\u5DF2\u52A0\u8F7D\u7684\u5168\u90E8\u9009\u9879\uFF08\u6309\u5B57\u6BB5\u6807\u8BC6\uFF0C\u5143\u7D20\u4E3A\u9009\u9879\u5B8C\u6574\u5BF9\u8C61\uFF09" });
13
+ for (const d of FORM_VARIABLE_DEFS) {
14
+ if (!declared.has(d.name)) env.registerVariable(d.name, d.type, d.meta);
15
+ }
10
16
  }
@@ -8,7 +8,7 @@ export const metadata = {
8
8
  name: "\u53D1\u9001\u8BF7\u6C42",
9
9
  icon: "fluent:cloud-arrow-up-20-regular",
10
10
  // This op publishes its parsed response as a chain-step output, so later
11
- // trigger rows can reference it via `steps["<row-id>"]`.
11
+ // trigger rows can reference it via `steps[?"<row-id>"]`.
12
12
  output: "HTTP \u54CD\u5E94\uFF08\u89E3\u6790\u540E\u7684 JSON\uFF09"
13
13
  };
14
14
  export function schema(configure) {
@@ -69,7 +69,7 @@ export function schema(configure) {
69
69
  description: md`
70
70
  发起一次 HTTP 请求(响应解析为 JSON)。请求失败(网络错误 / 非 2xx / 响应不可解析)
71
71
  或「执行结果」为 \`error\` 时中止本次触发链。在触发列表中串联多个本操作即可按顺序
72
- 调用多个接口——前一个失败后,后续操作不再执行。后面的步骤可经 \`steps["<步骤标识>"]\`
72
+ 调用多个接口——前一个失败后,后续操作不再执行。后面的步骤可经 \`steps[?"<步骤标识>"]\`
73
73
  引用前面步骤的响应。
74
74
 
75
75
  toast 完全由「提示标题」「提示内容」决定(含失败场景);不配置标题、或标题求值为空字符串则静默。
@@ -39,7 +39,7 @@ export function schema(configure) {
39
39
  title: "\u8BF7\u6C42",
40
40
  description: md`
41
41
  构造一个请求,其响应将被取回并保存为文件。
42
- 可经 \`steps["<步骤标识>"]\` 引用前序步骤的输出——把先前
42
+ 可经 \`steps[?"<步骤标识>"]\` 引用前序步骤的输出——把先前
43
43
  \`发送请求\` 行取得的凭据用于构造真正的下载请求。
44
44
  `
45
45
  }),
@@ -75,7 +75,7 @@ export function schema(configure) {
75
75
  发起一次 HTTP 请求:若响应是二进制(非 JSON)则保存为本地文件;若响应是 JSON,
76
76
  则视为后端返回的状态信封,经「执行结果」裁定成败而**不**保存。下载失败(网络错误 / 非 2xx)
77
77
  或「执行结果」为 \`error\` 会中止本次触发链——其后排队的操作不再执行。把本操作接在一个
78
- \`发送请求\` 行之后、并经 \`steps["<步骤标识>"]\` 引用其响应,即可实现
78
+ \`发送请求\` 行之后、并经 \`steps[?"<步骤标识>"]\` 引用其响应,即可实现
79
79
  “先取凭据再下载”的两步式。toast 完全由「提示标题」「提示内容」决定(含失败场景);
80
80
  不配置标题、或标题求值为空字符串则静默。
81
81
  `
@@ -1,12 +1,6 @@
1
1
  import { Effect } from "effect";
2
2
  import { navigateTo, useRouter } from "#app";
3
- function isExternal(url) {
4
- try {
5
- return new URL(url, window.location.href).origin !== window.location.origin;
6
- } catch {
7
- return false;
8
- }
9
- }
3
+ import { isAbsoluteUrl } from "../../../../share/navigation.js";
10
4
  export const handler = (params, ctx) => {
11
5
  const { url, mode } = params ?? {};
12
6
  if (!url || url.trim().length === 0) return Effect.void;
@@ -14,13 +8,13 @@ export const handler = (params, ctx) => {
14
8
  const steps = ctx.step?.outputs() ?? {};
15
9
  const resolved = yield* ctx.cel(url, { steps });
16
10
  const router = useRouter();
17
- const external = isExternal(resolved);
18
- yield* Effect.promise(async () => {
11
+ const absolute = isAbsoluteUrl(resolved);
12
+ yield* Effect.tryPromise(async () => {
19
13
  if (mode === "_blank") {
20
- window.open(external ? resolved : router.resolve(resolved).href, "_blank");
14
+ window.open(absolute ? resolved : router.resolve(resolved).href, "_blank");
21
15
  return;
22
16
  }
23
- await navigateTo(resolved, { external });
17
+ await navigateTo(resolved, { external: absolute });
24
18
  });
25
19
  });
26
20
  return program.pipe(
@@ -28,7 +28,7 @@ export function schema(configure) {
28
28
  }).annotations({
29
29
  title: "NavigationOperation",
30
30
  description: md`
31
- 按所选方式打开一个链接。链接表达式可经 \`steps["<步骤标识>"]\` 引用前序步骤的输出,
31
+ 按所选方式打开一个链接。链接表达式可经 \`steps[?"<步骤标识>"]\` 引用前序步骤的输出,
32
32
  因此可由先前请求的响应拼接目标地址。导航失败不中止本次触发链。
33
33
  `
34
34
  });
@@ -30,7 +30,7 @@ export type OperationMetadata = Readonly<{
30
30
  icon: string;
31
31
  /**
32
32
  * Present iff this operation publishes a chain-step output (`ctx.step.publish`)
33
- * for later trigger rows to read via `steps["<row-id>"]` — the value is the
33
+ * for later trigger rows to read via `steps[?"<row-id>"]` — the value is the
34
34
  * designer-facing label of what gets published (e.g. 'HTTP 响应'). The trigger
35
35
  * editor reads it to decide which rows to offer as step references, so a
36
36
  * non-publishing op never advertises a reference that resolves to nothing.
@@ -6,10 +6,12 @@ import { type CELVar } from '../../../utils/cel-context.js';
6
6
  * an op schema may be embedded under a host whose `configure` already
7
7
  * advertises it, and double registration throws.
8
8
  *
9
- * `dyn` (not `map<string, dyn>`) so `steps["<id>"].data.code` type-checks the
9
+ * `dyn` (not `map<string, dyn>`) so `steps[?"<id>"].data.code` type-checks the
10
10
  * same way `form.foo.bar` does — what a step publishes is backend-defined and
11
11
  * unknowable at design time. Keyed by trigger-row uuid; the picker renders
12
- * each key as its「第 N 步」position.
12
+ * each key as its「第 N 步」position, and writes the OPTIONAL lookup (`[?]`), so
13
+ * a step that published nothing evaluates to `undefined` instead of throwing
14
+ * `No such key`.
13
15
  */
14
16
  export declare function registerStepsVariableIfAbsent(env: Environment): void;
15
17
  /**
@@ -1,33 +1,13 @@
1
1
  <script setup>
2
2
  import { computed } from "vue";
3
- import { Effect } from "effect";
4
3
  import { Icon } from "@iconify/vue";
5
- import { cel as $cel } from "../../../../../utils/cel";
6
- import { celScope, injectCELContext } from "../../../../../utils/cel-context";
7
4
  import { cn } from "../../../../../utils/cn";
8
5
  defineOptions({ name: "ShwfedTableSelectRendererRuntime" });
9
6
  const props = defineProps({
10
7
  column: { type: null, required: true },
11
8
  ctx: { type: Object, required: true }
12
9
  });
13
- const celContext = injectCELContext();
14
- function evalPredicate(expression, fallback) {
15
- try {
16
- return Boolean(Effect.runSync($cel(expression, celScope(celContext))));
17
- } catch (e) {
18
- console.error("[shwfed-table] row-selection predicate failed", e);
19
- return fallback;
20
- }
21
- }
22
- const canSelect = computed(() => {
23
- if (props.column.enableRowSelection && !evalPredicate(props.column.enableRowSelection, true)) {
24
- return false;
25
- }
26
- if (props.column.mode === "checkbox" && props.column.enableMultiRowSelection && !evalPredicate(props.column.enableMultiRowSelection, true)) {
27
- return false;
28
- }
29
- return true;
30
- });
10
+ const canSelect = computed(() => props.ctx.row.getCanSelect());
31
11
  function onCheckboxClick(e) {
32
12
  if (!canSelect.value) return;
33
13
  const { row, table } = props.ctx;
@@ -1,6 +1,7 @@
1
1
  import { Schema } from 'effect';
2
2
  import type { ColumnDef } from '@tanstack/vue-table';
3
3
  import type { Environment } from '../../../../../vendor/cel-js/lib/index.js';
4
+ import type { ColumnRowSelection } from '../../../utils/resolve.js';
4
5
  export declare const type: "com.shwfed.table.column.select";
5
6
  export declare const compatibilityDate: "2026-04-14";
6
7
  export declare const metadata: {
@@ -20,4 +21,5 @@ export declare function schema(configure: (env: Environment) => void): Schema.St
20
21
  }>;
21
22
  export type Value = Schema.Schema.Type<ReturnType<typeof schema>>;
22
23
  export declare function defaults(): Partial<Value>;
24
+ export declare const rowSelection: ColumnRowSelection;
23
25
  export declare function toColumnDef(value: Value): Partial<ColumnDef<unknown, unknown>>;
@@ -1,4 +1,4 @@
1
- import { Schema } from "effect";
1
+ import { Effect, Schema } from "effect";
2
2
  import { h } from "vue";
3
3
  import { Expression } from "../../../../../share/expression.js";
4
4
  import { columnIdentityFields, registerRowVariablesIfAbsent } from "../../../utils/shared.js";
@@ -40,6 +40,21 @@ export function defaults() {
40
40
  mode: "checkbox"
41
41
  };
42
42
  }
43
+ export const rowSelection = (value, deps) => {
44
+ const predicates = [
45
+ value.enableRowSelection,
46
+ value.mode === "checkbox" ? value.enableMultiRowSelection : void 0
47
+ ].filter((e) => typeof e === "string" && e.trim() !== "");
48
+ if (predicates.length === 0) return () => true;
49
+ return (row, index) => predicates.every((expression) => {
50
+ try {
51
+ return Boolean(Effect.runSync(deps.$cel(expression, deps.rowScope(row, index))));
52
+ } catch (e) {
53
+ console.error("[shwfed-table] row-selection predicate failed", e);
54
+ return true;
55
+ }
56
+ });
57
+ };
43
58
  export function toColumnDef(value) {
44
59
  return {
45
60
  header: (ctx) => h(Header, { column: value, ctx }),
@@ -324,6 +324,20 @@ const getRowId = createRowKeyResolver({
324
324
  }
325
325
  }
326
326
  });
327
+ const rowSelectionPredicates = computed(() => effectiveColumns.value.flatMap((col) => {
328
+ const contribute = findColumn(col.type, col.compatibilityDate)?.rowSelection;
329
+ if (!contribute) return [];
330
+ return [contribute(col, {
331
+ $cel,
332
+ rowScope: (row, index) => celScope(inheritedContext, { row, index })
333
+ })];
334
+ }));
335
+ function canSelectRow(row) {
336
+ const predicates = rowSelectionPredicates.value;
337
+ if (predicates.length === 0) return true;
338
+ const index = getDisplayIndex(row);
339
+ return predicates.every((p) => p(row.original, index));
340
+ }
327
341
  const tableApi = useVueTable({
328
342
  get columns() {
329
343
  return columns.value;
@@ -331,6 +345,7 @@ const tableApi = useVueTable({
331
345
  get data() {
332
346
  return rowData.value;
333
347
  },
348
+ enableRowSelection: canSelectRow,
334
349
  get manualPagination() {
335
350
  return isManualPagination.value;
336
351
  },
@@ -352,6 +352,9 @@ export declare function TableConfig(configure: (env: Environment) => void): Sche
352
352
  readonly expanded?: boolean | {
353
353
  readonly [x: string]: boolean;
354
354
  } | undefined;
355
+ readonly rowSelection?: {
356
+ readonly [x: string]: boolean;
357
+ } | undefined;
355
358
  readonly columnVisibility?: {
356
359
  readonly [x: string]: boolean;
357
360
  } | undefined;
@@ -372,9 +375,6 @@ export declare function TableConfig(configure: (env: Environment) => void): Sche
372
375
  readonly columnSizing?: {
373
376
  readonly [x: string]: number;
374
377
  } | undefined;
375
- readonly rowSelection?: {
376
- readonly [x: string]: boolean;
377
- } | undefined;
378
378
  } | undefined;
379
379
  }, Schema.Struct<{
380
380
  kind: Schema.tag<"shwfed.component.table">;
@@ -1021,6 +1021,9 @@ export declare function createTableConfig(body: Omit<Schema.Schema.Type<ReturnTy
1021
1021
  readonly expanded?: boolean | {
1022
1022
  readonly [x: string]: boolean;
1023
1023
  } | undefined;
1024
+ readonly rowSelection?: {
1025
+ readonly [x: string]: boolean;
1026
+ } | undefined;
1024
1027
  readonly columnVisibility?: {
1025
1028
  readonly [x: string]: boolean;
1026
1029
  } | undefined;
@@ -1041,9 +1044,6 @@ export declare function createTableConfig(body: Omit<Schema.Schema.Type<ReturnTy
1041
1044
  readonly columnSizing?: {
1042
1045
  readonly [x: string]: number;
1043
1046
  } | undefined;
1044
- readonly rowSelection?: {
1045
- readonly [x: string]: boolean;
1046
- } | undefined;
1047
1047
  } | undefined;
1048
1048
  kind: typeof KIND;
1049
1049
  };
@@ -64,6 +64,26 @@ export type ColumnExpandDeps = Readonly<{
64
64
  seen: ReadonlySet<string>;
65
65
  }>;
66
66
  export type ColumnExpand = (value: any, deps: ColumnExpandDeps) => ColumnValue[];
67
+ /**
68
+ * Capabilities a column's row-selection predicate needs from the host.
69
+ */
70
+ export type RowSelectionDeps = Readonly<{
71
+ $cel: CelEvaluator;
72
+ /**
73
+ * CEL scope for one row — inherited host vars plus `row` / `index`, matching
74
+ * what a cell sees. Lazy, so a predicate only subscribes to the vars it reads.
75
+ */
76
+ rowScope: (row: unknown, index: number) => Record<string, unknown>;
77
+ }>;
78
+ /**
79
+ * Whether a row may be selected, per this column's config. A column exporting
80
+ * this contributes to the table-level `enableRowSelection` option, so TanStack
81
+ * itself knows which rows are selectable: select-all, shift-range and
82
+ * `row.getCanSelect()` then all agree with what the cell renders. Without it,
83
+ * a cell that merely *renders* itself disabled still gets checked by select-all
84
+ * — and the user cannot uncheck it. Columns with no selection semantics omit it.
85
+ */
86
+ export type ColumnRowSelection = (value: any, deps: RowSelectionDeps) => (row: unknown, index: number) => boolean;
67
87
  /**
68
88
  * Capabilities a column-migration function may need from the host. Mirrors
69
89
  * the form host's `MigrationContext` so cross-host helpers can share a
@@ -133,6 +153,12 @@ export type ColumnEntry = Readonly<{
133
153
  * `translateColumn` only ever sees the leaf columns it emits.
134
154
  */
135
155
  expand?: ColumnExpand;
156
+ /**
157
+ * Present iff this entry has a say in which rows are selectable (today: the
158
+ * select column). The host folds every present one into TanStack's
159
+ * `enableRowSelection`. See `ColumnRowSelection`.
160
+ */
161
+ rowSelection?: ColumnRowSelection;
136
162
  }>;
137
163
  export declare const COLUMNS: ReadonlyArray<ColumnEntry>;
138
164
  export declare function allColumnSchemas(configure: (env: Environment) => void): ReadonlyArray<AnySchema>;
@@ -85,7 +85,8 @@ export const COLUMNS = registry.ENTRIES.map((e) => ({
85
85
  ...e.module.migrate !== void 0 ? { migrate: e.module.migrate } : {},
86
86
  ...e.module.migrateFrom !== void 0 ? { migrateFrom: e.module.migrateFrom } : {},
87
87
  ...e.module.defaults !== void 0 ? { defaults: e.module.defaults } : {},
88
- ...e.module.expand !== void 0 ? { expand: e.module.expand } : {}
88
+ ...e.module.expand !== void 0 ? { expand: e.module.expand } : {},
89
+ ...e.module.rowSelection !== void 0 ? { rowSelection: e.module.rowSelection } : {}
89
90
  }));
90
91
  const migrationTargets = (() => {
91
92
  const map = /* @__PURE__ */ new Map();
@@ -39,7 +39,7 @@ export function buildStepEntries(roster) {
39
39
  return roster.filter((step) => step.valid).map((step) => ({
40
40
  id: `step:${step.id}`,
41
41
  group: "step",
42
- insert: `${STEPS_VAR}[${JSON.stringify(step.id)}]`,
42
+ insert: `${STEPS_VAR}[?${JSON.stringify(step.id)}]`,
43
43
  display: step.label,
44
44
  label: "\u524D\u5E8F\u6B65\u9AA4",
45
45
  type: "dyn",
@@ -76,7 +76,7 @@ const chipPlugin = ViewPlugin.fromClass(
76
76
  {
77
77
  decorations: (plugin) => plugin.decorations,
78
78
  // Atomic so the caret steps over a chip and Backspace removes the whole
79
- // `steps["id"]` at once — matching the scope/selection chips' feel.
79
+ // `steps[?"id"]` at once — matching the scope/selection chips' feel.
80
80
  provide: (plugin) => EditorView.atomicRanges.of((view) => view.plugin(plugin)?.decorations ?? Decoration.none)
81
81
  }
82
82
  );
@@ -1,6 +1,6 @@
1
1
  import { STEPS_VAR } from "../../../utils/cel-context.js";
2
2
  const STEP_REF_RE = new RegExp(
3
- `(?<![\\w$.])${STEPS_VAR}\\[("(?:[^"\\\\]|\\\\.)*")\\]`,
3
+ `(?<![\\w$.])${STEPS_VAR}\\[\\??("(?:[^"\\\\]|\\\\.)*")\\]`,
4
4
  "g"
5
5
  );
6
6
  export function scanStepRefs(text) {
@@ -4,10 +4,10 @@ export const Trigger = Schema.Struct({
4
4
  // Additive + optional like `compatibilityDate`/`params` below, so every
5
5
  // pre-existing `{ target, operation }` trigger keeps loading. Minted by the
6
6
  // trigger editor on add; it is the address under which this step's output is
7
- // published for later steps (`steps["<id>"]`), so it must survive reorders —
7
+ // published for later steps (`steps[?"<id>"]`), so it must survive reorders —
8
8
  // which the list index, the only other row identity, does not.
9
9
  id: Schema.optional(Schema.UUID.annotations({
10
- description: '\u89E6\u53D1\u884C\u7684\u7A33\u5B9A\u6807\u8BC6\uFF0C\u540E\u7EED\u6B65\u9AA4\u7ECF steps["<id>"] \u5F15\u7528\u672C\u6B65\u8F93\u51FA'
10
+ description: '\u89E6\u53D1\u884C\u7684\u7A33\u5B9A\u6807\u8BC6\uFF0C\u540E\u7EED\u6B65\u9AA4\u7ECF steps[?"<id>"] \u5F15\u7528\u672C\u6B65\u8F93\u51FA'
11
11
  })),
12
12
  target: Schema.UUID.annotations({
13
13
  description: "\u76EE\u6807\u7956\u5148\u5B9E\u4F8B\u7684 ID"
@@ -0,0 +1 @@
1
+ export declare function isAbsoluteUrl(url: string): boolean;
@@ -0,0 +1,4 @@
1
+ const ABSOLUTE = /^(?:[a-z][a-z0-9+\-.]*:|\/\/)/i;
2
+ export function isAbsoluteUrl(url) {
3
+ return ABSOLUTE.test(url.trim());
4
+ }
@@ -108,10 +108,10 @@ export declare function useSelectionRoster(): Ref<ReadonlyArray<SelectionFieldRe
108
108
  * surfaces it. Unlike a selection field a trigger row has no designer-given
109
109
  * name, so `label` is its position —「第 N 步」— computed live from the row's
110
110
  * current index and thus following drag-and-drop reorders for free (the
111
- * persisted reference is `steps["<id>"]`; the label is never stored).
111
+ * persisted reference is `steps[?"<id>"]`; the label is never stored).
112
112
  */
113
113
  export type StepRef = Readonly<{
114
- /** Trigger-row uuid keyed into `steps["<id>"]` — the publish address. */
114
+ /** Trigger-row uuid keyed into `steps[?"<id>"]` — the publish address. */
115
115
  id: string;
116
116
  /** Position label,「第 N 步」, derived from the row's current index. */
117
117
  label: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shwfed/config",
3
- "version": "3.0.3",
3
+ "version": "3.0.6",
4
4
  "description": "Configurable UI for SHWFED",
5
5
  "type": "module",
6
6
  "publishConfig": {