@shwfed/config 2.6.0 → 2.7.0

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 (59) hide show
  1. package/dist/mcp.mjs +1212 -1045
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +2 -1
  4. package/dist/preview/assets/config-B5FFtD0s.js +1 -0
  5. package/dist/preview/assets/config-BpWP2vu_.js +1 -0
  6. package/dist/preview/assets/{config-Co--BPbb.js → config-C2OqUTNd.js} +1 -1
  7. package/dist/preview/assets/{config-VChcvg_y.js → config-D7cjMBeK.js} +1 -1
  8. package/dist/preview/assets/config-DhORWTZC.js +1 -0
  9. package/dist/preview/assets/config-DuuYvFG_.js +1 -0
  10. package/dist/preview/assets/config-dpwN2-UY.js +1 -0
  11. package/dist/preview/assets/{config-Du7AdGIY.js → config-eP0EblYK.js} +1 -1
  12. package/dist/preview/assets/{config-LdNKbqCx.js → config-hs_pZ5MM.js} +1 -1
  13. package/dist/preview/assets/{definition.vue_vue_type_script_setup_true_lang-Ma8i-2ox.js → definition.vue_vue_type_script_setup_true_lang-B8-Uydoy.js} +1 -1
  14. package/dist/preview/assets/index-BGFrUxgg.js +680 -0
  15. package/dist/preview/assets/index-BoGW90Pq.css +1 -0
  16. package/dist/preview/assets/index-Bw16PZhL.js +1 -0
  17. package/dist/preview/assets/index-CG261V86.js +1 -0
  18. package/dist/preview/assets/item-aVe51Gy6.js +1 -0
  19. package/dist/preview/assets/runtime-3rNI0KDH.js +1 -0
  20. package/dist/preview/assets/runtime-BOn8EwHL.js +1 -0
  21. package/dist/preview/assets/runtime-Bwr-rb58.js +1 -0
  22. package/dist/preview/assets/runtime-CXQuhSAX.js +1 -0
  23. package/dist/preview/assets/runtime-CYGmRjmI.js +1 -0
  24. package/dist/preview/assets/runtime-Ca79Fs6I.js +1 -0
  25. package/dist/preview/assets/runtime-CfVt6IWe.js +1 -0
  26. package/dist/preview/assets/runtime-DYj-R8SZ.js +1 -0
  27. package/dist/preview/assets/runtime-Dd1GqYeP.js +1 -0
  28. package/dist/preview/index.html +2 -2
  29. package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/config.d.vue.ts +59 -0
  30. package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/config.vue +452 -0
  31. package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/config.vue.d.ts +59 -0
  32. package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/item.d.vue.ts +7 -0
  33. package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/item.vue +112 -0
  34. package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/item.vue.d.ts +7 -0
  35. package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/runtime.d.vue.ts +59 -0
  36. package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/runtime.vue +47 -0
  37. package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/runtime.vue.d.ts +59 -0
  38. package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/schema.d.ts +92 -0
  39. package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/schema.js +117 -0
  40. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.combobox.multi/schema.js +1 -1
  41. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.combobox.single/schema.js +1 -1
  42. package/package.json +2 -1
  43. package/dist/preview/assets/config-BF-HYbrD.js +0 -1
  44. package/dist/preview/assets/config-CevoqLCe.js +0 -1
  45. package/dist/preview/assets/config-DMOAQ9zl.js +0 -1
  46. package/dist/preview/assets/config-RACtdV3v.js +0 -1
  47. package/dist/preview/assets/config-oBOXGUjR.js +0 -1
  48. package/dist/preview/assets/index-C-nzF9-u.js +0 -1
  49. package/dist/preview/assets/index-CHEiFlnE.css +0 -1
  50. package/dist/preview/assets/index-rxUrWg1Y.js +0 -680
  51. package/dist/preview/assets/runtime-BNzaUtd-.js +0 -1
  52. package/dist/preview/assets/runtime-CAj4SjAs.js +0 -1
  53. package/dist/preview/assets/runtime-CE_42oyr.js +0 -1
  54. package/dist/preview/assets/runtime-CLMz0SYI.js +0 -1
  55. package/dist/preview/assets/runtime-COCfVWBL.js +0 -1
  56. package/dist/preview/assets/runtime-GfHY6wxJ.js +0 -1
  57. package/dist/preview/assets/runtime-Y00C-S73.js +0 -1
  58. package/dist/preview/assets/runtime-llw5ZA1Z.js +0 -1
  59. package/dist/preview/assets/runtime-wAJ77Q3a.js +0 -1
