@shwfed/config 2.0.2 → 2.1.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 (43) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/config.d.vue.ts +16 -6
  3. package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/config.vue +263 -76
  4. package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/config.vue.d.ts +16 -6
  5. package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/runtime.d.vue.ts +16 -6
  6. package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/runtime.vue +104 -12
  7. package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/runtime.vue.d.ts +16 -6
  8. package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/schema.d.ts +51 -15
  9. package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/schema.js +67 -22
  10. package/dist/runtime/components/config/index.vue +1 -1
  11. package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/config.d.vue.ts +131 -0
  12. package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/config.vue +170 -0
  13. package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/config.vue.d.ts +131 -0
  14. package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/runtime.d.vue.ts +8 -0
  15. package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/runtime.vue +52 -0
  16. package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/runtime.vue.d.ts +8 -0
  17. package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/schema.d.ts +112 -0
  18. package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/schema.js +44 -0
  19. package/dist/runtime/components/form/schema.d.ts +10 -0
  20. package/dist/runtime/components/form/schema.js +6 -2
  21. package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.date/schema.d.ts +1 -1
  22. package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.date/schema.js +2 -2
  23. package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.icon/schema.d.ts +1 -1
  24. package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.icon/schema.js +2 -2
  25. package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.number/schema.d.ts +1 -1
  26. package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.number/schema.js +2 -2
  27. package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.text/schema.d.ts +1 -1
  28. package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.text/schema.js +2 -2
  29. package/dist/runtime/components/table/columns/2026-05-13/com.shwfed.table.column.switch/schema.d.ts +1 -1
  30. package/dist/runtime/components/table/columns/2026-05-13/com.shwfed.table.column.switch/schema.js +2 -2
  31. package/dist/runtime/components/table/config.d.vue.ts +11 -1
  32. package/dist/runtime/components/table/config.vue +4 -0
  33. package/dist/runtime/components/table/config.vue.d.ts +11 -1
  34. package/dist/runtime/components/table/index.d.vue.ts +4 -0
  35. package/dist/runtime/components/table/index.vue +36 -8
  36. package/dist/runtime/components/table/index.vue.d.ts +4 -0
  37. package/dist/runtime/components/table/schema.d.ts +12 -0
  38. package/dist/runtime/components/table/schema.js +6 -1
  39. package/dist/runtime/components/table/utils/resolve.d.ts +1 -1
  40. package/dist/runtime/vendor/cel-js/PROMPT.md +6 -2
  41. package/dist/runtime/vendor/cel-js/lib/http-builder.d.ts +7 -1
  42. package/dist/runtime/vendor/cel-js/lib/http-builder.js +28 -5
  43. package/package.json +1 -1
