@shwfed/config 2.8.0 → 2.9.1

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 (141) hide show
  1. package/dist/mcp.mjs +65 -10
  2. package/dist/module.json +1 -1
  3. package/dist/preview/assets/{badge-BI1mdo92.js → badge-BFb-1pCT.js} +1 -1
  4. package/dist/preview/assets/config-0DneC44h.js +1 -0
  5. package/dist/preview/assets/{config-OrJljNWU.js → config-ByZ0313c.js} +1 -1
  6. package/dist/preview/assets/config-CBypmimA.js +1 -0
  7. package/dist/preview/assets/config-CRxO6DVD.js +1 -0
  8. package/dist/preview/assets/config-CgGwKTMB.js +1 -0
  9. package/dist/preview/assets/{config-DuzQXHvg.js → config-DFaQa_Ua.js} +1 -1
  10. package/dist/preview/assets/{config-XgVqueyq.js → config-DlA3bMzk.js} +1 -1
  11. package/dist/preview/assets/{config-DqUnpWZk.js → config-iysOo1dH.js} +1 -1
  12. package/dist/preview/assets/config-rr6HpsHl.js +1 -0
  13. package/dist/preview/assets/{definition.vue_vue_type_script_setup_true_lang-lEhYVEcN.js → definition.vue_vue_type_script_setup_true_lang-C1MSczMq.js} +1 -1
  14. package/dist/preview/assets/index-BlHAyQdm.css +1 -0
  15. package/dist/preview/assets/index-CLQQPdxj.js +1 -0
  16. package/dist/preview/assets/index-CpLuDJe9.js +688 -0
  17. package/dist/preview/assets/{index-BvLLQuQr.js → index-CuSwhbkD.js} +1 -1
  18. package/dist/preview/assets/{item-DMtXi_cx.js → item-iBF7XNGG.js} +1 -1
  19. package/dist/preview/assets/{runtime-wVZ1xP8L.js → runtime-BPK6zdoy.js} +1 -1
  20. package/dist/preview/assets/runtime-BmED451i.js +1 -0
  21. package/dist/preview/assets/{runtime-CjJBU_e1.js → runtime-CPiaq4_s.js} +1 -1
  22. package/dist/preview/assets/{runtime-CWqQzWuc.js → runtime-CY9TirG6.js} +1 -1
  23. package/dist/preview/assets/{runtime-CA58Mif7.js → runtime-D4WPJrz9.js} +1 -1
  24. package/dist/preview/assets/runtime-DrKKzpjq.js +1 -0
  25. package/dist/preview/assets/{runtime-DnXoWy2A.js → runtime-S7Afa8F4.js} +1 -1
  26. package/dist/preview/assets/{runtime-DeUmGsM_.js → runtime-Uz1zCLPO.js} +1 -1
  27. package/dist/preview/assets/{runtime-BUPuX-Gq.js → runtime-XjumEj6A.js} +1 -1
  28. package/dist/preview/index.html +2 -2
  29. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json/config.d.vue.ts +8 -0
  30. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json/config.vue +28 -0
  31. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json/config.vue.d.ts +8 -0
  32. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json/runtime.vue +17 -17
  33. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json/schema.d.ts +4 -0
  34. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json/schema.js +8 -0
  35. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json.confirm/config.d.vue.ts +8 -0
  36. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json.confirm/config.vue +25 -0
  37. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json.confirm/config.vue.d.ts +8 -0
  38. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json.confirm/runtime.vue +2 -7
  39. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json.confirm/schema.d.ts +4 -0
  40. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json.confirm/schema.js +8 -0
  41. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.date/config.d.vue.ts +16 -16
  42. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.date/config.vue.d.ts +16 -16
  43. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.d.vue.ts +18 -18
  44. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.vue.d.ts +18 -18
  45. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetime/config.d.vue.ts +16 -16
  46. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetime/config.vue.d.ts +16 -16
  47. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.d.vue.ts +18 -18
  48. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.vue.d.ts +18 -18
  49. package/dist/runtime/components/form/fields/2026-05-24/com.shwfed.form.field.month/config.d.vue.ts +16 -16
  50. package/dist/runtime/components/form/fields/2026-05-24/com.shwfed.form.field.month/config.vue.d.ts +16 -16
  51. package/dist/runtime/components/form/fields/2026-05-24/com.shwfed.form.field.monthrange/config.d.vue.ts +18 -18
  52. package/dist/runtime/components/form/fields/2026-05-24/com.shwfed.form.field.monthrange/config.vue.d.ts +18 -18
  53. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.combobox.multi/runtime.vue +13 -2
  54. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.combobox.multi/schema.d.ts +1 -0
  55. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.combobox.multi/schema.js +4 -1
  56. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.combobox.single/runtime.vue +10 -2
  57. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.combobox.single/schema.d.ts +1 -0
  58. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.combobox.single/schema.js +4 -1
  59. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.combobox.multi/runtime.vue +20 -4
  60. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.combobox.multi/schema.d.ts +1 -0
  61. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.combobox.multi/schema.js +4 -1
  62. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.combobox.single/runtime.vue +17 -9
  63. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.combobox.single/schema.d.ts +1 -0
  64. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.combobox.single/schema.js +4 -1
  65. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.multi/runtime.vue +25 -5
  66. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.multi/schema.d.ts +1 -0
  67. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.multi/schema.js +4 -1
  68. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.single/runtime.vue +18 -8
  69. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.single/schema.d.ts +1 -0
  70. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.single/schema.js +4 -1
  71. package/dist/runtime/components/form/index.vue +11 -3
  72. package/dist/runtime/components/form/schema.d.ts +4 -0
  73. package/dist/runtime/components/form/schema.js +1 -0
  74. package/dist/runtime/components/form/unit-config.vue +6 -1
  75. package/dist/runtime/components/form/utils/form-vars.js +2 -0
  76. package/dist/runtime/components/form/utils/resolve.d.ts +10 -0
  77. package/dist/runtime/components/table/columns/2026-05-24/com.shwfed.table.column.combobox-single.remote.options-remote/config.d.vue.ts +2 -2
  78. package/dist/runtime/components/table/columns/2026-05-24/com.shwfed.table.column.combobox-single.remote.options-remote/config.vue.d.ts +2 -2
  79. package/dist/runtime/components/table/columns/2026-05-24/com.shwfed.table.column.combobox-single.remote.options-static/config.d.vue.ts +2 -2
  80. package/dist/runtime/components/table/columns/2026-05-24/com.shwfed.table.column.combobox-single.remote.options-static/config.vue.d.ts +2 -2
  81. package/dist/runtime/components/table/columns/2026-05-25/com.shwfed.table.column.combobox-multi.remote.options-remote/config.d.vue.ts +2 -2
  82. package/dist/runtime/components/table/columns/2026-05-25/com.shwfed.table.column.combobox-multi.remote.options-remote/config.vue.d.ts +2 -2
  83. package/dist/runtime/components/table/columns/2026-05-25/com.shwfed.table.column.combobox-multi.remote.options-static/config.d.vue.ts +2 -2
  84. package/dist/runtime/components/table/columns/2026-05-25/com.shwfed.table.column.combobox-multi.remote.options-static/config.vue.d.ts +2 -2
  85. package/dist/runtime/components/table/columns/2026-05-26/com.shwfed.table.column.combobox-multi.remote/config.d.vue.ts +2 -2
  86. package/dist/runtime/components/table/columns/2026-05-26/com.shwfed.table.column.combobox-multi.remote/config.vue.d.ts +2 -2
  87. package/dist/runtime/components/table/columns/2026-05-26/com.shwfed.table.column.combobox-single.remote/config.d.vue.ts +2 -2
  88. package/dist/runtime/components/table/columns/2026-05-26/com.shwfed.table.column.combobox-single.remote/config.vue.d.ts +2 -2
  89. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-multi/config.d.vue.ts +2 -2
  90. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-multi/config.vue.d.ts +2 -2
  91. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-multi/runtime.vue +13 -2
  92. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-multi/schema.d.ts +1 -0
  93. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-multi/schema.js +4 -1
  94. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-single/config.d.vue.ts +2 -2
  95. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-single/config.vue.d.ts +2 -2
  96. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-single/runtime.vue +10 -2
  97. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-single/schema.d.ts +1 -0
  98. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-single/schema.js +4 -1
  99. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-multi/runtime.vue +16 -0
  100. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-multi/schema.d.ts +1 -0
  101. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-multi/schema.js +3 -1
  102. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-single/runtime.vue +13 -5
  103. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-single/schema.d.ts +1 -0
  104. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-single/schema.js +3 -1
  105. package/dist/runtime/components/table/config.vue +5 -1
  106. package/dist/runtime/components/table/row-provider.vue +8 -0
  107. package/dist/runtime/components/table/utils/resolve.d.ts +7 -0
  108. package/dist/runtime/components/table/utils/shared.js +4 -0
  109. package/dist/runtime/components/ui/expression-editor/CodeMirrorInput.d.vue.ts +1 -0
  110. package/dist/runtime/components/ui/expression-editor/CodeMirrorInput.vue +9 -1
  111. package/dist/runtime/components/ui/expression-editor/CodeMirrorInput.vue.d.ts +1 -0
  112. package/dist/runtime/components/ui/expression-editor/ExpressionEditor.d.vue.ts +2 -2
  113. package/dist/runtime/components/ui/expression-editor/ExpressionEditor.vue +33 -4
  114. package/dist/runtime/components/ui/expression-editor/ExpressionEditor.vue.d.ts +2 -2
  115. package/dist/runtime/components/ui/expression-editor/picker-entries.d.ts +3 -2
  116. package/dist/runtime/components/ui/expression-editor/picker-entries.js +14 -3
  117. package/dist/runtime/components/ui/expression-editor/selection-chip-extension.d.ts +4 -0
  118. package/dist/runtime/components/ui/expression-editor/selection-chip-extension.js +119 -0
  119. package/dist/runtime/components/ui/expression-editor/selection-refs.d.ts +8 -0
  120. package/dist/runtime/components/ui/expression-editor/selection-refs.js +29 -0
  121. package/dist/runtime/share/event-bus.d.ts +12 -12
  122. package/dist/runtime/utils/cel-context.d.ts +22 -0
  123. package/dist/runtime/utils/cel-context.js +8 -0
  124. package/dist/runtime/utils/selections-registry.d.ts +77 -0
  125. package/dist/runtime/utils/selections-registry.js +49 -0
  126. package/dist/runtime/vendor/cel-js/CLAUDE.md +2 -0
  127. package/dist/runtime/vendor/cel-js/PROMPT.md +8 -0
  128. package/dist/runtime/vendor/cel-js/lib/functions.js +4 -0
  129. package/package.json +1 -1
  130. package/dist/preview/assets/config-BNF2r9jW.js +0 -1
  131. package/dist/preview/assets/config-BxuGYvER.js +0 -1
  132. package/dist/preview/assets/config-Cy5w3JWQ.js +0 -1
  133. package/dist/preview/assets/config-Du5SuNSb.js +0 -1
  134. package/dist/preview/assets/config-ggyCcWNZ.js +0 -1
  135. package/dist/preview/assets/index-DsMR5NfK.css +0 -1
  136. package/dist/preview/assets/index-Yv78vz4W.js +0 -680
  137. package/dist/preview/assets/index-yrBKwEfk.js +0 -1
  138. package/dist/preview/assets/runtime-2S3Yr051.js +0 -1
  139. package/dist/preview/assets/runtime-C4jFTZ4Z.js +0 -1
  140. package/dist/runtime/share/form-validate.d.ts +0 -18
  141. package/dist/runtime/share/form-validate.js +0 -8
