@shwfed/config 1.0.0 → 1.0.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 (47) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +117 -23
  3. package/dist/runtime/components/config/footer.vue +0 -2
  4. package/dist/runtime/components/config/index.vue +13 -3
  5. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.d.vue.ts +2 -2
  6. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.vue +85 -10
  7. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.vue.d.ts +2 -2
  8. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/runtime.vue +18 -6
  9. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/schema.d.ts +1 -1
  10. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/schema.js +11 -4
  11. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.d.vue.ts +2 -2
  12. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.vue +85 -10
  13. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.vue.d.ts +2 -2
  14. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/runtime.vue +18 -6
  15. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/schema.d.ts +1 -1
  16. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/schema.js +11 -4
  17. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.d.vue.ts +2 -2
  18. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.vue +87 -11
  19. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.vue.d.ts +2 -2
  20. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/runtime.vue +18 -6
  21. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/schema.d.ts +1 -1
  22. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/schema.js +11 -4
  23. package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/config.vue +3 -0
  24. package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/runtime.vue +8 -0
  25. package/dist/runtime/components/form/index.vue +2 -2
  26. package/dist/runtime/components/table/ai/columns-button.vue +91 -98
  27. package/dist/runtime/components/table/ai/data-source-button.vue +88 -95
  28. package/dist/runtime/components/table/index.vue +5 -7
  29. package/dist/runtime/plugins/ai/index.d.ts +1 -5
  30. package/dist/runtime/plugins/ai/index.js +185 -441
  31. package/dist/runtime/plugins/cel/index.js +7 -0
  32. package/dist/runtime/vendor/cel-js/PROMPT.md +8 -0
  33. package/package.json +4 -4
  34. package/dist/chunks/index.mjs +0 -212742
  35. package/dist/runtime/components/ai/byok-button.d.vue.ts +0 -3
  36. package/dist/runtime/components/ai/byok-button.vue +0 -48
  37. package/dist/runtime/components/ai/byok-button.vue.d.ts +0 -3
  38. package/dist/runtime/components/ai/byok-settings.d.vue.ts +0 -3
  39. package/dist/runtime/components/ai/byok-settings.vue +0 -282
  40. package/dist/runtime/components/ai/byok-settings.vue.d.ts +0 -3
  41. package/dist/runtime/plugins/ai/gate.d.ts +0 -1
  42. package/dist/runtime/plugins/ai/gate.js +0 -8
  43. package/dist/runtime/plugins/ai/settings-state.d.ts +0 -1
  44. package/dist/runtime/plugins/ai/settings-state.js +0 -2
  45. package/dist/runtime/plugins/ai/store.d.ts +0 -17
  46. package/dist/runtime/plugins/ai/store.js +0 -40
  47. package/dist/shared/config.DW2OtAXe.mjs +0 -86529
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shwfed",
3
3
  "configKey": "shwfed",