@@ -0,0 +1,44 @@
1
+ import { Schema } from "effect";
2
+ import { Locale } from "../../../../../share/locale.js";
3
+ import { TableConfig, defaultTableConfig } from "../../../../table/schema.js";
4
+ import { commonFieldFields } from "../../../utils/common.js";
5
+ export const type = "com.shwfed.form.field.table";
6
+ export const compatibilityDate = "2026-05-18";
7
+ export const metadata = {
8
+ name: "\u8868\u683C",
9
+ icon: "fluent:table-20-regular",
10
+ w: { initial: 12, min: 6, max: Infinity },
11
+ h: { initial: 10, min: 5, max: Infinity, grow: true }
12
+ };
13
+ export function schema(configure) {
14
+ const Table = Schema.suspend(
15
+ () => TableConfig(configure)
16
+ );
17
+ return Schema.Struct({
18
+ type: Schema.Literal(type),
19
+ compatibilityDate: Schema.Literal(compatibilityDate),
20
+ ...commonFieldFields(configure),
21
+ label: Schema.optional(Locale.annotations({
22
+ title: "\u6807\u7B7E",
23
+ description: "\u8868\u683C\u4E0A\u65B9\u5C55\u793A\u7684\u6587\u672C\uFF1B\u7559\u7A7A\u5219\u4E0D\u6E32\u67D3\u6807\u7B7E"
24
+ })),
25
+ tooltip: Schema.optional(Locale.annotations({
26
+ title: "\u63D0\u793A",
27
+ description: "\u9F20\u6807\u60AC\u505C\u5728\u6807\u7B7E\u4E0A\u65F6\u5C55\u793A\u7684\u8BF4\u660E\uFF0C\u652F\u6301 Markdown"
28
+ })),
29
+ binding: Schema.optional(Schema.String.pipe(Schema.minLength(1)).annotations({
30
+ title: "\u7ED1\u5B9A\u8DEF\u5F84",
31
+ description: "\u8868\u683C\u5F53\u524D\u6570\u636E\u5B9E\u65F6\u5199\u5165\u8868\u5355\u72B6\u6001\u7684 `dot-prop` \u8DEF\u5F84\uFF0C\u4F8B\u5982 `rows`\uFF1B\u7559\u7A7A\u5219\u4E0D\u5199\u5165\u8868\u5355\u72B6\u6001"
32
+ })),
33
+ table: Table.annotations({
34
+ title: "\u8868\u683C\u914D\u7F6E",
35
+ description: "\u5185\u5D4C\u8868\u683C\u7684\u5B8C\u6574\u914D\u7F6E\uFF1B\u6570\u636E\u7531\u8868\u683C\u81EA\u5DF1\u7684\u6570\u636E\u6E90\u52A0\u8F7D\uFF0C\u7528\u6237\u7684\u672C\u5730\u589E\u5220\u6539\u5B9E\u65F6\u56DE\u5199\u5230\u300C\u7ED1\u5B9A\u8DEF\u5F84\u300D"
36
+ })
37
+ }).annotations({
38
+ title: "TableField",
39
+ description: "\u628A\u4E00\u4E2A\u53EF\u672C\u5730\u7F16\u8F91\u7684\u8868\u683C\u5D4C\u5165\u8868\u5355\uFF0C\u5176\u5B9E\u65F6\u6570\u636E\u5199\u5165\u7ED1\u5B9A\u8DEF\u5F84"
40
+ });
41
+ }
42
+ export function defaults() {
43
+ return { table: defaultTableConfig() };
44
+ }
@@ -68,6 +68,16 @@ export declare function FormUnit(configure: (env: Environment) => void): Schema.
68
68
  }>;
69
69
  }>>>;
70
70
  }>>;
71
+ /**
72
+ * Register the form-wide `now` / `form` live variables on `env`, skipping any
73
+ * already declared. Idempotent so it composes safely from either side: a
74
+ * `FormConfig` nested inside another (the `table` form field embeds a table
75
+ * whose `query` is its own sub-form, and the embedding `configure` advertises
76
+ * the same pair) would otherwise register `form` twice on one env and the CEL
77
+ * registry throws `'form' is already registered`. Mirrors
78
+ * `registerRowVariablesIfAbsent`.
79
+ */
80
+ export declare function registerFormVariablesIfAbsent(env: Environment): void;
71
81
  export declare function FormConfig(configure: (env: Environment) => void): Schema.refine<{
72
82
  readonly initial?: string | undefined;
73
83
  readonly kind: "shwfed.component.form";
@@ -37,10 +37,14 @@ export function FormUnit(configure) {
37
37
  description: "\u4E00\u7EC4\u8868\u5355\u5B57\u6BB5\u53CA\u5176\u5E03\u5C40"
38
38
  });
39
39
  }
40
+ export function registerFormVariablesIfAbsent(env) {
41
+ const declared = new Set(env.getDefinitions().variables.map((v) => v.name));
42
+ if (!declared.has("now")) env.registerVariable("now", "Date", { description: "\u5F53\u524D\u65E5\u671F/\u65F6\u95F4" });
43
+ if (!declared.has("form")) env.registerVariable("form", "dyn", { description: "\u5F53\u524D\u8868\u5355\u72B6\u6001" });
44
+ }
40
45
  export function FormConfig(configure) {
41
46
  const formConfigure = (env) => {
42
- env.registerVariable("now", "Date", { description: "\u5F53\u524D\u65E5\u671F/\u65F6\u95F4" });
43
- env.registerVariable("form", "dyn", { description: "\u5F53\u524D\u8868\u5355\u72B6\u6001" });
47
+ registerFormVariablesIfAbsent(env);
44
48
  configure(env);
45
49
  };
46
50
  const CelFormInitial = Expression({ configure: formConfigure, resultType: "dyn" });
@@ -37,4 +37,4 @@ export declare function schema(configure: (env: Environment) => void): Schema.St
37
37
  compatibilityDate: Schema.Literal<["2026-04-14"]>;
38
38
  }>;
