@shwfed/config 2.1.1 → 2.1.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.
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shwfed",
3
3
  "configKey": "shwfed",
4
- "version": "2.1.1",
4
+ "version": "2.1.2",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -22,6 +22,13 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
22
22
  readonly locale: "en" | "ja" | "ko";
23
23
  readonly message: string;
24
24
  }[]] | undefined;
25
+ readonly locale?: readonly [{
26
+ readonly locale: "zh";
27
+ readonly message: string;
28
+ }, ...{
29
+ readonly locale: "en" | "ja" | "ko";
30
+ readonly message: string;
31
+ }[]] | undefined;
25
32
  readonly displayName?: string | undefined;
26
33
  readonly compatibilityDate: "2026-05-13";
27
34
  readonly min?: number | undefined;
@@ -71,6 +78,13 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
71
78
  readonly locale: "en" | "ja" | "ko";
72
79
  readonly message: string;
73
80
  }[]] | undefined;
81
+ readonly locale?: readonly [{
82
+ readonly locale: "zh";
83
+ readonly message: string;
84
+ }, ...{
85
+ readonly locale: "en" | "ja" | "ko";
86
+ readonly message: string;
87
+ }[]] | undefined;
74
88
  readonly displayName?: string | undefined;
75
89
  readonly compatibilityDate: "2026-05-13";
76
90
  readonly min?: number | undefined;
@@ -233,6 +233,28 @@ const reorderable = computed({
233
233
  </Field>
234
234
  </div>
235
235
 
236
+ <Field orientation="vertical">
237
+ <FieldLabel class="text-xs text-zinc-500">
238
+ <template
239
+ v-if="fieldDescription('locale')"
240
+ #tooltip
241
+ >
242
+ <Markdown
243
+ :source="fieldDescription('locale')"
244
+ block
245
+ class="prose prose-sm prose-zinc"
246
+ />
247
+ </template>
248
+ {{ fieldTitle("locale") }}
249
+ </FieldLabel>
250
+ <LocaleField
251
+ markdown
252
+ translate-hint="列表项折叠摘要,Markdown 文本,保留表达式插值占位符不要翻译"
253
+ :model-value="value.locale"
254
+ @update:model-value="(v) => value = { ...value, locale: v }"
255
+ />
256
+ </Field>
257
+
236
258
  <div class="grid grid-cols-3 gap-3">
237
259
  <Field orientation="vertical">
238
260
  <FieldLabel class="text-xs text-zinc-500">
@@ -22,6 +22,13 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
22
22
  readonly locale: "en" | "ja" | "ko";
23
23
  readonly message: string;
24
24
  }[]] | undefined;
25
+ readonly locale?: readonly [{
26
+ readonly locale: "zh";
27
+ readonly message: string;
28
+ }, ...{
29
+ readonly locale: "en" | "ja" | "ko";
30
+ readonly message: string;
31
+ }[]] | undefined;
25
32
  readonly displayName?: string | undefined;
26
33
  readonly compatibilityDate: "2026-05-13";
27
34
  readonly min?: number | undefined;
@@ -71,6 +78,13 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
71
78
  readonly locale: "en" | "ja" | "ko";
72
79
  readonly message: string;
73
80
  }[]] | undefined;
81
+ readonly locale?: readonly [{
82
+ readonly locale: "zh";
83
+ readonly message: string;
84
+ }, ...{
85
+ readonly locale: "en" | "ja" | "ko";
86
+ readonly message: string;
87
+ }[]] | undefined;
74
88
  readonly displayName?: string | undefined;
75
89
  readonly compatibilityDate: "2026-05-13";
76
90
  readonly min?: number | undefined;
@@ -5,6 +5,7 @@ import { computed, ref, watch } from "vue";
5
5
  import { useI18n } from "vue-i18n";
6
6
  import { cel as _rawCel } from "../../../../../utils/cel";
7
7
  import { celBindings, injectCELContext } from "../../../../../utils/cel-context";