4
- "version": "1.0.0",
4
+ "version": "1.0.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -1,23 +1,117 @@
1
- import '@nuxt/kit';
2
- export { m as default } from './shared/config.DW2OtAXe.mjs';
3
- import '@tailwindcss/vite';
4
- import 'defu';
5
- import 'node:path';
6
- import 'node:fs';
7
- import 'node:buffer';
8
- import 'node:querystring';
9
- import 'node:url';
10
- import 'node:process';
11
- import 'path';
12
- import 'constants';
13
- import 'tty';
14
- import 'util';
15
- import 'node:module';
16
- import 'module';
17
- import 'fs';
18
- import 'os';
19
- import 'child_process';
20
- import 'crypto';
21
- import 'stream';
22
- import 'events';
23
- import 'vue/compiler-sfc';
1
+ import { defineNuxtModule, createResolver, addVitePlugin, extendViteConfig, addPlugin, addRouteMiddleware, addComponent, addImports } from '@nuxt/kit';
2
+ import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
3
+ import TailwindCSS from '@tailwindcss/vite';
4
+ import { defu } from 'defu';
5
+
6
+ const module$1 = defineNuxtModule({
7
+ meta: {
8
+ name: "shwfed",
9
+ configKey: "shwfed"
10
+ },
11
+ moduleDependencies: {
12
+ "@nuxt/fonts": {
13
+ version: "^0.14.0",
14
+ defaults: {
15
+ providers: {
16
+ google: false,
17
+ googleicons: false
18
+ },
19
+ families: [
20
+ { name: "Noto Sans", provider: "bunny" },
21
+ { name: "Noto Sans SC", provider: "bunny" },
22
+ { name: "Noto Sans JP", provider: "bunny" },
23
+ { name: "Fira Mono", provider: "bunny" }
24
+ ]
25
+ }
26
+ }
27
+ },
28
+ defaults: {},
29
+ setup(options, nuxt) {
30
+ const resolver = createResolver(import.meta.url);
31
+ const resolvedConfig = defu(options, {
32
+ git: {
33
+ commit: process.env.GIT_COMMIT,
34
+ branch: process.env.GIT_LOCAL_BRANCH
35
+ },
36
+ ci: {
37
+ job: process.env.JOB_NAME,
38
+ build: process.env.BUILD_NUMBER ? Number.parseInt(process.env.BUILD_NUMBER) : void 0
39
+ },
40
+ api: {
41
+ host: process.env.NUXT_PUBLIC_API_URI,
42
+ logout: process.env.NUXT_PUBLIC_SSO_LOGOUT_URI
43
+ },
44
+ headers: {
45
+ token: process.env.NUXT_PUBLIC_TOKEN_HEADER_NAME
46
+ },
47
+ config: options
48
+ });
49
+ nuxt.options.runtimeConfig.public.shwfed = resolvedConfig;
50
+ addVitePlugin(VueI18nPlugin({
51
+ ssr: true
52
+ }));
53
+ addVitePlugin(TailwindCSS());
54
+ nuxt.options.css.push(resolver.resolve("./runtime/style.css"));
55
+ nuxt.options.css.push("vue-sonner/style.css");
56
+ extendViteConfig((config) => {
57
+ config.optimizeDeps ||= {};
58
+ config.optimizeDeps.include ||= [];
59
+ config.optimizeDeps.include.push(
60
+ "reka-ui",
61
+ "reka-ui/namespaced",
62
+ "vue-sonner",
63
+ "@tanstack/vue-table",
64
+ "@tanstack/vue-virtual",
65
+ "effect",
66
+ "fx-fetch",
67
+ "@vueuse/core",
68
+ "date-fns",
69
+ "@date-fns/tz",
70
+ "class-variance-authority",
71
+ "vue-i18n",
72
+ "markdown-it",
73
+ "@iconify/vue",
74
+ "clsx",
75
+ "tailwind-merge",
76
+ "@atlaskit/pragmatic-drag-and-drop/combine",
77
+ "@atlaskit/pragmatic-drag-and-drop/element/adapter",
78
+ "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item",
79
+ "vaul-vue",
80
+ "dot-prop",
81
+ "@internationalized/date",
82
+ "reka-ui/date"
83
+ );
84
+ });
85
+ addPlugin(resolver.resolve("./runtime/plugins/i18n"));
86
+ addPlugin(resolver.resolve("./runtime/plugins/http"));
87
+ addPlugin(resolver.resolve("./runtime/plugins/cel"));
88
+ addPlugin(resolver.resolve("./runtime/plugins/toast"));
89
+ addPlugin(resolver.resolve("./runtime/plugins/ai"));
90
+ addRouteMiddleware({
91
+ path: resolver.resolve("./runtime/middleware/token"),
92
+ name: "shwfed-token",
93
+ global: true
94
+ });
95
+ addComponent({
96
+ name: "ShwfedApp",
97
+ filePath: resolver.resolve("./runtime/components/app.vue")
98
+ });
99
+ addComponent({
100
+ name: "ShwfedModal",
101
+ filePath: resolver.resolve("./runtime/components/modal.vue")
102
+ });
103
+ addComponent({
104
+ name: "ShwfedConfig",
105
+ filePath: resolver.resolve("./runtime/components/config/index.vue")
106
+ });
107
+ addImports([
108
+ { name: "provideCELContext", from: resolver.resolve("./runtime/plugins/cel/context") },
109
+ { name: "openModal", from: resolver.resolve("./runtime/composables/useOverlay") },
110
+ { name: "confirm", from: resolver.resolve("./runtime/composables/useOverlay") },
111
+ { name: "reject", from: resolver.resolve("./runtime/composables/useOverlay") },
112
+ { name: "useCel", from: resolver.resolve("./runtime/composables/useCel") }
113
+ ]);
114
+ }
115
+ });
116
+
117
+ export { module$1 as default };
@@ -1,6 +1,5 @@
1
1
  <script setup>