39
39
  export type Value = Schema.Schema.Type<ReturnType<typeof schema>>;
40
- export declare function toColumnDef(value: Value, { getLocaleText, $cel, getInheritedContext }: ColumnDefDeps): Partial<ColumnDef<unknown, unknown>>;
40
+ export declare function toColumnDef(value: Value, { getLocaleText, $cel, inheritedContext }: ColumnDefDeps): Partial<ColumnDef<unknown, unknown>>;
@@ -21,12 +21,12 @@ export function schema(configure) {
21
21
  })
22
22
  }).annotations({ title: "DateRenderer", description: "\u65E5\u671F\u6E32\u67D3\u5668" });
23
23
  }
24
- export function toColumnDef(value, { getLocaleText, $cel, getInheritedContext }) {
24
+ export function toColumnDef(value, { getLocaleText, $cel, inheritedContext }) {
25
25
  return {
26
26
  header: getLocaleText(value.title),
27
27
  accessorFn: (row, index) => {
28
28
  try {
29
- return Effect.runSync($cel(value.accessor, { ...getInheritedContext(), row, index }));
29
+ return Effect.runSync($cel(value.accessor, { ...inheritedContext, row, index }));
30
30
  } catch (e) {
31
31
  console.error("[shwfed-table] accessor failed", e);
32
32
  return void 0;
@@ -37,4 +37,4 @@ export declare function schema(configure: (env: Environment) => void): Schema.St
37
37
  compatibilityDate: Schema.Literal<["2026-04-14"]>;
38
38
  }>;
39
39
  export type Value = Schema.Schema.Type<ReturnType<typeof schema>>;
40
- export declare function toColumnDef(value: Value, { getLocaleText, $cel, getInheritedContext }: ColumnDefDeps): Partial<ColumnDef<unknown, unknown>>;
40
+ export declare function toColumnDef(value: Value, { getLocaleText, $cel, inheritedContext }: ColumnDefDeps): Partial<ColumnDef<unknown, unknown>>;
@@ -24,12 +24,12 @@ export function schema(configure) {
24
24
  align: Schema.optionalWith(Align.annotations({ title: "\u5BF9\u9F50" }), { default: () => "center" })
25
25
  }).annotations({ title: "IconRenderer", description: "\u56FE\u6807\u6E32\u67D3\u5668" });
26
26
  }
27
- export function toColumnDef(value, { getLocaleText, $cel, getInheritedContext }) {
27
+ export function toColumnDef(value, { getLocaleText, $cel, inheritedContext }) {
28
28
  return {
29
29
  header: getLocaleText(value.title),
30
30
  accessorFn: (row, index) => {
31
31
  try {
32
- return Effect.runSync($cel(value.accessor, { ...getInheritedContext(), row, index }));
32
+ return Effect.runSync($cel(value.accessor, { ...inheritedContext, row, index }));
33
33
  } catch (e) {
34
34
  console.error("[shwfed-table] accessor failed", e);
35
35
  return void 0;
@@ -41,4 +41,4 @@ export declare function schema(configure: (env: Environment) => void): Schema.St
41
41
  compatibilityDate: Schema.Literal<["2026-04-14"]>;
42
42
  }>;
43
43
  export type Value = Schema.Schema.Type<ReturnType<typeof schema>>;
44
- export declare function toColumnDef(value: Value, { getLocaleText, $cel, getInheritedContext }: ColumnDefDeps): Partial<ColumnDef<unknown, unknown>>;
44
+ export declare function toColumnDef(value: Value, { getLocaleText, $cel, inheritedContext }: ColumnDefDeps): Partial<ColumnDef<unknown, unknown>>;
@@ -29,12 +29,12 @@ export function schema(configure) {
29
29
  }))
30
30
  }).annotations({ title: "NumberRenderer", description: "\u6570\u503C\u6E32\u67D3\u5668" });
31
31
  }
32
- export function toColumnDef(value, { getLocaleText, $cel, getInheritedContext }) {
32
+ export function toColumnDef(value, { getLocaleText, $cel, inheritedContext }) {
33
33
  return {
34
34
  header: getLocaleText(value.title),
35
35
  accessorFn: (row, index) => {
36
36
  try {
37
- return Effect.runSync($cel(value.accessor, { ...getInheritedContext(), row, index }));
37
+ return Effect.runSync($cel(value.accessor, { ...inheritedContext, row, index }));
38
38
  } catch (e) {
39
39
  console.error("[shwfed-table] accessor failed", e);
40
40
  return void 0;
@@ -40,4 +40,4 @@ export declare function schema(configure: (env: Environment) => void): Schema.St
40
40
  compatibilityDate: Schema.Literal<["2026-04-14"]>;
41
41
  }>;
42
42
  export type Value = Schema.Schema.Type<ReturnType<typeof schema>>;
43
- export declare function toColumnDef(value: Value, { getLocaleText, $cel, getInheritedContext }: ColumnDefDeps): Partial<ColumnDef<unknown, unknown>>;
43
+ export declare function toColumnDef(value: Value, { getLocaleText, $cel, inheritedContext }: ColumnDefDeps): Partial<ColumnDef<unknown, unknown>>;
@@ -26,12 +26,12 @@ export function schema(configure) {
26
26
  }))
27
27
  }).annotations({ title: "TextRenderer", description: "\u6587\u672C\u6E32\u67D3\u5668" });
28
28
  }