8
+ import { interpolate } from "../../../../../utils/interpolate";
8
9
  import { getLocalizedText } from "../../../../../share/locale";
9
10
  import {
10
11
  useTreeDnd
@@ -31,6 +32,8 @@ const tooltipText = computed(
31
32
  const addLabelText = computed(
32
33
  () => getLocalizedText(props.config.addLabel, locale.value) || "\u65B0\u589E"
33
34
  );
35
+ const summaryTemplate = computed(() => getLocalizedText(props.config.locale, locale.value));
36
+ const collapsible = computed(() => !!summaryTemplate.value);
34
37
  function evalBool(expression, label) {
35
38
  if (!expression) return false;
36
39
  try {
@@ -50,6 +53,28 @@ const items = computed(() => {
50
53
  if (!Array.isArray(raw)) return [];
51
54
  return raw;
52
55
  });
56
+ const rowSummaries = computed(() => {
57
+ const template = summaryTemplate.value;
58
+ if (template == null) return [];
59
+ return items.value.map(
60
+ (item, i) => interpolate(template, (expression) => {
61
+ try {
62
+ const out = Effect.runSync($cel(expression, {
63
+ form: state.value ?? {},
64
+ item: item ?? {},
65
+ index: i
66
+ }));
67
+ return out == null ? "" : String(out);
68
+ } catch (err) {
69
+ console.error(
70
+ `[shwfed-form] list field: failed to evaluate summary expression \`${expression}\` for ${props.fieldId}[${i}]:`,
71
+ err
72
+ );
73
+ return "";
74
+ }
75
+ })
76
+ );
77
+ });
53
78
  function writeItems(next) {
54
79
  const path = props.config.binding;
55
80
  if (path == null) {
@@ -74,6 +99,16 @@ watch(
74
99
  },
75
100
  { immediate: true }
76
101
  );
102
+ const expandedKeys = ref(/* @__PURE__ */ new Set());
103
+ function isExpanded(key) {
104
+ return expandedKeys.value.has(key);
105
+ }
106
+ function toggleExpanded(key) {
107
+ const next = new Set(expandedKeys.value);
108
+ if (next.has(key)) next.delete(key);
109
+ else next.add(key);
110
+ expandedKeys.value = next;
111
+ }
77
112
  const atMax = computed(
78
113
  () => props.config.max != null && items.value.length >= props.config.max
79
114
  );
@@ -86,13 +121,23 @@ const canReorder = computed(() => !isDisabled.value && reorderable.value);
86
121
  function append() {
87
122
  if (!canAdd.value) return;
88
123
  writeItems([...items.value, {}]);
89
- rowKeys.value = [...rowKeys.value, makeId()];
124
+ const id = makeId();
125
+ rowKeys.value = [...rowKeys.value, id];
126
+ const next = new Set(expandedKeys.value);
127
+ next.add(id);
128
+ expandedKeys.value = next;
90
129
  }
91
130
  function removeAt(i) {
92
131
  if (!canRemove.value) return;
93
132
  if (i < 0 || i >= items.value.length) return;
133
+ const removedKey = rowKeys.value[i];
94
134
  writeItems(items.value.filter((_, j) => j !== i));
95
135
  rowKeys.value = rowKeys.value.filter((_, j) => j !== i);
136
+ if (removedKey != null && expandedKeys.value.has(removedKey)) {
137
+ const next = new Set(expandedKeys.value);
138
+ next.delete(removedKey);
139
+ expandedKeys.value = next;
140
+ }
96
141
  }
97
142
  function moveRow(from, to) {
98
143
  if (!canReorder.value) return;
@@ -169,19 +214,74 @@ function rowConfig(i) {
169
214
  v-for="(item, i) in items"
170
215
  :key="rowKeys[i]"
171
216
  :ref="dnd.rowRef(rowId(i), rowConfig(i))"
172
- class="relative flex items-center gap-2 rounded border border-zinc-200 bg-zinc-50/40 p-3"
217
+ class="relative rounded border border-zinc-200 bg-zinc-50/40 p-3"
173
218
  data-slot="list-row"
174
219
  :data-instruction="dnd.instructionFor(rowId(i)) ?? void 0"
175
220
  >
176
- <Icon
177
- v-if="reorderable"
178
- icon="fluent:re-order-dots-vertical-20-regular"
179
- class="drag-handle size-4 shrink-0 text-zinc-400"
180
- :class="canReorder ? 'cursor-grab' : 'cursor-not-allowed opacity-50'"
181
- data-slot="list-row-drag-handle"
182
- />
221
+ <div class="flex items-center gap-2">
222
+ <Icon
223
+ v-if="reorderable"
224
+ icon="fluent:re-order-dots-vertical-20-regular"
225
+ class="drag-handle size-4 shrink-0 text-zinc-400"
226
+ :class="canReorder ? 'cursor-grab' : 'cursor-not-allowed opacity-50'"
227
+ data-slot="list-row-drag-handle"
228
+ />
229
+
230
+ <InputGroupButton
231
+ v-if="collapsible"
232
+ size="icon-xs"
233
+ class="shrink-0"
234
+ data-slot="list-row-collapse"
235
+ :aria-expanded="isExpanded(rowId(i))"
236
+ :aria-label="isExpanded(rowId(i)) ? '\u6298\u53E0' : '\u5C55\u5F00'"
237
+ @click="toggleExpanded(rowId(i))"
238
+ >
239
+ <Icon
240
+ :icon="isExpanded(rowId(i)) ? 'fluent:chevron-down-20-regular' : 'fluent:chevron-right-20-regular'"
241
+ />
242
+ </InputGroupButton>
183
243
 
184
- <div class="min-w-0 flex-1">
244
+ <div
245
+ v-if="collapsible"
246
+ class="min-w-0 flex-1"
247
+ data-slot="list-row-summary"
248
+ >
249
+ <Markdown
250
+ :source="rowSummaries[i] ?? ''"
251
+ class="prose prose-xs prose-zinc"
252
+ />
253
+ </div>
254
+ <div
255
+ v-else
256
+ class="min-w-0 flex-1"
257
+ >
258
+ <Row
259
+ :model-value="item"
260
+ :unit="config.unit"
261
+ :index="i"
262
+ @update:model-value="(next) => updateRow(i, next)"
263
+ />
264
+ </div>
265
+
266
+ <InputGroupButton
267
+ variant="destructive"
268
+ size="icon-xs"
269
+ class="shrink-0"
270
+ :disabled="!canRemove"
271
+ data-slot="list-row-remove"
272
+ aria-label="删除"
273
+ @click="removeAt(i)"
274
+ >
275
+ <Icon icon="fluent:delete-20-regular" />
276
+ </InputGroupButton>
277
+ </div>
278
+
279
+ <div
280
+ v-if="collapsible"
281
+ v-show="isExpanded(rowId(i))"
282
+ class="mt-3 min-w-0"
283
+ data-slot="list-row-content"
284
+ >
185
285
  <Row
186
286
  :model-value="item"
187
287
  :unit="config.unit"
@@ -189,18 +289,6 @@ function rowConfig(i) {
189
289
  @update:model-value="(next) => updateRow(i, next)"
190
290
  />
191
291
  </div>
192
-
193
- <InputGroupButton
194
- variant="destructive"
195
- size="icon-xs"
196
- class="shrink-0"
197
- :disabled="!canRemove"
198
- data-slot="list-row-remove"
199
- aria-label="删除"
200
- @click="removeAt(i)"
201
- >
202
- <Icon icon="fluent:delete-20-regular" />
203
- </InputGroupButton>
204
292
  </div>
205
293
 
206
294
  <Button
@@ -36,6 +36,13 @@ export declare function schema(configure: (env: Environment) => void): Schema.re
36
36
  readonly locale: "en" | "ja" | "ko";
37
37
  readonly message: string;
38
38
  }[]] | undefined;
39
+ readonly locale?: readonly [{
40
+ readonly locale: "zh";
41
+ readonly message: string;
42
+ }, ...{
43
+ readonly locale: "en" | "ja" | "ko";
44
+ readonly message: string;
45
+ }[]] | undefined;
39
46
  readonly displayName?: string | undefined;
40
47
  readonly compatibilityDate: "2026-05-13";
41
48
  readonly min?: number | undefined;
@@ -94,6 +101,13 @@ export declare function schema(configure: (env: Environment) => void): Schema.re
94
101
  min: Schema.optional<Schema.refine<number, Schema.filter<typeof Schema.Number>>>;
95
102
  max: Schema.optional<Schema.refine<number, Schema.filter<typeof Schema.Number>>>;
96
103
  disabled: Schema.optional<Schema.Schema<string, string, never>>;
104
+ locale: Schema.optional<Schema.TupleType<readonly [Schema.Struct<{
105
+ locale: Schema.Literal<["zh"]>;
106
+ message: Schema.SchemaClass<string, string, never>;
107
+ }>], [Schema.Struct<{
108
+ locale: Schema.Literal<["ja", "en", "ko"]>;
109
+ message: Schema.SchemaClass<string, string, never>;
110
+ }>]>>;
97
111
  unit: Schema.suspend<Readonly<{
98
112
  fields: ReadonlyArray<import("../../../schema.js").FieldValue>;
99
113
  layouts: ReadonlyArray<import("../../../schema.js").LayoutSetValue>;
@@ -64,6 +64,10 @@ export function schema(configure) {
64
64
  title: "\u7981\u7528\u6761\u4EF6",
65
65
  description: "\u8FD4\u56DE `true` \u65F6\u6574\u4E2A\u5217\u8868\u53EA\u8BFB\uFF0C\u4E0D\u80FD\u65B0\u589E/\u5220\u9664/\u91CD\u6392"
66
66
  })),
67
+ locale: Schema.optional(Locale.annotations({
68
+ title: "\u5217\u8868\u9879\u6458\u8981",
69
+ description: "\u914D\u7F6E\u540E\u6BCF\u4E2A\u5217\u8868\u9879\u53EF\u6298\u53E0\uFF0C\u6298\u53E0\u65F6\u5C55\u793A\u6B64\u5904\u6587\u672C\u6E32\u67D3\u7684\u884C\u5185 Markdown \u4F5C\u4E3A\u6458\u8981\u3002\u652F\u6301 `{{ \u8868\u8FBE\u5F0F }}` \u63D2\u503C\uFF0C\u8868\u8FBE\u5F0F\u4E3A CEL\uFF0C\u53EF\u8BBF\u95EE `item`\uFF08\u5F53\u524D\u9879\uFF09\u3001`index`\uFF08\u5E8F\u53F7\uFF0C\u4ECE 0 \u5F00\u59CB\uFF09\u4E0E `form`"
70
+ })),
67
71
  unit: Unit.annotations({
68
72
  title: "\u5217\u8868\u9879\u914D\u7F6E",
69
73
  description: "\u5355\u4E2A\u5217\u8868\u9879\u7684\u5B57\u6BB5\u4E0E\u5E03\u5C40\uFF1B\u6BCF\u4E2A\u5217\u8868\u9879\u90FD\u6309\u6B64\u7ED3\u6784\u6E32\u67D3\u4E00\u4EFD"
@@ -0,0 +1 @@
1
+ export declare function interpolate(template: string, evaluate: (expression: string) => string): string;
@@ -0,0 +1,7 @@
1
+ const TOKEN = /\{\{([\s\S]*?)\}\}/g;
2
+ export function interpolate(template, evaluate) {
3
+ return template.replace(TOKEN, (_token, raw) => {
4
+ const expression = raw.trim();
5
+ return expression.length === 0 ? "" : evaluate(expression);
6
+ });
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shwfed/config",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "description": "Configurable UI for SHWFED",
5
5
  "type": "module",
6
6
  "publishConfig": {