@shwfed/config 2.2.0 → 2.2.2

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 (41) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/form/FormUnitRenderer.d.vue.ts +9 -0
  3. package/dist/runtime/components/form/FormUnitRenderer.vue +117 -0
  4. package/dist/runtime/components/form/FormUnitRenderer.vue.d.ts +9 -0
  5. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.d.vue.ts +2 -0
  6. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.vue +31 -0
  7. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.vue.d.ts +2 -0
  8. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/runtime.vue +12 -2
  9. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/schema.d.ts +1 -0
  10. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/schema.js +4 -0
  11. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.d.vue.ts +2 -0
  12. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.vue +31 -0
  13. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.vue.d.ts +2 -0
  14. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/runtime.vue +16 -8
  15. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/schema.d.ts +1 -0
  16. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/schema.js +4 -0
  17. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.d.vue.ts +2 -2
  18. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.vue +1 -1
  19. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.vue.d.ts +2 -2
  20. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/row.vue +7 -104
  21. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/schema.d.ts +1 -1
  22. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/config.d.vue.ts +53 -0
  23. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/config.vue +130 -0
  24. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/config.vue.d.ts +53 -0
  25. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/runtime.d.vue.ts +8 -0
  26. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/runtime.vue +107 -0
  27. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/runtime.vue.d.ts +8 -0
  28. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/schema.d.ts +54 -0
  29. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/schema.js +47 -0
  30. package/dist/runtime/components/form/index.vue +16 -108
  31. package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.number/runtime.vue +1 -0
  32. package/dist/runtime/plugins/i18n/index.js +8 -2
  33. package/dist/runtime/vendor/cel-js/CLAUDE.md +3 -1
  34. package/dist/runtime/vendor/cel-js/PROMPT.md +20 -0
  35. package/dist/runtime/vendor/cel-js/lib/http-builder.d.ts +2 -0
  36. package/dist/runtime/vendor/cel-js/lib/http-builder.js +24 -3
  37. package/dist/runtime/vendor/cel-js/lib/operators.js +139 -11
  38. package/dist/runtime/vendor/cel-js/lib/parser.d.ts +2 -1
  39. package/dist/runtime/vendor/cel-js/lib/parser.js +20 -3
  40. package/dist/runtime/vendor/cel-js/lib/serialize.js +5 -1
  41. package/package.json +1 -1
@@ -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,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;
@@ -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;
@@ -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>;
@@ -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, onMounted, ref, watch } from "vue";
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
- function pickLayout(sets) {
97
- for (const set of sets) {
98
- if (!set.media) return set;
99
- try {
100
- if (Effect.runSync($cel(set.media))) return set;
101
- } catch (err) {
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
- const activeLayout = ref();
108
- onMounted(() => {
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 ${f.id}:`, err);
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
- <div :style="gridStyle">
201
- <div
202
- v-for="r in rendered"
203
- :key="r.key"
204
- :style="r.style"
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>
@@ -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";
@@ -1,14 +1,20 @@
1
1
  import { defineNuxtPlugin } from "#app";
2
2
  import { createI18n } from "vue-i18n";
3
+ import { HttpRequestBuilder } from "../../vendor/cel-js/lib/http-builder.js";
3
4
  export default defineNuxtPlugin({
4
5
  name: "shwfed:i18n",
5
6
  setup: (nuxt) => {
6
- nuxt.vueApp.use(createI18n({
7
+ const i18n = createI18n({
7
8
  locale: navigator?.language,
8
9
  legacy: false,
9
10
  fallbackWarn: false,
10
11
  fallbackLocale: "zh",
11
12
  globalInjection: false
12
- }));
13
+ });
14
+ nuxt.vueApp.use(i18n);
15
+ HttpRequestBuilder.setDefaultHeader(
16
+ "Accept-Language",
17
+ () => i18n.global.locale.value
18
+ );
13
19
  }
14
20
  });