29
- export function toColumnDef(value, { getLocaleText, $cel, getInheritedContext }) {
29
+ export function toColumnDef(value, { getLocaleText, $cel, inheritedContext }) {
30
30
  return {
31
31
  header: getLocaleText(value.title),
32
32
  accessorFn: (row, index) => {
33
33
  try {
34
- return Effect.runSync($cel(value.accessor, { ...getInheritedContext(), row, index }));
34
+ return Effect.runSync($cel(value.accessor, { ...inheritedContext, row, index }));
35
35
  } catch (e) {
36
36
  console.error("[shwfed-table] accessor failed", e);
37
37
  return void 0;
@@ -39,4 +39,4 @@ export declare function schema(configure: (env: Environment) => void): Schema.St
39
39
  compatibilityDate: Schema.Literal<["2026-05-13"]>;
40
40
  }>;
41
41
  export type Value = Schema.Schema.Type<ReturnType<typeof schema>>;
42
- export declare function toColumnDef(value: Value, { getLocaleText, $cel, getInheritedContext }: ColumnDefDeps): Partial<ColumnDef<unknown, unknown>>;
42
+ export declare function toColumnDef(value: Value, { getLocaleText, $cel, inheritedContext }: ColumnDefDeps): Partial<ColumnDef<unknown, unknown>>;
@@ -43,12 +43,12 @@ export function schema(configure) {
43
43
  }))
44
44
  }).annotations({ title: "SwitchRenderer", description: "\u5F00\u5173\u6E32\u67D3\u5668\uFF08\u53EF\u7F16\u8F91\uFF09" });
45
45
  }
