@shwfed/config 2.10.7 → 2.10.9

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 (84) hide show
  1. package/dist/mcp.mjs +1171 -1049
  2. package/dist/module.json +1 -1
  3. package/dist/preview/assets/{FieldGroup.vue_vue_type_script_setup_true_lang-CV3u1wuG.js → FieldGroup.vue_vue_type_script_setup_true_lang-CysVgnB5.js} +1 -1
  4. package/dist/preview/assets/{badge-De7TI8Ef.js → badge-DdxT1J-1.js} +1 -1
  5. package/dist/preview/assets/{config-A0pwpJhu.js → config-073z__NM.js} +1 -1
  6. package/dist/preview/assets/{config-Cc-RiKc0.js → config-91HQEKhM.js} +1 -1
  7. package/dist/preview/assets/{config-DccYHX13.js → config-B8k8y1J1.js} +1 -1
  8. package/dist/preview/assets/{config-Ckn8NixF.js → config-BK8WZ3pm.js} +1 -1
  9. package/dist/preview/assets/{config-RuqJAgHJ.js → config-BX05eMo3.js} +1 -1
  10. package/dist/preview/assets/{config-BF4I0Pko.js → config-BXP8aJwW.js} +1 -1
  11. package/dist/preview/assets/{config-DqHlKqr-.js → config-BhPAyftm.js} +1 -1
  12. package/dist/preview/assets/{config-DBrYVSus.js → config-BzHBSkht.js} +1 -1
  13. package/dist/preview/assets/{config-BJL5R6TO.js → config-C3eoRHUo.js} +1 -1
  14. package/dist/preview/assets/{config-DGj2Xh1R.js → config-CJ-NzxkH.js} +1 -1
  15. package/dist/preview/assets/{config-C6AzmvwL.js → config-CYRj2ZVL.js} +1 -1
  16. package/dist/preview/assets/{config-CtW_DgDv.js → config-DLNjjMZY.js} +1 -1
  17. package/dist/preview/assets/{config-Bei-4M8U.js → config-DudxzmzF.js} +1 -1
  18. package/dist/preview/assets/{config-CnPPWHxs.js → config-cwlK3BMD.js} +1 -1
  19. package/dist/preview/assets/{definition.vue_vue_type_script_setup_true_lang-Ceh2rttd.js → definition.vue_vue_type_script_setup_true_lang-l59C6Dgs.js} +1 -1
  20. package/dist/preview/assets/index-BL58N7_5.js +763 -0
  21. package/dist/preview/assets/index-CA4hsfRv.js +1 -0
  22. package/dist/preview/assets/index-CyVzfRGQ.css +1 -0
  23. package/dist/preview/assets/{index-DAjdcrqQ.js → index-uAwC9Itb.js} +1 -1
  24. package/dist/preview/assets/{item-BNOUeB-e.js → item-C79mwh5T.js} +1 -1
  25. package/dist/preview/assets/{runtime-DzDI7B5M.js → runtime-1_EaFcPI.js} +1 -1
  26. package/dist/preview/assets/{runtime-BtDC6qh0.js → runtime-2yzxOLnF.js} +1 -1
  27. package/dist/preview/assets/{runtime-CXSBPV7c.js → runtime-8Fe67mkZ.js} +1 -1
  28. package/dist/preview/assets/{runtime-B-m0fZl0.js → runtime-8oME8Env.js} +1 -1
  29. package/dist/preview/assets/{runtime-DDz8U0kg.js → runtime-CCMixJjP.js} +1 -1
  30. package/dist/preview/assets/{runtime-CdrOV1KF.js → runtime-CD1Q4rZ3.js} +1 -1
  31. package/dist/preview/assets/{runtime-DCyGTrMp.js → runtime-Cb-qIo5p.js} +1 -1
  32. package/dist/preview/assets/{runtime-Bb0APo4d.js → runtime-CvgAq3mp.js} +1 -1
  33. package/dist/preview/assets/{runtime-D2Qtb13C.js → runtime-DkWCpjKe.js} +1 -1
  34. package/dist/preview/assets/{runtime-CtG2gR_k.js → runtime-mYNyG7UH.js} +1 -1
  35. package/dist/preview/assets/{schema-meta-DmDYh6aL.js → schema-meta-BlTKRs15.js} +1 -1
  36. package/dist/preview/index.html +2 -2
  37. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.d.vue.ts +4 -4
  38. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.vue.d.ts +4 -4
  39. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.d.vue.ts +6 -6
  40. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.vue.d.ts +6 -6
  41. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.d.vue.ts +2 -2
  42. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.vue.d.ts +2 -2
  43. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.d.vue.ts +2 -0
  44. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.vue +32 -1
  45. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.vue.d.ts +2 -0
  46. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/runtime.vue +2 -0
  47. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/schema.d.ts +2 -0
  48. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/schema.js +4 -0
  49. package/dist/runtime/components/form/fields/2026-05-17/com.shwfed.form.field.checkbox.group/config.vue +202 -112
  50. package/dist/runtime/components/form/fields/2026-05-17/com.shwfed.form.field.radio.group/config.vue +202 -112
  51. package/dist/runtime/components/form/fields/2026-05-24/com.shwfed.form.field.monthrange/config.d.vue.ts +4 -4
  52. package/dist/runtime/components/form/fields/2026-05-24/com.shwfed.form.field.monthrange/config.vue.d.ts +4 -4
  53. package/dist/runtime/components/form/fields/2026-06-14/com.shwfed.form.field.combobox.multi/config.vue +223 -132
  54. package/dist/runtime/components/form/fields/2026-06-14/com.shwfed.form.field.combobox.single/config.vue +223 -132
  55. package/dist/runtime/components/form/fields/2026-06-20/com.shwfed.form.field.tabs/config.d.vue.ts +59 -0
  56. package/dist/runtime/components/form/fields/2026-06-20/com.shwfed.form.field.tabs/config.vue +345 -0
  57. package/dist/runtime/components/form/fields/2026-06-20/com.shwfed.form.field.tabs/config.vue.d.ts +59 -0
  58. package/dist/runtime/components/form/fields/2026-06-20/com.shwfed.form.field.tabs/runtime.d.vue.ts +8 -0
  59. package/dist/runtime/components/form/fields/2026-06-20/com.shwfed.form.field.tabs/runtime.vue +113 -0
  60. package/dist/runtime/components/form/fields/2026-06-20/com.shwfed.form.field.tabs/runtime.vue.d.ts +8 -0
  61. package/dist/runtime/components/form/fields/2026-06-20/com.shwfed.form.field.tabs/schema.d.ts +79 -0
  62. package/dist/runtime/components/form/fields/2026-06-20/com.shwfed.form.field.tabs/schema.js +86 -0
  63. package/dist/runtime/components/form/unit-config.d.vue.ts +27 -0
  64. package/dist/runtime/components/form/unit-config.vue +117 -73
  65. package/dist/runtime/components/form/unit-config.vue.d.ts +27 -0
  66. package/dist/runtime/components/table/schema.js +24 -25
  67. package/dist/runtime/components/ui/date-picker/DatePickerInput.d.vue.ts +1 -1
  68. package/dist/runtime/components/ui/date-picker/DatePickerInput.vue.d.ts +1 -1
  69. package/dist/runtime/components/ui/date-picker/DatePickerTimeInput.d.vue.ts +1 -1
  70. package/dist/runtime/components/ui/date-picker/DatePickerTimeInput.vue.d.ts +1 -1
  71. package/dist/runtime/components/ui/date-range-picker/DateRangePickerInput.d.vue.ts +1 -1
  72. package/dist/runtime/components/ui/date-range-picker/DateRangePickerInput.vue.d.ts +1 -1
  73. package/dist/runtime/components/ui/date-range-picker/DateRangePickerTimeInput.d.vue.ts +2 -2
  74. package/dist/runtime/components/ui/date-range-picker/DateRangePickerTimeInput.vue.d.ts +2 -2
  75. package/dist/runtime/components/ui/markdown/Markdown.vue +19 -16
  76. package/dist/runtime/utils/markdown.d.ts +5 -2
  77. package/dist/runtime/utils/markdown.js +22 -1
  78. package/dist/runtime/vendor/cel-js/CLAUDE.md +1 -0
  79. package/dist/runtime/vendor/cel-js/lib/evaluator.d.ts +1 -0
  80. package/dist/runtime/vendor/cel-js/lib/evaluator.js +4 -0
  81. package/package.json +1 -1
  82. package/dist/preview/assets/index-BQIP6UfB.js +0 -763
  83. package/dist/preview/assets/index-Cj7bRG7B.css +0 -1
  84. package/dist/preview/assets/index-DpLMRp28.js +0 -1
