@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.
Files changed (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/builder/canvas.js +695 -0
  4. package/dist/builder/canvas.js.map +1 -0
  5. package/dist/builder/completion.js +49 -0
  6. package/dist/builder/completion.js.map +1 -0
  7. package/dist/builder/condition-editor.js +6 -0
  8. package/dist/builder/condition-editor.js.map +1 -0
  9. package/dist/builder/condition-editor.vue_vue_type_script_setup_true_lang.js +410 -0
  10. package/dist/builder/condition-editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
  11. package/dist/builder/condition.js +159 -0
  12. package/dist/builder/condition.js.map +1 -0
  13. package/dist/builder/context.js +477 -0
  14. package/dist/builder/context.js.map +1 -0
  15. package/dist/builder/core/commands.js +373 -0
  16. package/dist/builder/core/commands.js.map +1 -0
  17. package/dist/builder/core/history.js +76 -0
  18. package/dist/builder/core/history.js.map +1 -0
  19. package/dist/builder/core/transaction.js +298 -0
  20. package/dist/builder/core/transaction.js.map +1 -0
  21. package/dist/builder/drag.js +466 -0
  22. package/dist/builder/drag.js.map +1 -0
  23. package/dist/builder/expression-box.js +19 -0
  24. package/dist/builder/expression-box.js.map +1 -0
  25. package/dist/builder/expression-input.js +6 -0
  26. package/dist/builder/expression-input.js.map +1 -0
  27. package/dist/builder/expression-input.vue_vue_type_script_setup_true_lang.js +117 -0
  28. package/dist/builder/expression-input.vue_vue_type_script_setup_true_lang.js.map +1 -0
  29. package/dist/builder/extension.js +47 -0
  30. package/dist/builder/extension.js.map +1 -0
  31. package/dist/builder/form-builder.js +6 -0
  32. package/dist/builder/form-builder.js.map +1 -0
  33. package/dist/builder/form-builder.vue_vue_type_script_setup_true_lang.js +450 -0
  34. package/dist/builder/form-builder.vue_vue_type_script_setup_true_lang.js.map +1 -0
  35. package/dist/builder/index.js +22 -0
  36. package/dist/builder/index.js.map +1 -0
  37. package/dist/builder/inspector.js +6 -0
  38. package/dist/builder/inspector.js.map +1 -0
  39. package/dist/builder/inspector.vue_vue_type_script_setup_true_lang.js +442 -0
  40. package/dist/builder/inspector.vue_vue_type_script_setup_true_lang.js.map +1 -0
  41. package/dist/builder/json-dialog.js +6 -0
  42. package/dist/builder/json-dialog.js.map +1 -0
  43. package/dist/builder/json-dialog.vue_vue_type_script_setup_true_lang.js +281 -0
  44. package/dist/builder/json-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  45. package/dist/builder/locale.js +1271 -0
  46. package/dist/builder/locale.js.map +1 -0
  47. package/dist/builder/logic-fields.js +18 -0
  48. package/dist/builder/logic-fields.js.map +1 -0
  49. package/dist/builder/logic.js +332 -0
  50. package/dist/builder/logic.js.map +1 -0
  51. package/dist/builder/menu.js +92 -0
  52. package/dist/builder/menu.js.map +1 -0
  53. package/dist/builder/outline-row.js +6 -0
  54. package/dist/builder/outline-row.js.map +1 -0
  55. package/dist/builder/outline-row.vue_vue_type_script_setup_true_lang.js +88 -0
  56. package/dist/builder/outline-row.vue_vue_type_script_setup_true_lang.js.map +1 -0
  57. package/dist/builder/outline.js +6 -0
  58. package/dist/builder/outline.js.map +1 -0
  59. package/dist/builder/outline.vue_vue_type_script_setup_true_lang.js +111 -0
  60. package/dist/builder/outline.vue_vue_type_script_setup_true_lang.js.map +1 -0
  61. package/dist/builder/palette.js +6 -0
  62. package/dist/builder/palette.js.map +1 -0
  63. package/dist/builder/palette.vue_vue_type_script_setup_true_lang.js +96 -0
  64. package/dist/builder/palette.vue_vue_type_script_setup_true_lang.js.map +1 -0
  65. package/dist/builder/preview.js +6 -0
  66. package/dist/builder/preview.js.map +1 -0
  67. package/dist/builder/preview.vue_vue_type_script_setup_true_lang.js +111 -0
  68. package/dist/builder/preview.vue_vue_type_script_setup_true_lang.js.map +1 -0
  69. package/dist/builder/settings-binding.js +57 -0
  70. package/dist/builder/settings-binding.js.map +1 -0
  71. package/dist/builder/settings.js +487 -0
  72. package/dist/builder/settings.js.map +1 -0
  73. package/dist/builder/span-field.js +104 -0
  74. package/dist/builder/span-field.js.map +1 -0
  75. package/dist/code/expression-input.js +6 -0
  76. package/dist/code/expression-input.js.map +1 -0
  77. package/dist/code/expression-input.vue_vue_type_script_setup_true_lang.js +88 -0
  78. package/dist/code/expression-input.vue_vue_type_script_setup_true_lang.js.map +1 -0
  79. package/dist/code/extension.js +202 -0
  80. package/dist/code/extension.js.map +1 -0
  81. package/dist/code/index.js +19 -0
  82. package/dist/code/index.js.map +1 -0
  83. package/dist/code/language.js +43 -0
  84. package/dist/code/language.js.map +1 -0
  85. package/dist/collaboration/index.js +382 -0
  86. package/dist/collaboration/index.js.map +1 -0
  87. package/dist/collaboration/order.js +88 -0
  88. package/dist/collaboration/order.js.map +1 -0
  89. package/dist/core/check.js +314 -0
  90. package/dist/core/check.js.map +1 -0
  91. package/dist/core/document.js +112 -0
  92. package/dist/core/document.js.map +1 -0
  93. package/dist/core/expression.js +854 -0
  94. package/dist/core/expression.js.map +1 -0
  95. package/dist/core/formulas.js +242 -0
  96. package/dist/core/formulas.js.map +1 -0
  97. package/dist/core/rules.js +83 -0
  98. package/dist/core/rules.js.map +1 -0
  99. package/dist/core/values.js +80 -0
  100. package/dist/core/values.js.map +1 -0
  101. package/dist/fields/builtin.js +167 -0
  102. package/dist/fields/builtin.js.map +1 -0
  103. package/dist/fields/components.js +155 -0
  104. package/dist/fields/components.js.map +1 -0
  105. package/dist/fields/define.js +44 -0
  106. package/dist/fields/define.js.map +1 -0
  107. package/dist/fields/format.js +125 -0
  108. package/dist/fields/format.js.map +1 -0
  109. package/dist/fields/options.js +54 -0
  110. package/dist/fields/options.js.map +1 -0
  111. package/dist/index.js +20 -0
  112. package/dist/locale.js +161 -0
  113. package/dist/locale.js.map +1 -0
  114. package/dist/renderer/containers.js +430 -0
  115. package/dist/renderer/containers.js.map +1 -0
  116. package/dist/renderer/context.js +21 -0
  117. package/dist/renderer/context.js.map +1 -0
  118. package/dist/renderer/form-renderer.js +6 -0
  119. package/dist/renderer/form-renderer.js.map +1 -0
  120. package/dist/renderer/form-renderer.vue_vue_type_script_setup_true_lang.js +272 -0
  121. package/dist/renderer/form-renderer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  122. package/dist/renderer/index.js +19 -0
  123. package/dist/renderer/index.js.map +1 -0
  124. package/dist/renderer/node-view.js +385 -0
  125. package/dist/renderer/node-view.js.map +1 -0
  126. package/dist/style.css +1 -0
  127. package/dist/style.d.ts +1 -0
  128. package/dist/style.js +27 -0
  129. package/dist/types/builder/canvas.d.ts +5 -0
  130. package/dist/types/builder/completion.d.ts +35 -0
  131. package/dist/types/builder/condition-editor.vue.d.ts +17 -0
  132. package/dist/types/builder/condition.d.ts +37 -0
  133. package/dist/types/builder/context.d.ts +149 -0
  134. package/dist/types/builder/core/commands.d.ts +74 -0
  135. package/dist/types/builder/core/history.d.ts +23 -0
  136. package/dist/types/builder/core/transaction.d.ts +108 -0
  137. package/dist/types/builder/drag.d.ts +52 -0
  138. package/dist/types/builder/expression-box.d.ts +8 -0
  139. package/dist/types/builder/expression-input.vue.d.ts +29 -0
  140. package/dist/types/builder/extension.d.ts +149 -0
  141. package/dist/types/builder/form-builder.vue.d.ts +24 -0
  142. package/dist/types/builder/index.d.ts +76 -0
  143. package/dist/types/builder/inspector.vue.d.ts +3 -0
  144. package/dist/types/builder/json-dialog.vue.d.ts +10 -0
  145. package/dist/types/builder/locale.d.ts +195 -0
  146. package/dist/types/builder/logic-fields.d.ts +2 -0
  147. package/dist/types/builder/logic.d.ts +57 -0
  148. package/dist/types/builder/menu.d.ts +23 -0
  149. package/dist/types/builder/outline-row.vue.d.ts +19 -0
  150. package/dist/types/builder/outline.vue.d.ts +3 -0
  151. package/dist/types/builder/palette.vue.d.ts +3 -0
  152. package/dist/types/builder/preview.vue.d.ts +3 -0
  153. package/dist/types/builder/settings-binding.d.ts +23 -0
  154. package/dist/types/builder/settings.d.ts +49 -0
  155. package/dist/types/builder/span-field.d.ts +4 -0
  156. package/dist/types/builder/types.d.ts +59 -0
  157. package/dist/types/code/expression-input.vue.d.ts +23 -0
  158. package/dist/types/code/extension.d.ts +45 -0
  159. package/dist/types/code/index.d.ts +12 -0
  160. package/dist/types/code/language.d.ts +6 -0
  161. package/dist/types/collaboration/index.d.ts +64 -0
  162. package/dist/types/collaboration/order.d.ts +20 -0
  163. package/dist/types/core/check.d.ts +68 -0
  164. package/dist/types/core/document.d.ts +36 -0
  165. package/dist/types/core/expression.d.ts +175 -0
  166. package/dist/types/core/formulas.d.ts +44 -0
  167. package/dist/types/core/rules.d.ts +23 -0
  168. package/dist/types/core/types.d.ts +211 -0
  169. package/dist/types/core/values.d.ts +22 -0
  170. package/dist/types/fields/builtin.d.ts +9 -0
  171. package/dist/types/fields/components.d.ts +177 -0
  172. package/dist/types/fields/define.d.ts +93 -0
  173. package/dist/types/fields/format.d.ts +76 -0
  174. package/dist/types/fields/options.d.ts +11 -0
  175. package/dist/types/index.d.ts +3 -0
  176. package/dist/types/locale.d.ts +45 -0
  177. package/dist/types/renderer/containers.d.ts +20 -0
  178. package/dist/types/renderer/context.d.ts +186 -0
  179. package/dist/types/renderer/form-renderer.vue.d.ts +25 -0
  180. package/dist/types/renderer/index.d.ts +81 -0
  181. package/dist/types/renderer/node-view.d.ts +56 -0
  182. package/package.json +88 -4
@@ -0,0 +1,854 @@
1
+ // src/core/expression.ts
2
+ /** Where and why an expression does not parse. `start` and `end` are offsets into the source. */
3
+ var ExpressionError = class extends Error {
4
+ code;
5
+ start;
6
+ end;
7
+ constructor(code, message, start, end = start + 1) {
8
+ super(message);
9
+ this.name = "ExpressionError";
10
+ this.code = code;
11
+ this.start = start;
12
+ this.end = end;
13
+ }
14
+ };
15
+ var MAX_LENGTH = 4e3;
16
+ var MAX_DEPTH = 64;
17
+ var PUNCTUATION = [
18
+ "==",
19
+ "!=",
20
+ "<=",
21
+ ">=",
22
+ "&&",
23
+ "||",
24
+ "(",
25
+ ")",
26
+ "[",
27
+ "]",
28
+ ",",
29
+ ".",
30
+ "?",
31
+ ":",
32
+ "!",
33
+ "<",
34
+ ">",
35
+ "+",
36
+ "-",
37
+ "*",
38
+ "/",
39
+ "%"
40
+ ];
41
+ var isDigit = (char) => char >= "0" && char <= "9";
42
+ var isNameStart = (char) => /[A-Za-z_$À-￿]/.test(char);
43
+ var isNamePart = (char) => isNameStart(char) || isDigit(char);
44
+ var ESCAPES = {
45
+ n: "\n",
46
+ t: " ",
47
+ r: "\r",
48
+ b: "\b",
49
+ f: "\f",
50
+ v: "\v",
51
+ 0: "\0"
52
+ };
53
+ function tokenize(source) {
54
+ const tokens = [];
55
+ let i = 0;
56
+ while (i < source.length) {
57
+ const char = source[i];
58
+ if (/\s/.test(char)) {
59
+ i++;
60
+ continue;
61
+ }
62
+ const start = i;
63
+ if (isDigit(char) || char === "." && isDigit(source[i + 1] ?? "")) {
64
+ const match = /^(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/.exec(source.slice(i));
65
+ i += match[0].length;
66
+ tokens.push({
67
+ type: "number",
68
+ value: Number(match[0]),
69
+ start,
70
+ end: i
71
+ });
72
+ continue;
73
+ }
74
+ if (char === "'" || char === "\"") {
75
+ let value = "";
76
+ i++;
77
+ for (;;) {
78
+ if (i >= source.length) throw new ExpressionError("unterminated-string", "The text is missing its closing quote", start, i);
79
+ const next = source[i];
80
+ if (next === char) {
81
+ i++;
82
+ break;
83
+ }
84
+ if (next === "\\") {
85
+ const escaped = source[i + 1];
86
+ if (escaped === void 0) {
87
+ i++;
88
+ continue;
89
+ }
90
+ if (escaped === "u" && /^[\da-fA-F]{4}$/.test(source.slice(i + 2, i + 6))) {
91
+ value += String.fromCharCode(parseInt(source.slice(i + 2, i + 6), 16));
92
+ i += 6;
93
+ continue;
94
+ }
95
+ value += ESCAPES[escaped] ?? escaped;
96
+ i += 2;
97
+ continue;
98
+ }
99
+ value += next;
100
+ i++;
101
+ }
102
+ tokens.push({
103
+ type: "string",
104
+ value,
105
+ start,
106
+ end: i
107
+ });
108
+ continue;
109
+ }
110
+ if (isNameStart(char)) {
111
+ while (i < source.length && isNamePart(source[i])) i++;
112
+ tokens.push({
113
+ type: "name",
114
+ value: source.slice(start, i),
115
+ start,
116
+ end: i
117
+ });
118
+ continue;
119
+ }
120
+ const punct = PUNCTUATION.find((p) => source.startsWith(p, i));
121
+ if (punct) {
122
+ i += punct.length;
123
+ tokens.push({
124
+ type: "punct",
125
+ value: punct,
126
+ start,
127
+ end: i
128
+ });
129
+ continue;
130
+ }
131
+ if (char === "=") throw new ExpressionError("assignment", "Use == to compare two values", start, start + 1);
132
+ if (char === "&" || char === "|") throw new ExpressionError("unexpected-character", `Use ${char}${char} for ${char === "&" ? "and" : "or"}`, start, start + 1);
133
+ throw new ExpressionError("unexpected-character", `Unexpected character "${char}"`, start, start + 1);
134
+ }
135
+ tokens.push({
136
+ type: "end",
137
+ value: "",
138
+ start: source.length,
139
+ end: source.length
140
+ });
141
+ return tokens;
142
+ }
143
+ var BINARY_LEVELS = [
144
+ ["||"],
145
+ ["&&"],
146
+ ["==", "!="],
147
+ [
148
+ "<",
149
+ "<=",
150
+ ">",
151
+ ">=",
152
+ "in"
153
+ ],
154
+ ["+", "-"],
155
+ [
156
+ "*",
157
+ "/",
158
+ "%"
159
+ ]
160
+ ];
161
+ /** Parse an expression. Throws an `ExpressionError` that says where it went wrong. */
162
+ function parseExpression(source) {
163
+ if (source.length > MAX_LENGTH) throw new ExpressionError("too-long", `An expression may be at most ${MAX_LENGTH} characters`, 0, source.length);
164
+ const tokens = tokenize(source);
165
+ let index = 0;
166
+ let depth = 0;
167
+ const peek = () => tokens[index];
168
+ const next = () => tokens[index++];
169
+ const isPunct = (value) => peek().type === "punct" && peek().value === value;
170
+ const isOperator = (value) => value === "in" ? peek().type === "name" && peek().value === "in" : isPunct(value);
171
+ function fail(token) {
172
+ if (token.type === "end") throw new ExpressionError("unexpected-end", "The expression ends too early", token.start, token.end);
173
+ throw new ExpressionError("unexpected-token", `Unexpected "${source.slice(token.start, token.end)}"`, token.start, token.end);
174
+ }
175
+ function expect(value) {
176
+ if (!isPunct(value)) fail(peek());
177
+ return next();
178
+ }
179
+ function enter(token) {
180
+ if (++depth > MAX_DEPTH) throw new ExpressionError("too-deep", "The expression nests too deeply", token.start, token.end);
181
+ }
182
+ function condition() {
183
+ enter(peek());
184
+ const test = binary(0);
185
+ let result = test;
186
+ if (isPunct("?")) {
187
+ next();
188
+ const whenTrue = condition();
189
+ expect(":");
190
+ const whenFalse = condition();
191
+ result = {
192
+ kind: "condition",
193
+ test,
194
+ whenTrue,
195
+ whenFalse,
196
+ start: test.start,
197
+ end: whenFalse.end
198
+ };
199
+ }
200
+ depth--;
201
+ return result;
202
+ }
203
+ function binary(level) {
204
+ if (level === BINARY_LEVELS.length) return unary();
205
+ let left = binary(level + 1);
206
+ for (;;) {
207
+ const operator = BINARY_LEVELS[level].find(isOperator);
208
+ if (!operator) return left;
209
+ next();
210
+ const right = binary(level + 1);
211
+ left = {
212
+ kind: "binary",
213
+ operator,
214
+ left,
215
+ right,
216
+ start: left.start,
217
+ end: right.end
218
+ };
219
+ }
220
+ }
221
+ function unary() {
222
+ const token = peek();
223
+ if (token.type === "punct" && (token.value === "!" || token.value === "-")) {
224
+ next();
225
+ enter(token);
226
+ const argument = unary();
227
+ depth--;
228
+ return {
229
+ kind: "unary",
230
+ operator: token.value,
231
+ argument,
232
+ start: token.start,
233
+ end: argument.end
234
+ };
235
+ }
236
+ return member();
237
+ }
238
+ function member() {
239
+ let node = primary();
240
+ for (;;) if (isPunct(".")) {
241
+ next();
242
+ const token = next();
243
+ if (token.type !== "name") fail(token);
244
+ node = {
245
+ kind: "member",
246
+ object: node,
247
+ key: {
248
+ kind: "literal",
249
+ value: token.value,
250
+ start: token.start,
251
+ end: token.end
252
+ },
253
+ start: node.start,
254
+ end: token.end
255
+ };
256
+ } else if (isPunct("[")) {
257
+ next();
258
+ const key = condition();
259
+ const close = expect("]");
260
+ node = {
261
+ kind: "member",
262
+ object: node,
263
+ key,
264
+ start: node.start,
265
+ end: close.end
266
+ };
267
+ } else return node;
268
+ }
269
+ function list(open) {
270
+ const closing = open.value === "(" ? ")" : "]";
271
+ const items = [];
272
+ if (isPunct(closing)) return items;
273
+ for (;;) {
274
+ items.push(condition());
275
+ if (!isPunct(",")) return items;
276
+ next();
277
+ if (isPunct(closing)) return items;
278
+ }
279
+ }
280
+ function primary() {
281
+ const token = next();
282
+ switch (token.type) {
283
+ case "number":
284
+ case "string": return {
285
+ kind: "literal",
286
+ value: token.value,
287
+ start: token.start,
288
+ end: token.end
289
+ };
290
+ case "name": {
291
+ const name = token.value;
292
+ if (isPunct("(")) {
293
+ const args = list(next());
294
+ const close = expect(")");
295
+ return {
296
+ kind: "call",
297
+ name,
298
+ args,
299
+ start: token.start,
300
+ end: close.end
301
+ };
302
+ }
303
+ if (name === "true" || name === "false") return {
304
+ kind: "literal",
305
+ value: name === "true",
306
+ start: token.start,
307
+ end: token.end
308
+ };
309
+ if (name === "null") return {
310
+ kind: "literal",
311
+ value: null,
312
+ start: token.start,
313
+ end: token.end
314
+ };
315
+ if (name === "values" || name === "value" || name === "item") return {
316
+ kind: "name",
317
+ name,
318
+ start: token.start,
319
+ end: token.end
320
+ };
321
+ throw new ExpressionError("unknown-name", `Unknown name "${name}"; read fields with values.${name}, or item.${name} inside a list`, token.start, token.end);
322
+ }
323
+ case "punct":
324
+ if (token.value === "(") {
325
+ const inner = condition();
326
+ expect(")");
327
+ return inner;
328
+ }
329
+ if (token.value === "[") {
330
+ const items = list(token);
331
+ const close = expect("]");
332
+ return {
333
+ kind: "list",
334
+ items,
335
+ start: token.start,
336
+ end: close.end
337
+ };
338
+ }
339
+ return fail(token);
340
+ default: return fail(token);
341
+ }
342
+ }
343
+ const root = condition();
344
+ if (peek().type !== "end") fail(peek());
345
+ return root;
346
+ }
347
+ /** Whether a value counts as true: everything but `false`, `null`, `0`, `NaN`, `''` and `[]`. */
348
+ function truthy(value) {
349
+ if (Array.isArray(value)) return value.length > 0;
350
+ return Boolean(value);
351
+ }
352
+ /** Whether a value is empty: `null`, blank text, `[]` or `{}`. */
353
+ function isEmptyValue(value) {
354
+ if (value === null || value === void 0) return true;
355
+ if (typeof value === "string") return value.trim() === "";
356
+ if (Array.isArray(value)) return value.length === 0;
357
+ if (typeof value === "object" && !(value instanceof Date)) return Object.keys(value).length === 0;
358
+ return false;
359
+ }
360
+ var hasOwn = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
361
+ var BLOCKED = /* @__PURE__ */ new Set([
362
+ "__proto__",
363
+ "prototype",
364
+ "constructor"
365
+ ]);
366
+ /** `object[key]`, for the object's own data only; `null` when there is none. */
367
+ function read(object, key) {
368
+ if (object === null || object === void 0) return null;
369
+ if (typeof key === "number" && !Number.isInteger(key)) return null;
370
+ const name = String(key);
371
+ if (BLOCKED.has(name)) return null;
372
+ if (typeof object === "string") {
373
+ if (name === "length") return object.length;
374
+ return /^\d+$/.test(name) ? object[Number(name)] ?? null : null;
375
+ }
376
+ if (typeof object !== "object") return null;
377
+ const value = object[name];
378
+ if (value === void 0 || !hasOwn(object, name)) return null;
379
+ return value;
380
+ }
381
+ /** Equal by value: lists and objects compare their items, `null` equals a missing value. */
382
+ function equalValues(a, b) {
383
+ if (a === void 0) a = null;
384
+ if (b === void 0) b = null;
385
+ if (a === b) return true;
386
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
387
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
388
+ if (Array.isArray(a)) {
389
+ const other = b;
390
+ return a.length === other.length && a.every((item, i) => equalValues(item, other[i]));
391
+ }
392
+ if (a instanceof Date || b instanceof Date) return a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
393
+ const keysA = Object.keys(a);
394
+ const keysB = Object.keys(b);
395
+ return keysA.length === keysB.length && keysA.every((key) => hasOwn(b, key) && equalValues(a[key], b[key]));
396
+ }
397
+ /** A number from a number, numeric text, a boolean or nothing (`0`); `NaN` otherwise. */
398
+ function toNumber(value) {
399
+ if (typeof value === "number") return value;
400
+ if (value === null || value === void 0) return 0;
401
+ if (typeof value === "boolean") return Number(value);
402
+ if (typeof value === "string") return value.trim() === "" ? 0 : Number(value);
403
+ return NaN;
404
+ }
405
+ var finite = (value) => Number.isFinite(value) ? value : null;
406
+ /**
407
+ * A result of arithmetic, rid of the binary rounding noise that decimals
408
+ * pick up (`0.1 * 3` is `0.3`, not `0.30000000000000004`), or `null`
409
+ * when it is not a finite number.
410
+ */
411
+ var tidy = (value) => {
412
+ if (!Number.isFinite(value)) return null;
413
+ return Number.isInteger(value) ? value : Number(value.toPrecision(15));
414
+ };
415
+ /** Nothing, or blank text: what an untouched field holds. */
416
+ var blank = (value) => value === null || value === void 0 || typeof value === "string" && value.trim() === "";
417
+ var text = (value) => value === null || value === void 0 ? "" : String(value);
418
+ /** Text that reads as a number, e.g. what a text input holds. */
419
+ var numericText = (value) => typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value));
420
+ function compare(a, b) {
421
+ if (typeof a === "number" && numericText(b)) b = Number(b);
422
+ else if (typeof b === "number" && numericText(a)) a = Number(a);
423
+ if (typeof a === "number" && typeof b === "number") return Number.isNaN(a - b) ? null : a - b;
424
+ if (typeof a === "string" && typeof b === "string") return a < b ? -1 : a > b ? 1 : 0;
425
+ return null;
426
+ }
427
+ /**
428
+ * `==` in an expression: equal by value, and a number equals text that
429
+ * reads as the same number, as `<` and `*` read it. A text input holding
430
+ * `3` equals `3`.
431
+ */
432
+ function same(a, b) {
433
+ if (typeof a === "number" && numericText(b)) return a === Number(b);
434
+ if (typeof b === "number" && numericText(a)) return Number(a) === b;
435
+ return equalValues(a, b);
436
+ }
437
+ function contains(collection, item) {
438
+ if (Array.isArray(collection)) return collection.some((entry) => same(entry, item));
439
+ if (typeof collection === "string") return item !== null && item !== void 0 && collection.includes(text(item));
440
+ if (collection !== null && typeof collection === "object") {
441
+ const key = text(item);
442
+ return !BLOCKED.has(key) && hasOwn(collection, key);
443
+ }
444
+ return false;
445
+ }
446
+ function arithmetic(operator, a, b) {
447
+ if (operator === "+" && (typeof a === "string" || typeof b === "string") && !(blank(a) && blank(b))) return text(a) + text(b);
448
+ if (operator === "+" || operator === "-" ? blank(a) && blank(b) : blank(a) || blank(b)) return null;
449
+ if (operator === "+") return tidy(toNumber(a) + toNumber(b));
450
+ const x = toNumber(a);
451
+ const y = toNumber(b);
452
+ switch (operator) {
453
+ case "-": return tidy(x - y);
454
+ case "*": return tidy(x * y);
455
+ case "/": return tidy(x / y);
456
+ default: return tidy(x % y);
457
+ }
458
+ }
459
+ var pad = (n) => String(n).padStart(2, "0");
460
+ var formatDay = (date) => `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
461
+ /** A day as a count of days since 1970, read from `YYYY-MM-DD…` text, a `Date` or a timestamp. */
462
+ function dayNumber(value) {
463
+ let date = null;
464
+ if (value instanceof Date) date = value;
465
+ else if (typeof value === "number") date = new Date(value);
466
+ else if (typeof value === "string") {
467
+ const match = /^(\d{4})-(\d{1,2})-(\d{1,2})/.exec(value.trim());
468
+ if (match) return Date.UTC(+match[1], +match[2] - 1, +match[3]) / 864e5;
469
+ return null;
470
+ }
471
+ if (!date || Number.isNaN(date.getTime())) return null;
472
+ return Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / 864e5;
473
+ }
474
+ /** The numbers among the arguments, lists flattened one level. */
475
+ function numbersOf(args) {
476
+ const result = [];
477
+ let list = args;
478
+ if (Array.isArray(args[0]) && args.length === 1) list = args[0];
479
+ else if (Array.isArray(args[0]) && args.length === 2 && typeof args[1] === "string") {
480
+ const key = args[1];
481
+ list = args[0].map((item) => read(item, key));
482
+ }
483
+ for (const arg of list) {
484
+ if (arg === null || arg === void 0 || arg === "") continue;
485
+ const number = toNumber(arg);
486
+ if (!Number.isNaN(number)) result.push(number);
487
+ }
488
+ return result;
489
+ }
490
+ /**
491
+ * The functions every expression can call:
492
+ *
493
+ * - `len(x)`: length of text, number of items or of keys; `0` for nothing.
494
+ * - `empty(x)`: whether `x` is `null`, blank text, `[]` or `{}`.
495
+ * - `number(x)`: `x` as a number, or `null` when it is not one.
496
+ * - `sum(list)`, `min(list)`, `max(list)`: over a list, or over the arguments;
497
+ * `sum(list, 'key')` over one key of every item.
498
+ * - `round(x, digits?)`, `abs(x)`.
499
+ * - `today(offset?)`: today, or `offset` days from today, as `YYYY-MM-DD`.
500
+ * - `days(from, to)`: whole days from one date to the other.
501
+ * - `contains(list or text, x)`: like `x in list`.
502
+ */
503
+ var builtinFunctions = Object.freeze({
504
+ len(value) {
505
+ if (typeof value === "string" || Array.isArray(value)) return value.length;
506
+ if (value !== null && typeof value === "object") return Object.keys(value).length;
507
+ return 0;
508
+ },
509
+ empty: isEmptyValue,
510
+ number(value) {
511
+ if (value === null || value === void 0 || value === "") return null;
512
+ return finite(toNumber(value));
513
+ },
514
+ sum: (...args) => tidy(numbersOf(args).reduce((total, n) => total + n, 0)),
515
+ min(...args) {
516
+ const numbers = numbersOf(args);
517
+ return numbers.length ? Math.min(...numbers) : null;
518
+ },
519
+ max(...args) {
520
+ const numbers = numbersOf(args);
521
+ return numbers.length ? Math.max(...numbers) : null;
522
+ },
523
+ round(value, digits = 0) {
524
+ if (blank(value)) return null;
525
+ const number = toNumber(value);
526
+ const places = Math.max(0, Math.min(10, Math.trunc(toNumber(digits))));
527
+ if (!Number.isFinite(number)) return null;
528
+ const sign = number < 0 ? -1 : 1;
529
+ const shifted = Math.round(Number(`${Math.abs(number)}e${places}`));
530
+ return sign * Number(`${shifted}e-${places}`);
531
+ },
532
+ abs: (value) => blank(value) ? null : finite(Math.abs(toNumber(value))),
533
+ today(offset = 0) {
534
+ const date = /* @__PURE__ */ new Date();
535
+ date.setDate(date.getDate() + Math.trunc(toNumber(offset)));
536
+ return formatDay(date);
537
+ },
538
+ days(from, to) {
539
+ const a = dayNumber(from);
540
+ const b = dayNumber(to);
541
+ return a === null || b === null ? null : Math.round(b - a);
542
+ },
543
+ contains: (collection, item) => contains(collection, item)
544
+ });
545
+ var CallError = class extends Error {};
546
+ function compileNode(node) {
547
+ switch (node.kind) {
548
+ case "literal": {
549
+ const value = node.value;
550
+ return () => value;
551
+ }
552
+ case "list": {
553
+ const items = node.items.map(compileNode);
554
+ return (scope) => items.map((item) => item(scope));
555
+ }
556
+ case "name":
557
+ if (node.name === "values") return (scope) => scope.values ?? {};
558
+ if (node.name === "item") return (scope) => scope.item ?? null;
559
+ return (scope) => scope.value ?? null;
560
+ case "member": {
561
+ const object = compileNode(node.object);
562
+ if (node.key.kind === "literal") {
563
+ const key = node.key.value;
564
+ return (scope) => read(object(scope), key);
565
+ }
566
+ const key = compileNode(node.key);
567
+ return (scope) => read(object(scope), key(scope));
568
+ }
569
+ case "call": {
570
+ const name = node.name;
571
+ const args = node.args.map(compileNode);
572
+ return (scope) => {
573
+ const fn = scope.functions && hasOwn(scope.functions, name) && scope.functions[name] || (hasOwn(builtinFunctions, name) ? builtinFunctions[name] : void 0);
574
+ if (!fn) throw new CallError(`Unknown function "${name}"`);
575
+ const result = fn(...args.map((arg) => arg(scope)));
576
+ return result === void 0 ? null : result;
577
+ };
578
+ }
579
+ case "unary": {
580
+ const argument = compileNode(node.argument);
581
+ return node.operator === "!" ? (scope) => !truthy(argument(scope)) : (scope) => finite(-toNumber(argument(scope)));
582
+ }
583
+ case "condition": {
584
+ const test = compileNode(node.test);
585
+ const whenTrue = compileNode(node.whenTrue);
586
+ const whenFalse = compileNode(node.whenFalse);
587
+ return (scope) => truthy(test(scope)) ? whenTrue(scope) : whenFalse(scope);
588
+ }
589
+ case "binary": {
590
+ const left = compileNode(node.left);
591
+ const right = compileNode(node.right);
592
+ switch (node.operator) {
593
+ case "&&": return (scope) => {
594
+ const value = left(scope);
595
+ return truthy(value) ? right(scope) : value;
596
+ };
597
+ case "||": return (scope) => {
598
+ const value = left(scope);
599
+ return truthy(value) ? value : right(scope);
600
+ };
601
+ case "==": return (scope) => same(left(scope), right(scope));
602
+ case "!=": return (scope) => !same(left(scope), right(scope));
603
+ case "<": return (scope) => (compare(left(scope), right(scope)) ?? NaN) < 0;
604
+ case "<=": return (scope) => (compare(left(scope), right(scope)) ?? NaN) <= 0;
605
+ case ">": return (scope) => (compare(left(scope), right(scope)) ?? NaN) > 0;
606
+ case ">=": return (scope) => (compare(left(scope), right(scope)) ?? NaN) >= 0;
607
+ case "in": return (scope) => contains(right(scope), left(scope));
608
+ default: {
609
+ const operator = node.operator;
610
+ return (scope) => arithmetic(operator, left(scope), right(scope));
611
+ }
612
+ }
613
+ }
614
+ }
615
+ }
616
+ /**
617
+ * The `values` path a member chain reads, e.g. `contact.phone`, or
618
+ * `undefined` when the chain does not start at `values`. A key computed
619
+ * when it runs stops the path there: `values.rows[i].name` reads `rows`,
620
+ * and `values[key]` reads everything (`''`).
621
+ */
622
+ function pathOf(node, root = "values") {
623
+ const keys = [];
624
+ let current = node;
625
+ while (current.kind === "member") {
626
+ const key = current.key;
627
+ if (key.kind === "literal" && key.value !== null) keys.unshift(String(key.value));
628
+ else keys.length = 0;
629
+ current = current.object;
630
+ }
631
+ if (current.kind !== "name" || current.name !== root) return void 0;
632
+ return keys.join(".");
633
+ }
634
+ /** The computed keys along a member chain, e.g. `i` in `values.rows[i]`. */
635
+ function computedKeys(node) {
636
+ const keys = [];
637
+ for (let current = node; current.kind === "member"; current = current.object) if (current.key.kind !== "literal") keys.push(current.key);
638
+ return keys;
639
+ }
640
+ /** A path inside another one adds nothing: `a` covers `a.b`; `''` covers all. */
641
+ function outermost(paths) {
642
+ const sorted = [...paths].sort();
643
+ if (sorted.includes("")) return [""];
644
+ return sorted.filter((path) => !sorted.some((other) => other !== path && path.startsWith(`${other}.`)));
645
+ }
646
+ function analyse(root) {
647
+ const fields = /* @__PURE__ */ new Set();
648
+ const itemFields = /* @__PURE__ */ new Set();
649
+ const calls = /* @__PURE__ */ new Set();
650
+ let readsValue = false;
651
+ function visit(node) {
652
+ switch (node.kind) {
653
+ case "name":
654
+ if (node.name === "value") readsValue = true;
655
+ else if (node.name === "item") itemFields.add("");
656
+ else fields.add("");
657
+ return;
658
+ case "member": {
659
+ const path = pathOf(node);
660
+ if (path !== void 0) {
661
+ fields.add(path);
662
+ computedKeys(node).forEach(visit);
663
+ return;
664
+ }
665
+ const itemPath = pathOf(node, "item");
666
+ if (itemPath !== void 0) {
667
+ itemFields.add(itemPath);
668
+ computedKeys(node).forEach(visit);
669
+ return;
670
+ }
671
+ visit(node.object);
672
+ visit(node.key);
673
+ return;
674
+ }
675
+ case "call":
676
+ calls.add(node.name);
677
+ node.args.forEach(visit);
678
+ return;
679
+ case "list":
680
+ node.items.forEach(visit);
681
+ return;
682
+ case "unary":
683
+ visit(node.argument);
684
+ return;
685
+ case "binary":
686
+ visit(node.left);
687
+ visit(node.right);
688
+ return;
689
+ case "condition":
690
+ visit(node.test);
691
+ visit(node.whenTrue);
692
+ visit(node.whenFalse);
693
+ return;
694
+ }
695
+ }
696
+ visit(root);
697
+ return {
698
+ fields: outermost(fields),
699
+ itemFields: outermost(itemFields),
700
+ calls: [...calls],
701
+ readsValue
702
+ };
703
+ }
704
+ var warned = /* @__PURE__ */ new Set();
705
+ function warnOnce(key, message) {
706
+ if (warned.has(key)) return;
707
+ warned.add(key);
708
+ console.warn(`[@dou-ui/form-builder] ${message}`);
709
+ }
710
+ var compiled = /* @__PURE__ */ new Map();
711
+ var CACHE_SIZE = 2e3;
712
+ /**
713
+ * Parse an expression once and get a function that runs it. Results are
714
+ * cached by source text, so every field with the same expression shares
715
+ * one. Throws an `ExpressionError` when it does not parse.
716
+ */
717
+ function compileExpression(source) {
718
+ let result = compiled.get(source);
719
+ if (!result) {
720
+ try {
721
+ const ast = parseExpression(source);
722
+ const run = compileNode(ast);
723
+ const { fields, itemFields, calls, readsValue } = analyse(ast);
724
+ result = {
725
+ source,
726
+ ast,
727
+ fields,
728
+ itemFields,
729
+ calls,
730
+ readsValue,
731
+ evaluate(scope) {
732
+ try {
733
+ const value = run(scope);
734
+ return value === void 0 ? null : value;
735
+ } catch (error) {
736
+ warnOnce(`${source}\u0000${error.message}`, `form expression \`${source}\` failed: ${error.message}; it gives null`);
737
+ return null;
738
+ }
739
+ }
740
+ };
741
+ } catch (error) {
742
+ if (!(error instanceof ExpressionError)) throw error;
743
+ result = error;
744
+ }
745
+ if (compiled.size >= CACHE_SIZE) compiled.clear();
746
+ compiled.set(source, result);
747
+ }
748
+ if (result instanceof ExpressionError) throw result;
749
+ return result;
750
+ }
751
+ /**
752
+ * Like `compileExpression`, but warns once and gives `undefined` for an
753
+ * expression that does not parse, for code that renders whatever it is given.
754
+ */
755
+ function tryCompileExpression(source) {
756
+ try {
757
+ return compileExpression(source);
758
+ } catch (error) {
759
+ const { message, start } = error;
760
+ warnOnce(`parse\u0000${source}`, `form expression \`${source}\` does not parse: ${message} (at ${start}); it is ignored`);
761
+ return;
762
+ }
763
+ }
764
+ /** Parse and run an expression in one go. */
765
+ function evaluateExpression(source, scope) {
766
+ return compileExpression(source).evaluate(scope);
767
+ }
768
+ function checkExpression(source, options = {}) {
769
+ let ast;
770
+ try {
771
+ ast = compileExpression(source).ast;
772
+ } catch (error) {
773
+ const { code, message, start, end } = error;
774
+ return [{
775
+ code,
776
+ message,
777
+ text: source.slice(start, end),
778
+ start,
779
+ end
780
+ }];
781
+ }
782
+ const problems = [];
783
+ const known = options.fields;
784
+ const functions = /* @__PURE__ */ new Set([...Object.keys(builtinFunctions), ...options.functions ?? []]);
785
+ function visit(node) {
786
+ if (node.kind === "member") {
787
+ const path = pathOf(node);
788
+ if (path !== void 0) {
789
+ if (path !== "" && known && !known.some((name) => related(name, path))) problems.push({
790
+ code: "unknown-field",
791
+ message: `There is no field "${path}"`,
792
+ text: path,
793
+ start: node.start,
794
+ end: node.end
795
+ });
796
+ computedKeys(node).forEach(visit);
797
+ return;
798
+ }
799
+ const itemPath = pathOf(node, "item");
800
+ if (itemPath !== void 0) {
801
+ const own = options.itemFields;
802
+ if (itemPath !== "" && own && !own.some((name) => related(name, itemPath))) problems.push({
803
+ code: "unknown-field",
804
+ message: `There is no field "item.${itemPath}"`,
805
+ text: `item.${itemPath}`,
806
+ start: node.start,
807
+ end: node.end
808
+ });
809
+ computedKeys(node).forEach(visit);
810
+ return;
811
+ }
812
+ visit(node.object);
813
+ visit(node.key);
814
+ return;
815
+ }
816
+ if (node.kind === "call") {
817
+ if (!functions.has(node.name)) problems.push({
818
+ code: "unknown-function",
819
+ message: `There is no function "${node.name}"`,
820
+ text: node.name,
821
+ start: node.start,
822
+ end: node.start + node.name.length
823
+ });
824
+ node.args.forEach(visit);
825
+ return;
826
+ }
827
+ if (node.kind === "name" && node.name === "item" && options.itemFields?.length === 0) problems.push({
828
+ code: "unknown-field",
829
+ message: "There is no list item here to read",
830
+ text: "item",
831
+ start: node.start,
832
+ end: node.end
833
+ });
834
+ if (node.kind === "list") node.items.forEach(visit);
835
+ else if (node.kind === "unary") visit(node.argument);
836
+ else if (node.kind === "binary") {
837
+ visit(node.left);
838
+ visit(node.right);
839
+ } else if (node.kind === "condition") {
840
+ visit(node.test);
841
+ visit(node.whenTrue);
842
+ visit(node.whenFalse);
843
+ }
844
+ }
845
+ visit(ast);
846
+ return problems;
847
+ }
848
+ /** Whether a read of `path` can see the field `name`: the same path, or one inside the other. */
849
+ function related(name, path) {
850
+ return path === "" || name === path || name.startsWith(`${path}.`) || path.startsWith(`${name}.`);
851
+ }
852
+ export { ExpressionError, builtinFunctions, checkExpression, compileExpression, computedKeys, equalValues, evaluateExpression, isEmptyValue, parseExpression, pathOf, related, truthy, tryCompileExpression };
853
+
854
+ //# sourceMappingURL=expression.js.map