@shwfed/config 2.2.1 → 2.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.form/schema.d.ts +5 -2
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/schema.d.ts +5 -2
- package/dist/runtime/components/form/FormUnitRenderer.d.vue.ts +9 -0
- package/dist/runtime/components/form/FormUnitRenderer.vue +117 -0
- package/dist/runtime/components/form/FormUnitRenderer.vue.d.ts +9 -0
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.d.vue.ts +2 -2
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.vue +1 -1
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.vue.d.ts +2 -2
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/row.vue +7 -104
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/schema.d.ts +1 -1
- package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/config.d.vue.ts +53 -0
- package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/config.vue +130 -0
- package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/config.vue.d.ts +53 -0
- package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/runtime.d.vue.ts +8 -0
- package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/runtime.vue +107 -0
- package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/runtime.vue.d.ts +8 -0
- package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/schema.d.ts +54 -0
- package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/schema.js +47 -0
- package/dist/runtime/components/form/index.vue +16 -108
- package/dist/runtime/components/form/schema.d.ts +5 -2
- package/dist/runtime/components/form/schema.js +30 -20
- package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.number/runtime.vue +1 -0
- package/dist/runtime/components/table/schema.d.ts +5 -2
- package/dist/runtime/components/ui/date-picker/DatePicker.vue +0 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -80,10 +80,13 @@ export declare function schema(configure: (env: Environment) => void, _blockRef:
|
|
|
80
80
|
}>;
|
|
81
81
|
}>>>;
|
|
82
82
|
kind: Schema.tag<"shwfed.component.form">;
|
|
83
|
-
initial: Schema.optional<Schema.Struct<{
|
|
83
|
+
initial: Schema.optional<Schema.transform<Schema.Union<[Schema.Schema<string, string, never>, Schema.Struct<{
|
|
84
84
|
request: Schema.optional<Schema.Schema<string, string, never>>;
|
|
85
85
|
data: Schema.Schema<string, string, never>;
|
|
86
|
-
}
|
|
86
|
+
}>]>, Schema.Struct<{
|
|
87
|
+
request: Schema.optional<Schema.Schema<string, string, never>>;
|
|
88
|
+
data: Schema.Schema<string, string, never>;
|
|
89
|
+
}>>>;
|
|
87
90
|
readonly: Schema.optional<Schema.Schema<string, string, never>>;
|
|
88
91
|
}>>;
|
|
89
92
|
}>;
|
|
@@ -347,10 +347,13 @@ export declare function schema(configure: (env: Environment) => void, _blockRef:
|
|
|
347
347
|
}>;
|
|
348
348
|
}>>>;
|
|
349
349
|
kind: Schema.tag<"shwfed.component.form">;
|
|
350
|
-
initial: Schema.optional<Schema.Struct<{
|
|
350
|
+
initial: Schema.optional<Schema.transform<Schema.Union<[Schema.Schema<string, string, never>, Schema.Struct<{
|
|
351
351
|
request: Schema.optional<Schema.Schema<string, string, never>>;
|
|
352
352
|
data: Schema.Schema<string, string, never>;
|
|
353
|
-
}
|
|
353
|
+
}>]>, Schema.Struct<{
|
|
354
|
+
request: Schema.optional<Schema.Schema<string, string, never>>;
|
|
355
|
+
data: Schema.Schema<string, string, never>;
|
|
356
|
+
}>>>;
|
|
354
357
|
readonly: Schema.optional<Schema.Schema<string, string, never>>;
|
|
355
358
|
}>>>;
|
|
356
359
|
cellStyle: Schema.optional<Schema.Schema<string, string, never>>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FieldValue, FormUnitValue } from './schema.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
unit: FormUnitValue;
|
|
4
|
+
evaluateMedia: (expression: string) => boolean;
|
|
5
|
+
isHidden?: (field: FieldValue) => boolean;
|
|
6
|
+
};
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
|
+
declare const _default: typeof __VLS_export;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, onMounted, ref, watch } from "vue";
|
|
3
|
+
import { DEFAULT_GAP } from "./schema";
|
|
4
|
+
import { findField } from "./utils/resolve";
|
|
5
|
+
defineOptions({ name: "ShwfedFormUnitRenderer" });
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
unit: { type: Object, required: true },
|
|
8
|
+
evaluateMedia: { type: Function, required: true },
|
|
9
|
+
isHidden: { type: Function, required: false }
|
|
10
|
+
});
|
|
11
|
+
function pickLayout(sets) {
|
|
12
|
+
for (const set of sets) {
|
|
13
|
+
if (!set.media) return set;
|
|
14
|
+
try {
|
|
15
|
+
if (props.evaluateMedia(set.media)) return set;
|
|
16
|
+
} catch (err) {
|
|
17
|
+
console.error(`[shwfed-form] failed to evaluate layout media for ${set.name}:`, err);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return sets[sets.length - 1];
|
|
21
|
+
}
|
|
22
|
+
const activeLayout = ref();
|
|
23
|
+
onMounted(() => {
|
|
24
|
+
activeLayout.value = pickLayout(props.unit.layouts);
|
|
25
|
+
});
|
|
26
|
+
watch(() => props.unit.layouts, (sets) => {
|
|
27
|
+
activeLayout.value = pickLayout(sets);
|
|
28
|
+
}, { deep: false });
|
|
29
|
+
const warnedMissing = /* @__PURE__ */ new Set();
|
|
30
|
+
const placedFields = computed(() => {
|
|
31
|
+
const layout = activeLayout.value?.layout;
|
|
32
|
+
if (!layout) return [];
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const field of props.unit.fields) {
|
|
35
|
+
const placement = layout.placements[field.id];
|
|
36
|
+
if (!placement) continue;
|
|
37
|
+
if (props.isHidden?.(field)) continue;
|
|
38
|
+
const entry = findField(field.type, field.compatibilityDate);
|
|
39
|
+
if (!entry) {
|
|
40
|
+
const key = `${field.type}@${field.compatibilityDate}`;
|
|
41
|
+
if (!warnedMissing.has(key)) {
|
|
42
|
+
warnedMissing.add(key);
|
|
43
|
+
console.warn(`[shwfed-form] no registered field type for ${key}`);
|
|
44
|
+
}
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
out.push({ field, placement, entry });
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
});
|
|
51
|
+
function cellStyle(placement) {
|
|
52
|
+
const [[x1, y1], [x2, y2]] = placement.area;
|
|
53
|
+
const parts = [`grid-column: ${x1} / ${x2}`, `grid-row: ${y1} / ${y2}`, "min-width: 0"];
|
|
54
|
+
const h = placement.h && placement.h !== "stretch" ? placement.h : null;
|
|
55
|
+
const v = placement.v && placement.v !== "stretch" ? placement.v : null;
|
|
56
|
+
if (h || v) {
|
|
57
|
+
parts.push("display: flex");
|
|
58
|
+
const toFlex = (a) => a === "start" ? "flex-start" : a === "end" ? "flex-end" : "center";
|
|
59
|
+
if (h) parts.push(`justify-content: ${toFlex(h)}`);
|
|
60
|
+
if (v) parts.push(`align-items: ${toFlex(v)}`);
|
|
61
|
+
}
|
|
62
|
+
return parts.join("; ");
|
|
63
|
+
}
|
|
64
|
+
const gridStyle = computed(() => {
|
|
65
|
+
const l = activeLayout.value?.layout;
|
|
66
|
+
if (!l) return "";
|
|
67
|
+
const colTemplate = `repeat(${l.columns}, minmax(0, 1fr))`;
|
|
68
|
+
let rowTemplate = "";
|
|
69
|
+
if (l.rows) {
|
|
70
|
+
const grows = Array.from({ length: l.rows }, () => false);
|
|
71
|
+
for (const p of placedFields.value) {
|
|
72
|
+
if (!p.entry.metadata.h.grow) continue;
|
|
73
|
+
const [[, y1], [, y2]] = p.placement.area;
|
|
74
|
+
const lo = Math.max(1, y1);
|
|
75
|
+
const hi = Math.min(l.rows, y2 - 1);
|
|
76
|
+
for (let r = lo; r <= hi; r++) grows[r - 1] = true;
|
|
77
|
+
}
|
|
78
|
+
rowTemplate = grows.map((g) => g ? "auto" : "minmax(auto, 1fr)").join(" ");
|
|
79
|
+
}
|
|
80
|
+
const gap = `calc(${l.gap ?? DEFAULT_GAP} * 0.25rem)`;
|
|
81
|
+
const colGap = l.columns > 1 ? `min(${gap}, calc(100% / ${l.columns - 1}))` : gap;
|
|
82
|
+
const parts = [
|
|
83
|
+
"display: grid",
|
|
84
|
+
`grid-template-columns: ${colTemplate}`,
|
|
85
|
+
rowTemplate ? `grid-template-rows: ${rowTemplate}` : "",
|
|
86
|
+
`column-gap: ${colGap}`,
|
|
87
|
+
`row-gap: ${gap}`,
|
|
88
|
+
l.style ?? ""
|
|
89
|
+
].filter(Boolean);
|
|
90
|
+
return parts.join("; ");
|
|
91
|
+
});
|
|
92
|
+
const rendered = computed(
|
|
93
|
+
() => placedFields.value.map((p) => ({
|
|
94
|
+
key: p.field.id,
|
|
95
|
+
style: cellStyle(p.placement),
|
|
96
|
+
component: p.entry.runtime,
|
|
97
|
+
fieldId: p.field.id,
|
|
98
|
+
config: p.field
|
|
99
|
+
}))
|
|
100
|
+
);
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<template>
|
|
104
|
+
<div :style="gridStyle">
|
|
105
|
+
<div
|
|
106
|
+
v-for="r in rendered"
|
|
107
|
+
:key="r.key"
|
|
108
|
+
:style="r.style"
|
|
109
|
+
>
|
|
110
|
+
<component
|
|
111
|
+
:is="r.component"
|
|
112
|
+
:field-id="r.fieldId"
|
|
113
|
+
:config="r.config"
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</template>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FieldValue, FormUnitValue } from './schema.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
unit: FormUnitValue;
|
|
4
|
+
evaluateMedia: (expression: string) => boolean;
|
|
5
|
+
isHidden?: (field: FieldValue) => boolean;
|
|
6
|
+
};
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
|
+
declare const _default: typeof __VLS_export;
|
|
9
|
+
export default _default;
|
package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.d.vue.ts
CHANGED
|
@@ -33,7 +33,6 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
33
33
|
readonly compatibilityDate: "2026-05-13";
|
|
34
34
|
readonly min?: number | undefined;
|
|
35
35
|
readonly max?: number | undefined;
|
|
36
|
-
readonly binding?: string | undefined;
|
|
37
36
|
readonly unit: Readonly<{
|
|
38
37
|
fields: ReadonlyArray<import("../../../schema.js").FieldValue>;
|
|
39
38
|
layouts: readonly Readonly<{
|
|
@@ -42,6 +41,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
42
41
|
layout: import("../../../schema.js").LayoutValue;
|
|
43
42
|
}>[];
|
|
44
43
|
}>;
|
|
44
|
+
readonly binding?: string | undefined;
|
|
45
45
|
readonly addLabel?: readonly [{
|
|
46
46
|
readonly locale: "zh";
|
|
47
47
|
readonly message: string;
|
|
@@ -89,7 +89,6 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
89
89
|
readonly compatibilityDate: "2026-05-13";
|
|
90
90
|
readonly min?: number | undefined;
|
|
91
91
|
readonly max?: number | undefined;
|
|
92
|
-
readonly binding?: string | undefined;
|
|
93
92
|
readonly unit: Readonly<{
|
|
94
93
|
fields: ReadonlyArray<import("../../../schema.js").FieldValue>;
|
|
95
94
|
layouts: readonly Readonly<{
|
|
@@ -98,6 +97,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
98
97
|
layout: import("../../../schema.js").LayoutValue;
|
|
99
98
|
}>[];
|
|
100
99
|
}>;
|
|
100
|
+
readonly binding?: string | undefined;
|
|
101
101
|
readonly addLabel?: readonly [{
|
|
102
102
|
readonly locale: "zh";
|
|
103
103
|
readonly message: string;
|
package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.vue
CHANGED
|
@@ -96,7 +96,7 @@ const reorderable = computed({
|
|
|
96
96
|
:field-cel-scope="FIELD_CEL_SCOPE"
|
|
97
97
|
>
|
|
98
98
|
<template #extras-pane>
|
|
99
|
-
<div class="flex flex-col gap-3
|
|
99
|
+
<div class="flex flex-col gap-3">
|
|
100
100
|
<div class="grid grid-cols-2 gap-3">
|
|
101
101
|
<Field orientation="vertical">
|
|
102
102
|
<FieldLabel class="text-xs text-zinc-500">
|
package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.vue.d.ts
CHANGED
|
@@ -33,7 +33,6 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
33
33
|
readonly compatibilityDate: "2026-05-13";
|
|
34
34
|
readonly min?: number | undefined;
|
|
35
35
|
readonly max?: number | undefined;
|
|
36
|
-
readonly binding?: string | undefined;
|
|
37
36
|
readonly unit: Readonly<{
|
|
38
37
|
fields: ReadonlyArray<import("../../../schema.js").FieldValue>;
|
|
39
38
|
layouts: readonly Readonly<{
|
|
@@ -42,6 +41,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
42
41
|
layout: import("../../../schema.js").LayoutValue;
|
|
43
42
|
}>[];
|
|
44
43
|
}>;
|
|
44
|
+
readonly binding?: string | undefined;
|
|
45
45
|
readonly addLabel?: readonly [{
|
|
46
46
|
readonly locale: "zh";
|
|
47
47
|
readonly message: string;
|
|
@@ -89,7 +89,6 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
89
89
|
readonly compatibilityDate: "2026-05-13";
|
|
90
90
|
readonly min?: number | undefined;
|
|
91
91
|
readonly max?: number | undefined;
|
|
92
|
-
readonly binding?: string | undefined;
|
|
93
92
|
readonly unit: Readonly<{
|
|
94
93
|
fields: ReadonlyArray<import("../../../schema.js").FieldValue>;
|
|
95
94
|
layouts: readonly Readonly<{
|
|
@@ -98,6 +97,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
98
97
|
layout: import("../../../schema.js").LayoutValue;
|
|
99
98
|
}>[];
|
|
100
99
|
}>;
|
|
100
|
+
readonly binding?: string | undefined;
|
|
101
101
|
readonly addLabel?: readonly [{
|
|
102
102
|
readonly locale: "zh";
|
|
103
103
|
readonly message: string;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { cel as _rawCel } from "../../../../../utils/cel";
|
|
3
3
|
import { Effect } from "effect";
|
|
4
|
-
import { computed, onMounted, ref, watch } from "vue";
|
|
5
4
|
import { celBindings, injectCELContext, provideCELContext } from "../../../../../utils/cel-context";
|
|
6
|
-
import
|
|
5
|
+
import FormUnitRenderer from "../../../FormUnitRenderer.vue";
|
|
7
6
|
import { useDerived, useDerivedQuiescence } from "../../../utils/derived";
|
|
8
|
-
import { findField } from "../../../utils/resolve";
|
|
9
7
|
import { provideFormState } from "../../../utils/state";
|
|
10
8
|
defineOptions({ name: "ShwfedListFieldRow" });
|
|
11
9
|
const state = defineModel({ type: Object, ...{ required: true } });
|
|
@@ -37,109 +35,14 @@ useDerived({
|
|
|
37
35
|
formState,
|
|
38
36
|
quiescence: useDerivedQuiescence()
|
|
39
37
|
});
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
-
if (!set.media) return set;
|
|
43
|
-
try {
|
|
44
|
-
if (Effect.runSync($cel(set.media))) return set;
|
|
45
|
-
} catch (err) {
|
|
46
|
-
console.error(`[shwfed-form] list field: failed to evaluate layout media for ${set.name}:`, err);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return sets[sets.length - 1];
|
|
50
|
-
}
|
|
51
|
-
const activeLayout = ref();
|
|
52
|
-
onMounted(() => {
|
|
53
|
-
activeLayout.value = pickLayout(props.unit.layouts);
|
|
54
|
-
});
|
|
55
|
-
watch(() => props.unit.layouts, (sets) => {
|
|
56
|
-
activeLayout.value = pickLayout(sets);
|
|
57
|
-
}, { deep: false });
|
|
58
|
-
const warnedMissing = /* @__PURE__ */ new Set();
|
|
59
|
-
const placedFields = computed(() => {
|
|
60
|
-
const layout = activeLayout.value?.layout;
|
|
61
|
-
if (!layout) return [];
|
|
62
|
-
const out = [];
|
|
63
|
-
for (const field of props.unit.fields) {
|
|
64
|
-
const placement = layout.placements[field.id];
|
|
65
|
-
if (!placement) continue;
|
|
66
|
-
const entry = findField(field.type, field.compatibilityDate);
|
|
67
|
-
if (!entry) {
|
|
68
|
-
const key = `${field.type}@${field.compatibilityDate}`;
|
|
69
|
-
if (!warnedMissing.has(key)) {
|
|
70
|
-
warnedMissing.add(key);
|
|
71
|
-
console.warn(`[shwfed-form] list field: no registered field type for ${key}`);
|
|
72
|
-
}
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
out.push({ field, placement, entry });
|
|
76
|
-
}
|
|
77
|
-
return out;
|
|
78
|
-
});
|
|
79
|
-
const gridStyle = computed(() => {
|
|
80
|
-
const l = activeLayout.value?.layout;
|
|
81
|
-
if (!l) return "";
|
|
82
|
-
const colTemplate = `repeat(${l.columns}, minmax(0, 1fr))`;
|
|
83
|
-
let rowTemplate = "";
|
|
84
|
-
if (l.rows) {
|
|
85
|
-
const grows = Array.from({ length: l.rows }, () => false);
|
|
86
|
-
for (const p of placedFields.value) {
|
|
87
|
-
if (!p.entry.metadata.h.grow) continue;
|
|
88
|
-
const [[, y1], [, y2]] = p.placement.area;
|
|
89
|
-
const lo = Math.max(1, y1);
|
|
90
|
-
const hi = Math.min(l.rows, y2 - 1);
|
|
91
|
-
for (let r = lo; r <= hi; r++) grows[r - 1] = true;
|
|
92
|
-
}
|
|
93
|
-
rowTemplate = grows.map((g) => g ? "auto" : "minmax(auto, 1fr)").join(" ");
|
|
94
|
-
}
|
|
95
|
-
const gap = `calc(${l.gap ?? DEFAULT_GAP} * 0.25rem)`;
|
|
96
|
-
const colGap = l.columns > 1 ? `min(${gap}, calc(100% / ${l.columns - 1}))` : gap;
|
|
97
|
-
const parts = [
|
|
98
|
-
"display: grid",
|
|
99
|
-
`grid-template-columns: ${colTemplate}`,
|
|
100
|
-
rowTemplate ? `grid-template-rows: ${rowTemplate}` : "",
|
|
101
|
-
`column-gap: ${colGap}`,
|
|
102
|
-
`row-gap: ${gap}`,
|
|
103
|
-
l.style ?? ""
|
|
104
|
-
].filter(Boolean);
|
|
105
|
-
return parts.join("; ");
|
|
106
|
-
});
|
|
107
|
-
function cellStyle(placement) {
|
|
108
|
-
const [[x1, y1], [x2, y2]] = placement.area;
|
|
109
|
-
const parts = [`grid-column: ${x1} / ${x2}`, `grid-row: ${y1} / ${y2}`, "min-width: 0"];
|
|
110
|
-
const h = placement.h && placement.h !== "stretch" ? placement.h : null;
|
|
111
|
-
const v = placement.v && placement.v !== "stretch" ? placement.v : null;
|
|
112
|
-
if (h || v) {
|
|
113
|
-
parts.push("display: flex");
|
|
114
|
-
const toFlex = (a) => a === "start" ? "flex-start" : a === "end" ? "flex-end" : "center";
|
|
115
|
-
if (h) parts.push(`justify-content: ${toFlex(h)}`);
|
|
116
|
-
if (v) parts.push(`align-items: ${toFlex(v)}`);
|
|
117
|
-
}
|
|
118
|
-
return parts.join("; ");
|
|
38
|
+
function evaluateMedia(expression) {
|
|
39
|
+
return Effect.runSync($cel(expression));
|
|
119
40
|
}
|
|
120
|
-
const rendered = computed(
|
|
121
|
-
() => placedFields.value.map((p) => ({
|
|
122
|
-
key: p.field.id,
|
|
123
|
-
style: cellStyle(p.placement),
|
|
124
|
-
component: p.entry.runtime,
|
|
125
|
-
fieldId: p.field.id,
|
|
126
|
-
config: p.field
|
|
127
|
-
}))
|
|
128
|
-
);
|
|
129
41
|
</script>
|
|
130
42
|
|
|
131
43
|
<template>
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
:style="r.style"
|
|
137
|
-
>
|
|
138
|
-
<component
|
|
139
|
-
:is="r.component"
|
|
140
|
-
:field-id="r.fieldId"
|
|
141
|
-
:config="r.config"
|
|
142
|
-
/>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
44
|
+
<FormUnitRenderer
|
|
45
|
+
:unit="unit"
|
|
46
|
+
:evaluate-media="evaluateMedia"
|
|
47
|
+
/>
|
|
145
48
|
</template>
|
package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/schema.d.ts
CHANGED
|
@@ -47,11 +47,11 @@ export declare function schema(configure: (env: Environment) => void): Schema.re
|
|
|
47
47
|
readonly compatibilityDate: "2026-05-13";
|
|
48
48
|
readonly min?: number | undefined;
|
|
49
49
|
readonly max?: number | undefined;
|
|
50
|
-
readonly binding?: string | undefined;
|
|
51
50
|
readonly unit: Readonly<{
|
|
52
51
|
fields: ReadonlyArray<import("../../../schema.js").FieldValue>;
|
|
53
52
|
layouts: ReadonlyArray<import("../../../schema.js").LayoutSetValue>;
|
|
54
53
|
}>;
|
|
54
|
+
readonly binding?: string | undefined;
|
|
55
55
|
readonly addLabel?: readonly [{
|
|
56
56
|
readonly locale: "zh";
|
|
57
57
|
readonly message: string;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type Value } from './schema.js';
|
|
2
|
+
type __VLS_ModelProps = {
|
|
3
|
+
modelValue: Value;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
+
"update:modelValue": (value: {
|
|
7
|
+
readonly type: "com.shwfed.form.field.collapsible";
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly header: readonly [{
|
|
10
|
+
readonly locale: "zh";
|
|
11
|
+
readonly message: string;
|
|
12
|
+
}, ...{
|
|
13
|
+
readonly locale: "en" | "ja" | "ko";
|
|
14
|
+
readonly message: string;
|
|
15
|
+
}[]];
|
|
16
|
+
readonly displayName?: string | undefined;
|
|
17
|
+
readonly compatibilityDate: "2026-05-20";
|
|
18
|
+
readonly unit: Readonly<{
|
|
19
|
+
fields: ReadonlyArray<import("../../../schema.js").FieldValue>;
|
|
20
|
+
layouts: readonly Readonly<{
|
|
21
|
+
name: string;
|
|
22
|
+
media?: string;
|
|
23
|
+
layout: import("../../../schema.js").LayoutValue;
|
|
24
|
+
}>[];
|
|
25
|
+
}>;
|
|
26
|
+
readonly defaultCollapsed?: boolean | undefined;
|
|
27
|
+
}) => any;
|
|
28
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
29
|
+
"onUpdate:modelValue"?: ((value: {
|
|
30
|
+
readonly type: "com.shwfed.form.field.collapsible";
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly header: readonly [{
|
|
33
|
+
readonly locale: "zh";
|
|
34
|
+
readonly message: string;
|
|
35
|
+
}, ...{
|
|
36
|
+
readonly locale: "en" | "ja" | "ko";
|
|
37
|
+
readonly message: string;
|
|
38
|
+
}[]];
|
|
39
|
+
readonly displayName?: string | undefined;
|
|
40
|
+
readonly compatibilityDate: "2026-05-20";
|
|
41
|
+
readonly unit: Readonly<{
|
|
42
|
+
fields: ReadonlyArray<import("../../../schema.js").FieldValue>;
|
|
43
|
+
layouts: readonly Readonly<{
|
|
44
|
+
name: string;
|
|
45
|
+
media?: string;
|
|
46
|
+
layout: import("../../../schema.js").LayoutValue;
|
|
47
|
+
}>[];
|
|
48
|
+
}>;
|
|
49
|
+
readonly defaultCollapsed?: boolean | undefined;
|
|
50
|
+
}) => any) | undefined;
|
|
51
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
52
|
+
declare const _default: typeof __VLS_export;
|
|
53
|
+
export default _default;
|
package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/config.vue
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, inject, onBeforeUnmount, onMounted, ref } from "vue";
|
|
3
|
+
import { Field, FieldLabel } from "../../../../ui/field";
|
|
4
|
+
import { InputGroup, InputGroupInput } from "../../../../ui/input-group";
|
|
5
|
+
import { Locale as LocaleField } from "../../../../ui/locale";
|
|
6
|
+
import { Markdown } from "../../../../ui/markdown";
|
|
7
|
+
import { Switch } from "../../../../ui/switch";
|
|
8
|
+
import { FORM_FIELD_LAYOUT_KEY } from "../../../field-layout";
|
|
9
|
+
import {
|
|
10
|
+
getStructFieldDescription,
|
|
11
|
+
getStructFieldTitle
|
|
12
|
+
} from "../../../schema";
|
|
13
|
+
import ShwfedFormUnitConfig, {
|
|
14
|
+
} from "../../../unit-config.vue";
|
|
15
|
+
import { schema } from "./schema";
|
|
16
|
+
defineOptions({ name: "ShwfedCollapsibleFieldConfig" });
|
|
17
|
+
const value = defineModel({ type: null, ...{ required: true } });
|
|
18
|
+
const fieldSchema = schema(() => {
|
|
19
|
+
});
|
|
20
|
+
const fieldTitle = (f) => getStructFieldTitle(fieldSchema, f) ?? f;
|
|
21
|
+
const fieldDescription = (f) => getStructFieldDescription(fieldSchema, f);
|
|
22
|
+
const layout = inject(FORM_FIELD_LAYOUT_KEY, null);
|
|
23
|
+
onMounted(() => {
|
|
24
|
+
if (layout) layout.fullPane.value = true;
|
|
25
|
+
});
|
|
26
|
+
onBeforeUnmount(() => {
|
|
27
|
+
if (layout) layout.fullPane.value = false;
|
|
28
|
+
});
|
|
29
|
+
const unitModel = computed({
|
|
30
|
+
get: () => value.value.unit,
|
|
31
|
+
set: (next) => {
|
|
32
|
+
value.value = { ...value.value, unit: next };
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const EXTRAS = [
|
|
36
|
+
{ id: "general", label: "\u901A\u7528\u914D\u7F6E", icon: "fluent:settings-20-regular" }
|
|
37
|
+
];
|
|
38
|
+
const selection = ref({ kind: "extras", id: "general" });
|
|
39
|
+
const defaultCollapsed = computed({
|
|
40
|
+
get: () => value.value.defaultCollapsed ?? false,
|
|
41
|
+
set: (next) => {
|
|
42
|
+
if (next === true) {
|
|
43
|
+
value.value = { ...value.value, defaultCollapsed: true };
|
|
44
|
+
} else {
|
|
45
|
+
const { defaultCollapsed: _omit, ...rest } = value.value;
|
|
46
|
+
value.value = rest;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<ShwfedFormUnitConfig
|
|
54
|
+
v-model="unitModel"
|
|
55
|
+
v-model:selection="selection"
|
|
56
|
+
:extras="EXTRAS"
|
|
57
|
+
>
|
|
58
|
+
<template #extras-pane>
|
|
59
|
+
<div class="flex flex-col gap-2">
|
|
60
|
+
<Field orientation="vertical">
|
|
61
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
62
|
+
<template
|
|
63
|
+
v-if="fieldDescription('displayName')"
|
|
64
|
+
#tooltip
|
|
65
|
+
>
|
|
66
|
+
<Markdown
|
|
67
|
+
:source="fieldDescription('displayName')"
|
|
68
|
+
block
|
|
69
|
+
class="prose prose-sm prose-zinc"
|
|
70
|
+
/>
|
|
71
|
+
</template>
|
|
72
|
+
{{ fieldTitle("displayName") }}
|
|
73
|
+
</FieldLabel>
|
|
74
|
+
<InputGroup>
|
|
75
|
+
<InputGroupInput
|
|
76
|
+
:model-value="value.displayName ?? ''"
|
|
77
|
+
placeholder="例:基本信息"
|
|
78
|
+
@update:model-value="(v) => {
|
|
79
|
+
const s = String(v ?? '');
|
|
80
|
+
value = { ...value, displayName: s.length > 0 ? s : void 0 };
|
|
81
|
+
}"
|
|
82
|
+
/>
|
|
83
|
+
</InputGroup>
|
|
84
|
+
</Field>
|
|
85
|
+
|
|
86
|
+
<Field orientation="vertical">
|
|
87
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
88
|
+
<template
|
|
89
|
+
v-if="fieldDescription('header')"
|
|
90
|
+
#tooltip
|
|
91
|
+
>
|
|
92
|
+
<Markdown
|
|
93
|
+
:source="fieldDescription('header')"
|
|
94
|
+
block
|
|
95
|
+
class="prose prose-sm prose-zinc"
|
|
96
|
+
/>
|
|
97
|
+
</template>
|
|
98
|
+
{{ fieldTitle("header") }}
|
|
99
|
+
</FieldLabel>
|
|
100
|
+
<LocaleField
|
|
101
|
+
markdown
|
|
102
|
+
translate-hint="折叠区域标题,行内 Markdown 文本,保留表达式插值占位符不要翻译"
|
|
103
|
+
:model-value="value.header"
|
|
104
|
+
@update:model-value="(v) => value = { ...value, header: v }"
|
|
105
|
+
/>
|
|
106
|
+
</Field>
|
|
107
|
+
|
|
108
|
+
<Field
|
|
109
|
+
orientation="horizontal"
|
|
110
|
+
class="h-9 w-auto gap-2"
|
|
111
|
+
>
|
|
112
|
+
<Switch v-model="defaultCollapsed" />
|
|
113
|
+
<FieldLabel class="text-sm text-zinc-600">
|
|
114
|
+
<template
|
|
115
|
+
v-if="fieldDescription('defaultCollapsed')"
|
|
116
|
+
#tooltip
|
|
117
|
+
>
|
|
118
|
+
<Markdown
|
|
119
|
+
:source="fieldDescription('defaultCollapsed')"
|
|
120
|
+
block
|
|
121
|
+
class="prose prose-sm prose-zinc"
|
|
122
|
+
/>
|
|
123
|
+
</template>
|
|
124
|
+
{{ fieldTitle("defaultCollapsed") }}
|
|
125
|
+
</FieldLabel>
|
|
126
|
+
</Field>
|
|
127
|
+
</div>
|
|
128
|
+
</template>
|
|
129
|
+
</ShwfedFormUnitConfig>
|
|
130
|
+
</template>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type Value } from './schema.js';
|
|
2
|
+
type __VLS_ModelProps = {
|
|
3
|
+
modelValue: Value;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
+
"update:modelValue": (value: {
|
|
7
|
+
readonly type: "com.shwfed.form.field.collapsible";
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly header: readonly [{
|
|
10
|
+
readonly locale: "zh";
|
|
11
|
+
readonly message: string;
|
|
12
|
+
}, ...{
|
|
13
|
+
readonly locale: "en" | "ja" | "ko";
|
|
14
|
+
readonly message: string;
|
|
15
|
+
}[]];
|
|
16
|
+
readonly displayName?: string | undefined;
|
|
17
|
+
readonly compatibilityDate: "2026-05-20";
|
|
18
|
+
readonly unit: Readonly<{
|
|
19
|
+
fields: ReadonlyArray<import("../../../schema.js").FieldValue>;
|
|
20
|
+
layouts: readonly Readonly<{
|
|
21
|
+
name: string;
|
|
22
|
+
media?: string;
|
|
23
|
+
layout: import("../../../schema.js").LayoutValue;
|
|
24
|
+
}>[];
|
|
25
|
+
}>;
|
|
26
|
+
readonly defaultCollapsed?: boolean | undefined;
|
|
27
|
+
}) => any;
|
|
28
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
29
|
+
"onUpdate:modelValue"?: ((value: {
|
|
30
|
+
readonly type: "com.shwfed.form.field.collapsible";
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly header: readonly [{
|
|
33
|
+
readonly locale: "zh";
|
|
34
|
+
readonly message: string;
|
|
35
|
+
}, ...{
|
|
36
|
+
readonly locale: "en" | "ja" | "ko";
|
|
37
|
+
readonly message: string;
|
|
38
|
+
}[]];
|
|
39
|
+
readonly displayName?: string | undefined;
|
|
40
|
+
readonly compatibilityDate: "2026-05-20";
|
|
41
|
+
readonly unit: Readonly<{
|
|
42
|
+
fields: ReadonlyArray<import("../../../schema.js").FieldValue>;
|
|
43
|
+
layouts: readonly Readonly<{
|
|
44
|
+
name: string;
|
|
45
|
+
media?: string;
|
|
46
|
+
layout: import("../../../schema.js").LayoutValue;
|
|
47
|
+
}>[];
|
|
48
|
+
}>;
|
|
49
|
+
readonly defaultCollapsed?: boolean | undefined;
|
|
50
|
+
}) => any) | undefined;
|
|
51
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
52
|
+
declare const _default: typeof __VLS_export;
|
|
53
|
+
export default _default;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Value } from './schema.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
fieldId: string;
|
|
4
|
+
config: Value;
|
|
5
|
+
};
|
|
6
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/runtime.vue
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { Icon } from "@iconify/vue";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import { computed, ref } from "vue";
|
|
5
|
+
import { useI18n } from "vue-i18n";
|
|
6
|
+
import { cel as _rawCel } from "../../../../../utils/cel";
|
|
7
|
+
import { celBindings, injectCELContext } from "../../../../../utils/cel-context";
|
|
8
|
+
import { getLocalizedText } from "../../../../../share/locale";
|
|
9
|
+
import { interpolate } from "../../../../../utils/interpolate";
|
|
10
|
+
import { InputGroupButton } from "../../../../ui/input-group";
|
|
11
|
+
import { Markdown } from "../../../../ui/markdown";
|
|
12
|
+
import FormUnitRenderer from "../../../FormUnitRenderer.vue";
|
|
13
|
+
defineOptions({ name: "ShwfedCollapsibleFieldRuntime" });
|
|
14
|
+
const props = defineProps({
|
|
15
|
+
fieldId: { type: String, required: true },
|
|
16
|
+
config: { type: null, required: true }
|
|
17
|
+
});
|
|
18
|
+
const { locale } = useI18n();
|
|
19
|
+
const collapsed = ref(props.config.defaultCollapsed ?? false);
|
|
20
|
+
function toggle() {
|
|
21
|
+
collapsed.value = !collapsed.value;
|
|
22
|
+
}
|
|
23
|
+
const inherited = injectCELContext();
|
|
24
|
+
const $cel = (expression, context) => _rawCel(expression, { ...celBindings(inherited), ...context });
|
|
25
|
+
const headerSource = computed(() => {
|
|
26
|
+
const template = getLocalizedText(props.config.header, locale.value) ?? "";
|
|
27
|
+
if (!template) return "";
|
|
28
|
+
return interpolate(template, (expression) => {
|
|
29
|
+
try {
|
|
30
|
+
const out = Effect.runSync($cel(expression));
|
|
31
|
+
return out == null ? "" : String(out);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error(
|
|
34
|
+
`[shwfed-form] collapsible: failed to evaluate header expression \`${expression}\` for ${props.fieldId}:`,
|
|
35
|
+
err
|
|
36
|
+
);
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
function evaluateMedia(expression) {
|
|
42
|
+
return Effect.runSync($cel(expression));
|
|
43
|
+
}
|
|
44
|
+
function isHidden(field) {
|
|
45
|
+
const hidden = field.hidden;
|
|
46
|
+
if (!hidden) return false;
|
|
47
|
+
try {
|
|
48
|
+
return Effect.runSync($cel(hidden));
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error(`[shwfed-form] collapsible: failed to evaluate hidden for ${field.id}:`, err);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<template>
|
|
57
|
+
<!-- Mirrors the list-row card frame (`rounded border bg-zinc-50/40 p-3`)
|
|
58
|
+
so collapsibles and list rows share a visual language for nested
|
|
59
|
+
containers. The toggle chevron is its own `InputGroupButton`, matching
|
|
60
|
+
the list row's collapse affordance — header text is clickable
|
|
61
|
+
*targets* of focus, but only the chevron toggles. -->
|
|
62
|
+
<div
|
|
63
|
+
data-slot="collapsible-section"
|
|
64
|
+
class="relative rounded border border-zinc-200 bg-zinc-50/40 p-3"
|
|
65
|
+
>
|
|
66
|
+
<div class="flex items-center gap-2">
|
|
67
|
+
<InputGroupButton
|
|
68
|
+
size="icon-xs"
|
|
69
|
+
class="shrink-0"
|
|
70
|
+
data-slot="collapsible-toggle"
|
|
71
|
+
:aria-expanded="!collapsed"
|
|
72
|
+
:aria-label="collapsed ? '\u5C55\u5F00' : '\u6298\u53E0'"
|
|
73
|
+
@click="toggle"
|
|
74
|
+
>
|
|
75
|
+
<Icon
|
|
76
|
+
:icon="collapsed ? 'fluent:chevron-right-20-regular' : 'fluent:chevron-down-20-regular'"
|
|
77
|
+
/>
|
|
78
|
+
</InputGroupButton>
|
|
79
|
+
<div
|
|
80
|
+
class="min-w-0 flex-1"
|
|
81
|
+
data-slot="collapsible-header"
|
|
82
|
+
>
|
|
83
|
+
<Markdown
|
|
84
|
+
:source="headerSource"
|
|
85
|
+
class="prose prose-sm prose-zinc max-w-none"
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<!-- `v-show` (not `v-if`) so children stay mounted while collapsed:
|
|
91
|
+
derived expressions keep running, commit-bus drafts stay live, and
|
|
92
|
+
readonly inheritance keeps flowing. The parent grid's row track
|
|
93
|
+
relies on this field's `metadata.h.grow = true` to shrink the
|
|
94
|
+
collapsed section to its header height. -->
|
|
95
|
+
<div
|
|
96
|
+
v-show="!collapsed"
|
|
97
|
+
data-slot="collapsible-body"
|
|
98
|
+
class="mt-3 min-w-0"
|
|
99
|
+
>
|
|
100
|
+
<FormUnitRenderer
|
|
101
|
+
:unit="config.unit"
|
|
102
|
+
:evaluate-media="evaluateMedia"
|
|
103
|
+
:is-hidden="isHidden"
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</template>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Value } from './schema.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
fieldId: string;
|
|
4
|
+
config: Value;
|
|
5
|
+
};
|
|
6
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/schema.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Schema } from 'effect';
|
|
2
|
+
import type { Environment } from '../../../../../vendor/cel-js/lib/index.js';
|
|
3
|
+
export declare const type: "com.shwfed.form.field.collapsible";
|
|
4
|
+
export declare const compatibilityDate: "2026-05-20";
|
|
5
|
+
export declare const metadata: {
|
|
6
|
+
readonly name: "可折叠区域";
|
|
7
|
+
readonly icon: "fluent:chevron-down-20-regular";
|
|
8
|
+
readonly w: {
|
|
9
|
+
readonly initial: 12;
|
|
10
|
+
readonly min: 4;
|
|
11
|
+
readonly max: number;
|
|
12
|
+
};
|
|
13
|
+
readonly h: {
|
|
14
|
+
readonly initial: 4;
|
|
15
|
+
readonly min: 2;
|
|
16
|
+
readonly max: number;
|
|
17
|
+
readonly grow: true;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A collapsible section. Pure view — children are flat in the form state
|
|
22
|
+
* (no subtree introduced by the section), and the collapsed state lives only
|
|
23
|
+
* in the runtime component's local ref (not persisted, not authored as CEL).
|
|
24
|
+
*
|
|
25
|
+
* Deliberately does **not** include `commonFieldFields` — `displayName` /
|
|
26
|
+
* `hidden` / etc. are field-flavored concepts that don't fit a structural
|
|
27
|
+
* grouping. We carry only the minimum: an `id` for the parent layout to
|
|
28
|
+
* reference, a markdown `header` (CEL-interpolated), the `defaultCollapsed`
|
|
29
|
+
* authoring default, and the nested `unit` whose children are rendered
|
|
30
|
+
* inline.
|
|
31
|
+
*/
|
|
32
|
+
export declare function schema(configure: (env: Environment) => void): Schema.Struct<{
|
|
33
|
+
type: Schema.Literal<["com.shwfed.form.field.collapsible"]>;
|
|
34
|
+
compatibilityDate: Schema.Literal<["2026-05-20"]>;
|
|
35
|
+
id: Schema.refine<string, typeof Schema.String>;
|
|
36
|
+
displayName: Schema.optional<Schema.SchemaClass<string, string, never>>;
|
|
37
|
+
header: Schema.TupleType<readonly [Schema.Struct<{
|
|
38
|
+
locale: Schema.Literal<["zh"]>;
|
|
39
|
+
message: Schema.SchemaClass<string, string, never>;
|
|
40
|
+
}>], [Schema.Struct<{
|
|
41
|
+
locale: Schema.Literal<["ja", "en", "ko"]>;
|
|
42
|
+
message: Schema.SchemaClass<string, string, never>;
|
|
43
|
+
}>]>;
|
|
44
|
+
defaultCollapsed: Schema.optional<Schema.SchemaClass<boolean, boolean, never>>;
|
|
45
|
+
unit: Schema.suspend<Readonly<{
|
|
46
|
+
fields: ReadonlyArray<import("../../../schema.js").FieldValue>;
|
|
47
|
+
layouts: ReadonlyArray<import("../../../schema.js").LayoutSetValue>;
|
|
48
|
+
}>, Readonly<{
|
|
49
|
+
fields: ReadonlyArray<import("../../../schema.js").FieldValue>;
|
|
50
|
+
layouts: ReadonlyArray<import("../../../schema.js").LayoutSetValue>;
|
|
51
|
+
}>, never>;
|
|
52
|
+
}>;
|
|
53
|
+
export type Value = Schema.Schema.Type<ReturnType<typeof schema>>;
|
|
54
|
+
export declare function defaults(): Partial<Value>;
|
package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/schema.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
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.collapsible";
|
|
5
|
+
export const compatibilityDate = "2026-05-20";
|
|
6
|
+
export const metadata = {
|
|
7
|
+
name: "\u53EF\u6298\u53E0\u533A\u57DF",
|
|
8
|
+
icon: "fluent:chevron-down-20-regular",
|
|
9
|
+
w: { initial: 12, min: 4, max: Infinity },
|
|
10
|
+
h: { initial: 4, min: 2, max: Infinity, grow: true }
|
|
11
|
+
};
|
|
12
|
+
export function schema(configure) {
|
|
13
|
+
const Unit = Schema.suspend(() => FormUnit(configure));
|
|
14
|
+
return Schema.Struct({
|
|
15
|
+
type: Schema.Literal(type),
|
|
16
|
+
compatibilityDate: Schema.Literal(compatibilityDate),
|
|
17
|
+
id: Schema.UUID.annotations({ description: "\u5B57\u6BB5\u552F\u4E00\u6807\u8BC6\uFF1B\u5E03\u5C40\u901A\u8FC7\u8BE5 id \u5F15\u7528\u5B57\u6BB5" }),
|
|
18
|
+
displayName: Schema.optional(Schema.String.annotations({
|
|
19
|
+
title: "\u5185\u90E8\u540D\u79F0",
|
|
20
|
+
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\u6298\u53E0\u533A\u57DF\uFF1B\u8FD0\u884C\u65F6\u4E0D\u5C55\u793A"
|
|
21
|
+
})),
|
|
22
|
+
header: Locale.annotations({
|
|
23
|
+
title: "\u6807\u9898",
|
|
24
|
+
description: "\u6298\u53E0\u533A\u57DF\u7684\u6807\u9898\uFF0C\u6E32\u67D3\u4E3A\u884C\u5185 Markdown\u3002\u652F\u6301 `{{ \u8868\u8FBE\u5F0F }}` \u63D2\u503C\uFF0C\u8868\u8FBE\u5F0F\u4E3A CEL\uFF0C\u53EF\u8BBF\u95EE `form`\uFF08\u5F53\u524D\u8868\u5355\u72B6\u6001\uFF09\u4EE5\u53CA\u5F53\u524D\u4F5C\u7528\u57DF\u5185\u7684\u5176\u4ED6\u53D8\u91CF"
|
|
25
|
+
}),
|
|
26
|
+
defaultCollapsed: Schema.optional(Schema.Boolean.annotations({
|
|
27
|
+
title: "\u9ED8\u8BA4\u6298\u53E0",
|
|
28
|
+
description: "\u6302\u8F7D\u65F6\u662F\u5426\u9ED8\u8BA4\u6298\u53E0\uFF1B\u7528\u6237\u70B9\u51FB\u5C55\u5F00/\u6536\u8D77\u540E\u5373\u65F6\u751F\u6548\uFF0C**\u4E0D**\u6301\u4E45\u5316\uFF08\u5378\u8F7D\u5373\u4E22\u5F03\uFF09"
|
|
29
|
+
})),
|
|
30
|
+
unit: Unit.annotations({
|
|
31
|
+
title: "\u533A\u57DF\u5185\u5BB9",
|
|
32
|
+
description: "\u6298\u53E0\u533A\u57DF\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"
|
|
33
|
+
})
|
|
34
|
+
}).annotations({
|
|
35
|
+
title: "CollapsibleField",
|
|
36
|
+
description: "\u5E26 Markdown \u6807\u9898\u7684\u53EF\u6298\u53E0\u533A\u57DF\uFF0C\u4EC5\u7528\u4E8E\u89C6\u89C9\u5206\u7EC4\uFF0C\u4E0D\u5F71\u54CD\u5B57\u6BB5\u5728\u8868\u5355\u72B6\u6001\u4E2D\u7684\u5C42\u7EA7"
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export function defaults() {
|
|
40
|
+
return {
|
|
41
|
+
header: [{ locale: "zh", message: "" }],
|
|
42
|
+
unit: {
|
|
43
|
+
fields: [],
|
|
44
|
+
layouts: [{ name: "\u9ED8\u8BA4", layout: { columns: 1, placements: {} } }]
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -3,20 +3,19 @@ import { cel as _rawCel } from "../../utils/cel";
|
|
|
3
3
|
import { TZDate } from "@date-fns/tz";
|
|
4
4
|
import { Effect } from "effect";
|
|
5
5
|
import { Fetch } from "fx-fetch";
|
|
6
|
-
import { computed
|
|
6
|
+
import { computed } from "vue";
|
|
7
7
|
import {
|
|
8
8
|
celBindings,
|
|
9
9
|
injectCELContext,
|
|
10
10
|
provideCELContext
|
|
11
11
|
} from "../../utils/cel-context";
|
|
12
|
+
import FormUnitRenderer from "./FormUnitRenderer.vue";
|
|
12
13
|
import { provideCommitBus } from "./utils/commit-bus";
|
|
13
14
|
import { provideDerivedQuiescence, useDerived } from "./utils/derived";
|
|
14
15
|
import { useFormHistory } from "./utils/history";
|
|
15
16
|
import { evaluateInitial } from "./utils/initial";
|
|
16
17
|
import { provideFormReadonly } from "./utils/readonly";
|
|
17
|
-
import { findField } from "./utils/resolve";
|
|
18
18
|
import { provideFormState } from "./utils/state";
|
|
19
|
-
import { DEFAULT_GAP } from "./schema";
|
|
20
19
|
defineOptions({ name: "ShwfedForm" });
|
|
21
20
|
const state = defineModel({ type: Object, ...{ default: () => ({}) } });
|
|
22
21
|
const config = defineModel("config", { type: Object, ...{ required: true } });
|
|
@@ -93,122 +92,31 @@ defineExpose({
|
|
|
93
92
|
canUndo: formHistory.canUndo,
|
|
94
93
|
canRedo: formHistory.canRedo
|
|
95
94
|
});
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
console.error(`[shwfed-form] failed to evaluate layout media for ${set.name}:`, err);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return sets[sets.length - 1];
|
|
95
|
+
const unit = computed(() => ({
|
|
96
|
+
fields: config.value.fields,
|
|
97
|
+
layouts: config.value.layouts
|
|
98
|
+
}));
|
|
99
|
+
function evaluateMedia(expression) {
|
|
100
|
+
return Effect.runSync($cel(expression));
|
|
106
101
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
activeLayout.value = pickLayout(config.value.layouts);
|
|
110
|
-
});
|
|
111
|
-
watch(() => config.value.layouts, (sets) => {
|
|
112
|
-
activeLayout.value = pickLayout(sets);
|
|
113
|
-
}, { deep: false });
|
|
114
|
-
const warnedMissing = /* @__PURE__ */ new Set();
|
|
115
|
-
function isHidden(f) {
|
|
116
|
-
const hidden = f.hidden;
|
|
102
|
+
function isHidden(field) {
|
|
103
|
+
const hidden = field.hidden;
|
|
117
104
|
if (!hidden) return false;
|
|
118
105
|
try {
|
|
119
106
|
return Effect.runSync($cel(hidden, { form: state.value ?? {} }));
|
|
120
107
|
} catch (err) {
|
|
121
|
-
console.error(`[shwfed-form] failed to evaluate hidden for ${
|
|
108
|
+
console.error(`[shwfed-form] failed to evaluate hidden for ${field.id}:`, err);
|
|
122
109
|
return false;
|
|
123
110
|
}
|
|
124
111
|
}
|
|
125
|
-
function cellStyle(placement) {
|
|
126
|
-
const [[x1, y1], [x2, y2]] = placement.area;
|
|
127
|
-
const parts = [`grid-column: ${x1} / ${x2}`, `grid-row: ${y1} / ${y2}`, "min-width: 0"];
|
|
128
|
-
const h = placement.h && placement.h !== "stretch" ? placement.h : null;
|
|
129
|
-
const v = placement.v && placement.v !== "stretch" ? placement.v : null;
|
|
130
|
-
if (h || v) {
|
|
131
|
-
parts.push("display: flex");
|
|
132
|
-
const toFlex = (a) => a === "start" ? "flex-start" : a === "end" ? "flex-end" : "center";
|
|
133
|
-
if (h) parts.push(`justify-content: ${toFlex(h)}`);
|
|
134
|
-
if (v) parts.push(`align-items: ${toFlex(v)}`);
|
|
135
|
-
}
|
|
136
|
-
return parts.join("; ");
|
|
137
|
-
}
|
|
138
|
-
const placedFields = computed(() => {
|
|
139
|
-
const layout = activeLayout.value?.layout;
|
|
140
|
-
if (!layout) return [];
|
|
141
|
-
const out = [];
|
|
142
|
-
for (const field of config.value.fields) {
|
|
143
|
-
const placement = layout.placements[field.id];
|
|
144
|
-
if (!placement) continue;
|
|
145
|
-
if (isHidden(field)) continue;
|
|
146
|
-
const entry = findField(field.type, field.compatibilityDate);
|
|
147
|
-
if (!entry) {
|
|
148
|
-
const key = `${field.type}@${field.compatibilityDate}`;
|
|
149
|
-
if (!warnedMissing.has(key)) {
|
|
150
|
-
warnedMissing.add(key);
|
|
151
|
-
console.warn(`[shwfed-form] no registered field type for ${key}`);
|
|
152
|
-
}
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
out.push({ field, placement, entry });
|
|
156
|
-
}
|
|
157
|
-
return out;
|
|
158
|
-
});
|
|
159
|
-
const gridStyle = computed(() => {
|
|
160
|
-
const l = activeLayout.value?.layout;
|
|
161
|
-
if (!l) return "";
|
|
162
|
-
const colTemplate = `repeat(${l.columns}, minmax(0, 1fr))`;
|
|
163
|
-
let rowTemplate = "";
|
|
164
|
-
if (l.rows) {
|
|
165
|
-
const grows = Array.from({ length: l.rows }, () => false);
|
|
166
|
-
for (const p of placedFields.value) {
|
|
167
|
-
if (!p.entry.metadata.h.grow) continue;
|
|
168
|
-
const [[, y1], [, y2]] = p.placement.area;
|
|
169
|
-
const lo = Math.max(1, y1);
|
|
170
|
-
const hi = Math.min(l.rows, y2 - 1);
|
|
171
|
-
for (let r = lo; r <= hi; r++) grows[r - 1] = true;
|
|
172
|
-
}
|
|
173
|
-
rowTemplate = grows.map((g) => g ? "auto" : "minmax(auto, 1fr)").join(" ");
|
|
174
|
-
}
|
|
175
|
-
const gap = `calc(${l.gap ?? DEFAULT_GAP} * 0.25rem)`;
|
|
176
|
-
const colGap = l.columns > 1 ? `min(${gap}, calc(100% / ${l.columns - 1}))` : gap;
|
|
177
|
-
const parts = [
|
|
178
|
-
"display: grid",
|
|
179
|
-
`grid-template-columns: ${colTemplate}`,
|
|
180
|
-
rowTemplate ? `grid-template-rows: ${rowTemplate}` : "",
|
|
181
|
-
`column-gap: ${colGap}`,
|
|
182
|
-
`row-gap: ${gap}`,
|
|
183
|
-
l.style ?? ""
|
|
184
|
-
].filter(Boolean);
|
|
185
|
-
return parts.join("; ");
|
|
186
|
-
});
|
|
187
|
-
const rendered = computed(
|
|
188
|
-
() => placedFields.value.map((p) => ({
|
|
189
|
-
key: p.field.id,
|
|
190
|
-
style: cellStyle(p.placement),
|
|
191
|
-
component: p.entry.runtime,
|
|
192
|
-
fieldId: p.field.id,
|
|
193
|
-
config: p.field
|
|
194
|
-
}))
|
|
195
|
-
);
|
|
196
112
|
</script>
|
|
197
113
|
|
|
198
114
|
<template>
|
|
199
115
|
<ClientOnly>
|
|
200
|
-
<
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
>
|
|
206
|
-
<component
|
|
207
|
-
:is="r.component"
|
|
208
|
-
:field-id="r.fieldId"
|
|
209
|
-
:config="r.config"
|
|
210
|
-
/>
|
|
211
|
-
</div>
|
|
212
|
-
</div>
|
|
116
|
+
<FormUnitRenderer
|
|
117
|
+
:unit="unit"
|
|
118
|
+
:evaluate-media="evaluateMedia"
|
|
119
|
+
:is-hidden="isHidden"
|
|
120
|
+
/>
|
|
213
121
|
</ClientOnly>
|
|
214
122
|
</template>
|
|
@@ -138,10 +138,13 @@ export declare function FormConfig(configure: (env: Environment) => void): Schem
|
|
|
138
138
|
}>;
|
|
139
139
|
}>>>;
|
|
140
140
|
kind: Schema.tag<"shwfed.component.form">;
|
|
141
|
-
initial: Schema.optional<Schema.Struct<{
|
|
141
|
+
initial: Schema.optional<Schema.transform<Schema.Union<[Schema.Schema<string, string, never>, Schema.Struct<{
|
|
142
142
|
request: Schema.optional<Schema.Schema<string, string, never>>;
|
|
143
143
|
data: Schema.Schema<string, string, never>;
|
|
144
|
-
}
|
|
144
|
+
}>]>, Schema.Struct<{
|
|
145
|
+
request: Schema.optional<Schema.Schema<string, string, never>>;
|
|
146
|
+
data: Schema.Schema<string, string, never>;
|
|
147
|
+
}>>>;
|
|
145
148
|
readonly: Schema.optional<Schema.Schema<string, string, never>>;
|
|
146
149
|
}>>;
|
|
147
150
|
export declare function createFormConfig(body: Omit<Schema.Schema.Type<ReturnType<typeof FormConfig>>, 'kind'>): {
|
|
@@ -58,30 +58,40 @@ export function FormConfig(configure) {
|
|
|
58
58
|
resultType: "dyn"
|
|
59
59
|
});
|
|
60
60
|
const CelFormReadonly = Expression({ configure: formConfigure, resultType: "bool" });
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
description: md`
|
|
67
|
-
可选的 HTTP 请求表达式:返回 \`HttpRequest\`,运行时由宿主发起,并把响应体作为 \`json\` 传给「数据」表达式。
|
|
61
|
+
const InitialStruct = Schema.Struct({
|
|
62
|
+
request: Schema.optional(CelInitialRequest.annotations({
|
|
63
|
+
title: "\u8BF7\u6C42",
|
|
64
|
+
description: md`
|
|
65
|
+
可选的 HTTP 请求表达式:返回 \`HttpRequest\`,运行时由宿主发起,并把响应体作为 \`json\` 传给「数据」表达式。
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
留空时「数据」直接对 CEL 上下文求值(静态默认值,或引用 \`form\` 的表达式)。
|
|
68
|
+
`
|
|
69
|
+
})),
|
|
70
|
+
data: CelInitialData.annotations({
|
|
71
|
+
title: "\u6570\u636E",
|
|
72
|
+
description: md`
|
|
73
|
+
返回整个表单初始值的 CEL 表达式(应为一个对象):
|
|
76
74
|
|
|
77
|
-
|
|
75
|
+
- 可以互相依赖,但应避免循环引用。考虑一个含「用户名」与「用户角色」的表单:可默认填入当前登入人的用户名,再以此为依据填入其默认角色。
|
|
78
76
|
|
|
79
|
-
|
|
77
|
+
- 配置了「请求」时,可通过 \`json\` 引用响应体,例如 \`json.?data\`。
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
- 如果配置了初始值,重置这个表单将**重置为其初始值,而非空**。
|
|
80
|
+
`
|
|
81
|
+
})
|
|
82
|
+
});
|
|
83
|
+
const Initial = Schema.transform(
|
|
84
|
+
Schema.Union(CelInitialData, InitialStruct),
|
|
85
|
+
InitialStruct,
|
|
86
|
+
{
|
|
87
|
+
strict: true,
|
|
88
|
+
decode: (input) => typeof input === "string" ? { data: input } : input,
|
|
89
|
+
encode: (output) => output
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
return Schema.Struct({
|
|
93
|
+
kind: Schema.tag(KIND),
|
|
94
|
+
initial: Schema.optional(Initial.annotations({
|
|
85
95
|
title: "\u521D\u59CB\u503C",
|
|
86
96
|
description: "\u8868\u5355\u521D\u59CB\u503C\u7684\u6765\u6E90\uFF1A\u53EF\u9009\u7684 HTTP \u8BF7\u6C42\uFF0C\u52A0\u4E00\u4E2A\u8FD4\u56DE\u521D\u59CB\u503C\u5BF9\u8C61\u7684 CEL \u8868\u8FBE\u5F0F"
|
|
87
97
|
})),
|
package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.number/runtime.vue
CHANGED
|
@@ -44,6 +44,7 @@ function toChineseUppercase(n) {
|
|
|
44
44
|
return isNegative ? `\u8D1F${result}` : result;
|
|
45
45
|
}
|
|
46
46
|
function formatNumber(value, column) {
|
|
47
|
+
if (value === void 0 || value === null) return "-";
|
|
47
48
|
const n = Number(value);
|
|
48
49
|
if (Number.isNaN(n)) return "-";
|
|
49
50
|
const displayMode = column.displayMode ?? "plain";
|
|
@@ -432,10 +432,13 @@ export declare function TableConfig(configure: (env: Environment) => void): Sche
|
|
|
432
432
|
}>;
|
|
433
433
|
}>>>;
|
|
434
434
|
kind: Schema.tag<"shwfed.component.form">;
|
|
435
|
-
initial: Schema.optional<Schema.Struct<{
|
|
435
|
+
initial: Schema.optional<Schema.transform<Schema.Union<[Schema.Schema<string, string, never>, Schema.Struct<{
|
|
436
436
|
request: Schema.optional<Schema.Schema<string, string, never>>;
|
|
437
437
|
data: Schema.Schema<string, string, never>;
|
|
438
|
-
}
|
|
438
|
+
}>]>, Schema.Struct<{
|
|
439
|
+
request: Schema.optional<Schema.Schema<string, string, never>>;
|
|
440
|
+
data: Schema.Schema<string, string, never>;
|
|
441
|
+
}>>>;
|
|
439
442
|
readonly: Schema.optional<Schema.Schema<string, string, never>>;
|
|
440
443
|
}>>>;
|
|
441
444
|
cellStyle: Schema.optional<Schema.Schema<string, string, never>>;
|
|
@@ -214,7 +214,6 @@ function applyShortcut(index) {
|
|
|
214
214
|
<PopoverContent
|
|
215
215
|
align="start"
|
|
216
216
|
:class="cn('w-auto p-0', props.popoverClass)"
|
|
217
|
-
:style="{ minWidth: 'var(--reka-popover-trigger-width)' }"
|
|
218
217
|
@open-auto-focus="(event) => event.preventDefault()"
|
|
219
218
|
@interact-outside="onInteractOutside"
|
|
220
219
|
>
|