@@ -0,0 +1,86 @@
1
+ import { Schema } from "effect";
2
+ import { Locale } from "../../../../../share/locale.js";
3
+ import { FormUnit } from "../../../schema.js";
4
+ export const type = "com.shwfed.form.field.tabs";
5
+ export const compatibilityDate = "2026-06-20";
6
+ export const metadata = {
7
+ name: "\u6807\u7B7E\u9875",
8
+ icon: "fluent:tabs-20-regular",
9
+ w: { initial: 12, min: 4, max: Infinity },
10
+ h: { initial: 6, min: 2, max: Infinity, grow: true },
11
+ // Hosts a nested sub-form per tab (each tab's own fields + layout) — too
12
+ // complex for the fullscreen inline-config pane; keep the drill-down.
13
+ inlineConfig: false
14
+ };
15
+ export const TabMeta = Schema.Struct({
16
+ label: Locale.annotations({
17
+ title: "\u6807\u7B7E\u540D",
18
+ description: "\u8BE5\u6807\u7B7E\u9875\u7684\u672C\u5730\u5316\u663E\u793A\u540D"
19
+ })
20
+ });
21
+ export function schema(configure) {
22
+ const Unit = Schema.suspend(() => FormUnit(configure));
23
+ const Tab = Schema.Struct({
24
+ id: Schema.UUID.annotations({ description: "\u6807\u7B7E\u9875\u552F\u4E00\u6807\u8BC6" }),
25
+ ...TabMeta.fields,
26
+ unit: Unit.annotations({
27
+ identifier: "FormFieldTabsUnit",
28
+ title: "\u6807\u7B7E\u5185\u5BB9",
29
+ description: "\u8BE5\u6807\u7B7E\u5185\u7684\u5B57\u6BB5\u4E0E\u5E03\u5C40\uFF1B\u8FD9\u4E9B\u5B57\u6BB5\u5728\u8868\u5355\u72B6\u6001\u4E2D\u4FDD\u6301\u6241\u5E73\uFF0C\u4E0D\u4F1A\u56E0\u4E3A\u5206\u7EC4\u800C\u5F15\u5165\u65B0\u7684\u72B6\u6001\u5C42\u7EA7"
30
+ })
31
+ }).annotations({
32
+ title: "Tab",
33
+ description: "\u5355\u4E2A\u6807\u7B7E\u9875\uFF1A\u540D\u79F0 + \u5185\u5BB9 Unit"
34
+ });
35
+ return Schema.Struct({
36
+ type: Schema.Literal(type),
37
+ compatibilityDate: Schema.Literal(compatibilityDate),
38
+ id: Schema.UUID.annotations({ description: "\u5B57\u6BB5\u552F\u4E00\u6807\u8BC6\uFF1B\u5E03\u5C40\u901A\u8FC7\u8BE5 id \u5F15\u7528\u5B57\u6BB5" }),
39
+ displayName: Schema.optional(Schema.String.annotations({
40
+ title: "\u5185\u90E8\u540D\u79F0",
41
+ description: "\u4EC5\u5728\u7F16\u8F91\u5668\u5185\u53EF\u89C1\u7684\u533A\u57DF\u540D\uFF0C\u7528\u4E8E\u5728\u4FA7\u8FB9\u680F\u548C\u5E03\u5C40\u7F16\u8F91\u5668\u4E2D\u8BC6\u522B\u8BE5\u6807\u7B7E\u9875\uFF1B\u8FD0\u884C\u65F6\u4E0D\u5C55\u793A"
42
+ })),
43
+ style: Schema.optional(Schema.String.annotations({
44
+ title: "\u5BB9\u5668\u6837\u5F0F",
45
+ description: "\u5E94\u7528\u5728\u6807\u7B7E\u9875\u6700\u5916\u5C42\u5BB9\u5668\u4E0A\u7684 CSS \u6837\u5F0F\u5B57\u7B26\u4E32"
46
+ })),
47
+ tabs: Schema.Array(Tab).pipe(
48
+ Schema.minItems(1),
49
+ Schema.filter((tabs) => {
50
+ const ids = /* @__PURE__ */ new Set();
51
+ for (const t of tabs) {
52
+ if (ids.has(t.id)) return `\u6807\u7B7E\u9875 id \u91CD\u590D: ${t.id}`;
53
+ ids.add(t.id);
54
+ }
55
+ return true;
56
+ })
57
+ ).annotations({
58
+ title: "\u6807\u7B7E\u9875",
59
+ description: "\u81F3\u5C11 1 \u9879\uFF0C\u6309\u987A\u5E8F\u6E32\u67D3"
60
+ })
61
+ }).annotations({
62
+ title: "TabsField",
63
+ description: "\u5C06\u4E00\u7EC4\u5E26\u6807\u7B7E\u7684\u5B50\u8868\u5355\u5355\u5143\u4F5C\u4E3A\u6807\u7B7E\u9875\u5206\u7EC4\uFF1B\u4EC5\u7528\u4E8E\u89C6\u89C9\u5206\u7EC4\uFF0C\u4E0D\u5F71\u54CD\u5B57\u6BB5\u5728\u8868\u5355\u72B6\u6001\u4E2D\u7684\u5C42\u7EA7"
64
+ });
65
+ }
66
+ function makeId() {
67
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
68
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
69
+ const r = Math.random() * 16 | 0;
70
+ return (c === "x" ? r : r & 3 | 8).toString(16);
71
+ });
72
+ }
73
+ export function defaults() {
74
+ return {
75
+ tabs: [
76
+ {
77
+ id: makeId(),
78
+ label: [{ locale: "zh", message: "\u6807\u7B7E 1" }],
79
+ unit: {
80
+ fields: [],
81
+ layouts: [{ name: "\u9ED8\u8BA4", layout: { columns: 1, placements: {} } }]
82
+ }
83
+ }
84
+ ]
85
+ };
86
+ }
@@ -33,12 +33,39 @@ type __VLS_Props = {
33
33
  * host component, above this provide.
34
34
  */
35
35
  fieldCelScope?: CELContext;
36
+ /**
37
+ * Identity of the unit currently bound to `v-model`. When a host swaps the
38
+ * bound unit *in place* (e.g. the tabs field switching the active tab's
39
+ * unit) the editor's drill stack / active-layout index belong to the old
40
+ * unit and would point at fields/layouts that don't exist in the new one.
41
+ * Changing `scopeKey` resets that transient view state without forcing a
42
+ * remount (which would flicker host-injected rail content). Hosts that own
43
+ * a single stable unit (collapsible, list, form root) leave it undefined.
44
+ */
45
+ scopeKey?: string;
36
46
  };