46
- export function toColumnDef(value, { getLocaleText, $cel, getInheritedContext }) {
46
+ export function toColumnDef(value, { getLocaleText, $cel, inheritedContext }) {
47
47
  return {
48
48
  header: getLocaleText(value.title),
49
49
  accessorFn: (row, index) => {
50
50
  try {
51
- return Effect.runSync($cel(value.accessor, { ...getInheritedContext(), row, index }));
51
+ return Effect.runSync($cel(value.accessor, { ...inheritedContext, row, index }));
52
52
  } catch (e) {
53
53
  console.error("[shwfed-table] switch accessor failed", e);
54
54
  return void 0;
@@ -7,10 +7,20 @@ type __VLS_ModelProps = {
7
7
  modelValue: AnyRecord;
8
8
  };
9
9
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
- declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
10
+ declare var __VLS_201: {};
11
+ type __VLS_Slots = {} & {
12
+ 'general-extra'?: (props: typeof __VLS_201) => any;
13
+ };
14
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
15
  "update:modelValue": (value: AnyRecord) => any;
12
16
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
13
17
  "onUpdate:modelValue"?: ((value: AnyRecord) => any) | undefined;
14
18
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
15
20
  declare const _default: typeof __VLS_export;
16
21
  export default _default;
22
+ type __VLS_WithSlots<T, S> = T & {
23
+ new (): {
24
+ $slots: S;
25
+ };
26
+ };
@@ -1251,6 +1251,10 @@ const tableQueryValue = computed({
1251
1251
  </InputGroup>
1252
1252
  </Field>
1253
1253
 
1254
+ <!-- Host extension slot: a wrapping editor (e.g. the `table` form
1255
+ field config) injects its own fields into the general pane. -->
1256
+ <slot name="general-extra" />
1257
+
1254
1258
  <div class="flex items-center gap-2">
1255
1259
  <h3 class="text-xs font-medium text-zinc-500">
1256
1260
  {{ generalFieldTitle("dataSource") }}
@@ -7,10 +7,20 @@ type __VLS_ModelProps = {
7
7
  modelValue: AnyRecord;
8
8
  };
9
9
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
- declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
10
+ declare var __VLS_201: {};
11
+ type __VLS_Slots = {} & {
12
+ 'general-extra'?: (props: typeof __VLS_201) => any;
13
+ };
14
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
15
  "update:modelValue": (value: AnyRecord) => any;
12
16
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
13
17
  "onUpdate:modelValue"?: ((value: AnyRecord) => any) | undefined;
14
18
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
15
20
  declare const _default: typeof __VLS_export;
16
21
  export default _default;
22
+ type __VLS_WithSlots<T, S> = T & {
23
+ new (): {
24
+ $slots: S;
25
+ };
26
+ };
@@ -12,6 +12,7 @@ declare const _default: typeof __VLS_export;
12
12
  export default _default;
13
13
  declare const __VLS_export: import("vue").DefineComponent<{
14
14
  config?: TableConfigValue;
15
+ rows?: Array<unknown>;
15
16
  }, import("@tanstack/vue-table").Table<unknown>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
16
17
  "update:config": (value: Readonly<{
17
18
  kind: "shwfed.component.table";
@@ -52,8 +53,10 @@ declare const __VLS_export: import("vue").DefineComponent<{
52
53
  rowSelection: import("effect/Schema").optional<import("effect/Schema").Record$<typeof import("effect/Schema").String, typeof import("effect/Schema").Boolean>>;
53
54
  }>>;
54
55
  }> | undefined) => any;
56
+ "update:rows": (value: unknown[]) => any;
55
57
  }, string, import("vue").PublicProps, Readonly<{
56
58
  config?: TableConfigValue;
59
+ rows?: Array<unknown>;
57
60
  }> & Readonly<{
58
61
  "onUpdate:config"?: ((value: Readonly<{
59
62
  kind: "shwfed.component.table";
@@ -94,4 +97,5 @@ declare const __VLS_export: import("vue").DefineComponent<{
94
97
  rowSelection: import("effect/Schema").optional<import("effect/Schema").Record$<typeof import("effect/Schema").String, typeof import("effect/Schema").Boolean>>;
95
98
  }>>;
96
99
  }> | undefined) => any) | undefined;
100
+ "onUpdate:rows"?: ((value: unknown[]) => any) | undefined;
97
101
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -13,7 +13,7 @@ import { useVirtualizer } from "@tanstack/vue-virtual";
13
13
  import { Effect, Fiber, Option } from "effect";
14
14
  import { Fetch } from "fx-fetch";
15
15
  import { Pagination } from "reka-ui/namespaced";
16
- import { computed, defineComponent, h, onMounted, ref, watch } from "vue";
16
+ import { computed, defineComponent, h, onMounted, ref, toRaw, watch } from "vue";
17
17
  import { useI18n } from "vue-i18n";
18
18
  import { celBindings, provideCELContext, injectCELContext } from "../../utils/cel-context";
19
19
  import { getLocalizedText } from "../../share/locale";
@@ -29,7 +29,7 @@ import { provideEventTarget } from "../../share/event-bus";
29
29
  import { findColumn } from "./utils/resolve";
30
30
  import { interpolateMarkdown } from "./utils/runtime";
31
31
  const config = defineModel("config", { type: Object });
32
- const rowData = ref([]);
32
+ const rowData = defineModel("rows", { type: Array, ...{ default: () => [] } });
33
33
  const serverTotal = ref(void 0);
34
34
  const isFetching = ref(false);
35
35
  const queryState = ref({});
@@ -79,15 +79,17 @@ function translateColumn(column) {
79
79
  if (!entry) {
80
80
  throw new Error(`[shwfed-table] no column registered for ${column.type}@${column.compatibilityDate}`);
81
81
  }
82
- const contribution = entry.toColumnDef(column, {
83
- getLocaleText,
84
- $cel,
85
- getInheritedContext: () => celBindings(inheritedContext)
86
- });
82
+ const contribution = entry.toColumnDef(column, { getLocaleText, $cel, inheritedContext: celBindings(inheritedContext) });
87
83
  return {
88
84
  ...contribution,
89
85
  id: column.id,
90
- cell: (ctx) => h(entry.runtime, { column, ctx })
86
+ // `ctx` is the functional-component props object Vue mutates in place on
87
+ // every update, so its identity stays stable across data refetches.
88
+ // Forwarding it unchanged makes `entry.runtime` see reference-equal props,
89
+ // so `shouldUpdateComponent` skips the update and the cell (and its CEL
90
+ // evaluation) never refreshes after the row data changes. Spread into a
91
+ // fresh object so the child re-renders whenever the context does.
92
+ cell: (ctx) => h(entry.runtime, { column, ctx: { ...ctx } })
91
93
  };
92
94
  }
93
95
  function makeGroupColumn(group, children) {
@@ -299,6 +301,14 @@ async function resetQuery() {
299
301
  queryState.value = {};
300
302
  }
301
303
  }
304
+ function cloneRow(row) {
305
+ if (!row || typeof row !== "object") return row;
306
+ try {
307
+ return structuredClone(toRaw(row));
308
+ } catch {
309
+ return { ...row };
310
+ }
311
+ }
302
312
  const tableInstanceId = config.value?.id ?? crypto.randomUUID();
303
313
  provideTableInstanceId(tableInstanceId);
304
314
  provideEventTarget(tableInstanceId, {
@@ -310,6 +320,24 @@ provideEventTarget(tableInstanceId, {
310
320
  "search": () => Effect.promise(fetchDataSource),
311
321
  "clear-selection": () => Effect.sync(() => {
312
322
  tableApi.resetRowSelection(true);
323
+ }),
324
+ // Local row edits — mutate `rowData` in place, no server round-trip. Each
325
+ // re-assigns the array so the `rows` model re-emits and a host (the
326
+ // `table` form field) sees the live list. Intended for client-side data:
327
+ // with server pagination (`dataSource.total`) `rowData` is only the
328
+ // current page, so a fetch will overwrite these edits.
329
+ "add-row": () => Effect.sync(() => {
330
+ rowData.value = [...rowData.value, {}];
331
+ }),
332
+ "duplicate-selected": () => Effect.sync(() => {
333
+ const clones = tableApi.getSelectedRowModel().rows.map((r) => cloneRow(r.original));
334
+ if (clones.length > 0) rowData.value = [...rowData.value, ...clones];
335
+ }),
336
+ "delete-selected": () => Effect.sync(() => {
337
+ const drop = new Set(tableApi.getSelectedRowModel().rows.map((r) => r.index));
338
+ if (drop.size === 0) return;
339
+ rowData.value = rowData.value.filter((_, i) => !drop.has(i));
340
+ tableApi.resetRowSelection(true);
313
341
  })
314
342
  });
315
343
  const queryRef = ref(null);
@@ -12,6 +12,7 @@ declare const _default: typeof __VLS_export;
12
12
  export default _default;
13
13
  declare const __VLS_export: import("vue").DefineComponent<{
14
14
  config?: TableConfigValue;
15
+ rows?: Array<unknown>;
15
16
  }, import("@tanstack/vue-table").Table<unknown>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
16
17
  "update:config": (value: Readonly<{
17
18
  kind: "shwfed.component.table";
@@ -52,8 +53,10 @@ declare const __VLS_export: import("vue").DefineComponent<{
52
53
  rowSelection: import("effect/Schema").optional<import("effect/Schema").Record$<typeof import("effect/Schema").String, typeof import("effect/Schema").Boolean>>;
53
54
  }>>;
54
55
  }> | undefined) => any;
56
+ "update:rows": (value: unknown[]) => any;
55
57
  }, string, import("vue").PublicProps, Readonly<{
56
58
  config?: TableConfigValue;
59
+ rows?: Array<unknown>;
57
60
  }> & Readonly<{
58
61
  "onUpdate:config"?: ((value: Readonly<{
59
62
  kind: "shwfed.component.table";
@@ -94,4 +97,5 @@ declare const __VLS_export: import("vue").DefineComponent<{
94
97
  rowSelection: import("effect/Schema").optional<import("effect/Schema").Record$<typeof import("effect/Schema").String, typeof import("effect/Schema").Boolean>>;
95
98
  }>>;
96
99
  }> | undefined) => any) | undefined;
100
+ "onUpdate:rows"?: ((value: unknown[]) => any) | undefined;
97
101
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -23,6 +23,18 @@ export declare const metadata: {
23
23
  readonly id: "clear-selection";
24
24
  readonly name: "清空所有选中行";
25
25
  readonly icon: "fluent:select-all-off-20-regular";
26
+ }, {
27
+ readonly id: "add-row";
28
+ readonly name: "新增一行";
29
+ readonly icon: "fluent:add-20-regular";
30
+ }, {
31
+ readonly id: "duplicate-selected";
32
+ readonly name: "复制选中行";
33
+ readonly icon: "fluent:copy-20-regular";
34
+ }, {
35
+ readonly id: "delete-selected";
36
+ readonly name: "删除选中行";
37
+ readonly icon: "fluent:delete-20-regular";
26
38
  }];
27
39
  };
28
40
  declare const InitialState: Schema.Struct<{
@@ -13,11 +13,16 @@ export const metadata = {
13
13
  // Atomic operations — compose them on one button (ordered triggers) to get
14
14
  // the legacy combined behaviours. Only `search` actually fetches data;
15
15
  // `reset-query` / `reset-pagination` / `clear-selection` just prepare state.
16
+ // The `*-row` / `*-selected` trio edits the row list locally — no server
17
+ // round-trip — for tables wired up as an editable surface.
16
18
  operations: [
17
19
  { id: "reset-query", name: "\u91CD\u7F6E\u641C\u7D22\u6761\u4EF6", icon: "fluent:arrow-reset-20-regular" },
18
20
  { id: "reset-pagination", name: "\u91CD\u7F6E\u5206\u9875\u5E76\u6267\u884C\u641C\u7D22", icon: "fluent:arrow-previous-20-regular" },
19
21
  { id: "search", name: "\u6267\u884C\u641C\u7D22", icon: "fluent:search-20-regular" },
20
- { id: "clear-selection", name: "\u6E05\u7A7A\u6240\u6709\u9009\u4E2D\u884C", icon: "fluent:select-all-off-20-regular" }
22
+ { id: "clear-selection", name: "\u6E05\u7A7A\u6240\u6709\u9009\u4E2D\u884C", icon: "fluent:select-all-off-20-regular" },
23
+ { id: "add-row", name: "\u65B0\u589E\u4E00\u884C", icon: "fluent:add-20-regular" },
24
+ { id: "duplicate-selected", name: "\u590D\u5236\u9009\u4E2D\u884C", icon: "fluent:copy-20-regular" },
25
+ { id: "delete-selected", name: "\u5220\u9664\u9009\u4E2D\u884C", icon: "fluent:delete-20-regular" }
21
26
  ]
22
27
  };
23
28
  const SortItem = Schema.Struct({
@@ -13,7 +13,7 @@ export type ColumnMetadata = Readonly<{
13
13
  export type ColumnDefDeps = Readonly<{
14
14
  getLocaleText: (value: LocaleValue | undefined) => string | undefined;
15
15
  $cel: CelEvaluator;
16
- getInheritedContext: () => Record<string, unknown>;
16
+ inheritedContext: Record<string, unknown>;
17
17
  }>;
18
18
  type ToColumnDef = (value: any, deps: ColumnDefDeps) => Partial<ColumnDef<unknown, unknown>>;
19
19
  export type ColumnEntry = Readonly<{
@@ -229,12 +229,16 @@ http.get("https://api.example.com/users")
229
229
  .query(form)
230
230
  .query("page", string(pageIndex))
231
231
 
232
- // Set request body (auto-serializes to JSON):
232
+ // Set request body. An object serializes to JSON; set the Content-Type
233
+ // header yourself. Optional values are unwrapped — a present `.?` field
234
+ // keeps its value, an absent one becomes `null` (the key is kept):
233
235
  http.post("https://api.example.com/users")
234
- .body({"name": "Alice", "email": "alice@example.com"})
236
+ .header("Content-Type", "application/json")
237
+ .body({"name": "Alice", "channel": query.?channel})
235
238
 
236
239
  // Full example — the expression returns an HttpRequest; the host sends it:
237
240
  http.post("https://api.example.com/users")
241
+ .header("Content-Type", "application/json")
238
242
  .header("Authorization", "Bearer " + token)
239
243
  .body({"name": "Alice"})
240
244
  ```
@@ -15,7 +15,13 @@ export declare class HttpRequestBuilder {
15
15
  header(name: string, value: string): this;
16
16
  query(key: string, value: string | number): this;
17
17
  query(record: Record<string, unknown>): this;
18
- body(data: FormData | unknown): this;
18
+ /**
19
+ * Set the request body. A pre-built `FormData` / `Blob` / `ArrayBuffer` /
20
+ * string is used verbatim; anything else is normalized (see
21
+ * `normalizeBody`) and JSON-serialized. The `Content-Type` header is *not*
22
+ * set here — the caller adds `.header('Content-Type', …)` explicitly.
23
+ */
24
+ body(data: unknown): this;
19
25
  /**
20
26
  * A structural snapshot of the request this builder will issue — an equal
21
27
  * snapshot means an equal HTTP request. Handy for change-detection, e.g.
@@ -1,9 +1,28 @@
1
- import { Effect } from "effect";
1
+ import { Effect, Option } from "effect";
2
2
  import {
3
3
  Fetch,
4
4
  Request as FxRequest,
5
5
  Response as FxResponse
6
6
  } from "fx-fetch";
7
+ import { Decimal } from "./decimal.js";
8
+ function normalizeBody(value) {
9
+ if (value === null || value === void 0) return value;
10
+ if (value instanceof Decimal) return value.toNumber();
11
+ if (Option.isOption(value)) {
12
+ return Option.isSome(value) ? normalizeBody(value.value) : null;
13
+ }
14
+ if (Array.isArray(value)) return value.map(normalizeBody);
15
+ if (typeof value === "object") {
16
+ const proto = Object.getPrototypeOf(value);
17
+ if (proto !== Object.prototype && proto !== null) return value;
18
+ const out = {};
19
+ for (const key of Object.keys(value)) {
20
+ out[key] = normalizeBody(value[key]);
21
+ }
22
+ return out;
23
+ }
24
+ return value;
25
+ }
7
26
  function extractFilename(response, url) {
8
27
  const disposition = FxResponse.getHeader(response, "content-disposition");
9
28
  if (disposition) {
@@ -53,11 +72,15 @@ export class HttpRequestBuilder {
53
72
  }
54
73
  throw new Error(`query: nested object values are not supported (key: "${key}")`);
55
74
  }
75
+ /**
76
+ * Set the request body. A pre-built `FormData` / `Blob` / `ArrayBuffer` /
77
+ * string is used verbatim; anything else is normalized (see
78
+ * `normalizeBody`) and JSON-serialized. The `Content-Type` header is *not*
79
+ * set here — the caller adds `.header('Content-Type', …)` explicitly.
80
+ */
56
81
  body(data) {
57
- this.#body = data instanceof FormData || data instanceof Blob || data instanceof ArrayBuffer || typeof data === "string" ? data : JSON.stringify(data);
58
- if (!(data instanceof FormData || data instanceof Blob || data instanceof ArrayBuffer || typeof data === "string")) {
59
- this.#headers.push(["Content-Type", "application/json"]);
60
- }
82
+ const value = normalizeBody(data);
83
+ this.#body = value instanceof FormData || value instanceof Blob || value instanceof ArrayBuffer || typeof value === "string" ? value : JSON.stringify(value);
61
84
  return this;
62
85
  }
63
86
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shwfed/config",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "Configurable UI for SHWFED",
5
5
  "type": "module",
6
6
  "publishConfig": {