@@ -5,6 +5,7 @@ import { computed, ref, shallowRef, watch } from "vue";
5
5
  import { useI18n } from "vue-i18n";
6
6
  import { cel as _rawCel } from "../../../../../utils/cel";
7
7
  import { celBindings, injectCELContext } from "../../../../../utils/cel-context";
8
+ import { publishSelection } from "../../../../../utils/selections-registry";
8
9
  import { getLocalizedText } from "../../../../../share/locale";
9
10
  import { fetchJsonOption } from "../../../../../share/request";
10
11
  import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "../../../../ui/input-group";
@@ -203,6 +204,21 @@ const roots = computed(
203
204
  nodeChildren: nodeChildrenList
204
205
  })
205
206
  );
207
+ const selectedLeaves = computed(
208
+ () => committedKeys.value.map((k) => {
209
+ const path = findWrappedPath(roots.value, k);
210
+ if (!path || path.length === 0) return void 0;
211
+ return path[path.length - 1];
212
+ }).filter((node) => node !== void 0)
213
+ );
214
+ publishSelection(
215
+ props.column.id,
216
+ () => selectedLeaves.value.length > 0 ? selectedLeaves.value : void 0,
217
+ {
218
+ identity: (list) => list.map((w) => w.key).join(" "),
219
+ raw: (list) => list.map((w) => w.raw)
220
+ }
221
+ );
206
222
  const pendingKeys = ref(null);