37
47
  type __VLS_Slots = {
38
48
  /** Pane content for the currently-selected extra. Receives `{ id }`. */
39
49
  'extras-pane'(props: {
40
50
  id: string;
41
51
  }): unknown;
52
+ /**
53
+ * Extra rail content rendered as its own half-height list above the layouts
54
+ * list (top-level view only — hidden while drilled into a field). The tabs
55
+ * field injects its tab list here so the rail reads as two parallel lists.
56
+ */
57
+ 'rail-extra'(): unknown;
58
+ /**
59
+ * Foot button for the `rail-extra` list, pinned below its scroll area (the
60
+ * tabs field's “新增标签页”). Mirrors the layouts list's “新增布局”.
61
+ */
62
+ 'rail-extra-foot'(): unknown;
63
+ /**
64
+ * Content rendered above the layout canvas in the 布局 view. The tabs field
65
+ * injects the active tab's label editor here, mirroring the block-tabs
66
+ * config's pane header.
67
+ */
68
+ 'pane-header'(): unknown;
42
69
  };
43
70
  type __VLS_ModelProps = {
44
71
  modelValue: FormUnitValue;
@@ -31,13 +31,18 @@ const selection = defineModel("selection", { type: Object, ...{
31
31
  const props = defineProps({
32
32
  extras: { type: Array, required: false },
33
33
  configure: { type: Function, required: false },
34
- fieldCelScope: { type: Object, required: false }
34
+ fieldCelScope: { type: Object, required: false },
35
+ scopeKey: { type: String, required: false }
35
36
  });
36
37
  defineSlots();
37
38
  const activeLayoutIndex = ref(0);
38
39
  const stack = ref([{ kind: "top" }]);
39
40
  const top = computed(() => stack.value[stack.value.length - 1]);
40
41
  const isDrilled = computed(() => stack.value.length > 1);
42
+ watch(() => props.scopeKey, () => {
43
+ if (stack.value.length > 1) stack.value = [{ kind: "top" }];
44
+ activeLayoutIndex.value = 0;
45
+ });
41
46
  function popOne() {
42
47
  if (stack.value.length <= 1) return;
43
48
  stack.value = stack.value.slice(0, -1);
@@ -313,47 +318,66 @@ function isExtraActive(id) {
313
318
  :to="takeoverTarget"
314
319
  >
315
320
  <template v-if="!isDrilled">
316
- <ScrollArea class="flex-1">
317
- <template v-if="extras && extras.length > 0">
318
- <div
319
- v-for="extra in extras"
320
- :key="extra.id"
321
- class="row general-row pl-2"
322
- :class="[
321
+ <!-- Extras rows pinned at the top of the rail. -->
322
+ <template v-if="extras && extras.length > 0">
323
+ <div
324
+ v-for="extra in extras"
325
+ :key="extra.id"
326
+ class="row general-row pl-2"
327
+ :class="[
323
328
  isExtraActive(extra.id) ? 'bg-[color-mix(in_srgb,var(--primary)_10%,white)] text-(--primary)' : 'text-zinc-600 hover:bg-zinc-50'
324
329
  ]"
325
- @click.stop="selectExtra(extra.id)"
326
- >
327
- <Icon
328
- :icon="extra.icon"
329
- class="size-4 shrink-0"
330
- />
331
- <span class="flex-1 truncate">{{ extra.label }}</span>
332
- </div>
330
+ @click.stop="selectExtra(extra.id)"
331
+ >
332
+ <Icon
333
+ :icon="extra.icon"
334
+ class="size-4 shrink-0"
335
+ />
336
+ <span class="flex-1 truncate">{{ extra.label }}</span>
337
+ </div>
333
338
 
334
- <Separator class="my-1" />
335
- </template>
339
+ <Separator class="my-1" />
340
+ </template>
336
341
 
337
- <LayoutsSidebar
338
- ref="sidebarRef"
339
- v-model="layoutsModel"
340
- v-model:active-index="activeLayoutIndex"
341
- :active="selection.kind === 'layout'"
342
- @select="selectLayout()"
343
- />
344
- </ScrollArea>
342
+ <!-- Host-injected rail list (e.g. the tabs field's tab list): its own
343
+ half-height scroll + foot button, so the rail reads as two parallel
344
+ lists rather than one long one. -->
345
+ <template v-if="$slots['rail-extra']">
346
+ <div class="flex flex-1 min-h-0 flex-col">
347
+ <ScrollArea class="flex-1">
348
+ <slot name="rail-extra" />
349
+ </ScrollArea>
350
+ <slot name="rail-extra-foot" />
351
+ </div>
345
352
 
346
- <Button
347
- variant="ghost"
348
- size="sm"
349
- @click="addLayout()"
350
- >
351
- <Icon
352
- icon="fluent:add-20-regular"
353
- class="size-4"
354
- />
355
- <span>新增布局</span>
356
- </Button>
353
+ <Separator class="my-1" />
354
+ </template>
355
+
356
+ <!-- Layouts list — its own half-height scroll + add button. -->
357
+ <div class="flex flex-1 min-h-0 flex-col">
358
+ <ScrollArea class="flex-1">
359
+ <LayoutsSidebar
360
+ ref="sidebarRef"
361
+ v-model="layoutsModel"
362
+ v-model:active-index="activeLayoutIndex"
363
+ :active="selection.kind === 'layout'"
364
+ @select="selectLayout()"
365
+ />
366
+ </ScrollArea>
367
+
368
+ <Button
369
+ variant="ghost"
370
+ size="sm"
371
+ class="w-full justify-start"
372
+ @click="addLayout()"
373
+ >
374
+ <Icon
375
+ icon="fluent:add-20-regular"
376
+ class="size-4"
377
+ />
378
+ <span>新增布局</span>
379
+ </Button>
380
+ </div>
357
381
  </template>
358
382
  <template v-else>
359
383
  <ScrollArea class="flex-1">
@@ -390,47 +414,66 @@ function isExtraActive(id) {
390
414
  class="flex w-64 shrink-0 flex-col"
391
415
  >
392
416
  <template v-if="!isDrilled">
393
- <ScrollArea class="flex-1">
394
- <template v-if="extras && extras.length > 0">
395
- <div
396
- v-for="extra in extras"
397
- :key="extra.id"
398
- class="row general-row pl-2"
399
- :class="[
417
+ <!-- Extras rows pinned at the top of the rail. -->
418
+ <template v-if="extras && extras.length > 0">
419
+ <div
420
+ v-for="extra in extras"
421
+ :key="extra.id"
422
+ class="row general-row pl-2"
423
+ :class="[
400
424
  isExtraActive(extra.id) ? 'bg-[color-mix(in_srgb,var(--primary)_10%,white)] text-(--primary)' : 'text-zinc-600 hover:bg-zinc-50'
401
425
  ]"
402
- @click.stop="selectExtra(extra.id)"
403
- >
404
- <Icon
405
- :icon="extra.icon"
406
- class="size-4 shrink-0"
407
- />
408
- <span class="flex-1 truncate">{{ extra.label }}</span>
409
- </div>
426
+ @click.stop="selectExtra(extra.id)"
427
+ >
428
+ <Icon
429
+ :icon="extra.icon"
430
+ class="size-4 shrink-0"
431
+ />
432
+ <span class="flex-1 truncate">{{ extra.label }}</span>
433
+ </div>
410
434
 
411
- <Separator class="my-1" />
412
- </template>
435
+ <Separator class="my-1" />
436
+ </template>
413
437
 
414
- <LayoutsSidebar
415
- ref="sidebarRef"
416
- v-model="layoutsModel"
417
- v-model:active-index="activeLayoutIndex"
418
- :active="selection.kind === 'layout'"
419
- @select="selectLayout()"
420
- />
421
- </ScrollArea>
438
+ <!-- Host-injected rail list (e.g. the tabs field's tab list): its own
439
+ half-height scroll + foot button, so the rail reads as two
440
+ parallel lists rather than one long one. -->
441
+ <template v-if="$slots['rail-extra']">
442
+ <div class="flex flex-1 min-h-0 flex-col">
443
+ <ScrollArea class="flex-1">
444
+ <slot name="rail-extra" />
445
+ </ScrollArea>
446
+ <slot name="rail-extra-foot" />
447
+ </div>
422
448
 
423
- <Button
424
- variant="ghost"
425
- size="sm"
426
- @click="addLayout()"
427
- >
428
- <Icon
429
- icon="fluent:add-20-regular"
430
- class="size-4"
431
- />
432
- <span>新增布局</span>
433
- </Button>
449
+ <Separator class="my-1" />
450
+ </template>
451
+
452
+ <!-- Layouts list — its own half-height scroll + add button. -->
453
+ <div class="flex flex-1 min-h-0 flex-col">
454
+ <ScrollArea class="flex-1">
455
+ <LayoutsSidebar
456
+ ref="sidebarRef"
457
+ v-model="layoutsModel"
458
+ v-model:active-index="activeLayoutIndex"
459
+ :active="selection.kind === 'layout'"
460
+ @select="selectLayout()"
461
+ />
462
+ </ScrollArea>
463
+
464
+ <Button
465
+ variant="ghost"
466
+ size="sm"
467
+ class="w-full justify-start"
468
+ @click="addLayout()"
469
+ >
470
+ <Icon
471
+ icon="fluent:add-20-regular"
472
+ class="size-4"
473
+ />
474
+ <span>新增布局</span>
475
+ </Button>
476
+ </div>
434
477
  </template>
435
478
  <template v-else>
436
479
  <ScrollArea class="flex-1">
@@ -473,6 +516,7 @@ function isExtraActive(id) {
473
516
  v-else-if="!isDrilled && selection.kind === 'layout'"
474
517
  class="flex flex-1 flex-col gap-2"
475
518
  >
519
+ <slot name="pane-header" />
476
520
  <LayoutMetaStrip
477
521
  v-model="layoutsModel"
478
522
  :index="activeLayoutIndex"
@@ -33,12 +33,39 @@ type __VLS_Props = {
33
33
  * host component, above this provide.
34
34
  */
35
35
  fieldCelScope?: CELContext;
36
+ /**
37
+ * Identity of the unit currently bound to `v-model`. When a host swaps the
38
+ * bound unit *in place* (e.g. the tabs field switching the active tab's
39
+ * unit) the editor's drill stack / active-layout index belong to the old
40
+ * unit and would point at fields/layouts that don't exist in the new one.
41
+ * Changing `scopeKey` resets that transient view state without forcing a
42
+ * remount (which would flicker host-injected rail content). Hosts that own
43
+ * a single stable unit (collapsible, list, form root) leave it undefined.
44
+ */
45
+ scopeKey?: string;
36
46
  };
37
47
  type __VLS_Slots = {
38
48
  /** Pane content for the currently-selected extra. Receives `{ id }`. */
39
49
  'extras-pane'(props: {
40
50
  id: string;
41
51
  }): unknown;
52
+ /**
53
+ * Extra rail content rendered as its own half-height list above the layouts
54
+ * list (top-level view only — hidden while drilled into a field). The tabs
55
+ * field injects its tab list here so the rail reads as two parallel lists.
56
+ */
57
+ 'rail-extra'(): unknown;
58
+ /**
59
+ * Foot button for the `rail-extra` list, pinned below its scroll area (the
60
+ * tabs field's “新增标签页”). Mirrors the layouts list's “新增布局”.
61
+ */
62
+ 'rail-extra-foot'(): unknown;
63
+ /**
64
+ * Content rendered above the layout canvas in the 布局 view. The tabs field
65
+ * injects the active tab's label editor here, mirroring the block-tabs
66
+ * config's pane header.
67
+ */
68
+ 'pane-header'(): unknown;
42
69
  };
43
70
  type __VLS_ModelProps = {
44
71
  modelValue: FormUnitValue;
@@ -137,52 +137,49 @@ const SORTS_VAR_DESCRIPTION = md`
137
137
 
138
138
  未排序的字段,**不会出现在这个列表中**。
139
139
  `;
140
+ function registerVariableIfAbsent(env, name, type, opts) {
141
+ const declared = env.getDefinitions().variables.some((v) => v.name === name);
142
+ if (!declared) env.registerVariable(name, type, opts);
143
+ }
140
144
  export function configureTableActionsScope(env, configure) {
141
145
  configure(env);
142
- const declared = new Set(env.getDefinitions().variables.map((v) => v.name));
143
- if (!declared.has("selected")) {
144
- env.registerVariable("selected", "list<dyn>", {
145
- label: "\u5DF2\u9009\u884C",
146
- description: "\u5F53\u524D\u5DF2\u52FE\u9009\u7684\u6240\u6709\u884C\uFF08`row` \u7684\u6570\u7EC4\uFF09"
147
- });
148
- }
149
- if (!declared.has("query")) {
150
- env.registerVariable("query", "dyn", {
151
- label: "\u641C\u7D22\u6761\u4EF6",
152
- description: QUERY_VAR_DESCRIPTION
153
- });
154
- }
155
- if (!declared.has("sorts")) {
156
- env.registerVariable("sorts", "list<map<string, string>>", {
157
- label: "\u6392\u5E8F\u72B6\u6001",
158
- description: SORTS_VAR_DESCRIPTION
159
- });
160
- }
146
+ registerVariableIfAbsent(env, "selected", "list<dyn>", {
147
+ label: "\u5DF2\u9009\u884C",
148
+ description: "\u5F53\u524D\u5DF2\u52FE\u9009\u7684\u6240\u6709\u884C\uFF08`row` \u7684\u6570\u7EC4\uFF09"
149
+ });
150
+ registerVariableIfAbsent(env, "query", "dyn", {
151
+ label: "\u641C\u7D22\u6761\u4EF6",
152
+ description: QUERY_VAR_DESCRIPTION
153
+ });
154
+ registerVariableIfAbsent(env, "sorts", "list<map<string, string>>", {
155
+ label: "\u6392\u5E8F\u72B6\u6001",
156
+ description: SORTS_VAR_DESCRIPTION
157
+ });
161
158
  }
162
159
  export function registerDataSourceRequestVars(env) {
163
- env.registerVariable("pageIndex", "number", {
160
+ registerVariableIfAbsent(env, "pageIndex", "number", {
164
161
  label: "\u5F53\u524D\u9875\u7801",
165
162
  description: "\u4ECE `0` \u5F00\u59CB\uFF1B\u672A\u542F\u7528\u5206\u9875\u65F6\u4E3A `0`"
166
163
  });
167
- env.registerVariable("pageSize", "number", {
164
+ registerVariableIfAbsent(env, "pageSize", "number", {
168
165
  label: "\u6BCF\u9875\u884C\u6570",
169
166
  description: "\u5F53\u524D\u6BCF\u9875\u884C\u6570\uFF1B\u672A\u542F\u7528\u5206\u9875\u65F6\u4E3A `0`"
170
167
  });
171
- env.registerVariable("sorts", "list<map<string, string>>", {
168
+ registerVariableIfAbsent(env, "sorts", "list<map<string, string>>", {
172
169
  label: "\u6392\u5E8F\u72B6\u6001",
173
170
  description: SORTS_VAR_DESCRIPTION
174
171
  });
175
- env.registerVariable("query", "dyn", {
172
+ registerVariableIfAbsent(env, "query", "dyn", {
176
173
  label: "\u641C\u7D22\u6761\u4EF6",
177
174
  description: QUERY_VAR_DESCRIPTION
178
175
  });
179
176
  }
180
177
  export function registerDataSourceResponseVars(env) {
181
- env.registerVariable("json", "optional<dyn>", {
178
+ registerVariableIfAbsent(env, "json", "optional<dyn>", {
182
179
  label: "HTTP \u54CD\u5E94\u4F53",
183
180
  description: JSON_VAR_DESCRIPTION
184
181
  });
185
- env.registerVariable("query", "dyn", {
182
+ registerVariableIfAbsent(env, "query", "dyn", {
186
183
  label: "\u641C\u7D22\u6761\u4EF6",
187
184
  description: QUERY_VAR_DESCRIPTION
188
185
  });
@@ -273,6 +270,8 @@ export function TableConfig(configure) {
273
270
  configure: (env) => {
274
271
  configure(env);
275
272
  registerRowVariablesIfAbsent(env);
273
+ const declared = new Set(env.getDefinitions().variables.map((v) => v.name));
274
+ if (declared.has("selected")) env.deleteVariable("selected");
276
275
  env.registerVariable("id", "string", { description: "\u5217 ID" });
277
276
  env.registerVariable("selected", "bool", { description: "\u884C\u662F\u5426\u9009\u4E2D" });
278
277
  env.registerVariable("pinned", "dyn", { description: "\u5217\u56FA\u5B9A\u72B6\u6001" });
@@ -24,8 +24,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
24
24
  "onUpdate:modelValue"?: ((args_0: Date | undefined) => any) | undefined;
25
25
  }>, {
26
26
  size: "sm" | "md" | "lg";
27
- clearable: boolean;
28
27
  clearIcon: string;
28
+ clearable: boolean;
29
29
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
30
30
  declare const _default: typeof __VLS_export;
31
31
  export default _default;
@@ -24,8 +24,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
24
24
  "onUpdate:modelValue"?: ((args_0: Date | undefined) => any) | undefined;
25
25
  }>, {
26
26
  size: "sm" | "md" | "lg";
27
- clearable: boolean;
28
27
  clearIcon: string;
28
+ clearable: boolean;
29
29
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
30
30
  declare const _default: typeof __VLS_export;
31
31
  export default _default;
@@ -22,8 +22,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
22
22
  size: "sm" | "md" | "lg";
23
23
  granularity: "hour" | "minute" | "second";
24
24
  hourCycle: 12 | 24;
25
- clearable: boolean;
26
25
  clearIcon: string;
26
+ clearable: boolean;
27
27
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
28
28
  declare const _default: typeof __VLS_export;
29
29
  export default _default;
@@ -22,8 +22,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
22
22
  size: "sm" | "md" | "lg";
23
23
  granularity: "hour" | "minute" | "second";
24
24
  hourCycle: 12 | 24;
25
- clearable: boolean;
26
25
  clearIcon: string;
26
+ clearable: boolean;
27
27
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
28
28
  declare const _default: typeof __VLS_export;
29
29
  export default _default;
@@ -29,8 +29,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
29
29
  "onUpdate:endValue"?: ((args_0: Date | undefined) => any) | undefined;
30
30
  }>, {
31
31
  size: "sm" | "md" | "lg";
32
- clearable: boolean;
33
32
  clearIcon: string;
33
+ clearable: boolean;
34
34
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
35
35
  declare const _default: typeof __VLS_export;
36
36
  export default _default;
@@ -29,8 +29,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
29
29
  "onUpdate:endValue"?: ((args_0: Date | undefined) => any) | undefined;
30
30
  }>, {
31
31
  size: "sm" | "md" | "lg";
32
- clearable: boolean;
33
32
  clearIcon: string;
33
+ clearable: boolean;
34
34
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
35
35
  declare const _default: typeof __VLS_export;
36
36
  export default _default;
@@ -26,9 +26,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
26
26
  size: "sm" | "md" | "lg";
27
27
  granularity: "hour" | "minute" | "second";
28
28
  hourCycle: 12 | 24;
29
- clearable: boolean;
30
- clearIcon: string;
31
29
  rangeSeparatorIcon: string;
30
+ clearIcon: string;
31
+ clearable: boolean;
32
32
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
33
33
  declare const _default: typeof __VLS_export;
34
34
  export default _default;
@@ -26,9 +26,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
26
26
  size: "sm" | "md" | "lg";
27
27
  granularity: "hour" | "minute" | "second";
28
28
  hourCycle: 12 | 24;
29
- clearable: boolean;
30
- clearIcon: string;
31
29
  rangeSeparatorIcon: string;
30
+ clearIcon: string;
31
+ clearable: boolean;
32
32
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
33
33
  declare const _default: typeof __VLS_export;
34
34
  export default _default;