@@ -0,0 +1,452 @@
1
+ <script setup>
2
+ import { computed, ref } from "vue";
3
+ import { Icon } from "@iconify/vue";
4
+ import { Button } from "../../../../ui/button";
5
+ import { Field, FieldLabel } from "../../../../ui/field";
6
+ import { InputGroup, InputGroupInput, InputGroupNumberField } from "../../../../ui/input-group";
7
+ import { ExpressionEditor } from "../../../../ui/expression-editor";
8
+ import { Locale as LocaleField } from "../../../../ui/locale";
9
+ import { Markdown } from "../../../../ui/markdown";
10
+ import { ScrollArea } from "../../../../ui/scroll-area";
11
+ import { Separator } from "../../../../ui/separator";
12
+ import {
13
+ useTreeDnd
14
+ } from "../../../../../composables/useTreeDnd";
15
+ import {
16
+ dataSourceSchema,
17
+ getStructFieldDescription,
18
+ getStructFieldTitle,
19
+ itemSchema,
20
+ schema
21
+ } from "./schema";
22
+ defineOptions({ name: "ShwfedBlockAnimatedNumberConfig" });
23
+ const block = defineModel({ type: null, ...{ required: true } });
24
+ const blockSchema = schema(() => {
25
+ });
26
+ const itemSchemaObj = itemSchema(() => {
27
+ });
28
+ const dsSchema = dataSourceSchema(() => {
29
+ });
30
+ const fieldTitle = (f) => getStructFieldTitle(blockSchema, f) ?? f;
31
+ const fieldDescription = (f) => getStructFieldDescription(blockSchema, f);
32
+ const itemTitle = (f) => getStructFieldTitle(itemSchemaObj, f) ?? f;
33
+ const itemDescription = (f) => getStructFieldDescription(itemSchemaObj, f);
34
+ const dsTitle = (f) => getStructFieldTitle(dsSchema, f) ?? f;
35
+ const dsDescription = (f) => getStructFieldDescription(dsSchema, f);
36
+ const JSON_VAR = {
37
+ json: { type: "optional<dyn>", label: "HTTP \u54CD\u5E94\u4F53\uFF08\u914D\u7F6E\u4E86\u8BF7\u6C42\u65F6\u53EF\u7528\uFF09" }
38
+ };
39
+ const viewMode = ref("item");
40
+ const selectedId = ref(block.value.items[0]?.id ?? "");
41
+ const selectedItem = computed(
42
+ () => block.value.items.find((i) => i.id === selectedId.value) ?? null
43
+ );
44
+ function selectGeneral() {
45
+ viewMode.value = "general";
46
+ }
47
+ function selectItem(id) {
48
+ viewMode.value = "item";
49
+ selectedId.value = id;
50
+ }
51
+ function itemLabel(it, i) {
52
+ const name = it.name?.trim();
53
+ return name && name.length > 0 ? name : `\u6570\u5B57 ${i + 1}`;
54
+ }
55
+ function setDisplayName(v) {
56
+ const s = String(v ?? "");
57
+ block.value = { ...block.value, displayName: s.length > 0 ? s : void 0 };
58
+ }
59
+ function setLocale(v) {
60
+ block.value = { ...block.value, locale: v };
61
+ }
62
+ function writeItems(next) {
63
+ block.value = { ...block.value, items: next };
64
+ }
65
+ function addItem() {
66
+ const it = { id: crypto.randomUUID(), dataSource: { value: "0" } };
67
+ writeItems([...block.value.items, it]);
68
+ selectItem(it.id);
69
+ }
70
+ function removeItem(id) {
71
+ if (block.value.items.length <= 1) return;
72
+ const i = block.value.items.findIndex((x) => x.id === id);
73
+ if (i < 0) return;
74
+ const next = block.value.items.filter((x) => x.id !== id);
75
+ writeItems(next);
76
+ if (selectedId.value === id) {
77
+ selectedId.value = next[Math.max(0, i - 1)]?.id ?? "";
78
+ }
79
+ }
80
+ function patchItem(id, patch) {
81
+ writeItems(block.value.items.map((i) => i.id === id ? { ...i, ...patch } : i));
82
+ }
83
+ function setName(id, v) {
84
+ const s = String(v ?? "");
85
+ patchItem(id, { name: s.length > 0 ? s : void 0 });
86
+ }
87
+ function patchDataSource(id, patch) {
88
+ const it = block.value.items.find((x) => x.id === id);
89
+ if (!it) return;
90
+ patchItem(id, { dataSource: { ...it.dataSource, ...patch } });
91
+ }
92
+ function setRequest(id, v) {
93
+ const it = block.value.items.find((x) => x.id === id);
94
+ if (!it) return;
95
+ if (v.trim() === "") {
96
+ patchItem(id, { dataSource: { value: it.dataSource.value } });
97
+ return;
98
+ }
99
+ patchDataSource(id, { request: v });
100
+ }
101
+ function setValue(id, v) {
102
+ patchDataSource(id, { value: v });
103
+ }
104
+ function setInterval(id, v) {
105
+ patchItem(id, { pollingInterval: typeof v === "number" && v > 0 ? Math.round(v) : void 0 });
106
+ }
107
+ function setPrefix(id, v) {
108
+ patchItem(id, { prefix: v.length > 0 ? v : void 0 });
109
+ }
110
+ function setSuffix(id, v) {
111
+ patchItem(id, { suffix: v.length > 0 ? v : void 0 });
112
+ }
113
+ function setStyle(id, v) {
114
+ patchItem(id, { style: v.trim().length > 0 ? v : void 0 });
115
+ }
116
+ const ROW_KIND = "animated-number-item-row";
117
+ function rowId(i) {
118
+ return `item-${i}`;
119
+ }
120
+ function moveItem(from, to) {
121
+ if (from === to || from < 0 || to < 0) return;
122
+ if (from >= block.value.items.length || to >= block.value.items.length) return;
123
+ const next = [...block.value.items];
124
+ const [moved] = next.splice(from, 1);
125
+ if (!moved) return;
126
+ next.splice(to, 0, moved);
127
+ writeItems(next);
128
+ }
129
+ function onRowDrop(e) {
130
+ if (e.source.kind !== ROW_KIND || e.target.kind !== ROW_KIND) return;
131
+ const from = block.value.items.findIndex((_, i) => rowId(i) === e.source.id);
132
+ const to = block.value.items.findIndex((_, i) => rowId(i) === e.target.id);
133
+ if (from < 0 || to < 0) return;
134
+ if (e.instruction === "reorder-above") moveItem(from, from < to ? to - 1 : to);
135
+ else if (e.instruction === "reorder-below") moveItem(from, from <= to ? to : to + 1);
136
+ }
137
+ const dnd = useTreeDnd({ onRowDrop });
138
+ const pickDragHandle = (el) => el.querySelector(".drag-handle");
139
+ function rowConfig(i) {
140
+ return {
141
+ kind: ROW_KIND,
142
+ canDrop: (src) => src.kind === ROW_KIND,
143
+ blockInstructions: (src) => {
144
+ const blocked = ["make-child", "reparent"];
145
+ if (src.id === rowId(i)) blocked.push("reorder-above", "reorder-below");
146
+ return blocked;
147
+ },
148
+ dragHandle: pickDragHandle
149
+ };
150
+ }
151
+ </script>
152
+
153
+ <template>
154
+ <div class="flex min-w-0 gap-3 min-h-96">
155
+ <!-- Left rail: general config + reorderable items -->
156
+ <div class="flex w-52 shrink-0 flex-col">
157
+ <div
158
+ class="row pl-2"
159
+ :class="viewMode === 'general' ? 'bg-[color-mix(in_srgb,var(--primary)_10%,white)] text-(--primary)' : 'text-zinc-700 hover:bg-zinc-50'"
160
+ @click="selectGeneral()"
161
+ >
162
+ <Icon
163
+ icon="fluent:settings-20-regular"
164
+ class="size-4 shrink-0"
165
+ />
166
+ <span class="flex-1 truncate">通用配置</span>
167
+ </div>
168
+
169
+ <Separator class="my-2" />
170
+
171
+ <ScrollArea class="flex-1">
172
+ <div class="flex flex-col gap-0.5">
173
+ <div
174
+ v-for="(it, i) in block.items"
175
+ :key="it.id"
176
+ :ref="dnd.rowRef(rowId(i), rowConfig(i))"
177
+ class="row group"
178
+ :class="viewMode === 'item' && selectedId === it.id ? 'bg-[color-mix(in_srgb,var(--primary)_10%,white)] text-(--primary)' : 'text-zinc-700 hover:bg-zinc-50'"
179
+ :data-instruction="dnd.instructionFor(rowId(i)) ?? void 0"
180
+ @click="selectItem(it.id)"
181
+ >
182
+ <Icon
183
+ icon="fluent:re-order-dots-vertical-20-regular"
184
+ class="drag-handle size-4 shrink-0 cursor-grab text-zinc-400"
185
+ />
186
+ <span class="flex-1 truncate">{{ itemLabel(it, i) }}</span>
187
+ <button
188
+ type="button"
189
+ class="ml-auto inline-flex size-5 shrink-0 items-center justify-center rounded text-zinc-300 opacity-0 transition-opacity group-hover:opacity-100 hover:bg-red-50 hover:text-red-500 disabled:opacity-0"
190
+ :disabled="block.items.length <= 1"
191
+ aria-label="删除数字项"
192
+ @click.stop="removeItem(it.id)"
193
+ >
194
+ <Icon
195
+ icon="fluent:delete-20-regular"
196
+ class="size-3.5"
197
+ />
198
+ </button>
199
+ </div>
200
+ </div>
201
+ </ScrollArea>
202
+
203
+ <Button
204
+ variant="ghost"
205
+ size="sm"
206
+ class="mt-1 w-full justify-center"
207
+ @click="addItem()"
208
+ >
209
+ <Icon
210
+ icon="fluent:add-20-regular"
211
+ class="size-4"
212
+ />
213
+ <span>新增数字</span>
214
+ </Button>
215
+ </div>
216
+
217
+ <Separator orientation="vertical" />
218
+
219
+ <!-- Right pane -->
220
+ <div class="min-w-0 flex-1">
221
+ <!-- General -->
222
+ <div
223
+ v-if="viewMode === 'general'"
224
+ class="flex flex-col gap-4"
225
+ >
226
+ <Field orientation="vertical">
227
+ <FieldLabel class="text-xs text-zinc-500">
228
+ <template
229
+ v-if="fieldDescription('displayName')"
230
+ #tooltip
231
+ >
232
+ <Markdown
233
+ :source="fieldDescription('displayName')"
234
+ block
235
+ class="prose prose-sm prose-zinc"
236
+ />
237
+ </template>
238
+ {{ fieldTitle("displayName") }}
239
+ </FieldLabel>
240
+ <InputGroup>
241
+ <InputGroupInput
242
+ :model-value="block.displayName ?? ''"
243
+ placeholder="例:核心指标"
244
+ @update:model-value="setDisplayName"
245
+ />
246
+ </InputGroup>
247
+ </Field>
248
+
249
+ <Field orientation="vertical">
250
+ <FieldLabel class="text-xs text-zinc-500">
251
+ <template
252
+ v-if="fieldDescription('locale')"
253
+ #tooltip
254
+ >
255
+ <Markdown
256
+ :source="fieldDescription('locale')"
257
+ block
258
+ class="prose prose-sm prose-zinc"
259
+ />
260
+ </template>
261
+ {{ fieldTitle("locale") }}
262
+ </FieldLabel>
263
+ <LocaleField
264
+ markdown
265
+ translate-hint="animated number caption"
266
+ :model-value="block.locale"
267
+ @update:model-value="setLocale"
268
+ />
269
+ </Field>
270
+ </div>
271
+
272
+ <!-- Item editor -->
273
+ <div
274
+ v-else-if="selectedItem"
275
+ class="flex flex-col gap-4"
276
+ >
277
+ <Field orientation="vertical">
278
+ <FieldLabel class="text-xs text-zinc-500">
279
+ <template
280
+ v-if="itemDescription('name')"
281
+ #tooltip
282
+ >
283
+ <Markdown
284
+ :source="itemDescription('name')"
285
+ block
286
+ class="prose prose-sm prose-zinc"
287
+ />
288
+ </template>
289
+ {{ itemTitle("name") }}
290
+ </FieldLabel>
291
+ <InputGroup>
292
+ <InputGroupInput
293
+ :model-value="selectedItem.name ?? ''"
294
+ placeholder="例:在线人数"
295
+ @update:model-value="(v) => setName(selectedItem.id, v)"
296
+ />
297
+ </InputGroup>
298
+ </Field>
299
+
300
+ <Field orientation="vertical">
301
+ <FieldLabel class="text-xs text-zinc-500">
302
+ <template
303
+ v-if="dsDescription('request')"
304
+ #tooltip
305
+ >
306
+ <Markdown
307
+ :source="dsDescription('request')"
308
+ block
309
+ class="prose prose-sm prose-zinc"
310
+ />
311
+ </template>
312
+ {{ dsTitle("request") }}
313
+ </FieldLabel>
314
+ <ExpressionEditor
315
+ :model-value="selectedItem.dataSource.request ?? ''"
316
+ placeholder="可选;如 http.get('/api/online')"
317
+ result-type="HttpRequest"
318
+ multiline
319
+ class="min-h-20"
320
+ @update:model-value="(v) => setRequest(selectedItem.id, v)"
321
+ />
322
+ </Field>
323
+
324
+ <div class="grid grid-cols-2 gap-3">
325
+ <Field orientation="vertical">
326
+ <FieldLabel class="text-xs text-zinc-500">
327
+ <template
328
+ v-if="dsDescription('value')"
329
+ #tooltip
330
+ >
331
+ <Markdown
332
+ :source="dsDescription('value')"
333
+ block
334
+ class="prose prose-sm prose-zinc"
335
+ />
336
+ </template>
337
+ {{ dsTitle("value") }}
338
+ </FieldLabel>
339
+ <ExpressionEditor
340
+ :model-value="selectedItem.dataSource.value"
341
+ placeholder="返回数字的表达式,如 json.count"
342
+ result-type="number"
343
+ :extra-vars="JSON_VAR"
344
+ @update:model-value="(v) => setValue(selectedItem.id, v)"
345
+ />
346
+ </Field>
347
+
348
+ <Field orientation="vertical">
349
+ <FieldLabel class="text-xs text-zinc-500">
350
+ <template
351
+ v-if="itemDescription('pollingInterval')"
352
+ #tooltip
353
+ >
354
+ <Markdown
355
+ :source="itemDescription('pollingInterval')"
356
+ block
357
+ class="prose prose-sm prose-zinc"
358
+ />
359
+ </template>
360
+ {{ itemTitle("pollingInterval") }}
361
+ </FieldLabel>
362
+ <InputGroup>
363
+ <InputGroupNumberField
364
+ :model-value="selectedItem.pollingInterval"
365
+ :min="1"
366
+ @update:model-value="(v) => setInterval(selectedItem.id, v)"
367
+ />
368
+ </InputGroup>
369
+ </Field>
370
+ </div>
371
+
372
+ <Separator />
373
+
374
+ <div class="grid grid-cols-2 gap-3">
375
+ <Field orientation="vertical">
376
+ <FieldLabel class="text-xs text-zinc-500">
377
+ <template
378
+ v-if="itemDescription('prefix')"
379
+ #tooltip
380
+ >
381
+ <Markdown
382
+ :source="itemDescription('prefix')"
383
+ block
384
+ class="prose prose-sm prose-zinc"
385
+ />
386
+ </template>
387
+ {{ itemTitle("prefix") }}
388
+ </FieldLabel>
389
+ <ExpressionEditor
390
+ :model-value="selectedItem.prefix ?? ''"
391
+ placeholder="如 '¥'"
392
+ result-type="string"
393
+ :extra-vars="JSON_VAR"
394
+ @update:model-value="(v) => setPrefix(selectedItem.id, v)"
395
+ />
396
+ </Field>
397
+
398
+ <Field orientation="vertical">
399
+ <FieldLabel class="text-xs text-zinc-500">
400
+ <template
401
+ v-if="itemDescription('suffix')"
402
+ #tooltip
403
+ >
404
+ <Markdown
405
+ :source="itemDescription('suffix')"
406
+ block
407
+ class="prose prose-sm prose-zinc"
408
+ />
409
+ </template>
410
+ {{ itemTitle("suffix") }}
411
+ </FieldLabel>
412
+ <ExpressionEditor
413
+ :model-value="selectedItem.suffix ?? ''"
414
+ placeholder="如 ' 人'"
415
+ result-type="string"
416
+ :extra-vars="JSON_VAR"
417
+ @update:model-value="(v) => setSuffix(selectedItem.id, v)"
418
+ />
419
+ </Field>
420
+ </div>
421
+
422
+ <Field orientation="vertical">
423
+ <FieldLabel class="text-xs text-zinc-500">
424
+ <template
425
+ v-if="itemDescription('style')"
426
+ #tooltip
427
+ >
428
+ <Markdown
429
+ :source="itemDescription('style')"
430
+ block
431
+ class="prose prose-sm prose-zinc"
432
+ />
433
+ </template>
434
+ {{ itemTitle("style") }}
435
+ </FieldLabel>
436
+ <ExpressionEditor
437
+ :model-value="selectedItem.style ?? ''"
438
+ placeholder="返回 CSS 字符串或样式对象的表达式,如 'font-size: 3rem'"
439
+ :extra-vars="JSON_VAR"
440
+ multiline
441
+ class="min-h-16"
442
+ @update:model-value="(v) => setStyle(selectedItem.id, v)"
443
+ />
444
+ </Field>
445
+ </div>
446
+ </div>
447
+ </div>
448
+ </template>
449
+
450
+ <style scoped>
451
+ .row{align-items:center;border-radius:.25rem;cursor:pointer;display:flex;font-size:.875rem;gap:.375rem;padding-block:.375rem;padding-left:.5rem;padding-right:.5rem;position:relative;transition:background-color .1s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none}[data-instruction=reorder-above]:before,[data-instruction=reorder-below]:after{background:var(--primary,#2563eb);content:"";height:2px;left:.5rem;pointer-events:none;position:absolute;right:.5rem;z-index:1}[data-instruction=reorder-above]:before{top:-1px}[data-instruction=reorder-below]:after{bottom:-1px}
452
+ </style>
@@ -0,0 +1,59 @@
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.block.animated.number";
8
+ readonly id: string;
9
+ readonly locale?: readonly [{
10
+ readonly locale: "zh";
11
+ readonly message: string;
12
+ }, ...{
13
+ readonly locale: "en" | "ja" | "ko";
14
+ readonly message: string;
15
+ }[]] | undefined;
16
+ readonly displayName?: string | undefined;
17
+ readonly compatibilityDate: "2026-06-01";
18
+ readonly items: readonly {
19
+ readonly name?: string | undefined;
20
+ readonly style?: string | undefined;
21
+ readonly id: string;
22
+ readonly prefix?: string | undefined;
23
+ readonly dataSource: {
24
+ readonly value: string;
25
+ readonly request?: string | undefined;
26
+ };
27
+ readonly pollingInterval?: number | undefined;
28
+ readonly suffix?: string | undefined;
29
+ }[];
30
+ }) => any;
31
+ }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
32
+ "onUpdate:modelValue"?: ((value: {
33
+ readonly type: "com.shwfed.block.animated.number";
34
+ readonly id: string;
35
+ readonly locale?: readonly [{
36
+ readonly locale: "zh";
37
+ readonly message: string;
38
+ }, ...{
39
+ readonly locale: "en" | "ja" | "ko";
40
+ readonly message: string;
41
+ }[]] | undefined;
42
+ readonly displayName?: string | undefined;
43
+ readonly compatibilityDate: "2026-06-01";
44
+ readonly items: readonly {
45
+ readonly name?: string | undefined;
46
+ readonly style?: string | undefined;
47
+ readonly id: string;
48
+ readonly prefix?: string | undefined;
49
+ readonly dataSource: {
50
+ readonly value: string;
51
+ readonly request?: string | undefined;
52
+ };
53
+ readonly pollingInterval?: number | undefined;
54
+ readonly suffix?: string | undefined;
55
+ }[];
56
+ }) => any) | undefined;
57
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
58
+ declare const _default: typeof __VLS_export;
59
+ export default _default;
@@ -0,0 +1,7 @@
1
+ import type { ItemValue } from './schema.js';
2
+ type __VLS_Props = {
3
+ item: ItemValue;
4
+ };
5
+ 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>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,112 @@
1
+ <script setup>
2
+ import { computed, onMounted, ref, watch } from "vue";
3
+ import { Effect, Option } from "effect";
4
+ import { Fetch } from "fx-fetch";
5
+ import { useIntervalFn, useWindowFocus } from "@vueuse/core";
6
+ import NumberFlow from "@number-flow/vue";
7
+ import { cel } from "../../../../../utils/cel";
8
+ import { celBindings, injectCELContext } from "../../../../../utils/cel-context";
9
+ defineOptions({ name: "ShwfedBlockAnimatedNumberItem" });
10
+ const props = defineProps({
11
+ item: { type: null, required: true }
12
+ });
13
+ const inheritedContext = injectCELContext();
14
+ const json = ref(void 0);
15
+ const numberValue = ref(0);
16
+ function baseContext() {
17
+ return celBindings(inheritedContext);
18
+ }
19
+ function toFiniteNumber(v) {
20
+ if (typeof v === "number" && Number.isFinite(v)) return v;
21
+ if (typeof v === "string" && v.trim() !== "") {
22
+ const n = Number(v);
23
+ return Number.isFinite(n) ? n : 0;
24
+ }
25
+ return 0;
26
+ }
27
+ const jsonOption = computed(
28
+ () => json.value === void 0 ? Option.none() : Option.some(json.value)
29
+ );
30
+ function evalString(expression) {
31
+ if (!expression) return "";
32
+ try {
33
+ const r = Effect.runSync(cel(expression, { ...baseContext(), json: jsonOption.value }));
34
+ return r === null || r === void 0 ? "" : String(r);
35
+ } catch {
36
+ return "";
37
+ }
38
+ }
39
+ const prefixText = computed(() => evalString(props.item.prefix));
40
+ const suffixText = computed(() => evalString(props.item.suffix));
41
+ function evalStyle(expression) {
42
+ if (!expression) return void 0;
43
+ try {
44
+ const r = Effect.runSync(cel(expression, { ...baseContext(), json: jsonOption.value }));
45
+ if (r === null || r === void 0) return void 0;
46
+ if (typeof r === "string") return r;
47
+ if (typeof r === "object") return r;
48
+ return String(r);
49
+ } catch {
50
+ return void 0;
51
+ }
52
+ }
53
+ const styleValue = computed(() => evalStyle(props.item.style));
54
+ async function refresh() {
55
+ const ds = props.item.dataSource;
56
+ const ctx = baseContext();
57
+ const program = Effect.gen(function* () {
58
+ let jsonOpt = Option.none();
59
+ if (ds.request) {
60
+ const builder = yield* cel(ds.request, ctx);
61
+ jsonOpt = Option.some(yield* builder.json());
62
+ }
63
+ const value = yield* cel(ds.value, { ...ctx, json: jsonOpt });
64
+ return { json: Option.getOrUndefined(jsonOpt), value };
65
+ });
66
+ try {
67
+ const result = await Effect.runPromise(Effect.provide(program, Fetch.layer));
68
+ json.value = result.json;
69
+ numberValue.value = toFiniteNumber(result.value);
70
+ } catch (e) {
71
+ console.warn("[shwfed-animated-number] fetch failed", e);
72
+ }
73
+ }
74
+ const pollingEnabled = computed(() => (props.item.pollingInterval ?? 0) > 0);
75
+ const intervalMs = computed(() => Math.max(1, props.item.pollingInterval ?? 1) * 1e3);
76
+ const focused = useWindowFocus();
77
+ const { pause, resume } = useIntervalFn(() => void refresh(), intervalMs, { immediate: false });
78
+ onMounted(() => {
79
+ void refresh();
80
+ if (pollingEnabled.value && focused.value) resume();
81
+ });
82
+ watch(focused, (isFocused) => {
83
+ if (!pollingEnabled.value) return;
84
+ if (isFocused) {
85
+ void refresh();
86
+ resume();
87
+ } else {
88
+ pause();
89
+ }
90
+ });
91
+ watch(
92
+ () => [
93
+ props.item.dataSource.request ?? "",
94
+ props.item.dataSource.value,
95
+ props.item.pollingInterval ?? 0
96
+ ].join("|"),
97
+ () => {
98
+ void refresh();
99
+ if (pollingEnabled.value && focused.value) resume();
100
+ else pause();
101
+ }
102
+ );
103
+ </script>
104
+
105
+ <template>
106
+ <NumberFlow
107
+ :value="numberValue"
108
+ :prefix="prefixText"
109
+ :suffix="suffixText"
110
+ :style="styleValue"
111
+ />
112
+ </template>
@@ -0,0 +1,7 @@
1
+ import type { ItemValue } from './schema.js';
2
+ type __VLS_Props = {
3
+ item: ItemValue;
4
+ };
5
+ 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>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;