@dou-ui/form-builder 0.0.0-stage → 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/builder/canvas.js +695 -0
- package/dist/builder/canvas.js.map +1 -0
- package/dist/builder/completion.js +49 -0
- package/dist/builder/completion.js.map +1 -0
- package/dist/builder/condition-editor.js +6 -0
- package/dist/builder/condition-editor.js.map +1 -0
- package/dist/builder/condition-editor.vue_vue_type_script_setup_true_lang.js +410 -0
- package/dist/builder/condition-editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/condition.js +159 -0
- package/dist/builder/condition.js.map +1 -0
- package/dist/builder/context.js +477 -0
- package/dist/builder/context.js.map +1 -0
- package/dist/builder/core/commands.js +373 -0
- package/dist/builder/core/commands.js.map +1 -0
- package/dist/builder/core/history.js +76 -0
- package/dist/builder/core/history.js.map +1 -0
- package/dist/builder/core/transaction.js +298 -0
- package/dist/builder/core/transaction.js.map +1 -0
- package/dist/builder/drag.js +466 -0
- package/dist/builder/drag.js.map +1 -0
- package/dist/builder/expression-box.js +19 -0
- package/dist/builder/expression-box.js.map +1 -0
- package/dist/builder/expression-input.js +6 -0
- package/dist/builder/expression-input.js.map +1 -0
- package/dist/builder/expression-input.vue_vue_type_script_setup_true_lang.js +117 -0
- package/dist/builder/expression-input.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/extension.js +47 -0
- package/dist/builder/extension.js.map +1 -0
- package/dist/builder/form-builder.js +6 -0
- package/dist/builder/form-builder.js.map +1 -0
- package/dist/builder/form-builder.vue_vue_type_script_setup_true_lang.js +450 -0
- package/dist/builder/form-builder.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/index.js +22 -0
- package/dist/builder/index.js.map +1 -0
- package/dist/builder/inspector.js +6 -0
- package/dist/builder/inspector.js.map +1 -0
- package/dist/builder/inspector.vue_vue_type_script_setup_true_lang.js +442 -0
- package/dist/builder/inspector.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/json-dialog.js +6 -0
- package/dist/builder/json-dialog.js.map +1 -0
- package/dist/builder/json-dialog.vue_vue_type_script_setup_true_lang.js +281 -0
- package/dist/builder/json-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/locale.js +1271 -0
- package/dist/builder/locale.js.map +1 -0
- package/dist/builder/logic-fields.js +18 -0
- package/dist/builder/logic-fields.js.map +1 -0
- package/dist/builder/logic.js +332 -0
- package/dist/builder/logic.js.map +1 -0
- package/dist/builder/menu.js +92 -0
- package/dist/builder/menu.js.map +1 -0
- package/dist/builder/outline-row.js +6 -0
- package/dist/builder/outline-row.js.map +1 -0
- package/dist/builder/outline-row.vue_vue_type_script_setup_true_lang.js +88 -0
- package/dist/builder/outline-row.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/outline.js +6 -0
- package/dist/builder/outline.js.map +1 -0
- package/dist/builder/outline.vue_vue_type_script_setup_true_lang.js +111 -0
- package/dist/builder/outline.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/palette.js +6 -0
- package/dist/builder/palette.js.map +1 -0
- package/dist/builder/palette.vue_vue_type_script_setup_true_lang.js +96 -0
- package/dist/builder/palette.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/preview.js +6 -0
- package/dist/builder/preview.js.map +1 -0
- package/dist/builder/preview.vue_vue_type_script_setup_true_lang.js +111 -0
- package/dist/builder/preview.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/settings-binding.js +57 -0
- package/dist/builder/settings-binding.js.map +1 -0
- package/dist/builder/settings.js +487 -0
- package/dist/builder/settings.js.map +1 -0
- package/dist/builder/span-field.js +104 -0
- package/dist/builder/span-field.js.map +1 -0
- package/dist/code/expression-input.js +6 -0
- package/dist/code/expression-input.js.map +1 -0
- package/dist/code/expression-input.vue_vue_type_script_setup_true_lang.js +88 -0
- package/dist/code/expression-input.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/code/extension.js +202 -0
- package/dist/code/extension.js.map +1 -0
- package/dist/code/index.js +19 -0
- package/dist/code/index.js.map +1 -0
- package/dist/code/language.js +43 -0
- package/dist/code/language.js.map +1 -0
- package/dist/collaboration/index.js +382 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/order.js +88 -0
- package/dist/collaboration/order.js.map +1 -0
- package/dist/core/check.js +314 -0
- package/dist/core/check.js.map +1 -0
- package/dist/core/document.js +112 -0
- package/dist/core/document.js.map +1 -0
- package/dist/core/expression.js +854 -0
- package/dist/core/expression.js.map +1 -0
- package/dist/core/formulas.js +242 -0
- package/dist/core/formulas.js.map +1 -0
- package/dist/core/rules.js +83 -0
- package/dist/core/rules.js.map +1 -0
- package/dist/core/values.js +80 -0
- package/dist/core/values.js.map +1 -0
- package/dist/fields/builtin.js +167 -0
- package/dist/fields/builtin.js.map +1 -0
- package/dist/fields/components.js +155 -0
- package/dist/fields/components.js.map +1 -0
- package/dist/fields/define.js +44 -0
- package/dist/fields/define.js.map +1 -0
- package/dist/fields/format.js +125 -0
- package/dist/fields/format.js.map +1 -0
- package/dist/fields/options.js +54 -0
- package/dist/fields/options.js.map +1 -0
- package/dist/index.js +20 -0
- package/dist/locale.js +161 -0
- package/dist/locale.js.map +1 -0
- package/dist/renderer/containers.js +430 -0
- package/dist/renderer/containers.js.map +1 -0
- package/dist/renderer/context.js +21 -0
- package/dist/renderer/context.js.map +1 -0
- package/dist/renderer/form-renderer.js +6 -0
- package/dist/renderer/form-renderer.js.map +1 -0
- package/dist/renderer/form-renderer.vue_vue_type_script_setup_true_lang.js +272 -0
- package/dist/renderer/form-renderer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/renderer/index.js +19 -0
- package/dist/renderer/index.js.map +1 -0
- package/dist/renderer/node-view.js +385 -0
- package/dist/renderer/node-view.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +27 -0
- package/dist/types/builder/canvas.d.ts +5 -0
- package/dist/types/builder/completion.d.ts +35 -0
- package/dist/types/builder/condition-editor.vue.d.ts +17 -0
- package/dist/types/builder/condition.d.ts +37 -0
- package/dist/types/builder/context.d.ts +149 -0
- package/dist/types/builder/core/commands.d.ts +74 -0
- package/dist/types/builder/core/history.d.ts +23 -0
- package/dist/types/builder/core/transaction.d.ts +108 -0
- package/dist/types/builder/drag.d.ts +52 -0
- package/dist/types/builder/expression-box.d.ts +8 -0
- package/dist/types/builder/expression-input.vue.d.ts +29 -0
- package/dist/types/builder/extension.d.ts +149 -0
- package/dist/types/builder/form-builder.vue.d.ts +24 -0
- package/dist/types/builder/index.d.ts +76 -0
- package/dist/types/builder/inspector.vue.d.ts +3 -0
- package/dist/types/builder/json-dialog.vue.d.ts +10 -0
- package/dist/types/builder/locale.d.ts +195 -0
- package/dist/types/builder/logic-fields.d.ts +2 -0
- package/dist/types/builder/logic.d.ts +57 -0
- package/dist/types/builder/menu.d.ts +23 -0
- package/dist/types/builder/outline-row.vue.d.ts +19 -0
- package/dist/types/builder/outline.vue.d.ts +3 -0
- package/dist/types/builder/palette.vue.d.ts +3 -0
- package/dist/types/builder/preview.vue.d.ts +3 -0
- package/dist/types/builder/settings-binding.d.ts +23 -0
- package/dist/types/builder/settings.d.ts +49 -0
- package/dist/types/builder/span-field.d.ts +4 -0
- package/dist/types/builder/types.d.ts +59 -0
- package/dist/types/code/expression-input.vue.d.ts +23 -0
- package/dist/types/code/extension.d.ts +45 -0
- package/dist/types/code/index.d.ts +12 -0
- package/dist/types/code/language.d.ts +6 -0
- package/dist/types/collaboration/index.d.ts +64 -0
- package/dist/types/collaboration/order.d.ts +20 -0
- package/dist/types/core/check.d.ts +68 -0
- package/dist/types/core/document.d.ts +36 -0
- package/dist/types/core/expression.d.ts +175 -0
- package/dist/types/core/formulas.d.ts +44 -0
- package/dist/types/core/rules.d.ts +23 -0
- package/dist/types/core/types.d.ts +211 -0
- package/dist/types/core/values.d.ts +22 -0
- package/dist/types/fields/builtin.d.ts +9 -0
- package/dist/types/fields/components.d.ts +177 -0
- package/dist/types/fields/define.d.ts +93 -0
- package/dist/types/fields/format.d.ts +76 -0
- package/dist/types/fields/options.d.ts +11 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/locale.d.ts +45 -0
- package/dist/types/renderer/containers.d.ts +20 -0
- package/dist/types/renderer/context.d.ts +186 -0
- package/dist/types/renderer/form-renderer.vue.d.ts +25 -0
- package/dist/types/renderer/index.d.ts +81 -0
- package/dist/types/renderer/node-view.d.ts +56 -0
- package/package.json +88 -4
package/dist/locale.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { useLocale } from "dou-ui";
|
|
2
|
+
import { computed, toValue } from "vue";
|
|
3
|
+
// src/locale.ts
|
|
4
|
+
var zhCN = {
|
|
5
|
+
prev: "上一步",
|
|
6
|
+
next: "下一步",
|
|
7
|
+
step: "第 {index} 步,共 {count} 步:{title}",
|
|
8
|
+
add: "添加一项",
|
|
9
|
+
remove: "删除第 {index} 项",
|
|
10
|
+
move: "拖动第 {index} 项调整顺序",
|
|
11
|
+
item: "第 {index} 项",
|
|
12
|
+
empty: "还没有内容",
|
|
13
|
+
invalid: "有错误",
|
|
14
|
+
noValue: "—",
|
|
15
|
+
yes: "是",
|
|
16
|
+
no: "否",
|
|
17
|
+
separator: "、",
|
|
18
|
+
rangeSeparator: " ~ ",
|
|
19
|
+
expression: {
|
|
20
|
+
"unexpected-character": "这里不能出现“{text}”",
|
|
21
|
+
"unterminated-string": "文字缺少结尾的引号",
|
|
22
|
+
"unexpected-token": "这里不能出现“{text}”",
|
|
23
|
+
"unexpected-end": "表达式还没写完",
|
|
24
|
+
"unknown-name": "读取字段请写成 values.{text}",
|
|
25
|
+
assignment: "比较请用 ==",
|
|
26
|
+
"too-deep": "表达式嵌套太深",
|
|
27
|
+
"too-long": "表达式太长",
|
|
28
|
+
"unknown-field": "没有字段“{text}”",
|
|
29
|
+
"unknown-function": "没有函数“{text}”"
|
|
30
|
+
},
|
|
31
|
+
document: {
|
|
32
|
+
version: "不认识第 {text} 版的描述",
|
|
33
|
+
"duplicate-id": "节点 id“{text}”重复了",
|
|
34
|
+
"missing-name": "字段缺少字段名",
|
|
35
|
+
"duplicate-name": "字段名“{text}”重复了",
|
|
36
|
+
"unknown-type": "没有“{text}”这种字段",
|
|
37
|
+
"misplaced-page": "页面只能放在标签页或分步里",
|
|
38
|
+
"not-a-page": "标签页和分步里只能放页面",
|
|
39
|
+
empty: "这里还没有内容",
|
|
40
|
+
expression: "表达式有误:{text}",
|
|
41
|
+
"formula-cycle": "公式互相引用:{text}",
|
|
42
|
+
"unknown-validator": "没有校验函数“{text}”",
|
|
43
|
+
"check-and-validator": "同一条规则不能同时写表达式和校验函数"
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var zhTW = {
|
|
47
|
+
prev: "上一步",
|
|
48
|
+
next: "下一步",
|
|
49
|
+
step: "第 {index} 步,共 {count} 步:{title}",
|
|
50
|
+
add: "新增一項",
|
|
51
|
+
remove: "刪除第 {index} 項",
|
|
52
|
+
move: "拖曳第 {index} 項調整順序",
|
|
53
|
+
item: "第 {index} 項",
|
|
54
|
+
empty: "還沒有內容",
|
|
55
|
+
invalid: "有錯誤",
|
|
56
|
+
noValue: "—",
|
|
57
|
+
yes: "是",
|
|
58
|
+
no: "否",
|
|
59
|
+
separator: "、",
|
|
60
|
+
rangeSeparator: " ~ ",
|
|
61
|
+
expression: {
|
|
62
|
+
"unexpected-character": "這裡不能出現「{text}」",
|
|
63
|
+
"unterminated-string": "文字缺少結尾的引號",
|
|
64
|
+
"unexpected-token": "這裡不能出現「{text}」",
|
|
65
|
+
"unexpected-end": "運算式還沒寫完",
|
|
66
|
+
"unknown-name": "讀取欄位請寫成 values.{text}",
|
|
67
|
+
assignment: "比較請用 ==",
|
|
68
|
+
"too-deep": "運算式巢狀太深",
|
|
69
|
+
"too-long": "運算式太長",
|
|
70
|
+
"unknown-field": "沒有欄位「{text}」",
|
|
71
|
+
"unknown-function": "沒有函式「{text}」"
|
|
72
|
+
},
|
|
73
|
+
document: {
|
|
74
|
+
version: "不認得第 {text} 版的描述",
|
|
75
|
+
"duplicate-id": "節點 id「{text}」重複了",
|
|
76
|
+
"missing-name": "欄位缺少欄位名稱",
|
|
77
|
+
"duplicate-name": "欄位名稱「{text}」重複了",
|
|
78
|
+
"unknown-type": "沒有「{text}」這種欄位",
|
|
79
|
+
"misplaced-page": "頁面只能放在分頁或分步裡",
|
|
80
|
+
"not-a-page": "分頁和分步裡只能放頁面",
|
|
81
|
+
empty: "這裡還沒有內容",
|
|
82
|
+
expression: "運算式有誤:{text}",
|
|
83
|
+
"formula-cycle": "公式互相引用:{text}",
|
|
84
|
+
"unknown-validator": "沒有校驗函式「{text}」",
|
|
85
|
+
"check-and-validator": "同一條規則不能同時寫運算式和校驗函式"
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var en = {
|
|
89
|
+
prev: "Back",
|
|
90
|
+
next: "Next",
|
|
91
|
+
step: "Step {index} of {count}: {title}",
|
|
92
|
+
add: "Add",
|
|
93
|
+
remove: "Remove item {index}",
|
|
94
|
+
move: "Drag item {index} to reorder",
|
|
95
|
+
item: "Item {index}",
|
|
96
|
+
empty: "Nothing here yet",
|
|
97
|
+
invalid: "has errors",
|
|
98
|
+
noValue: "—",
|
|
99
|
+
yes: "Yes",
|
|
100
|
+
no: "No",
|
|
101
|
+
separator: ", ",
|
|
102
|
+
rangeSeparator: " – ",
|
|
103
|
+
expression: {
|
|
104
|
+
"unexpected-character": "“{text}” can’t go here",
|
|
105
|
+
"unterminated-string": "The text is missing its closing quote",
|
|
106
|
+
"unexpected-token": "“{text}” can’t go here",
|
|
107
|
+
"unexpected-end": "The expression ends too early",
|
|
108
|
+
"unknown-name": "Read fields as values.{text}",
|
|
109
|
+
assignment: "Use == to compare",
|
|
110
|
+
"too-deep": "The expression nests too deeply",
|
|
111
|
+
"too-long": "The expression is too long",
|
|
112
|
+
"unknown-field": "There is no field “{text}”",
|
|
113
|
+
"unknown-function": "There is no function “{text}”"
|
|
114
|
+
},
|
|
115
|
+
document: {
|
|
116
|
+
version: "Version {text} of the document is not known",
|
|
117
|
+
"duplicate-id": "The node id “{text}” is used twice",
|
|
118
|
+
"missing-name": "The field has no name",
|
|
119
|
+
"duplicate-name": "The field name “{text}” is used twice",
|
|
120
|
+
"unknown-type": "There is no field type “{text}”",
|
|
121
|
+
"misplaced-page": "A page belongs in tabs or steps",
|
|
122
|
+
"not-a-page": "Tabs and steps hold pages only",
|
|
123
|
+
empty: "There is nothing in here yet",
|
|
124
|
+
expression: "The expression is wrong: {text}",
|
|
125
|
+
"formula-cycle": "The formulas read each other: {text}",
|
|
126
|
+
"unknown-validator": "There is no validator “{text}”",
|
|
127
|
+
"check-and-validator": "A rule cannot have both an expression and a validator"
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
var formRendererLocales = {
|
|
131
|
+
"zh-CN": zhCN,
|
|
132
|
+
"zh-TW": zhTW,
|
|
133
|
+
en
|
|
134
|
+
};
|
|
135
|
+
/** The form builder's texts, picked by the name of DouUI's current locale. */
|
|
136
|
+
function useFormRendererMessages(override) {
|
|
137
|
+
const { locale } = useLocale();
|
|
138
|
+
return computed(() => {
|
|
139
|
+
const name = locale.value.name;
|
|
140
|
+
const base = formRendererLocales[name] ?? formRendererLocales[name.split("-")[0]] ?? (name.startsWith("zh") ? zhCN : en);
|
|
141
|
+
const extra = toValue(override);
|
|
142
|
+
if (!extra) return base;
|
|
143
|
+
return {
|
|
144
|
+
...base,
|
|
145
|
+
...extra,
|
|
146
|
+
expression: {
|
|
147
|
+
...base.expression,
|
|
148
|
+
...extra.expression
|
|
149
|
+
},
|
|
150
|
+
document: {
|
|
151
|
+
...base.document,
|
|
152
|
+
...extra.document
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/** Fills `{name}` placeholders. */
|
|
158
|
+
var formatMessage = (text, params) => text.replace(/\{(\w+)\}/g, (match, key) => String(params[key] ?? match));
|
|
159
|
+
export { formRendererLocales, formatMessage, useFormRendererMessages };
|
|
160
|
+
|
|
161
|
+
//# sourceMappingURL=locale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale.js","names":[],"sources":["../src/locale.ts"],"sourcesContent":["import { useLocale } from 'dou-ui'\nimport { computed, toValue } from 'vue'\nimport type { MaybeRefOrGetter } from 'vue'\nimport type { ExpressionProblem } from './core/expression'\nimport type { DocumentProblem } from './core/check'\n\nexport interface FormRendererMessages {\n /** Buttons of `steps`. */\n prev: string\n next: string\n /** Names the page of `steps` that shows, read out when Back or Next goes to it: `{index}`, `{count}`, `{title}`. */\n step: string\n /** The add button of a list, unless the list sets `addText`. */\n add: string\n /** The remove button of a list item, read out: `{index}` counts from 1. */\n remove: string\n /** The drag handle of a list item, read out: `{index}`. */\n move: string\n /** Names a list item: `{index}`. */\n item: string\n /** Shown in a list without items. */\n empty: string\n /** Read out after the title of a tab whose fields have errors. */\n invalid: string\n /** What a `readonly` renderer shows for a field without a value. */\n noValue: string\n /** A `readonly` renderer's text for `true`, e.g. a switch that is on. */\n yes: string\n /** A `readonly` renderer's text for `false`. */\n no: string\n /** Between the items of a list of values in a `readonly` renderer. */\n separator: string\n /** Between both ends of a range of dates, times or numbers in a `readonly` renderer. */\n rangeSeparator: string\n /** What an editor shows for each problem of an expression; `{text}` is the source at fault. */\n expression: Record<ExpressionProblem['code'], string>\n /** What an editor shows for each problem of a document; `{text}` is the id, name or type at fault. */\n document: Record<DocumentProblem['code'], string>\n}\n\nconst zhCN: FormRendererMessages = {\n prev: '上一步',\n next: '下一步',\n step: '第 {index} 步,共 {count} 步:{title}',\n add: '添加一项',\n remove: '删除第 {index} 项',\n move: '拖动第 {index} 项调整顺序',\n item: '第 {index} 项',\n empty: '还没有内容',\n invalid: '有错误',\n noValue: '—',\n yes: '是',\n no: '否',\n separator: '、',\n rangeSeparator: ' ~ ',\n expression: {\n 'unexpected-character': '这里不能出现“{text}”',\n 'unterminated-string': '文字缺少结尾的引号',\n 'unexpected-token': '这里不能出现“{text}”',\n 'unexpected-end': '表达式还没写完',\n 'unknown-name': '读取字段请写成 values.{text}',\n assignment: '比较请用 ==',\n 'too-deep': '表达式嵌套太深',\n 'too-long': '表达式太长',\n 'unknown-field': '没有字段“{text}”',\n 'unknown-function': '没有函数“{text}”',\n },\n document: {\n version: '不认识第 {text} 版的描述',\n 'duplicate-id': '节点 id“{text}”重复了',\n 'missing-name': '字段缺少字段名',\n 'duplicate-name': '字段名“{text}”重复了',\n 'unknown-type': '没有“{text}”这种字段',\n 'misplaced-page': '页面只能放在标签页或分步里',\n 'not-a-page': '标签页和分步里只能放页面',\n empty: '这里还没有内容',\n expression: '表达式有误:{text}',\n 'formula-cycle': '公式互相引用:{text}',\n 'unknown-validator': '没有校验函数“{text}”',\n 'check-and-validator': '同一条规则不能同时写表达式和校验函数',\n },\n}\n\nconst zhTW: FormRendererMessages = {\n prev: '上一步',\n next: '下一步',\n step: '第 {index} 步,共 {count} 步:{title}',\n add: '新增一項',\n remove: '刪除第 {index} 項',\n move: '拖曳第 {index} 項調整順序',\n item: '第 {index} 項',\n empty: '還沒有內容',\n invalid: '有錯誤',\n noValue: '—',\n yes: '是',\n no: '否',\n separator: '、',\n rangeSeparator: ' ~ ',\n expression: {\n 'unexpected-character': '這裡不能出現「{text}」',\n 'unterminated-string': '文字缺少結尾的引號',\n 'unexpected-token': '這裡不能出現「{text}」',\n 'unexpected-end': '運算式還沒寫完',\n 'unknown-name': '讀取欄位請寫成 values.{text}',\n assignment: '比較請用 ==',\n 'too-deep': '運算式巢狀太深',\n 'too-long': '運算式太長',\n 'unknown-field': '沒有欄位「{text}」',\n 'unknown-function': '沒有函式「{text}」',\n },\n document: {\n version: '不認得第 {text} 版的描述',\n 'duplicate-id': '節點 id「{text}」重複了',\n 'missing-name': '欄位缺少欄位名稱',\n 'duplicate-name': '欄位名稱「{text}」重複了',\n 'unknown-type': '沒有「{text}」這種欄位',\n 'misplaced-page': '頁面只能放在分頁或分步裡',\n 'not-a-page': '分頁和分步裡只能放頁面',\n empty: '這裡還沒有內容',\n expression: '運算式有誤:{text}',\n 'formula-cycle': '公式互相引用:{text}',\n 'unknown-validator': '沒有校驗函式「{text}」',\n 'check-and-validator': '同一條規則不能同時寫運算式和校驗函式',\n },\n}\n\nconst en: FormRendererMessages = {\n prev: 'Back',\n next: 'Next',\n step: 'Step {index} of {count}: {title}',\n add: 'Add',\n remove: 'Remove item {index}',\n move: 'Drag item {index} to reorder',\n item: 'Item {index}',\n empty: 'Nothing here yet',\n invalid: 'has errors',\n noValue: '—',\n yes: 'Yes',\n no: 'No',\n separator: ', ',\n rangeSeparator: ' – ',\n expression: {\n 'unexpected-character': '“{text}” can’t go here',\n 'unterminated-string': 'The text is missing its closing quote',\n 'unexpected-token': '“{text}” can’t go here',\n 'unexpected-end': 'The expression ends too early',\n 'unknown-name': 'Read fields as values.{text}',\n assignment: 'Use == to compare',\n 'too-deep': 'The expression nests too deeply',\n 'too-long': 'The expression is too long',\n 'unknown-field': 'There is no field “{text}”',\n 'unknown-function': 'There is no function “{text}”',\n },\n document: {\n version: 'Version {text} of the document is not known',\n 'duplicate-id': 'The node id “{text}” is used twice',\n 'missing-name': 'The field has no name',\n 'duplicate-name': 'The field name “{text}” is used twice',\n 'unknown-type': 'There is no field type “{text}”',\n 'misplaced-page': 'A page belongs in tabs or steps',\n 'not-a-page': 'Tabs and steps hold pages only',\n empty: 'There is nothing in here yet',\n expression: 'The expression is wrong: {text}',\n 'formula-cycle': 'The formulas read each other: {text}',\n 'unknown-validator': 'There is no validator “{text}”',\n 'check-and-validator': 'A rule cannot have both an expression and a validator',\n },\n}\n\nexport const formRendererLocales: Record<string, FormRendererMessages> = {\n 'zh-CN': zhCN,\n 'zh-TW': zhTW,\n en,\n}\n\n/** Texts to change; the `expression` and `document` groups are merged, so only the ones given are replaced. */\nexport type FormRendererMessagesOverride = {\n [K in keyof FormRendererMessages]?: FormRendererMessages[K] extends string\n ? string\n : Partial<FormRendererMessages[K]>\n}\n\n/** The form builder's texts, picked by the name of DouUI's current locale. */\nexport function useFormRendererMessages(\n override?: MaybeRefOrGetter<FormRendererMessagesOverride | undefined>,\n) {\n const { locale } = useLocale()\n return computed<FormRendererMessages>(() => {\n const name = locale.value.name\n const base =\n formRendererLocales[name] ??\n formRendererLocales[name.split('-')[0]!] ??\n (name.startsWith('zh') ? zhCN : en)\n const extra = toValue(override)\n if (!extra) return base\n return {\n ...base,\n ...extra,\n expression: { ...base.expression, ...extra.expression },\n document: { ...base.document, ...extra.document },\n } as FormRendererMessages\n })\n}\n\n/** Fills `{name}` placeholders. */\nexport const formatMessage = (text: string, params: Record<string, string | number>) =>\n text.replace(/\\{(\\w+)\\}/g, (match, key: string) => String(params[key] ?? match))\n"],"mappings":";;;AAwCA,IAAM,OAA6B;CACjC,MAAM;CACN,MAAM;CACN,MAAM;CACN,KAAK;CACL,QAAQ;CACR,MAAM;CACN,MAAM;CACN,OAAO;CACP,SAAS;CACT,SAAS;CACT,KAAK;CACL,IAAI;CACJ,WAAW;CACX,gBAAgB;CAChB,YAAY;EACV,wBAAwB;EACxB,uBAAuB;EACvB,oBAAoB;EACpB,kBAAkB;EAClB,gBAAgB;EAChB,YAAY;EACZ,YAAY;EACZ,YAAY;EACZ,iBAAiB;EACjB,oBAAoB;CACtB;CACA,UAAU;EACR,SAAS;EACT,gBAAgB;EAChB,gBAAgB;EAChB,kBAAkB;EAClB,gBAAgB;EAChB,kBAAkB;EAClB,cAAc;EACd,OAAO;EACP,YAAY;EACZ,iBAAiB;EACjB,qBAAqB;EACrB,uBAAuB;CACzB;AACF;AAEA,IAAM,OAA6B;CACjC,MAAM;CACN,MAAM;CACN,MAAM;CACN,KAAK;CACL,QAAQ;CACR,MAAM;CACN,MAAM;CACN,OAAO;CACP,SAAS;CACT,SAAS;CACT,KAAK;CACL,IAAI;CACJ,WAAW;CACX,gBAAgB;CAChB,YAAY;EACV,wBAAwB;EACxB,uBAAuB;EACvB,oBAAoB;EACpB,kBAAkB;EAClB,gBAAgB;EAChB,YAAY;EACZ,YAAY;EACZ,YAAY;EACZ,iBAAiB;EACjB,oBAAoB;CACtB;CACA,UAAU;EACR,SAAS;EACT,gBAAgB;EAChB,gBAAgB;EAChB,kBAAkB;EAClB,gBAAgB;EAChB,kBAAkB;EAClB,cAAc;EACd,OAAO;EACP,YAAY;EACZ,iBAAiB;EACjB,qBAAqB;EACrB,uBAAuB;CACzB;AACF;AAEA,IAAM,KAA2B;CAC/B,MAAM;CACN,MAAM;CACN,MAAM;CACN,KAAK;CACL,QAAQ;CACR,MAAM;CACN,MAAM;CACN,OAAO;CACP,SAAS;CACT,SAAS;CACT,KAAK;CACL,IAAI;CACJ,WAAW;CACX,gBAAgB;CAChB,YAAY;EACV,wBAAwB;EACxB,uBAAuB;EACvB,oBAAoB;EACpB,kBAAkB;EAClB,gBAAgB;EAChB,YAAY;EACZ,YAAY;EACZ,YAAY;EACZ,iBAAiB;EACjB,oBAAoB;CACtB;CACA,UAAU;EACR,SAAS;EACT,gBAAgB;EAChB,gBAAgB;EAChB,kBAAkB;EAClB,gBAAgB;EAChB,kBAAkB;EAClB,cAAc;EACd,OAAO;EACP,YAAY;EACZ,iBAAiB;EACjB,qBAAqB;EACrB,uBAAuB;CACzB;AACF;AAEA,IAAa,sBAA4D;CACvE,SAAS;CACT,SAAS;CACT;AACF;;AAUA,SAAgB,wBACd,UACA;CACA,MAAM,EAAE,WAAW,UAAU;CAC7B,OAAO,eAAqC;EAC1C,MAAM,OAAO,OAAO,MAAM;EAC1B,MAAM,OACJ,oBAAoB,SACpB,oBAAoB,KAAK,MAAM,GAAG,CAAC,CAAC,QACnC,KAAK,WAAW,IAAI,IAAI,OAAO;EAClC,MAAM,QAAQ,QAAQ,QAAQ;EAC9B,IAAI,CAAC,OAAO,OAAO;EACnB,OAAO;GACL,GAAG;GACH,GAAG;GACH,YAAY;IAAE,GAAG,KAAK;IAAY,GAAG,MAAM;GAAW;GACtD,UAAU;IAAE,GAAG,KAAK;IAAU,GAAG,MAAM;GAAS;EAClD;CACF,CAAC;AACH;;AAGA,IAAa,iBAAiB,MAAc,WAC1C,KAAK,QAAQ,eAAe,OAAO,QAAgB,OAAO,OAAO,QAAQ,KAAK,CAAC"}
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { getPath, isList, setPath, valueNodes } from "../core/document.js";
|
|
2
|
+
import { emptyItem } from "../core/values.js";
|
|
3
|
+
import { formatMessage } from "../locale.js";
|
|
4
|
+
import { disabledKey, pathIn, rendererKey, scopeKey } from "./context.js";
|
|
5
|
+
import { rulesOf } from "../core/rules.js";
|
|
6
|
+
import { DouButton, DouFormItem, DouSortable, DouSortableHandle, DouSteps, DouTabPanel, DouTabs } from "dou-ui";
|
|
7
|
+
import { computed, defineComponent, h, inject, nextTick, onScopeDispose, provide, shallowRef, toRaw, useId, watch } from "vue";
|
|
8
|
+
import { PlusIcon, TrashIcon } from "@dou-ui/icons";
|
|
9
|
+
// src/renderer/containers.ts
|
|
10
|
+
/** Whether a field at `path` sits among `nodes`, inside a list item included. */
|
|
11
|
+
function holdsPath(nodes, path, prefix = "") {
|
|
12
|
+
return valueNodes(nodes).some((node) => {
|
|
13
|
+
const own = prefix + node.name;
|
|
14
|
+
return own === path || isList(node) && path.startsWith(`${own}.`);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** The paths of every field among `nodes`, the fields of each list item included. */
|
|
18
|
+
function valuePaths(nodes, model, prefix = "") {
|
|
19
|
+
const paths = [];
|
|
20
|
+
for (const node of valueNodes(nodes)) {
|
|
21
|
+
const own = prefix + node.name;
|
|
22
|
+
paths.push(own);
|
|
23
|
+
if (!isList(node)) continue;
|
|
24
|
+
const items = getPath(model, own);
|
|
25
|
+
if (Array.isArray(items)) items.forEach((_, index) => paths.push(...valuePaths(node.children, model, `${own}.${index}.`)));
|
|
26
|
+
}
|
|
27
|
+
return paths;
|
|
28
|
+
}
|
|
29
|
+
/** The pages of tabs or steps that show, as the node's own view decides them. */
|
|
30
|
+
function shownPages(props, condition) {
|
|
31
|
+
return computed(() => props.node.children.filter((child) => !!child && condition(child.visibleWhen, true, child)));
|
|
32
|
+
}
|
|
33
|
+
/** One page of tabs or steps: its description and its own grid. */
|
|
34
|
+
var pageOf = (kit, node, props) => h(kit.view, {
|
|
35
|
+
key: node.id,
|
|
36
|
+
node,
|
|
37
|
+
columns: props.columns,
|
|
38
|
+
inset: props.inset,
|
|
39
|
+
asPage: true
|
|
40
|
+
});
|
|
41
|
+
function tabsView(kit, props, renderer, scope, disabled, condition) {
|
|
42
|
+
provide(disabledKey, () => disabled.value);
|
|
43
|
+
const pages = shownPages(props, condition);
|
|
44
|
+
const chosen = shallowRef();
|
|
45
|
+
const selected = computed(() => pages.value.some((shown) => shown.id === chosen.value) ? chosen.value : pages.value[0]?.id);
|
|
46
|
+
onScopeDispose(renderer.addRevealer((path) => {
|
|
47
|
+
const found = pages.value.find((shown) => holdsPath(shown.children, path, scope.prefix()));
|
|
48
|
+
if (found) chosen.value = found.id;
|
|
49
|
+
return !!found;
|
|
50
|
+
}));
|
|
51
|
+
return () => h(DouTabs, {
|
|
52
|
+
class: "dou-form-renderer__tabs",
|
|
53
|
+
modelValue: selected.value,
|
|
54
|
+
"onUpdate:modelValue": (value) => {
|
|
55
|
+
if (value !== void 0) chosen.value = String(value);
|
|
56
|
+
}
|
|
57
|
+
}, () => pages.value.map((node) => h(DouTabPanel, {
|
|
58
|
+
key: node.id,
|
|
59
|
+
value: node.id,
|
|
60
|
+
label: node.title,
|
|
61
|
+
eager: true
|
|
62
|
+
}, {
|
|
63
|
+
default: () => pageOf(kit, node, props),
|
|
64
|
+
label: () => {
|
|
65
|
+
const failed = renderer.errorPaths().some((path) => holdsPath(node.children, path, scope.prefix()));
|
|
66
|
+
return h("span", { class: "dou-form-renderer__tab-title" }, [node.title, failed ? h("span", { class: "dou-form-renderer__tab-error" }, [h("span", { class: "dou-form-renderer__sr-only" }, `(${renderer.messages().invalid})`)]) : null]);
|
|
67
|
+
}
|
|
68
|
+
})));
|
|
69
|
+
}
|
|
70
|
+
function stepsView(kit, props, renderer, scope, disabled, condition) {
|
|
71
|
+
provide(disabledKey, () => disabled.value);
|
|
72
|
+
const pages = shownPages(props, condition);
|
|
73
|
+
if (renderer.readonly()) {
|
|
74
|
+
const uid = useId();
|
|
75
|
+
return () => h("div", { class: "dou-form-renderer__steps is-readonly" }, pages.value.map((node) => {
|
|
76
|
+
const titleId = `dou-form-step-${uid}-${node.id}`;
|
|
77
|
+
return h("div", {
|
|
78
|
+
key: node.id,
|
|
79
|
+
class: "dou-form-renderer__section",
|
|
80
|
+
role: "group",
|
|
81
|
+
"aria-labelledby": titleId
|
|
82
|
+
}, [h("div", { class: "dou-form-renderer__section-header" }, [h("div", {
|
|
83
|
+
id: titleId,
|
|
84
|
+
class: "dou-form-renderer__section-title"
|
|
85
|
+
}, node.title)]), pageOf(kit, node, props)]);
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
const chosen = shallowRef();
|
|
89
|
+
/** How far people got: the steps up to here can be opened again. */
|
|
90
|
+
const reached = shallowRef(0);
|
|
91
|
+
const failed = shallowRef(false);
|
|
92
|
+
const checking = shallowRef(false);
|
|
93
|
+
const current = computed(() => {
|
|
94
|
+
const index = pages.value.findIndex((shown) => shown.id === chosen.value);
|
|
95
|
+
return index < 0 ? 0 : index;
|
|
96
|
+
});
|
|
97
|
+
watch(current, () => failed.value = false);
|
|
98
|
+
watch(renderer.resets, () => {
|
|
99
|
+
chosen.value = void 0;
|
|
100
|
+
reached.value = 0;
|
|
101
|
+
failed.value = false;
|
|
102
|
+
});
|
|
103
|
+
function go(index) {
|
|
104
|
+
const target = pages.value[index];
|
|
105
|
+
if (!target) return;
|
|
106
|
+
chosen.value = target.id;
|
|
107
|
+
reached.value = Math.max(reached.value, index);
|
|
108
|
+
}
|
|
109
|
+
const stepEls = /* @__PURE__ */ new Map();
|
|
110
|
+
function turn(index) {
|
|
111
|
+
go(index);
|
|
112
|
+
const id = pages.value[index]?.id;
|
|
113
|
+
if (id) nextTick(() => stepEls.get(id)?.focus({ preventScroll: true }));
|
|
114
|
+
}
|
|
115
|
+
async function next() {
|
|
116
|
+
const shown = pages.value[current.value];
|
|
117
|
+
if (!shown || checking.value) return;
|
|
118
|
+
checking.value = true;
|
|
119
|
+
try {
|
|
120
|
+
const paths = valuePaths(shown.children, renderer.model(), scope.prefix());
|
|
121
|
+
const { valid, errors } = await renderer.validate(paths);
|
|
122
|
+
if (!valid) {
|
|
123
|
+
failed.value = true;
|
|
124
|
+
const first = Object.keys(errors)[0];
|
|
125
|
+
if (first) renderer.scrollToField(first);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
turn(current.value + 1);
|
|
129
|
+
} finally {
|
|
130
|
+
checking.value = false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
onScopeDispose(renderer.addRevealer((path) => {
|
|
134
|
+
const index = pages.value.findIndex((page) => holdsPath(page.children, path, scope.prefix()));
|
|
135
|
+
if (index >= 0) go(index);
|
|
136
|
+
return index >= 0;
|
|
137
|
+
}));
|
|
138
|
+
return () => {
|
|
139
|
+
const list = pages.value;
|
|
140
|
+
const index = current.value;
|
|
141
|
+
const messages = renderer.messages();
|
|
142
|
+
const last = index >= list.length - 1;
|
|
143
|
+
const errorPaths = renderer.errorPaths();
|
|
144
|
+
const failing = new Set(list.filter((step) => errorPaths.some((path) => holdsPath(step.children, path, scope.prefix()))).map((step) => step.id));
|
|
145
|
+
const items = list.map((step, at) => ({
|
|
146
|
+
title: step.title,
|
|
147
|
+
disabled: at > reached.value,
|
|
148
|
+
status: failing.has(step.id) ? "error" : void 0
|
|
149
|
+
}));
|
|
150
|
+
const footer = [];
|
|
151
|
+
if (index > 0) footer.push(h(DouButton, {
|
|
152
|
+
key: "prev",
|
|
153
|
+
variant: "outline",
|
|
154
|
+
onClick: () => turn(index - 1)
|
|
155
|
+
}, () => messages.prev));
|
|
156
|
+
if (!last) footer.push(h(DouButton, {
|
|
157
|
+
key: "next",
|
|
158
|
+
tone: "primary",
|
|
159
|
+
loading: checking.value,
|
|
160
|
+
onClick: next
|
|
161
|
+
}, () => messages.next));
|
|
162
|
+
else if (renderer.footerOwner() === props.node.id) footer.push(renderer.footer());
|
|
163
|
+
return h("div", { class: "dou-form-renderer__steps" }, [
|
|
164
|
+
h(DouSteps, {
|
|
165
|
+
items,
|
|
166
|
+
current: index,
|
|
167
|
+
status: failed.value ? "error" : "process",
|
|
168
|
+
clickable: true,
|
|
169
|
+
"onUpdate:current": (at) => {
|
|
170
|
+
if (at <= reached.value) go(at);
|
|
171
|
+
}
|
|
172
|
+
}),
|
|
173
|
+
...list.map((node, at) => h("div", {
|
|
174
|
+
key: node.id,
|
|
175
|
+
ref: (el) => {
|
|
176
|
+
if (el) stepEls.set(node.id, el);
|
|
177
|
+
else stepEls.delete(node.id);
|
|
178
|
+
},
|
|
179
|
+
class: "dou-form-renderer__step",
|
|
180
|
+
hidden: at !== index || void 0,
|
|
181
|
+
role: "group",
|
|
182
|
+
tabindex: -1,
|
|
183
|
+
"aria-label": formatMessage(messages.step, {
|
|
184
|
+
index: at + 1,
|
|
185
|
+
count: list.length,
|
|
186
|
+
title: node.title ?? ""
|
|
187
|
+
})
|
|
188
|
+
}, [pageOf(kit, node, props)])),
|
|
189
|
+
h("div", { class: "dou-form-renderer__steps-footer" }, footer)
|
|
190
|
+
]);
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/** Ids of list items, by the item object, so each keeps its view while the list is reordered. */
|
|
194
|
+
var itemIds = /* @__PURE__ */ new WeakMap();
|
|
195
|
+
var nextItem = 0;
|
|
196
|
+
function itemKey(item) {
|
|
197
|
+
if (item === null || typeof item !== "object") return `v:${String(item)}`;
|
|
198
|
+
const raw = toRaw(item);
|
|
199
|
+
let id = itemIds.get(raw);
|
|
200
|
+
if (!id) itemIds.set(raw, id = `i${++nextItem}`);
|
|
201
|
+
return id;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The copy of an item that follows the pointer. It sits on `body`, away
|
|
205
|
+
* from the form whose width picks the columns, whose classes set the layout
|
|
206
|
+
* and whose surroundings may change the gaps (the designer's settings panel
|
|
207
|
+
* narrows them), so it is wrapped in an element with the form's classes and
|
|
208
|
+
* every grid in it keeps the columns and gaps it has now, in pixels.
|
|
209
|
+
*/
|
|
210
|
+
function listGhost(el) {
|
|
211
|
+
const clone = el.cloneNode(true);
|
|
212
|
+
freezeGrids(el, clone);
|
|
213
|
+
const form = el.closest(".dou-form");
|
|
214
|
+
if (!form) return clone;
|
|
215
|
+
const wrapper = document.createElement("div");
|
|
216
|
+
wrapper.className = `${form.className} dou-form-renderer__list-ghost`;
|
|
217
|
+
wrapper.style.containerType = "normal";
|
|
218
|
+
wrapper.append(clone);
|
|
219
|
+
return wrapper;
|
|
220
|
+
}
|
|
221
|
+
/** Writes the computed grid tracks, gaps and cell placement of `source` onto its copy. */
|
|
222
|
+
function freezeGrids(source, copy) {
|
|
223
|
+
const from = [source, ...source.querySelectorAll("*")];
|
|
224
|
+
const to = [copy, ...copy.querySelectorAll("*")];
|
|
225
|
+
from.forEach((element, index) => {
|
|
226
|
+
const target = to[index];
|
|
227
|
+
if (!target) return;
|
|
228
|
+
const style = getComputedStyle(element);
|
|
229
|
+
if (style.display === "grid" || style.display === "inline-grid") {
|
|
230
|
+
target.style.gridTemplateColumns = style.gridTemplateColumns;
|
|
231
|
+
target.style.columnGap = style.columnGap;
|
|
232
|
+
target.style.rowGap = style.rowGap;
|
|
233
|
+
}
|
|
234
|
+
if (element.classList.contains("dou-form-renderer__cell")) target.style.gridColumn = style.gridColumn;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/** The rules of a list itself: its own, and its bounds on the number of items. */
|
|
238
|
+
function listRules(node, renderer, path) {
|
|
239
|
+
const rules = rulesOf(node, {
|
|
240
|
+
functions: renderer.functions(),
|
|
241
|
+
validators: renderer.validators(),
|
|
242
|
+
path
|
|
243
|
+
});
|
|
244
|
+
const bounds = {};
|
|
245
|
+
if (node.min !== void 0 && node.min > 0) bounds.min = node.min;
|
|
246
|
+
if (node.max !== void 0) bounds.max = node.max;
|
|
247
|
+
return bounds.min === void 0 && bounds.max === void 0 ? rules : [...rules, bounds];
|
|
248
|
+
}
|
|
249
|
+
function listView(kit, props, renderer, scope, disabled) {
|
|
250
|
+
provide(disabledKey, () => disabled.value);
|
|
251
|
+
const uid = useId();
|
|
252
|
+
const list = () => props.node;
|
|
253
|
+
const path = () => pathIn(scope, list().name);
|
|
254
|
+
const items = computed(() => {
|
|
255
|
+
const value = getPath(renderer.model(), path());
|
|
256
|
+
return Array.isArray(value) ? value : [];
|
|
257
|
+
});
|
|
258
|
+
const rules = computed(() => listRules(list(), renderer, path));
|
|
259
|
+
function write(next) {
|
|
260
|
+
const name = path();
|
|
261
|
+
setPath(renderer.model(), name, next);
|
|
262
|
+
renderer.changed(name, getPath(renderer.model(), name));
|
|
263
|
+
}
|
|
264
|
+
const root = shallowRef();
|
|
265
|
+
const footer = shallowRef();
|
|
266
|
+
onScopeDispose(renderer.trackError(path, () => footer.value?.error));
|
|
267
|
+
/** The items of this list, not of lists inside it nor ones still animating out. */
|
|
268
|
+
const itemElements = () => [...root.value?.querySelectorAll(".dou-form-renderer__list-item") ?? []].filter((el) => el.closest(".dou-form-renderer__list") === root.value && !el.closest("[class*=\"-leave-\"]"));
|
|
269
|
+
/** Focus the first field of an item, or the add button when there is none. */
|
|
270
|
+
async function focusItem(index) {
|
|
271
|
+
await nextTick();
|
|
272
|
+
await nextTick();
|
|
273
|
+
const elements = itemElements();
|
|
274
|
+
(elements[Math.min(index, elements.length - 1)]?.querySelector(":scope > .dou-form-renderer__grid :is(input, textarea, select, button, [tabindex=\"0\"]):not([disabled])") ?? root.value?.querySelector(".dou-form-renderer__list-add"))?.focus();
|
|
275
|
+
}
|
|
276
|
+
function add() {
|
|
277
|
+
write([...items.value, emptyItem(list(), renderer.fields())]);
|
|
278
|
+
focusItem(items.value.length - 1);
|
|
279
|
+
}
|
|
280
|
+
function remove(index) {
|
|
281
|
+
write(items.value.filter((_, at) => at !== index));
|
|
282
|
+
focusItem(index);
|
|
283
|
+
}
|
|
284
|
+
return () => {
|
|
285
|
+
const node = list();
|
|
286
|
+
const rows = items.value;
|
|
287
|
+
const messages = renderer.messages();
|
|
288
|
+
const readonly = renderer.readonly() || renderer.controlsReadonly();
|
|
289
|
+
const off = disabled.value;
|
|
290
|
+
const canAdd = !off && (node.max === void 0 || rows.length < node.max);
|
|
291
|
+
const canRemove = !off && rows.length > (node.min ?? 0);
|
|
292
|
+
const sortable = node.sortable !== false && !off && !readonly && rows.length > 1;
|
|
293
|
+
const labelId = node.label ? `dou-form-list-${uid}` : void 0;
|
|
294
|
+
const required = node.required || rules.value.some((rule) => rule.required);
|
|
295
|
+
return h("div", {
|
|
296
|
+
ref: root,
|
|
297
|
+
class: [
|
|
298
|
+
"dou-form-renderer__list",
|
|
299
|
+
off && "is-disabled",
|
|
300
|
+
readonly && "is-readonly"
|
|
301
|
+
],
|
|
302
|
+
role: "group",
|
|
303
|
+
"aria-labelledby": labelId,
|
|
304
|
+
"aria-disabled": off || void 0
|
|
305
|
+
}, [
|
|
306
|
+
node.label || node.help ? h("div", { class: "dou-form-renderer__section-header" }, [node.label && h("div", {
|
|
307
|
+
id: labelId,
|
|
308
|
+
class: ["dou-form-renderer__section-title", required && "is-required"]
|
|
309
|
+
}, node.label), node.help && h("div", { class: "dou-form-renderer__section-description" }, node.help)]) : null,
|
|
310
|
+
rows.length ? h(DouSortable, {
|
|
311
|
+
class: "dou-form-renderer__list-items",
|
|
312
|
+
modelValue: rows,
|
|
313
|
+
"onUpdate:modelValue": (next) => write(next),
|
|
314
|
+
itemKey,
|
|
315
|
+
ghost: listGhost,
|
|
316
|
+
handle: true,
|
|
317
|
+
disabled: !sortable,
|
|
318
|
+
itemLabel: (_item, index) => formatMessage(messages.item, { index: index + 1 })
|
|
319
|
+
}, { default: ({ item, index }) => {
|
|
320
|
+
const raw = toRaw(item);
|
|
321
|
+
const at = rows.findIndex((row) => toRaw(row) === raw);
|
|
322
|
+
return h(ListItemView, {
|
|
323
|
+
list: node,
|
|
324
|
+
render: kit.renderNodes,
|
|
325
|
+
path: path(),
|
|
326
|
+
item,
|
|
327
|
+
index: at < 0 ? index : at,
|
|
328
|
+
position: index,
|
|
329
|
+
columns: node.columns ?? props.columns,
|
|
330
|
+
inset: props.inset + 1,
|
|
331
|
+
sortable,
|
|
332
|
+
canRemove,
|
|
333
|
+
readonly,
|
|
334
|
+
onRemove: () => remove(at < 0 ? index : at)
|
|
335
|
+
});
|
|
336
|
+
} }) : h("div", { class: "dou-form-renderer__list-empty" }, readonly ? messages.noValue : messages.empty),
|
|
337
|
+
readonly ? null : h(DouFormItem, {
|
|
338
|
+
ref: footer,
|
|
339
|
+
class: "dou-form-renderer__list-footer",
|
|
340
|
+
prop: path(),
|
|
341
|
+
rules: rules.value
|
|
342
|
+
}, () => h(DouButton, {
|
|
343
|
+
class: "dou-form-renderer__list-add",
|
|
344
|
+
variant: "outline",
|
|
345
|
+
disabled: !canAdd,
|
|
346
|
+
onClick: add
|
|
347
|
+
}, {
|
|
348
|
+
leading: () => h(PlusIcon),
|
|
349
|
+
default: () => node.addText ?? messages.add
|
|
350
|
+
}))
|
|
351
|
+
]);
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
/** One item of a list: its fields read and write the item. */
|
|
355
|
+
var ListItemView = defineComponent({
|
|
356
|
+
name: "DouFormListItem",
|
|
357
|
+
props: {
|
|
358
|
+
list: {
|
|
359
|
+
type: Object,
|
|
360
|
+
required: true
|
|
361
|
+
},
|
|
362
|
+
render: {
|
|
363
|
+
type: Function,
|
|
364
|
+
required: true
|
|
365
|
+
},
|
|
366
|
+
path: {
|
|
367
|
+
type: String,
|
|
368
|
+
required: true
|
|
369
|
+
},
|
|
370
|
+
item: {
|
|
371
|
+
type: Object,
|
|
372
|
+
required: true
|
|
373
|
+
},
|
|
374
|
+
/** Where the item is in the value. */
|
|
375
|
+
index: {
|
|
376
|
+
type: Number,
|
|
377
|
+
required: true
|
|
378
|
+
},
|
|
379
|
+
/** Where the item is shown, which differs while it is dragged. */
|
|
380
|
+
position: {
|
|
381
|
+
type: Number,
|
|
382
|
+
required: true
|
|
383
|
+
},
|
|
384
|
+
columns: {
|
|
385
|
+
type: Number,
|
|
386
|
+
required: true
|
|
387
|
+
},
|
|
388
|
+
inset: {
|
|
389
|
+
type: Number,
|
|
390
|
+
required: true
|
|
391
|
+
},
|
|
392
|
+
sortable: Boolean,
|
|
393
|
+
canRemove: Boolean,
|
|
394
|
+
/** Without the remove button. */
|
|
395
|
+
readonly: Boolean
|
|
396
|
+
},
|
|
397
|
+
emits: ["remove"],
|
|
398
|
+
setup(props, { emit }) {
|
|
399
|
+
const renderer = inject(rendererKey);
|
|
400
|
+
provide(scopeKey, {
|
|
401
|
+
prefix: () => `${props.path}.${props.index}.`,
|
|
402
|
+
item: () => props.item
|
|
403
|
+
});
|
|
404
|
+
return () => {
|
|
405
|
+
const messages = renderer.messages();
|
|
406
|
+
const number = props.position + 1;
|
|
407
|
+
return h("div", { class: "dou-form-renderer__list-item" }, [h("div", { class: "dou-form-renderer__list-item-header" }, [
|
|
408
|
+
props.sortable ? h(DouSortableHandle, {
|
|
409
|
+
class: "dou-form-renderer__list-handle",
|
|
410
|
+
label: formatMessage(messages.move, { index: number })
|
|
411
|
+
}) : null,
|
|
412
|
+
h("span", { class: "dou-form-renderer__list-item-title" }, formatMessage(messages.item, { index: number })),
|
|
413
|
+
props.readonly ? null : h(DouButton, {
|
|
414
|
+
class: "dou-form-renderer__list-remove",
|
|
415
|
+
variant: "ghost",
|
|
416
|
+
size: "sm",
|
|
417
|
+
disabled: !props.canRemove,
|
|
418
|
+
"aria-label": formatMessage(messages.remove, { index: number }),
|
|
419
|
+
onClick: () => emit("remove")
|
|
420
|
+
}, { leading: () => h(TrashIcon) })
|
|
421
|
+
]), h("div", {
|
|
422
|
+
class: "dou-form-renderer__grid",
|
|
423
|
+
style: { "--dou-form-columns": props.columns }
|
|
424
|
+
}, props.render(props.list.children, props.columns, props.inset))]);
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
export { holdsPath, listView, stepsView, tabsView, valuePaths };
|
|
429
|
+
|
|
430
|
+
//# sourceMappingURL=containers.js.map
|