207
223
  const open = ref(false);
208
224
  watch(open, (next, prev) => {
@@ -7,6 +7,7 @@ export declare const compatibilityDate: "2026-05-28";
7
7
  export declare const metadata: {
8
8
  readonly name: "下拉树(多选)";
9
9
  readonly icon: "fluent:tree-evergreen-20-regular";
10
+ readonly selection: true;
10
11
  };
11
12
  export declare function schema(configure: (env: Environment) => void): Schema.Struct<{
12
13
  placeholder: Schema.optional<Schema.TupleType<readonly [Schema.Struct<{
@@ -13,7 +13,9 @@ export const type = "com.shwfed.table.column.tree-combobox-multi";
13
13
  export const compatibilityDate = "2026-05-28";
14
14
  export const metadata = {
15
15
  name: "\u4E0B\u62C9\u6811\uFF08\u591A\u9009\uFF09",
16
- icon: "fluent:tree-evergreen-20-regular"
16
+ icon: "fluent:tree-evergreen-20-regular",
17
+ // Publishes its resolved nodes (array) to the per-row `selections` registry (runtime.vue), readable via `selections["<id>"]`.
18
+ selection: true
17
19
  };
18
20
  const isListLike = (t) => t === "dyn" || t.startsWith("list") || t.startsWith("optional");
19
21
  const isKeyType = (t) => t === "string" || t === "number" || t === "dyn";
@@ -5,6 +5,7 @@ import { computed, ref, shallowRef, watch } from "vue";
5
5
  import { useI18n } from "vue-i18n";
6
6
  import { cel as _rawCel } from "../../../../../utils/cel";
7
7
  import { celBindings, injectCELContext } from "../../../../../utils/cel-context";
8
+ import { publishSelection } from "../../../../../utils/selections-registry";
8
9
  import { getLocalizedText } from "../../../../../share/locale";
9
10
  import { fetchJsonOption } from "../../../../../share/request";
10
11
  import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "../../../../ui/input-group";
@@ -206,14 +207,21 @@ const roots = computed(
206
207
  nodeChildren: nodeChildrenList
207
208
  })
208
209
  );
209
- const triggerLabel = computed(() => {
210
+ const selectedWrapped = computed(() => {
210
211
  const k = model.value;
211
- if (!k) return "";
212
+ if (!k) return void 0;
212
213
  const path = findWrappedPath(roots.value, k);
213
- if (!path || path.length === 0) return "";
214
- const leaf = path[path.length - 1];
215
- return wrappedLabelText(leaf);
214
+ if (!path || path.length === 0) return void 0;
215
+ return path[path.length - 1];
216
216
  });
217
+ const triggerLabel = computed(
218
+ () => selectedWrapped.value ? wrappedLabelText(selectedWrapped.value) : ""
219
+ );
220
+ publishSelection(
221
+ props.column.id,
222
+ () => selectedWrapped.value,
223
+ { identity: (w) => w.key, raw: (w) => w.raw }
224
+ );
217
225
  const initialExpanded = computed(() => {
218
226
  if (!props.column.expandAll) return [];
219
227
  const out = [];
@@ -7,6 +7,7 @@ export declare const compatibilityDate: "2026-05-28";
7
7
  export declare const metadata: {
8
8
  readonly name: "下拉树(单选)";
9
9
  readonly icon: "fluent:tree-evergreen-20-regular";
10
+ readonly selection: true;
10
11
  };
11
12
  export declare function schema(configure: (env: Environment) => void): Schema.Struct<{
12
13
  placeholder: Schema.optional<Schema.TupleType<readonly [Schema.Struct<{
@@ -13,7 +13,9 @@ export const type = "com.shwfed.table.column.tree-combobox-single";
13
13
  export const compatibilityDate = "2026-05-28";
14
14
  export const metadata = {
15
15
  name: "\u4E0B\u62C9\u6811\uFF08\u5355\u9009\uFF09",
16
- icon: "fluent:tree-evergreen-20-regular"
16
+ icon: "fluent:tree-evergreen-20-regular",
17
+ // Publishes its resolved node (backend record) to the per-row `selections` registry (runtime.vue), readable via `selections["<id>"]`.
18
+ selection: true
17
19
  };
18
20
  const isListLike = (t) => t === "dyn" || t.startsWith("list") || t.startsWith("optional");
19
21
  const isKeyType = (t) => t === "string" || t === "number" || t === "dyn";
@@ -1,6 +1,6 @@
1
1
  <script setup>
2
2
  import { computed, defineComponent, inject, onBeforeUnmount, provide, ref, watch } from "vue";
3
- import { provideCELContext, provideScopeAncestor } from "../../utils/cel-context";
3
+ import { provideCELContext, provideScopeAncestor, provideSelectionRoster } from "../../utils/cel-context";
4
4
  import { BREADCRUMB_EXTENSION_KEY } from "../config/breadcrumb-extension";
5
5
  import { TABLE_COLUMN_LAYOUT_KEY } from "./column-layout";
6
6
  import { Icon } from "@iconify/vue";
@@ -310,6 +310,10 @@ const selectedColumnEntry = computed(() => {
310
310
  if (!c) return void 0;
311
311
  return findColumnEntry(c.type, c.compatibilityDate);
312
312
  });
313
+ const selectionRoster = computed(
314
+ () => editingColumns.value.filter((c) => findColumnEntry(c.type, c.compatibilityDate)?.metadata?.selection).map((c) => ({ id: c.id, name: columnLabel(c) }))
315
+ );
316
+ provideSelectionRoster(selectionRoster);
313
317
  const pinnedLeftIds = computed(() => {
314
318
  const ids = editingGeneralConfig.value.initialState?.columnPinning?.left;
315
319
  return new Set(Array.isArray(ids) ? ids : []);
@@ -3,6 +3,7 @@ import { Effect } from "effect";
3
3
  import { customRef } from "vue";
4
4
  import { cel as _rawCel } from "../../utils/cel";
5
5
  import { celBindings, injectCELContext, provideCELContext, provideScopeAddress } from "../../utils/cel-context";
6
+ import { provideSelectionsRegistry } from "../../utils/selections-registry";
6
7
  import { useDerived, useDerivedQuiescence } from "../form/utils/derived";
7
8
  import { provideFormState } from "../form/utils/state";
8
9
  defineOptions({ name: "ShwfedTableRowProvider" });
@@ -24,6 +25,7 @@ const rowRef = customRef((track, trigger) => ({
24
25
  trigger();
25
26
  }
26
27
  }));
28
+ const selections = provideSelectionsRegistry();
27
29
  provideCELContext({
28
30
  row: {
29
31
  type: "dyn",
@@ -36,6 +38,12 @@ provideCELContext({
36
38
  label: "\u884C\u5E8F\u53F7",
37
39
  description: "\u5F53\u524D\u884C\u5728\u6240\u6709\u6570\u636E\u4E2D\u7684\u5E8F\u53F7\uFF0C\u4ECE `0` \u5F00\u59CB\u3002",
38
40
  value: () => props.displayIndex
41
+ },
42
+ selections: {
43
+ type: "dyn",
44
+ label: "\u9009\u4E2D\u9879",
45
+ description: "\u672C\u884C\u5404\u9009\u62E9\u5217\u5F53\u524D\u9009\u4E2D\u9879\u7684\u5B8C\u6574\u5BF9\u8C61\uFF08\u6309\u5217\u6807\u8BC6\uFF09\u3002",
46
+ value: () => selections.entries.value
39
47
  }
40
48
  });
41
49
  if (props.scopeId) {
@@ -9,6 +9,13 @@ type SchemaFactory = (configure: (env: Environment) => void) => AnySchema;
9
9
  export type ColumnMetadata = Readonly<{
10
10
  name: string;
11
11
  icon: string;
12
+ /**
13
+ * True when this column publishes its resolved option to the per-row
14
+ * `selections` registry (its runtime keyed by `column.id`). Set it together
15
+ * with the runtime publish so the expression editor never advertises a column
16
+ * that writes nothing. Mirrors `FieldMetadata.selection` on the form host.
17
+ */
18
+ selection?: boolean;
12
19
  }>;
13
20
  export type ColumnDefDeps = Readonly<{
14
21
  getLocaleText: (value: LocaleValue | undefined) => string | undefined;
@@ -1,6 +1,7 @@
1
1
  import { Schema } from "effect";
2
2
  import { Icon } from "@iconify/vue";
3
3
  import { h } from "vue";
4
+ import { SELECTIONS_VAR } from "../../../utils/cel-context.js";
4
5
  import { Expression, LocaleMarkdown } from "../../../share/expression.js";
5
6
  import { Locale } from "../../../share/locale.js";
6
7
  import { md } from "../../../share/markdown.js";
@@ -97,6 +98,9 @@ export function registerRowVariablesIfAbsent(env) {
97
98
  if (!declared.has("index")) {
98
99
  env.registerVariable("index", "number", { label: "\u884C\u5E8F\u53F7", description: "\u884C\u7D22\u5F15" });
99
100
  }
101
+ if (!declared.has(SELECTIONS_VAR)) {
102
+ env.registerVariable(SELECTIONS_VAR, "dyn", { label: "\u9009\u4E2D\u9879", description: "\u672C\u884C\u5404\u9009\u62E9\u5217\u5F53\u524D\u9009\u4E2D\u9879\u7684\u5B8C\u6574\u5BF9\u8C61\uFF08\u6309\u5217\u6807\u8BC6\uFF09" });
103
+ }
100
104
  }
101
105
  function configureWithRow(configure) {
102
106
  return (env) => {
@@ -7,6 +7,7 @@ type __VLS_Props = {
7
7
  multiline?: boolean;
8
8
  placeholder?: string;
9
9
  scopeLookup: Map<string, ScopeInfo>;
10
+ selectionLookup: Map<string, string>;
10
11
  };
11
12
  declare function insertAtSelection(text: string): void;
12
13
  declare function focus(): void;
@@ -6,6 +6,7 @@ import { onBeforeUnmount, onMounted, useTemplateRef, watch } from "vue";
6
6
  import { cn } from "../../../utils/cn";
7
7
  import { celHighlighting } from "./cel-language";
8
8
  import { scopeChips, scopeLookupFacet } from "./chip-extension";
9
+ import { selectionChips, selectionLookupFacet } from "./selection-chip-extension";
9
10
  defineOptions({ name: "UiCodeMirrorInput" });
10
11
  const props = defineProps({
11
12
  class: { type: [Boolean, null, String, Object, Array], required: false, skipCheck: true },
@@ -13,12 +14,14 @@ const props = defineProps({
13
14
  modelValue: { type: String, required: false },
14
15
  multiline: { type: Boolean, required: false },
15
16
  placeholder: { type: String, required: false },
16
- scopeLookup: { type: Map, required: true }
17
+ scopeLookup: { type: Map, required: true },
18
+ selectionLookup: { type: Map, required: true }
17
19
  });
18
20
  const emits = defineEmits(["update:modelValue"]);
19
21
  const host = useTemplateRef("host");
20
22
  let view = null;
21
23
  const lookupCompartment = new Compartment();
24
+ const selectionLookupCompartment = new Compartment();
22
25
  const modeCompartment = new Compartment();
23
26
  let applyingExternal = false;
24
27
  const editorTheme = EditorView.theme({
@@ -73,6 +76,7 @@ function makeState(doc) {
73
76
  keymap.of([...defaultKeymap, ...historyKeymap]),
74
77
  cmPlaceholder(props.placeholder ?? ""),
75
78
  lookupCompartment.of(scopeLookupFacet.of(props.scopeLookup)),
79
+ selectionLookupCompartment.of(selectionLookupFacet.of(props.selectionLookup)),
76
80
  modeCompartment.of(modeExtension(!!props.multiline)),
77
81
  editorTheme,
78
82
  // CEL syntax colours sit *under* the chips: the chip plugin's
@@ -80,6 +84,7 @@ function makeState(doc) {
80
84
  // marks here only show through on ordinary expression text.
81
85
  celHighlighting(),
82
86
  scopeChips(),
87
+ selectionChips(),
83
88
  EditorView.contentAttributes.of({ "aria-label": props.placeholder ?? "Expression" }),
84
89
  EditorView.updateListener.of((update) => {
85
90
  if (!update.docChanged || applyingExternal) return;
@@ -110,6 +115,9 @@ watch(() => props.modelValue, (next) => {
110
115
  watch(() => props.scopeLookup, (lookup) => {
111
116
  view?.dispatch({ effects: lookupCompartment.reconfigure(scopeLookupFacet.of(lookup)) });
112
117
  });
118
+ watch(() => props.selectionLookup, (lookup) => {
119
+ view?.dispatch({ effects: selectionLookupCompartment.reconfigure(selectionLookupFacet.of(lookup)) });
120
+ });
113
121
  watch(() => props.multiline, (multiline) => {
114
122
  view?.dispatch({ effects: modeCompartment.reconfigure(modeExtension(!!multiline)) });
115
123
  });
@@ -7,6 +7,7 @@ type __VLS_Props = {
7
7
  multiline?: boolean;
8
8
  placeholder?: string;
9
9
  scopeLookup: Map<string, ScopeInfo>;
10
+ selectionLookup: Map<string, string>;
10
11
  };
11
12
  declare function insertAtSelection(text: string): void;
12
13
  declare function focus(): void;
@@ -14,11 +14,11 @@ type __VLS_Props = {
14
14
  extraVars?: Record<string, VarSpec>;
15
15
  unlistedVarsAreDyn?: boolean;
16
16
  };
17
- declare var __VLS_13: {}, __VLS_129: {};
17
+ declare var __VLS_13: {}, __VLS_150: {};
18
18
  type __VLS_Slots = {} & {
19
19
  leading?: (props: typeof __VLS_13) => any;
20
20
  } & {
21
- trailing?: (props: typeof __VLS_129) => any;
21
+ trailing?: (props: typeof __VLS_150) => any;
22
22
  };
23
23
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
24
24
  "update:modelValue": (payload: string) => any;
@@ -2,11 +2,12 @@
2
2
  import { Icon } from "@iconify/vue";
3
3
  import { computed, ref, useSlots, useTemplateRef, watch } from "vue";
4
4
  import { buildCheckEnvironment, evaluateExpression } from "../../../share/expression";
5
- import { injectCELContext, useScopeAncestry } from "../../../utils/cel-context";
5
+ import { injectCELContext, useScopeAncestry, useSelectionRoster } from "../../../utils/cel-context";
6
6
  import { Markdown } from "../markdown";
7
7
  import CodeMirrorInput from "./CodeMirrorInput.vue";
8
8
  import { buildScopeLookup } from "./scope-refs";
9
- import { buildScopeEntries, buildVarEntries } from "./picker-entries";
9
+ import { buildSelectionLookup } from "./selection-refs";
10
+ import { buildScopeEntries, buildSelectionEntries, buildVarEntries } from "./picker-entries";
10
11
  import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "../command";
11
12
  import { InputGroup, InputGroupAddon, InputGroupButton } from "../input-group";
12
13
  import { Popover, PopoverContent, PopoverTrigger } from "../popover";
@@ -24,10 +25,13 @@ const props = defineProps({
24
25
  const emits = defineEmits(["update:modelValue"]);
25
26
  const celContext = injectCELContext();
26
27
  const scopeAncestry = useScopeAncestry();
28
+ const selectionRoster = useSelectionRoster();
27
29
  const varEntries = computed(() => buildVarEntries(celContext, props.extraVars));
28
30
  const scopeEntries = computed(() => buildScopeEntries(scopeAncestry.value.slice(1)));
31
+ const selectionEntries = computed(() => buildSelectionEntries(selectionRoster.value));
29
32
  const scopeLookup = computed(() => buildScopeLookup(scopeAncestry.value));
30
- const hasVars = computed(() => varEntries.value.length > 0 || scopeEntries.value.length > 0);
33
+ const selectionLookup = computed(() => buildSelectionLookup(selectionRoster.value));
34
+ const hasVars = computed(() => varEntries.value.length > 0 || scopeEntries.value.length > 0 || selectionEntries.value.length > 0);
31
35
  const checkEnvironment = computed(() => {
32
36
  const vars = /* @__PURE__ */ new Map();
33
37
  for (const [name, entry] of Object.entries(celContext)) vars.set(name, entry.type);
@@ -46,6 +50,7 @@ const open = ref(false);
46
50
  const entries = ref([]);
47
51
  const shownVars = computed(() => entries.value.filter((e) => e.group === "var"));
48
52
  const shownScopes = computed(() => entries.value.filter((e) => e.group === "scope"));
53
+ const shownSelections = computed(() => entries.value.filter((e) => e.group === "selection"));
49
54
  const hoveredName = ref(null);
50
55
  const editorRef = useTemplateRef("editor");
51
56
  function insertVariable(text) {
@@ -54,7 +59,7 @@ function insertVariable(text) {
54
59
  }
55
60
  watch(open, (v) => {
56
61
  if (v) {
57
- entries.value = [...varEntries.value, ...scopeEntries.value];
62
+ entries.value = [...varEntries.value, ...selectionEntries.value, ...scopeEntries.value];
58
63
  hoveredName.value = entries.value[0]?.id ?? null;
59
64
  }
60
65
  });
@@ -91,6 +96,7 @@ const addonAlign = computed(
91
96
  :placeholder="props.placeholder"
92
97
  :multiline="props.multiline"
93
98
  :scope-lookup="scopeLookup"
99
+ :selection-lookup="selectionLookup"
94
100
  :class="props.class"
95
101
  @update:model-value="(v) => emits('update:modelValue', v)"
96
102
  />
@@ -141,6 +147,29 @@ const addonAlign = computed(
141
147
  </span>
142
148
  </CommandItem>
143
149
  </CommandGroup>
150
+ <CommandGroup
151
+ v-if="shownSelections.length > 0"
152
+ heading="选中项"
153
+ >
154
+ <CommandItem
155
+ v-for="entry in shownSelections"
156
+ :key="entry.id"
157
+ :value="`${entry.display} ${entry.id}`"
158
+ class="cursor-pointer gap-2"
159
+ @select="insertVariable(entry.insert)"
160
+ @mouseenter="hoveredName = entry.id"
161
+ @focus="hoveredName = entry.id"
162
+ >
163
+ <Icon
164
+ icon="fluent:cursor-click-20-regular"
165
+ class="size-3.5 shrink-0 text-teal-500"
166
+ />
167
+ <span class="flex-1 truncate text-xs text-zinc-700">{{ entry.display }}</span>
168
+ <span class="rounded bg-teal-100 px-1.5 py-0.5 font-mono text-[10px] leading-none text-teal-700 select-none">
169
+ {{ entry.type }}
170
+ </span>
171
+ </CommandItem>
172
+ </CommandGroup>
144
173
  <CommandGroup
145
174
  v-if="shownScopes.length > 0"
146
175
  heading="跨层引用"
@@ -14,11 +14,11 @@ type __VLS_Props = {
14
14
  extraVars?: Record<string, VarSpec>;
15
15
  unlistedVarsAreDyn?: boolean;
16
16
  };
17
- declare var __VLS_13: {}, __VLS_129: {};
17
+ declare var __VLS_13: {}, __VLS_150: {};
18
18
  type __VLS_Slots = {} & {
19
19
  leading?: (props: typeof __VLS_13) => any;
20
20
  } & {
21
- trailing?: (props: typeof __VLS_129) => any;
21
+ trailing?: (props: typeof __VLS_150) => any;
22
22
  };
23
23
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
24
24
  "update:modelValue": (payload: string) => any;
@@ -1,7 +1,7 @@
1
- import { type ScopeAncestor } from '../../../utils/cel-context.js';
1
+ import { type ScopeAncestor, type SelectionFieldRef } from '../../../utils/cel-context.js';
2
2
  export type PickEntry = {
3
3
  id: string;
4
- group: 'var' | 'scope';
4
+ group: 'var' | 'scope' | 'selection';
5
5
  insert: string;
6
6
  display: string;
7
7
  label: string;
@@ -14,4 +14,5 @@ export type PickVarMeta = {
14
14
  description?: string;
15
15
  };
16
16
  export declare function buildVarEntries(celContext: Record<string, PickVarMeta>, extraVars?: Record<string, PickVarMeta>): PickEntry[];
17
+ export declare function buildSelectionEntries(roster: ReadonlyArray<SelectionFieldRef>): PickEntry[];
17
18
  export declare function buildScopeEntries(ancestry: ReadonlyArray<ScopeAncestor>): PickEntry[];
@@ -1,19 +1,30 @@
1
- import { SCOPE_ADDRESS_VAR } from "../../../utils/cel-context.js";
1
+ import { SCOPE_ADDRESS_VAR, SELECTIONS_VAR } from "../../../utils/cel-context.js";
2
2
  import { scopeOrigin } from "./scope-refs.js";
3
+ const SUPPRESSED_VARS = /* @__PURE__ */ new Set([SCOPE_ADDRESS_VAR, SELECTIONS_VAR]);
3
4
  export function buildVarEntries(celContext, extraVars) {
4
5
  const out = [];
5
6
  const seen = /* @__PURE__ */ new Set();
6
7
  for (const [name, meta] of Object.entries(celContext)) {
7
- if (name === SCOPE_ADDRESS_VAR) continue;
8
+ if (SUPPRESSED_VARS.has(name)) continue;
8
9
  seen.add(name);
9
10
  out.push({ id: name, group: "var", insert: name, display: name, label: meta.label, type: meta.type, description: meta.description });
10
11
  }
11
12
  for (const [name, meta] of Object.entries(extraVars ?? {})) {
12
- if (seen.has(name) || name === SCOPE_ADDRESS_VAR) continue;
13
+ if (seen.has(name) || SUPPRESSED_VARS.has(name)) continue;
13
14
  out.push({ id: name, group: "var", insert: name, display: name, label: meta.label, type: meta.type, description: meta.description });
14
15
  }
15
16
  return out;
16
17
  }
18
+ export function buildSelectionEntries(roster) {
19
+ return roster.map((field) => ({
20
+ id: `selection:${field.id}`,
21
+ group: "selection",
22
+ insert: `${SELECTIONS_VAR}[${JSON.stringify(field.id)}]`,
23
+ display: field.name,
24
+ label: "\u9009\u4E2D\u9879",
25
+ type: "dyn"
26
+ }));
27
+ }
17
28
  export function buildScopeEntries(ancestry) {
18
29
  const out = [];
19
30
  for (const ancestor of ancestry) {
@@ -0,0 +1,4 @@
1
+ import { type Extension, Facet } from '@codemirror/state';
2
+ export declare const selectionLookupFacet: Facet<Map<string, string>, Map<string, string>>;
3
+ /** The full selection inline-chip extension: decoration plugin + theme. */
4
+ export declare function selectionChips(): Extension;
@@ -0,0 +1,119 @@
1
+ import { Facet, RangeSetBuilder } from "@codemirror/state";
2
+ import {
3
+ Decoration,
4
+ EditorView,
5
+ ViewPlugin,
6
+ WidgetType
7
+ } from "@codemirror/view";
8
+ import { scanSelectionRefs } from "./selection-refs.js";
9
+ export const selectionLookupFacet = Facet.define({
10
+ combine: (values) => values.length > 0 ? values[values.length - 1] : /* @__PURE__ */ new Map()
11
+ });
12
+ class SelectionChipWidget extends WidgetType {
13
+ constructor(id, name) {
14
+ super();
15
+ this.id = id;
16
+ this.name = name;
17
+ }
18
+ eq(other) {
19
+ return other.id === this.id && other.name === this.name;
20
+ }
21
+ toDOM() {
22
+ const chip = document.createElement("span");
23
+ chip.className = "cm-selection-chip";
24
+ chip.setAttribute("data-selection-id", this.id);
25
+ const tag = document.createElement("span");
26
+ tag.className = "cm-selection-chip-tag";
27
+ tag.textContent = "\u9009\u4E2D\u9879";
28
+ chip.appendChild(tag);
29
+ const text = document.createElement("span");
30
+ text.className = "cm-selection-chip-label";
31
+ if (this.name !== null) {
32
+ text.textContent = this.name;
33
+ chip.title = `\u9009\u4E2D\u9879 \xB7 ${this.name}`;
34
+ } else {
35
+ chip.classList.add("cm-selection-chip-dangling");
36
+ const short = this.id.length > 8 ? `${this.id.slice(0, 8)}\u2026` : this.id;
37
+ text.textContent = short;
38
+ chip.title = `\u672A\u77E5\u5B57\u6BB5 ${this.id}`;
39
+ }
40
+ chip.appendChild(text);
41
+ return chip;
42
+ }
43
+ ignoreEvent() {
44
+ return false;
45
+ }
46
+ }
47
+ function buildChips(view) {
48
+ const lookup = view.state.facet(selectionLookupFacet);
49
+ const builder = new RangeSetBuilder();
50
+ for (const ref of scanSelectionRefs(view.state.doc.toString())) {
51
+ const name = lookup.get(ref.id) ?? null;
52
+ builder.add(
53
+ ref.from,
54
+ ref.to,
55
+ Decoration.replace({ widget: new SelectionChipWidget(ref.id, name) })
56
+ );
57
+ }
58
+ return builder.finish();
59
+ }
60
+ const chipPlugin = ViewPlugin.fromClass(
61
+ class {
62
+ decorations;
63
+ constructor(view) {
64
+ this.decorations = buildChips(view);
65
+ }
66
+ update(update) {
67
+ if (update.docChanged || update.viewportChanged || update.startState.facet(selectionLookupFacet) !== update.state.facet(selectionLookupFacet)) {
68
+ this.decorations = buildChips(update.view);
69
+ }
70
+ }
71
+ },
72
+ {
73
+ decorations: (plugin) => plugin.decorations,
74
+ // Atomic so the caret steps over a chip and Backspace removes the whole
75
+ // `selections["id"]` at once — matching the scope chip's delete-as-unit feel.
76
+ provide: (plugin) => EditorView.atomicRanges.of((view) => view.plugin(plugin)?.decorations ?? Decoration.none)
77
+ }
78
+ );
79
+ const chipTheme = EditorView.theme({
80
+ ".cm-selection-chip": {
81
+ display: "inline-flex",
82
+ alignItems: "center",
83
+ gap: "0.25rem",
84
+ margin: "0 1px",
85
+ padding: "0.125rem 0.25rem",
86
+ borderRadius: "0.25rem",
87
+ background: "#f0fdfa",
88
+ border: "1px solid #99f6e4",
89
+ color: "#0f766e",
90
+ fontFamily: "ui-sans-serif, system-ui, sans-serif",
91
+ verticalAlign: "baseline",
92
+ cursor: "default",
93
+ whiteSpace: "nowrap"
94
+ },
95
+ ".cm-selection-chip-dangling": {
96
+ background: "#fef2f2",
97
+ borderColor: "#fecaca",
98
+ color: "#b91c1c"
99
+ },
100
+ ".cm-selection-chip-tag": {
101
+ borderRadius: "0.125rem",
102
+ background: "#ccfbf1",
103
+ padding: "0.05rem 0.2rem",
104
+ fontSize: "9px",
105
+ lineHeight: "1",
106
+ color: "#0d9488"
107
+ },
108
+ ".cm-selection-chip-dangling .cm-selection-chip-tag": {
109
+ background: "#fee2e2",
110
+ color: "#b91c1c"
111
+ },
112
+ ".cm-selection-chip-label": {
113
+ fontSize: "0.75rem",
114
+ lineHeight: "1.1"
115
+ }
116
+ });
117
+ export function selectionChips() {
118
+ return [chipPlugin, chipTheme];
119
+ }
@@ -0,0 +1,8 @@
1
+ import { type SelectionFieldRef } from '../../../utils/cel-context.js';
2
+ export type SelectionRef = {
3
+ from: number;
4
+ to: number;
5
+ id: string;
6
+ };
7
+ export declare function scanSelectionRefs(text: string): SelectionRef[];
8
+ export declare function buildSelectionLookup(roster: ReadonlyArray<SelectionFieldRef>): Map<string, string>;
@@ -0,0 +1,29 @@
1
+ import { SELECTIONS_VAR } from "../../../utils/cel-context.js";
2
+ const SELECTION_REF_RE = new RegExp(
3
+ `(?<![\\w$.])${SELECTIONS_VAR}\\[("(?:[^"\\\\]|\\\\.)*")\\]`,
4
+ "g"
5
+ );
6
+ export function scanSelectionRefs(text) {
7
+ const out = [];
8
+ SELECTION_REF_RE.lastIndex = 0;
9
+ let match;
10
+ while ((match = SELECTION_REF_RE.exec(text)) !== null) {
11
+ let id;
12
+ try {
13
+ id = JSON.parse(match[1]);
14
+ } catch {
15
+ continue;
16
+ }
17
+ out.push({
18
+ from: match.index,
19
+ to: match.index + match[0].length,
20
+ id
21
+ });
22
+ }
23
+ return out;
24
+ }
25
+ export function buildSelectionLookup(roster) {
26
+ const lookup = /* @__PURE__ */ new Map();
27
+ for (const field of roster) lookup.set(field.id, field.name);
28
+ return lookup;
29
+ }
@@ -51,19 +51,19 @@ export type EventChannel = Readonly<{
51
51
  * A handler MAY still *halt* the chain — the "guard" shape: stop everything
52
52
  * queued after it. `dispatchTriggers` runs the list with `Effect.forEach`,
53
53
  * which short-circuits the moment one request's effect aborts, so triggers
54
- * after the guard never dispatch. The page's `close` op is the canonical case
55
- * (it halts unconditionally — nothing should run after a close). It halts with
56
- * `Effect.die`, not `Effect.fail`: a defect aborts the chain without widening
57
- * the `void` error channel here (and `ActionEffect`'s `catchTags` union over in
58
- * `actions/components/group.vue`). The defect propagates to that runner's
59
- * `runPromise`, whose outer try/catch absorbs it silently `catchTags` only
60
- * intercepts typed failures, so no toast.
54
+ * after the guard never dispatch. Two ops use this: the page's `close` (halts
55
+ * unconditionally — nothing should run after a close) and the form's `validate`
56
+ * (halts only when an `error`-severity result remains, gating a submit). Both
57
+ * halt with `Effect.die`, not `Effect.fail`: a defect aborts the chain without
58
+ * widening the `void` error channel here (and `ActionEffect`'s `catchTags`
59
+ * union over in `actions/components/group.vue`). The defect propagates to that
60
+ * runner's `runPromise`, whose outer try/catch absorbs it silently — `catchTags`
61
+ * only intercepts typed failures, so no toast.
61
62
  *
62
- * Pre-submit form validation is the same guard shape but doesn't run over this
63
- * bus yet it injects the separate `share/form-validate` seam. The intended
64
- * end state folds it in as a fallible `validate` operation here, at which point
65
- * the error channel widens to carry a typed validation failure (see the note in
66
- * `form-validate.ts`).
63
+ * Pre-submit form validation rides this bus as that `validate` guard: a
64
+ * submit-style button (e.g. the HTTP-request buttons) puts it first in its own
65
+ * pre-request trigger list, so validation is explicit and configured rather
66
+ * than an implicit host gate.
67
67
  */
68
68
  export type OperationHandlers = Readonly<Record<string, () => Effect.Effect<void>>>;
69
69
  export declare const EVENT_CHANNEL_KEY: InjectionKey<EventChannel>;