2
2
  import { Icon } from "@iconify/vue";
3
- import AiByokButton from "../ai/byok-button.vue";
4
3
  import { Button } from "../ui/button";
5
4
  defineOptions({ name: "ShwfedConfigEditorFooter" });
6
5
  const props = defineProps({
@@ -23,7 +22,6 @@ function onConfirm() {
23
22
 
24
23
  <template>
25
24
  <div class="flex w-full items-center justify-between gap-3">
26
- <AiByokButton />
27
25
  <div class="flex flex-1 flex-col gap-1 text-xs">
28
26
  <span
29
27
  v-if="props.state.validationError.value"
@@ -1,5 +1,6 @@
1
1
  <script setup>
2
2
  import { ref, watch } from "vue";
3
+ import { injectCELContext } from "../../plugins/cel/context";
3
4
  import SlotRenderer from "../../share/slot-renderer.vue";
4
5
  import ShwfedModal from "../modal.vue";
5
6
  import EditorBody from "./config.vue";
@@ -13,11 +14,20 @@ const props = defineProps({
13
14
  configure: { type: Function, required: false },
14
15
  editable: { type: Boolean, required: false }
15
16
  });
16
- const configure = props.configure ?? (() => {
17
+ const inheritedCEL = injectCELContext();
18
+ const hostConfigure = props.configure ?? (() => {
17
19
  });
20
+ const mergedConfigure = (env) => {
21
+ hostConfigure(env);
22
+ const existing = new Set(env.getDefinitions().variables.map((v) => v.name));
23
+ for (const [name, v] of Object.entries(inheritedCEL)) {
24
+ if (existing.has(name)) continue;
25
+ env.registerVariable(name, v.type, { description: v.description });
26
+ }
27
+ };
18
28
  const editorOpen = ref(false);
19
29
  const editorState = useConfigEditor(config, {
20
- configure,
30
+ configure: mergedConfigure,
21
31
  onClose: () => {
22
32
  editorOpen.value = false;
23
33
  }
@@ -45,7 +55,7 @@ function updateSlot(next) {
45
55
  >
46
56
  <SlotRenderer
47
57
  :slot-value="config.slot"
48
- :configure="configure"
58
+ :configure="mergedConfigure"
49
59
  :find-entry="findBlock"
50
60
  @update:slot-value="updateSlot"
51
61
  >
@@ -44,7 +44,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
44
44
  readonly locale: "en" | "ja" | "ko";
45
45
  readonly message: string;
46
46
  }[]] | undefined;
47
- readonly binding?: string | undefined;
47
+ readonly binding?: string | readonly [string, string] | undefined;
48
48
  readonly presets?: readonly {
49
49
  readonly label: readonly [{
50
50
  readonly locale: "zh";
@@ -100,7 +100,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
100
100
  readonly locale: "en" | "ja" | "ko";
101
101
  readonly message: string;
102
102
  }[]] | undefined;
103
- readonly binding?: string | undefined;
103
+ readonly binding?: string | readonly [string, string] | undefined;
104
104
  readonly presets?: readonly {
105
105
  readonly label: readonly [{
106
106
  readonly locale: "zh";
@@ -69,8 +69,11 @@ function setPresetStart(index, expr) {
69
69
  function setPresetEnd(index, expr) {
70
70
  patchPreset(index, { end: expr });
71
71
  }
72
- const pathText = computed({
73
- get: () => value.value.binding ?? "",
72
+ const bindingMode = computed(
73
+ () => Array.isArray(value.value.binding) ? "split" : "single"
74
+ );
75
+ const singlePathText = computed({
76
+ get: () => typeof value.value.binding === "string" ? value.value.binding : "",
74
77
  set: (next) => {
75
78
  const trimmed = next.trim();
76
79
  if (trimmed.length === 0) {
@@ -81,6 +84,28 @@ const pathText = computed({
81
84
  }
82
85
  }
83
86
  });
87
+ const splitStartText = computed(
88
+ () => Array.isArray(value.value.binding) ? value.value.binding[0] : ""
89
+ );
90
+ const splitEndText = computed(
91
+ () => Array.isArray(value.value.binding) ? value.value.binding[1] : ""
92
+ );
93
+ function setSplitStart(next) {
94
+ const cur = Array.isArray(value.value.binding) ? value.value.binding : ["", ""];
95
+ value.value = { ...value.value, binding: [next.trim(), cur[1]] };
96
+ }
97
+ function setSplitEnd(next) {
98
+ const cur = Array.isArray(value.value.binding) ? value.value.binding : ["", ""];
99
+ value.value = { ...value.value, binding: [cur[0], next.trim()] };
100
+ }
101
+ function toggleBindingMode() {
102
+ if (bindingMode.value === "single") {
103
+ value.value = { ...value.value, binding: ["", ""] };
104
+ } else {
105
+ const { binding: _omit, ...rest } = value.value;
106
+ value.value = rest;
107
+ }
108
+ }
84
109
  const formatText = computed({
85
110
  get: () => value.value.format ?? "",
86
111
  set: (next) => {
@@ -167,20 +192,70 @@ const valueFormatExample = computed(() => previewFormat(value.value.valueFormat
167
192
  <FieldLabel class="text-xs text-zinc-500">
168
193
  <template #tooltip>
169
194
  <Markdown
170
- source="写入表单状态的嵌套键路径,使用 `.` 分隔;写入值为 `[start, end]` 字符串数组"
195
+ source="写入表单状态的嵌套键路径,使用 `.` 分隔。单路径模式写入 `[start, end]` 字符串数组;拆分模式分别写入起始与结束两端"
171
196
  block
172
197
  class="prose prose-sm prose-zinc"
173
198
  />
174
199
  </template>
175
200
  {{ fieldTitle("binding") }}
176
201
  </FieldLabel>
177
- <InputGroup>
178
- <InputGroupInput
179
- v-model="pathText"
180
- placeholder="例:event.range"
181
- class="font-mono"
182
- />
183
- </InputGroup>
202
+ <template v-if="bindingMode === 'single'">
203
+ <InputGroup>
204
+ <InputGroupInput
205
+ v-model="singlePathText"
206
+ placeholder="例:event.range"
207
+ class="font-mono"
208
+ />
209
+ <InputGroupAddon align="inline-end">
210
+ <InputGroupButton
211
+ size="icon-xs"
212
+ aria-label="拆分起止绑定路径"
213
+ @click="toggleBindingMode"
214
+ >
215
+ <Icon icon="fluent:split-horizontal-20-regular" />
216
+ </InputGroupButton>
217
+ </InputGroupAddon>
218
+ </InputGroup>
219
+ </template>
220
+ <template v-else>
221
+ <div class="grid grid-cols-2 gap-2">
222
+ <InputGroup>
223
+ <InputGroupAddon align="inline-start">
224
+ <InputGroupText class="text-xs text-zinc-400">
225
+
226
+ </InputGroupText>
227
+ </InputGroupAddon>
228
+ <InputGroupInput
229
+ :model-value="splitStartText"
230
+ placeholder="例:event.start"
231
+ class="font-mono"
232
+ @update:model-value="(v) => setSplitStart(String(v ?? ''))"
233
+ />
234
+ </InputGroup>
235
+ <InputGroup>
236
+ <InputGroupAddon align="inline-start">
237
+ <InputGroupText class="text-xs text-zinc-400">
238
+
239
+ </InputGroupText>
240
+ </InputGroupAddon>
241
+ <InputGroupInput
242
+ :model-value="splitEndText"
243
+ placeholder="例:event.end"
244
+ class="font-mono"
245
+ @update:model-value="(v) => setSplitEnd(String(v ?? ''))"
246
+ />
247
+ <InputGroupAddon align="inline-end">
248
+ <InputGroupButton
249
+ size="icon-xs"
250
+ aria-label="合并起止绑定路径"
251
+ @click="toggleBindingMode"
252
+ >
253
+ <Icon icon="fluent:merge-vertical-20-regular" />
254
+ </InputGroupButton>
255
+ </InputGroupAddon>
256
+ </InputGroup>
257
+ </div>
258
+ </template>
184
259
  </Field>
185
260
  </div>
186
261
 
@@ -44,7 +44,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
44
44
  readonly locale: "en" | "ja" | "ko";
45
45
  readonly message: string;
46
46
  }[]] | undefined;
47
- readonly binding?: string | undefined;
47
+ readonly binding?: string | readonly [string, string] | undefined;
48
48
  readonly presets?: readonly {
49
49
  readonly label: readonly [{
50
50
  readonly locale: "zh";
@@ -100,7 +100,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
100
100
  readonly locale: "en" | "ja" | "ko";
101
101
  readonly message: string;
102
102
  }[]] | undefined;
103
- readonly binding?: string | undefined;
103
+ readonly binding?: string | readonly [string, string] | undefined;
104
104
  readonly presets?: readonly {
105
105
  readonly label: readonly [{
106
106
  readonly locale: "zh";
@@ -71,20 +71,32 @@ function asRange(raw) {
71
71
  if (a.length === 0 || b.length === 0) return void 0;
72
72
  return [a, b];
73
73
  }
74
+ function asString(raw) {
75
+ return typeof raw === "string" && raw.length > 0 ? raw : void 0;
76
+ }
74
77
  const uncontrolled = ref(void 0);
75
78
  const model = computed({
76
79
  get: () => {
77
- const path = props.config.binding;
78
- if (path == null) return uncontrolled.value;
79
- return asRange(getAt(path));
80
+ const binding = props.config.binding;
81
+ if (binding == null) return uncontrolled.value;
82
+ if (typeof binding === "string") return asRange(getAt(binding));
83
+ const a = asString(getAt(binding[0]));
84
+ const b = asString(getAt(binding[1]));
85
+ if (a == null || b == null) return void 0;
86
+ return [a, b];
80
87
  },
81
88
  set: (next) => {
82
- const path = props.config.binding;
83
- if (path == null) {
89
+ const binding = props.config.binding;
90
+ if (binding == null) {
84
91
  uncontrolled.value = next;
85
92
  return;
86
93
  }
87
- setAt(path, next);
94
+ if (typeof binding === "string") {
95
+ setAt(binding, next);
96
+ return;
97
+ }
98
+ setAt(binding[0], next?.[0]);
99
+ setAt(binding[1], next?.[1]);
88
100
  }
89
101
  });
90
102
  const readonlyText = computed(() => {
@@ -58,7 +58,7 @@ export declare function schema(configure: (env: Environment) => void): Schema.St
58
58
  message: Schema.SchemaClass<string, string, never>;
59
59
  }>]>>;
60
60
  orientation: Schema.optional<Schema.Literal<["vertical", "floating"]>>;
61
- binding: Schema.optional<Schema.refine<string, typeof Schema.String>>;
61
+ binding: Schema.optional<Schema.Union<[Schema.filter<typeof Schema.String>, Schema.filter<Schema.Tuple2<Schema.filter<typeof Schema.String>, Schema.filter<typeof Schema.String>>>]>>;
62
62
  disabled: Schema.optional<Schema.Schema<string, string, never>>;
63
63
  readonly: Schema.optional<Schema.Schema<string, string, never>>;
64
64
  format: Schema.optional<Schema.refine<string, typeof Schema.String>>;
@@ -4,6 +4,16 @@ import { Locale } from "../../../../../share/locale.js";
4
4
  import { commonFieldFields, FieldOrientationSchema } from "../../../utils/common.js";
5
5
  export const type = "com.shwfed.form.field.daterange";
6
6
  export const compatibilityDate = "2026-04-27";
7
+ const BindingPath = Schema.String.pipe(Schema.minLength(1));
8
+ const SplitBinding = Schema.Tuple(BindingPath, BindingPath).pipe(
9
+ Schema.filter(([a, b]) => a !== b, {
10
+ message: () => "\u8D77\u59CB\u4E0E\u7ED3\u675F\u7ED1\u5B9A\u8DEF\u5F84\u4E0D\u80FD\u76F8\u540C"
11
+ })
12
+ );
13
+ const bindingSchema = Schema.Union(BindingPath, SplitBinding).annotations({
14
+ title: "\u7ED1\u5B9A\u8DEF\u5F84",
15
+ description: "\u5199\u5165\u8868\u5355\u72B6\u6001\u7684 `dot-prop` \u8DEF\u5F84\uFF1B\u586B\u5199\u5355\u4E2A\u8DEF\u5F84\u65F6\u5199\u5165 `[start, end]` \u5B57\u7B26\u4E32\u6570\u7EC4\uFF0C\u4F8B\u5982 `event.range`\uFF1B\u586B\u5199 `[\u8D77\u59CB\u8DEF\u5F84, \u7ED3\u675F\u8DEF\u5F84]` \u5143\u7EC4\u65F6\u5206\u522B\u5199\u5165\u4E24\u7AEF\uFF1B\u7559\u7A7A\u5219\u4E3A\u975E\u53D7\u63A7\u5B57\u6BB5"
16
+ });
7
17
  export const metadata = {
8
18
  name: "\u65E5\u671F\u8303\u56F4",
9
19
  icon: "fluent:calendar-arrow-right-20-regular",
@@ -54,10 +64,7 @@ export function schema(configure) {
54
64
  description: "\u9F20\u6807\u60AC\u505C\u5728\u6807\u7B7E\u4E0A\u65F6\u5C55\u793A\u7684\u8BF4\u660E"
55
65
  })),
56
66
  orientation: Schema.optional(FieldOrientationSchema),
57
- binding: Schema.optional(Schema.String.pipe(Schema.minLength(1)).annotations({
58
- title: "\u7ED1\u5B9A\u8DEF\u5F84",
59
- description: "\u5199\u5165\u8868\u5355\u72B6\u6001\u7684 `dot-prop` \u8DEF\u5F84\uFF0C\u4F8B\u5982 `event.range`\uFF1B\u5199\u5165\u503C\u4E3A `[start, end]` \u5B57\u7B26\u4E32\u6570\u7EC4\uFF1B\u7559\u7A7A\u5219\u4E3A\u975E\u53D7\u63A7\u5B57\u6BB5"
60
- })),
67
+ binding: Schema.optional(bindingSchema),
61
68
  disabled: Schema.optional(CelBool.annotations({
62
69
  title: "\u7981\u7528\u6761\u4EF6",
63
70
  description: "\u8FD4\u56DE `true` \u65F6\u8F93\u5165\u6846\u4ECD\u7136\u6E32\u67D3\u4F46\u4E0D\u53EF\u7F16\u8F91"
@@ -46,7 +46,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
46
46
  readonly locale: "en" | "ja" | "ko";
47
47
  readonly message: string;
48
48
  }[]] | undefined;
49
- readonly binding?: string | undefined;
49
+ readonly binding?: string | readonly [string, string] | undefined;
50
50
  readonly presets?: readonly {
51
51
  readonly label: readonly [{
52
52
  readonly locale: "zh";
@@ -104,7 +104,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
104
104
  readonly locale: "en" | "ja" | "ko";
105
105
  readonly message: string;
106
106
  }[]] | undefined;
107
- readonly binding?: string | undefined;
107
+ readonly binding?: string | readonly [string, string] | undefined;
108
108
  readonly presets?: readonly {
109
109
  readonly label: readonly [{
110
110
  readonly locale: "zh";
@@ -69,8 +69,11 @@ function setPresetStart(index, expr) {
69
69
  function setPresetEnd(index, expr) {
70
70
  patchPreset(index, { end: expr });
71
71
  }
72
- const pathText = computed({
73
- get: () => value.value.binding ?? "",
72
+ const bindingMode = computed(
73
+ () => Array.isArray(value.value.binding) ? "split" : "single"
74
+ );
75
+ const singlePathText = computed({
76
+ get: () => typeof value.value.binding === "string" ? value.value.binding : "",
74
77
  set: (next) => {
75
78
  const trimmed = next.trim();
76
79
  if (trimmed.length === 0) {
@@ -81,6 +84,28 @@ const pathText = computed({
81
84
  }
82
85
  }
83
86
  });
87
+ const splitStartText = computed(
88
+ () => Array.isArray(value.value.binding) ? value.value.binding[0] : ""
89
+ );
90
+ const splitEndText = computed(
91
+ () => Array.isArray(value.value.binding) ? value.value.binding[1] : ""
92
+ );
93
+ function setSplitStart(next) {
94
+ const cur = Array.isArray(value.value.binding) ? value.value.binding : ["", ""];
95
+ value.value = { ...value.value, binding: [next.trim(), cur[1]] };
96
+ }
97
+ function setSplitEnd(next) {
98
+ const cur = Array.isArray(value.value.binding) ? value.value.binding : ["", ""];
99
+ value.value = { ...value.value, binding: [cur[0], next.trim()] };
100
+ }
101
+ function toggleBindingMode() {
102
+ if (bindingMode.value === "single") {
103
+ value.value = { ...value.value, binding: ["", ""] };
104
+ } else {
105
+ const { binding: _omit, ...rest } = value.value;
106
+ value.value = rest;
107
+ }
108
+ }
84
109
  const formatText = computed({
85
110
  get: () => value.value.format ?? "",
86
111
  set: (next) => {
@@ -189,20 +214,70 @@ const valueFormatExample = computed(() => previewFormat(value.value.valueFormat
189
214
  <FieldLabel class="text-xs text-zinc-500">
190
215
  <template #tooltip>
191
216
  <Markdown
192
- source="写入表单状态的嵌套键路径,使用 `.` 分隔;写入值为 `[start, end]` 字符串数组"
217
+ source="写入表单状态的嵌套键路径,使用 `.` 分隔。单路径模式写入 `[start, end]` 字符串数组;拆分模式分别写入起始与结束两端"
193
218
  block
194
219
  class="prose prose-sm prose-zinc"
195
220
  />
196
221
  </template>
197
222
  {{ fieldTitle("binding") }}
198
223
  </FieldLabel>
199
- <InputGroup>
200
- <InputGroupInput
201
- v-model="pathText"
202
- placeholder="例:event.range"
203
- class="font-mono"
204
- />
205
- </InputGroup>
224
+ <template v-if="bindingMode === 'single'">
225
+ <InputGroup>
226
+ <InputGroupInput
227
+ v-model="singlePathText"
228
+ placeholder="例:event.range"
229
+ class="font-mono"
230
+ />
231
+ <InputGroupAddon align="inline-end">
232
+ <InputGroupButton
233
+ size="icon-xs"
234
+ aria-label="拆分起止绑定路径"
235
+ @click="toggleBindingMode"
236
+ >
237
+ <Icon icon="fluent:split-horizontal-20-regular" />
238
+ </InputGroupButton>
239
+ </InputGroupAddon>
240
+ </InputGroup>
241
+ </template>
242
+ <template v-else>
243
+ <div class="grid grid-cols-2 gap-2">
244
+ <InputGroup>
245
+ <InputGroupAddon align="inline-start">
246
+ <InputGroupText class="text-xs text-zinc-400">
247
+
248
+ </InputGroupText>
249
+ </InputGroupAddon>
250
+ <InputGroupInput
251
+ :model-value="splitStartText"
252
+ placeholder="例:event.start"
253
+ class="font-mono"
254
+ @update:model-value="(v) => setSplitStart(String(v ?? ''))"
255
+ />
256
+ </InputGroup>
257
+ <InputGroup>
258
+ <InputGroupAddon align="inline-start">
259
+ <InputGroupText class="text-xs text-zinc-400">
260
+
261
+ </InputGroupText>
262
+ </InputGroupAddon>
263
+ <InputGroupInput
264
+ :model-value="splitEndText"
265
+ placeholder="例:event.end"
266
+ class="font-mono"
267
+ @update:model-value="(v) => setSplitEnd(String(v ?? ''))"
268
+ />
269
+ <InputGroupAddon align="inline-end">
270
+ <InputGroupButton
271
+ size="icon-xs"
272
+ aria-label="合并起止绑定路径"
273
+ @click="toggleBindingMode"
274
+ >
275
+ <Icon icon="fluent:merge-vertical-20-regular" />
276
+ </InputGroupButton>
277
+ </InputGroupAddon>
278
+ </InputGroup>
279
+ </div>
280
+ </template>
206
281
  </Field>
207
282
  </div>
208
283
 
@@ -46,7 +46,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
46
46
  readonly locale: "en" | "ja" | "ko";
47
47
  readonly message: string;
48
48
  }[]] | undefined;
49
- readonly binding?: string | undefined;
49
+ readonly binding?: string | readonly [string, string] | undefined;
50
50
  readonly presets?: readonly {
51
51
  readonly label: readonly [{
52
52
  readonly locale: "zh";
@@ -104,7 +104,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
104
104
  readonly locale: "en" | "ja" | "ko";
105
105
  readonly message: string;
106
106
  }[]] | undefined;
107
- readonly binding?: string | undefined;
107
+ readonly binding?: string | readonly [string, string] | undefined;
108
108
  readonly presets?: readonly {
109
109
  readonly label: readonly [{
110
110
  readonly locale: "zh";
@@ -71,20 +71,32 @@ function asRange(raw) {
71
71
  if (a.length === 0 || b.length === 0) return void 0;
72
72
  return [a, b];
73
73
  }
74
+ function asString(raw) {
75
+ return typeof raw === "string" && raw.length > 0 ? raw : void 0;
76
+ }
74
77
  const uncontrolled = ref(void 0);
75
78
  const model = computed({
76
79
  get: () => {
77
- const path = props.config.binding;
78
- if (path == null) return uncontrolled.value;
79
- return asRange(getAt(path));
80
+ const binding = props.config.binding;
81
+ if (binding == null) return uncontrolled.value;
82
+ if (typeof binding === "string") return asRange(getAt(binding));
83
+ const a = asString(getAt(binding[0]));
84
+ const b = asString(getAt(binding[1]));
85
+ if (a == null || b == null) return void 0;
86
+ return [a, b];
80
87
  },
81
88
  set: (next) => {
82
- const path = props.config.binding;
83
- if (path == null) {
89
+ const binding = props.config.binding;
90
+ if (binding == null) {
84
91
  uncontrolled.value = next;
85
92
  return;
86
93
  }
87
- setAt(path, next);
94
+ if (typeof binding === "string") {
95
+ setAt(binding, next);
96
+ return;
97
+ }
98
+ setAt(binding[0], next?.[0]);
99
+ setAt(binding[1], next?.[1]);
88
100
  }
89
101
  });
90
102
  const readonlyText = computed(() => {
@@ -68,7 +68,7 @@ export declare function schema(configure: (env: Environment) => void): Schema.St
68
68
  message: Schema.SchemaClass<string, string, never>;
69
69
  }>]>>;
70
70
  orientation: Schema.optional<Schema.Literal<["vertical", "floating"]>>;
71
- binding: Schema.optional<Schema.refine<string, typeof Schema.String>>;
71
+ binding: Schema.optional<Schema.Union<[Schema.filter<typeof Schema.String>, Schema.filter<Schema.Tuple2<Schema.filter<typeof Schema.String>, Schema.filter<typeof Schema.String>>>]>>;
72
72
  disabled: Schema.optional<Schema.Schema<string, string, never>>;
73
73
  readonly: Schema.optional<Schema.Schema<string, string, never>>;
74
74
  format: Schema.optional<Schema.refine<string, typeof Schema.String>>;