@shwfed/config 2.12.14 → 2.12.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/runtime/components/actions/components/group.vue +1 -0
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/config.d.vue.ts +68 -68
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/config.vue.d.ts +68 -68
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/runtime.d.vue.ts +68 -68
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/runtime.vue.d.ts +68 -68
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/schema.d.ts +34 -34
- package/dist/runtime/components/config/use-editor.js +16 -0
- package/dist/runtime/components/config/utils/linkage.d.ts +29 -0
- package/dist/runtime/components/config/utils/linkage.js +57 -0
- package/dist/runtime/components/config/utils/validation-error.d.ts +1 -0
- package/dist/runtime/components/config/utils/validation-error.js +1 -1
- package/dist/runtime/components/form/FormUnitRenderer.vue +6 -1
- package/dist/runtime/components/form/fields/2026-04-22/com.shwfed.form.field.textarea/config.d.vue.ts +2 -2
- package/dist/runtime/components/form/fields/2026-04-22/com.shwfed.form.field.textarea/config.vue.d.ts +2 -2
- package/dist/runtime/components/sidebar/use-editor.js +13 -0
- package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-multi/config.d.vue.ts +2 -2
- package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-multi/config.vue.d.ts +2 -2
- package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-single/config.d.vue.ts +2 -2
- package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-single/config.vue.d.ts +2 -2
- package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-multi/config.d.vue.ts +2 -2
- package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-multi/config.vue.d.ts +2 -2
- package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-single/config.d.vue.ts +2 -2
- package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-single/config.vue.d.ts +2 -2
- package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-multi/config.d.vue.ts +2 -2
- package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-multi/config.vue.d.ts +2 -2
- package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-single/config.d.vue.ts +2 -2
- package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-single/config.vue.d.ts +2 -2
- package/dist/runtime/components/table/config.vue +10 -4
- package/dist/runtime/components/table/index.vue +6 -1
- package/dist/runtime/components/table/schema.d.ts +68 -68
- package/dist/runtime/components/ui/expression-editor/ExpressionEditor.vue +21 -3
- package/dist/runtime/components/ui/expression-editor/index.d.ts +1 -0
- package/dist/runtime/components/ui/expression-editor/index.js +5 -0
- package/dist/runtime/components/ui/expression-editor/validity.d.ts +12 -0
- package/dist/runtime/components/ui/expression-editor/validity.js +27 -0
- package/dist/runtime/share/layout.js +2 -2
- package/dist/runtime/share/slot-renderer.vue +6 -1
- package/dist/runtime/share/viewport.d.ts +16 -0
- package/dist/runtime/share/viewport.js +11 -0
- package/dist/runtime/utils/cel-context.d.ts +15 -0
- package/dist/runtime/utils/cel-context.js +18 -1
- package/dist/runtime/utils/cel.js +6 -1
- package/dist/runtime/vendor/cel-js/CLAUDE.md +1 -1
- package/dist/runtime/vendor/cel-js/PROMPT.md +15 -0
- package/dist/runtime/vendor/cel-js/lib/functions.js +28 -0
- package/package.json +1 -1
|
@@ -45,10 +45,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
45
45
|
readonly data: string;
|
|
46
46
|
readonly request?: string | undefined;
|
|
47
47
|
};
|
|
48
|
+
readonly enableSorting?: boolean | undefined;
|
|
48
49
|
readonly nodeKey: string;
|
|
49
50
|
readonly nodeChildren: string;
|
|
50
51
|
readonly nodeLabel: string;
|
|
51
|
-
readonly enableSorting?: boolean | undefined;
|
|
52
52
|
readonly nodeTooltip?: readonly [{
|
|
53
53
|
readonly locale: "zh";
|
|
54
54
|
readonly message: string;
|
|
@@ -103,10 +103,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
103
103
|
readonly data: string;
|
|
104
104
|
readonly request?: string | undefined;
|
|
105
105
|
};
|
|
106
|
+
readonly enableSorting?: boolean | undefined;
|
|
106
107
|
readonly nodeKey: string;
|
|
107
108
|
readonly nodeChildren: string;
|
|
108
109
|
readonly nodeLabel: string;
|
|
109
|
-
readonly enableSorting?: boolean | undefined;
|
|
110
110
|
readonly nodeTooltip?: readonly [{
|
|
111
111
|
readonly locale: "zh";
|
|
112
112
|
readonly message: string;
|
|
@@ -45,10 +45,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
45
45
|
readonly data: string;
|
|
46
46
|
readonly request?: string | undefined;
|
|
47
47
|
};
|
|
48
|
+
readonly enableSorting?: boolean | undefined;
|
|
48
49
|
readonly nodeKey: string;
|
|
49
50
|
readonly nodeChildren: string;
|
|
50
51
|
readonly nodeLabel: string;
|
|
51
|
-
readonly enableSorting?: boolean | undefined;
|
|
52
52
|
readonly nodeTooltip?: readonly [{
|
|
53
53
|
readonly locale: "zh";
|
|
54
54
|
readonly message: string;
|
|
@@ -103,10 +103,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
103
103
|
readonly data: string;
|
|
104
104
|
readonly request?: string | undefined;
|
|
105
105
|
};
|
|
106
|
+
readonly enableSorting?: boolean | undefined;
|
|
106
107
|
readonly nodeKey: string;
|
|
107
108
|
readonly nodeChildren: string;
|
|
108
109
|
readonly nodeLabel: string;
|
|
109
|
-
readonly enableSorting?: boolean | undefined;
|
|
110
110
|
readonly nodeTooltip?: readonly [{
|
|
111
111
|
readonly locale: "zh";
|
|
112
112
|
readonly message: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, defineComponent, inject, onBeforeUnmount, provide, ref, watch } from "vue";
|
|
3
|
-
import { celBindings, injectCELContext, provideCELContext, provideScopeAncestor, provideSelectionRoster, SELECTIONS_VAR } from "../../utils/cel-context";
|
|
3
|
+
import { celBindings, injectCELContext, provideCELContext, provideRowUnreachableScope, provideScopeAncestor, provideSelectionRoster, SELECTIONS_VAR } from "../../utils/cel-context";
|
|
4
4
|
import { BREADCRUMB_EXTENSION_KEY } from "../config/breadcrumb-extension";
|
|
5
5
|
import { TABLE_COLUMN_LAYOUT_KEY } from "./column-layout";
|
|
6
6
|
import { Icon } from "@iconify/vue";
|
|
@@ -152,6 +152,7 @@ const TableActionsCELContext = defineComponent({
|
|
|
152
152
|
value: []
|
|
153
153
|
}
|
|
154
154
|
});
|
|
155
|
+
provideRowUnreachableScope();
|
|
155
156
|
return () => slots.default?.();
|
|
156
157
|
}
|
|
157
158
|
});
|
|
@@ -305,14 +306,19 @@ const tableScopeAncestor = computed(() => {
|
|
|
305
306
|
typeIcon: tableMetadata.icon,
|
|
306
307
|
members: [
|
|
307
308
|
{ key: "row", label: "\u5F53\u524D\u884C", type: "dyn", description: "\u8BE5\u8868\u683C\u5F53\u524D\u884C\u7684\u6574\u884C\u6570\u636E" },
|
|
308
|
-
{ key: "index", label: "\u884C\u5E8F\u53F7", type: "number", description: "\u8BE5\u8868\u683C\u5F53\u524D\u884C\u7684\u5E8F\u53F7\uFF0C\u4ECE `0` \u5F00\u59CB" }
|
|
309
|
+
{ key: "index", label: "\u884C\u5E8F\u53F7", type: "number", description: "\u8BE5\u8868\u683C\u5F53\u524D\u884C\u7684\u5E8F\u53F7\uFF0C\u4ECE `0` \u5F00\u59CB" },
|
|
310
|
+
// Registered at the table HOST frame (index.vue), so it is reachable from
|
|
311
|
+
// table-level actions (top toolbar / bottom bar) — where the relative
|
|
312
|
+
// `selected` is shadowed by an inner table — as well as from cells.
|
|
313
|
+
{ key: "selected", label: "\u5DF2\u9009\u884C", type: "list<dyn>", description: "\u8BE5\u8868\u683C\u5F53\u524D\u52FE\u9009\u7684\u6240\u6709\u884C" }
|
|
309
314
|
],
|
|
310
315
|
// Each row-provider rebinds the relative `selections` to its own per-row
|
|
311
316
|
// registry (see row-provider.vue), so a table row shadows an enclosing
|
|
312
317
|
// form's selection options even though the table exposes no `selections`
|
|
313
318
|
// member of its own — list it here so that form's options stay offerable to
|
|
314
|
-
// a cell expression inside this table.
|
|
315
|
-
|
|
319
|
+
// a cell expression inside this table. `selected` likewise: the table host
|
|
320
|
+
// rebinds it, shadowing an enclosing table's selection for everything below.
|
|
321
|
+
shadows: ["row", "index", "selected", SELECTIONS_VAR]
|
|
316
322
|
};
|
|
317
323
|
});
|
|
318
324
|
provideScopeAncestor(tableScopeAncestor);
|
|
@@ -17,7 +17,7 @@ import { Pagination, ScrollArea } from "reka-ui/namespaced";
|
|
|
17
17
|
import { computed, h, onMounted, ref, toRaw, watch } from "vue";
|
|
18
18
|
import { asRequest, fetchJsonOption } from "../../share/request";
|
|
19
19
|
import { useI18n } from "vue-i18n";
|
|
20
|
-
import { celBindings, celScope, provideCELContext, injectCELContext } from "../../utils/cel-context";
|
|
20
|
+
import { celBindings, celScope, provideCELContext, injectCELContext, provideScopeAddress } from "../../utils/cel-context";
|
|
21
21
|
import TableRowProvider from "./row-provider.vue";
|
|
22
22
|
import * as addRowOp from "../operations/2026-07-06/com.shwfed.operation.table.add-row/schema";
|
|
23
23
|
import { getLocalizedText } from "../../share/locale";
|
|
@@ -353,6 +353,11 @@ provideCELContext({
|
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
355
|
});
|
|
356
|
+
if (config.value?.id) {
|
|
357
|
+
provideScopeAddress(config.value.id, {
|
|
358
|
+
selected: () => tableApi.getSelectedRowModel().rows.map((r) => r.original)
|
|
359
|
+
});
|
|
360
|
+
}
|
|
356
361
|
provideSharedFetchLayer();
|
|
357
362
|
const hasDragHandleColumn = computed(() => (config.value?.columns ?? []).some((c) => c.type === DRAG_HANDLE_COLUMN_TYPE));
|
|
358
363
|
const reorderDisabledReason = computed(() => {
|
|
@@ -158,40 +158,6 @@ export declare function TableConfig(configure: (env: Environment) => void): Sche
|
|
|
158
158
|
readonly message: string;
|
|
159
159
|
}[]] | undefined;
|
|
160
160
|
}[];
|
|
161
|
-
readonly query?: {
|
|
162
|
-
readonly style?: string | undefined;
|
|
163
|
-
readonly id?: string | undefined;
|
|
164
|
-
readonly initial?: {
|
|
165
|
-
readonly data: string;
|
|
166
|
-
readonly request?: string | undefined;
|
|
167
|
-
} | undefined;
|
|
168
|
-
readonly displayName?: string | undefined;
|
|
169
|
-
readonly kind: "shwfed.component.form";
|
|
170
|
-
readonly fields: readonly any[];
|
|
171
|
-
readonly layouts: readonly {
|
|
172
|
-
readonly name: string;
|
|
173
|
-
readonly layout: {
|
|
174
|
-
readonly style?: string | undefined;
|
|
175
|
-
readonly columns: number;
|
|
176
|
-
readonly gap?: number | undefined;
|
|
177
|
-
readonly rows?: number | undefined;
|
|
178
|
-
readonly placements: {
|
|
179
|
-
readonly [x: string]: {
|
|
180
|
-
readonly area: readonly [readonly [number, number], readonly [number, number]];
|
|
181
|
-
readonly v?: "stretch" | "center" | "end" | "start" | undefined;
|
|
182
|
-
readonly h?: "stretch" | "center" | "end" | "start" | undefined;
|
|
183
|
-
};
|
|
184
|
-
};
|
|
185
|
-
};
|
|
186
|
-
readonly media?: string | undefined;
|
|
187
|
-
}[];
|
|
188
|
-
readonly readonly?: string | undefined;
|
|
189
|
-
} | undefined;
|
|
190
|
-
readonly dataSource?: {
|
|
191
|
-
readonly data: string;
|
|
192
|
-
readonly total?: string | undefined;
|
|
193
|
-
readonly request?: string | undefined;
|
|
194
|
-
} | undefined;
|
|
195
161
|
readonly actions?: {
|
|
196
162
|
readonly size: "default" | "sm" | "xs";
|
|
197
163
|
readonly style?: string | undefined;
|
|
@@ -259,6 +225,40 @@ export declare function TableConfig(configure: (env: Environment) => void): Sche
|
|
|
259
225
|
readonly variant?: "default" | "link" | "destructive" | "primary" | "ghost" | undefined;
|
|
260
226
|
}[];
|
|
261
227
|
} | undefined;
|
|
228
|
+
readonly query?: {
|
|
229
|
+
readonly style?: string | undefined;
|
|
230
|
+
readonly id?: string | undefined;
|
|
231
|
+
readonly initial?: {
|
|
232
|
+
readonly data: string;
|
|
233
|
+
readonly request?: string | undefined;
|
|
234
|
+
} | undefined;
|
|
235
|
+
readonly displayName?: string | undefined;
|
|
236
|
+
readonly kind: "shwfed.component.form";
|
|
237
|
+
readonly fields: readonly any[];
|
|
238
|
+
readonly layouts: readonly {
|
|
239
|
+
readonly name: string;
|
|
240
|
+
readonly layout: {
|
|
241
|
+
readonly style?: string | undefined;
|
|
242
|
+
readonly columns: number;
|
|
243
|
+
readonly gap?: number | undefined;
|
|
244
|
+
readonly rows?: number | undefined;
|
|
245
|
+
readonly placements: {
|
|
246
|
+
readonly [x: string]: {
|
|
247
|
+
readonly area: readonly [readonly [number, number], readonly [number, number]];
|
|
248
|
+
readonly v?: "stretch" | "center" | "end" | "start" | undefined;
|
|
249
|
+
readonly h?: "stretch" | "center" | "end" | "start" | undefined;
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
readonly media?: string | undefined;
|
|
254
|
+
}[];
|
|
255
|
+
readonly readonly?: string | undefined;
|
|
256
|
+
} | undefined;
|
|
257
|
+
readonly dataSource?: {
|
|
258
|
+
readonly data: string;
|
|
259
|
+
readonly total?: string | undefined;
|
|
260
|
+
readonly request?: string | undefined;
|
|
261
|
+
} | undefined;
|
|
262
262
|
readonly bottomActions?: {
|
|
263
263
|
readonly size: "default" | "sm" | "xs";
|
|
264
264
|
readonly style?: string | undefined;
|
|
@@ -827,40 +827,6 @@ export declare function createTableConfig(body: Omit<Schema.Schema.Type<ReturnTy
|
|
|
827
827
|
readonly message: string;
|
|
828
828
|
}[]] | undefined;
|
|
829
829
|
}[];
|
|
830
|
-
query?: {
|
|
831
|
-
readonly style?: string | undefined;
|
|
832
|
-
readonly id?: string | undefined;
|
|
833
|
-
readonly initial?: {
|
|
834
|
-
readonly data: string;
|
|
835
|
-
readonly request?: string | undefined;
|
|
836
|
-
} | undefined;
|
|
837
|
-
readonly displayName?: string | undefined;
|
|
838
|
-
readonly kind: "shwfed.component.form";
|
|
839
|
-
readonly fields: readonly any[];
|
|
840
|
-
readonly layouts: readonly {
|
|
841
|
-
readonly name: string;
|
|
842
|
-
readonly layout: {
|
|
843
|
-
readonly style?: string | undefined;
|
|
844
|
-
readonly columns: number;
|
|
845
|
-
readonly gap?: number | undefined;
|
|
846
|
-
readonly rows?: number | undefined;
|
|
847
|
-
readonly placements: {
|
|
848
|
-
readonly [x: string]: {
|
|
849
|
-
readonly area: readonly [readonly [number, number], readonly [number, number]];
|
|
850
|
-
readonly v?: "stretch" | "center" | "end" | "start" | undefined;
|
|
851
|
-
readonly h?: "stretch" | "center" | "end" | "start" | undefined;
|
|
852
|
-
};
|
|
853
|
-
};
|
|
854
|
-
};
|
|
855
|
-
readonly media?: string | undefined;
|
|
856
|
-
}[];
|
|
857
|
-
readonly readonly?: string | undefined;
|
|
858
|
-
} | undefined;
|
|
859
|
-
dataSource?: {
|
|
860
|
-
readonly data: string;
|
|
861
|
-
readonly total?: string | undefined;
|
|
862
|
-
readonly request?: string | undefined;
|
|
863
|
-
} | undefined;
|
|
864
830
|
actions?: {
|
|
865
831
|
readonly size: "default" | "sm" | "xs";
|
|
866
832
|
readonly style?: string | undefined;
|
|
@@ -928,6 +894,40 @@ export declare function createTableConfig(body: Omit<Schema.Schema.Type<ReturnTy
|
|
|
928
894
|
readonly variant?: "default" | "link" | "destructive" | "primary" | "ghost" | undefined;
|
|
929
895
|
}[];
|
|
930
896
|
} | undefined;
|
|
897
|
+
query?: {
|
|
898
|
+
readonly style?: string | undefined;
|
|
899
|
+
readonly id?: string | undefined;
|
|
900
|
+
readonly initial?: {
|
|
901
|
+
readonly data: string;
|
|
902
|
+
readonly request?: string | undefined;
|
|
903
|
+
} | undefined;
|
|
904
|
+
readonly displayName?: string | undefined;
|
|
905
|
+
readonly kind: "shwfed.component.form";
|
|
906
|
+
readonly fields: readonly any[];
|
|
907
|
+
readonly layouts: readonly {
|
|
908
|
+
readonly name: string;
|
|
909
|
+
readonly layout: {
|
|
910
|
+
readonly style?: string | undefined;
|
|
911
|
+
readonly columns: number;
|
|
912
|
+
readonly gap?: number | undefined;
|
|
913
|
+
readonly rows?: number | undefined;
|
|
914
|
+
readonly placements: {
|
|
915
|
+
readonly [x: string]: {
|
|
916
|
+
readonly area: readonly [readonly [number, number], readonly [number, number]];
|
|
917
|
+
readonly v?: "stretch" | "center" | "end" | "start" | undefined;
|
|
918
|
+
readonly h?: "stretch" | "center" | "end" | "start" | undefined;
|
|
919
|
+
};
|
|
920
|
+
};
|
|
921
|
+
};
|
|
922
|
+
readonly media?: string | undefined;
|
|
923
|
+
}[];
|
|
924
|
+
readonly readonly?: string | undefined;
|
|
925
|
+
} | undefined;
|
|
926
|
+
dataSource?: {
|
|
927
|
+
readonly data: string;
|
|
928
|
+
readonly total?: string | undefined;
|
|
929
|
+
readonly request?: string | undefined;
|
|
930
|
+
} | undefined;
|
|
931
931
|
bottomActions?: {
|
|
932
932
|
readonly size: "default" | "sm" | "xs";
|
|
933
933
|
readonly style?: string | undefined;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { Icon } from "@iconify/vue";
|
|
3
|
-
import { computed, ref, useSlots, useTemplateRef, watch } from "vue";
|
|
3
|
+
import { computed, onScopeDispose, ref, useSlots, useTemplateRef, watch } from "vue";
|
|
4
4
|
import { buildCheckEnvironment, evaluateExpression } from "../../../share/expression";
|
|
5
5
|
import { OPTIONS_VAR, SELECTIONS_VAR, injectCELContext, useScopeAncestry, useSelectionRoster, useStepRoster } from "../../../utils/cel-context";
|
|
6
6
|
import { Markdown } from "../markdown";
|
|
7
7
|
import CodeMirrorInput from "./CodeMirrorInput.vue";
|
|
8
|
-
import { buildScopeLookup } from "./scope-refs";
|
|
8
|
+
import { buildScopeLookup, scanScopeRefs } from "./scope-refs";
|
|
9
|
+
import { useExpressionValidity } from "./validity";
|
|
9
10
|
import { buildSelectionLookup } from "./selection-refs";
|
|
10
11
|
import { buildStepLookup } from "./step-refs";
|
|
11
12
|
import { buildOptionEntries, buildScopeEntries, buildSelectionEntries, buildStepEntries, buildVarEntries, selectShadowedScopes } from "./picker-entries";
|
|
@@ -62,11 +63,28 @@ const checkEnvironment = computed(() => {
|
|
|
62
63
|
{ unlistedVariablesAreDyn: props.unlistedVarsAreDyn }
|
|
63
64
|
);
|
|
64
65
|
});
|
|
66
|
+
const danglingScopeError = computed(() => {
|
|
67
|
+
const value = props.modelValue ?? props.defaultValue ?? "";
|
|
68
|
+
if (value.trim() === "") return null;
|
|
69
|
+
const lookup = scopeLookup.value;
|
|
70
|
+
for (const ref2 of scanScopeRefs(value)) {
|
|
71
|
+
const info = lookup.get(ref2.id);
|
|
72
|
+
if (!info || !info.members.has(ref2.member)) {
|
|
73
|
+
return "\u8868\u8FBE\u5F0F\u5F15\u7528\u4E86\u65E0\u6548\u7684\u8DE8\u5C42\u4F5C\u7528\u57DF\uFF08\u6765\u6E90\u5DF2\u4E0D\u5728\u53EF\u5BFB\u5740\u7684\u4E0A\u5C42\u4F5C\u7528\u57DF\u4E2D\uFF09";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
});
|
|
65
78
|
const validationError = computed(() => {
|
|
66
79
|
const value = props.modelValue ?? props.defaultValue ?? "";
|
|
67
80
|
if (value.trim() === "") return null;
|
|
68
|
-
return evaluateExpression(checkEnvironment.value, value, props.resultType);
|
|
81
|
+
return danglingScopeError.value ?? evaluateExpression(checkEnvironment.value, value, props.resultType);
|
|
69
82
|
});
|
|
83
|
+
const validityRegistry = useExpressionValidity();
|
|
84
|
+
if (validityRegistry) {
|
|
85
|
+
const unregister = validityRegistry.register(() => danglingScopeError.value === null);
|
|
86
|
+
onScopeDispose(unregister);
|
|
87
|
+
}
|
|
70
88
|
const open = ref(false);
|
|
71
89
|
const entries = ref([]);
|
|
72
90
|
const shownVars = computed(() => entries.value.filter((e) => e.group === "var"));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ExpressionValidityRegistry = {
|
|
2
|
+
/**
|
|
3
|
+
* Register a mounted editor's validity thunk (`true` ⇒ no ancestry error).
|
|
4
|
+
* Returns an unregister fn to call from `onScopeDispose`.
|
|
5
|
+
*/
|
|
6
|
+
register: (isValid: () => boolean) => () => void;
|
|
7
|
+
/** `true` when every currently-registered editor reports valid. */
|
|
8
|
+
allValid: () => boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function createExpressionValidityRegistry(): ExpressionValidityRegistry;
|
|
11
|
+
export declare function provideExpressionValidity(registry: ExpressionValidityRegistry): void;
|
|
12
|
+
export declare function useExpressionValidity(): ExpressionValidityRegistry | undefined;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getCurrentInstance, inject, provide } from "vue";
|
|
2
|
+
const KEY = Symbol("shwfed-expression-validity");
|
|
3
|
+
export function createExpressionValidityRegistry() {
|
|
4
|
+
const entries = /* @__PURE__ */ new Map();
|
|
5
|
+
return {
|
|
6
|
+
register(isValid) {
|
|
7
|
+
const token = Symbol("expr");
|
|
8
|
+
entries.set(token, isValid);
|
|
9
|
+
return () => {
|
|
10
|
+
entries.delete(token);
|
|
11
|
+
};
|
|
12
|
+
},
|
|
13
|
+
allValid() {
|
|
14
|
+
for (const isValid of entries.values()) {
|
|
15
|
+
if (!isValid()) return false;
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function provideExpressionValidity(registry) {
|
|
22
|
+
if (!getCurrentInstance()) return;
|
|
23
|
+
provide(KEY, registry);
|
|
24
|
+
}
|
|
25
|
+
export function useExpressionValidity() {
|
|
26
|
+
return inject(KEY, void 0);
|
|
27
|
+
}
|
|
@@ -59,7 +59,7 @@ function configureMedia(configure) {
|
|
|
59
59
|
return (env) => {
|
|
60
60
|
env.registerFunction(
|
|
61
61
|
"media(string): bool",
|
|
62
|
-
(
|
|
62
|
+
(q) => typeof window !== "undefined" && window.matchMedia(q).matches,
|
|
63
63
|
{ description: "\u5339\u914D CSS \u5A92\u4F53\u67E5\u8BE2\uFF0C\u7B49\u4EF7\u4E8E `window.matchMedia(q).matches`\uFF1BSSR \u4E0A\u4E0B\u6587\u8FD4\u56DE `false`" }
|
|
64
64
|
);
|
|
65
65
|
configure(env);
|
|
@@ -76,7 +76,7 @@ export function LayoutSet(configure) {
|
|
|
76
76
|
resultType: "bool"
|
|
77
77
|
}).annotations({
|
|
78
78
|
title: "\u5339\u914D\u6761\u4EF6",
|
|
79
|
-
description: "\u8FD4\u56DE `bool` \u7684 CEL \u8868\u8FBE\u5F0F\
|
|
79
|
+
description: "\u8FD4\u56DE `bool` \u7684 CEL \u8868\u8FBE\u5F0F\uFF1B\u968F\u89C6\u53E3\u53D8\u5316\u4E0E\u6240\u5F15\u7528\u7684\u54CD\u5E94\u5F0F\u6570\u636E\u81EA\u52A8\u91CD\u65B0\u6C42\u503C\uFF0C\u7A7A\u8868\u793A\u65E0\u6761\u4EF6\u5339\u914D\u3002\u53EF\u7528 `media('(min-width: 1024px)')` \u5305\u88C5 CSS \u5A92\u4F53\u67E5\u8BE2\u3002"
|
|
80
80
|
})),
|
|
81
81
|
layout: Layout
|
|
82
82
|
}).annotations({
|
|
@@ -4,6 +4,7 @@ import { computed } from "vue";
|
|
|
4
4
|
import { cel as _rawCel } from "../utils/cel";
|
|
5
5
|
import { celBindings, injectCELContext } from "../utils/cel-context";
|
|
6
6
|
import { DEFAULT_GAP } from "./layout";
|
|
7
|
+
import { useViewportRevision } from "./viewport";
|
|
7
8
|
defineOptions({ name: "ShwfedSlotRenderer" });
|
|
8
9
|
const props = defineProps({
|
|
9
10
|
slotValue: { type: Object, required: true },
|
|
@@ -25,7 +26,11 @@ function pickLayout(sets) {
|
|
|
25
26
|
}
|
|
26
27
|
return sets[sets.length - 1];
|
|
27
28
|
}
|
|
28
|
-
const
|
|
29
|
+
const viewportRevision = useViewportRevision();
|
|
30
|
+
const activeLayout = computed(() => {
|
|
31
|
+
void viewportRevision.value;
|
|
32
|
+
return pickLayout(props.slotValue.layouts);
|
|
33
|
+
});
|
|
29
34
|
const warnedMissing = /* @__PURE__ */ new Set();
|
|
30
35
|
const placedBlocks = computed(() => {
|
|
31
36
|
const layout = activeLayout.value?.layout;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Ref } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* A reactive counter that increments on any viewport change (resize /
|
|
4
|
+
* orientation). Read its `.value` inside a `computed` to make that computed
|
|
5
|
+
* re-evaluate when the viewport changes.
|
|
6
|
+
*
|
|
7
|
+
* This is the reactive dependency a `media('…')` CEL query otherwise lacks:
|
|
8
|
+
* `window.matchMedia(q).matches` is a one-shot, untracked read, so a layout
|
|
9
|
+
* gated on `media('(max-width: 900px)')` would be frozen at its mount-time
|
|
10
|
+
* value and never re-select when the window is resized (or DevTools docked).
|
|
11
|
+
* Touching this ref inside the selection `computed` closes that gap.
|
|
12
|
+
*
|
|
13
|
+
* SSR-safe and self-cleaning: `useEventListener` no-ops without a `window` and
|
|
14
|
+
* detaches automatically when the owning component's effect scope disposes.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useViewportRevision(): Ref<number>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
import { useEventListener } from "@vueuse/core";
|
|
3
|
+
export function useViewportRevision() {
|
|
4
|
+
const revision = ref(0);
|
|
5
|
+
const bump = () => {
|
|
6
|
+
revision.value++;
|
|
7
|
+
};
|
|
8
|
+
useEventListener("resize", bump, { passive: true });
|
|
9
|
+
useEventListener("orientationchange", bump, { passive: true });
|
|
10
|
+
return revision;
|
|
11
|
+
}
|
|
@@ -67,6 +67,21 @@ export declare function provideScopeAncestor(ancestor: Ref<ScopeAncestor | null>
|
|
|
67
67
|
* first. Empty when nothing up the config tree exposes an addressable scope.
|
|
68
68
|
*/
|
|
69
69
|
export declare function useScopeAncestry(): Ref<ReadonlyArray<ScopeAncestor>>;
|
|
70
|
+
/**
|
|
71
|
+
* Re-provide the inherited scope ancestry with the NEAREST frame's per-row
|
|
72
|
+
* members (`row` / `index`) dropped — its `selected` (and any other member)
|
|
73
|
+
* survives. Called by a table-LEVEL action editor (top toolbar / bottom bar):
|
|
74
|
+
* such an action renders OUTSIDE any row, so at runtime the owning table's
|
|
75
|
+
* `__scopes__[id]` there carries only `selected` (registered at the table host
|
|
76
|
+
* frame), never `row`/`index` (registered per row). Everything the action opens
|
|
77
|
+
* — nested modals and their tables — inherits this downgraded chain, so a
|
|
78
|
+
* cross-layer picker below never offers the owning table's 当前行 / 行序号, which
|
|
79
|
+
* would throw `No such key: row` at evaluation. Outer frames are untouched: each
|
|
80
|
+
* table masks only its own here, and an outer table reached via a genuine row
|
|
81
|
+
* action keeps its row/index reachable. `shadows` is left intact — this frame
|
|
82
|
+
* still masks the relative `row`/`index` for descendants at runtime.
|
|
83
|
+
*/
|
|
84
|
+
export declare function provideRowUnreachableScope(): void;
|
|
70
85
|
/** One selection-like field in the editing form, as the picker/chip surfaces it. */
|
|
71
86
|
export type SelectionFieldRef = Readonly<{
|
|
72
87
|
/** Field uuid keyed into `selections["<id>"]` — the registry/publish key. */
|
|
@@ -30,7 +30,8 @@ export function provideScopeAddress(uuid, accessor) {
|
|
|
30
30
|
// bracket access throws a located `No such key` at the offending uuid.
|
|
31
31
|
value: () => {
|
|
32
32
|
const base = parentScopes ? typeof parentScopes.value === "function" ? parentScopes.value() : parentScopes.value : {};
|
|
33
|
-
|
|
33
|
+
const prior = base[uuid];
|
|
34
|
+
return { ...base, [uuid]: { ...prior, ...resolveScopeAccessor(accessor) } };
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
});
|
|
@@ -49,6 +50,22 @@ export function provideScopeAncestor(ancestor) {
|
|
|
49
50
|
export function useScopeAncestry() {
|
|
50
51
|
return inject(SCOPE_ANCESTRY_KEY, void 0) ?? shallowRef([]);
|
|
51
52
|
}
|
|
53
|
+
export function provideRowUnreachableScope() {
|
|
54
|
+
const parent = inject(SCOPE_ANCESTRY_KEY, void 0);
|
|
55
|
+
provide(
|
|
56
|
+
SCOPE_ANCESTRY_KEY,
|
|
57
|
+
computed(() => {
|
|
58
|
+
const chain = parent?.value ?? [];
|
|
59
|
+
if (chain.length === 0) return chain;
|
|
60
|
+
const [nearest, ...rest] = chain;
|
|
61
|
+
const members = nearest.members.filter((m) => {
|
|
62
|
+
const shortcut = m.shortcut ?? m.key;
|
|
63
|
+
return shortcut !== "row" && shortcut !== "index";
|
|
64
|
+
});
|
|
65
|
+
return [{ ...nearest, members }, ...rest];
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
}
|
|
52
69
|
const SELECTION_ROSTER_KEY = Symbol("cel-selection-roster");
|
|
53
70
|
export function provideSelectionRoster(roster) {
|
|
54
71
|
provide(SELECTION_ROSTER_KEY, roster);
|
|
@@ -22,7 +22,12 @@ function unwrapCelValue(value) {
|
|
|
22
22
|
const env = new Environment({ unlistedVariablesAreDyn: true });
|
|
23
23
|
env.registerFunction(
|
|
24
24
|
"media(string): bool",
|
|
25
|
-
(
|
|
25
|
+
// Global (non-method) function: the evaluator passes the CEL argument as the
|
|
26
|
+
// first parameter (no receiver/`self` prepended — that shape is method-only,
|
|
27
|
+
// as in `list<number>.sum()` below). A leading `_self` here would bind the
|
|
28
|
+
// query string to `_self` and the AST node to `q`, so `matchMedia` would
|
|
29
|
+
// stringify the AST and never match — the "media always false" bug.
|
|
30
|
+
(q) => typeof window !== "undefined" && window.matchMedia(q).matches
|
|
26
31
|
);
|
|
27
32
|
env.registerFunction(
|
|
28
33
|
"list<number>.sum(): number",
|
|
@@ -85,7 +85,7 @@ Internal-only (not re-exported from `index.js`): `parse`, standalone `evaluate`/
|
|
|
85
85
|
|
|
86
86
|
Primitives: `string`, `bool`, `number`, `bytes`, `null_type`, `type`
|
|
87
87
|
Composites: `list<T>`, `map<K, V>`, `optional<T>`, `dyn`, `message`
|
|
88
|
-
Custom: `Date` (backed by `TZDate` from `@date-fns/tz`), `URL` (backed by the native `URL`;
|
|
88
|
+
Custom: `Date` (backed by `TZDate` from `@date-fns/tz`), `URL` (backed by the native `URL`; host-supplied only — no CEL constructor. Reader `URL.query(string): optional<string>` returns the *first* value of a search param — `None` when absent, distinguishing it from a present-but-empty value. Writers return a **copy**, never mutating the receiver (a `URL` is host-injected shared state, unlike a fresh-per-expression `HttpRequestBuilder`): the set/replace `URL.query(string, string | number | optional<string> | optional<number>): URL` collapses repeated entries and on `None` *deletes* the key (the result mirrors the optional's state — deliberately unlike `HttpRequest.query`, whose `None` is a drop/no-op), while `URL.appendQuery(...)` with the same four value overloads appends an entry and no-ops on `None`. Number values render Decimal-exact, like `string(number)`. `string(URL)` is the full `href`.), `File` (native), `FormData` (native — see `form-builtins.ts`), `http` / `HttpRequest` (local addition — see `http-builtins.ts`)
|
|
89
89
|
|
|
90
90
|
## Function Signature Format
|
|
91
91
|
|
|
@@ -105,6 +105,7 @@ number(d) // Date → epoch-millisecond timestamp
|
|
|
105
105
|
string(42) // "42"
|
|
106
106
|
string(true) // "true"
|
|
107
107
|
string(d) // Date → ISO 8601 string
|
|
108
|
+
string(location) // URL → the full href string
|
|
108
109
|
bool("true") // true (also accepts "t", "1", "TRUE", "True")
|
|
109
110
|
bool("false") // false (also accepts "f", "0", "FALSE", "False")
|
|
110
111
|
bytes("text") // UTF-8 encode to bytes
|
|
@@ -310,6 +311,20 @@ location.query("missing").hasValue() // false — absent params are None
|
|
|
310
311
|
location.query("id").orValue("") // first value when a param repeats
|
|
311
312
|
```
|
|
312
313
|
|
|
314
|
+
`query(name, value)` **sets** a search param and returns a new `URL` — the
|
|
315
|
+
original is never modified. The value may be a `string`, a `number`, or an
|
|
316
|
+
optional of either: `Some(v)` sets the param, `None` **removes** it, so the
|
|
317
|
+
result always mirrors the optional's state. `appendQuery(name, value)` takes
|
|
318
|
+
the same value types but **adds** an entry alongside existing ones (`None`
|
|
319
|
+
appends nothing). Convert the result with `string(...)` when the host expects
|
|
320
|
+
a URL string.
|
|
321
|
+
```cel
|
|
322
|
+
location.query("page", 2) // ?page=1 → ?page=2 (replaced)
|
|
323
|
+
location.query("page", q.?page) // sets ?page= from the field, or removes it when absent
|
|
324
|
+
location.appendQuery("tag", "a") // ?tag=x → ?tag=x&tag=a (kept both)
|
|
325
|
+
string(location.query("page", 2)) // "https://…?page=2" — e.g. a navigation target
|
|
326
|
+
```
|
|
327
|
+
|
|
313
328
|
## HTTP
|
|
314
329
|
|
|
315
330
|
Build HTTP requests using the `http` builder. A CEL expression only *describes* a request — it always evaluates to an `HttpRequest`, never issues it. The host decides when and how to send it (downloading a file, reading a JSON response, etc.). There is no way to perform the request or read its response from CEL.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { evaluationError } from "./errors.js";
|
|
2
2
|
import { Decimal } from "./decimal.js";
|
|
3
3
|
import { TYPES, Type } from "./registry.js";
|
|
4
|
+
import { Option } from "effect";
|
|
4
5
|
import { register as registerOptional, optionalOf } from "./optional.js";
|
|
5
6
|
import { objKeys, arrayFrom } from "./globals.js";
|
|
6
7
|
import { TZDate } from "@date-fns/tz";
|
|
@@ -410,6 +411,33 @@ export function registerFunctions(registry) {
|
|
|
410
411
|
registry.registerType("File", File);
|
|
411
412
|
registry.registerType("URL", URL);
|
|
412
413
|
functionOverload("URL.query(string): optional<string>", (url, key) => optionalOf(url.searchParams.get(key) ?? void 0));
|
|
414
|
+
const withSearchParams = (u, write) => {
|
|
415
|
+
const url = new URL(u.href);
|
|
416
|
+
write(url.searchParams);
|
|
417
|
+
return url;
|
|
418
|
+
};
|
|
419
|
+
const numToParam = (v) => Decimal.from(v).toString();
|
|
420
|
+
functionOverload("URL.query(string, string): URL", (u, k, v) => withSearchParams(u, (p) => p.set(k, v)));
|
|
421
|
+
functionOverload("URL.query(string, number): URL", (u, k, v) => withSearchParams(u, (p) => p.set(k, numToParam(v))));
|
|
422
|
+
functionOverload("URL.query(string, optional<string>): URL", (u, k, v) => {
|
|
423
|
+
const value = v;
|
|
424
|
+
return withSearchParams(u, (p) => Option.isSome(value) ? p.set(k, value.value) : p.delete(k));
|
|
425
|
+
});
|
|
426
|
+
functionOverload("URL.query(string, optional<number>): URL", (u, k, v) => {
|
|
427
|
+
const value = v;
|
|
428
|
+
return withSearchParams(u, (p) => Option.isSome(value) ? p.set(k, numToParam(value.value)) : p.delete(k));
|
|
429
|
+
});
|
|
430
|
+
functionOverload("URL.appendQuery(string, string): URL", (u, k, v) => withSearchParams(u, (p) => p.append(k, v)));
|
|
431
|
+
functionOverload("URL.appendQuery(string, number): URL", (u, k, v) => withSearchParams(u, (p) => p.append(k, numToParam(v))));
|
|
432
|
+
functionOverload("URL.appendQuery(string, optional<string>): URL", (u, k, v) => {
|
|
433
|
+
const value = v;
|
|
434
|
+
return Option.isSome(value) ? withSearchParams(u, (p) => p.append(k, value.value)) : u;
|
|
435
|
+
});
|
|
436
|
+
functionOverload("URL.appendQuery(string, optional<number>): URL", (u, k, v) => {
|
|
437
|
+
const value = v;
|
|
438
|
+
return Option.isSome(value) ? withSearchParams(u, (p) => p.append(k, numToParam(value.value))) : u;
|
|
439
|
+
});
|
|
440
|
+
functionOverload("string(URL): string", (v) => v.href);
|
|
413
441
|
registerOptional(registry);
|
|
414
442
|
}
|
|
415
443
|
function stringSize(str) {
|