@dou-ui/form-builder 0.0.0-stage → 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 @@
1
+ {"version":3,"file":"expression.js","names":[],"sources":["../../src/core/expression.ts"],"sourcesContent":["/**\n * A small expression language for form logic: show a field when another\n * has some value, compute a total, check one date against another.\n *\n * Expressions come from stored form data, so they are parsed and run by\n * this module and never handed to `eval` or `new Function`. They can read\n * `values` (the whole form) and `value` (the field's own value), call the\n * built-in functions and the ones the app registers, and nothing else.\n *\n * values.type == 'laptop' && values.price > 5000\n * round(values.price * values.count, 2)\n * values.city in ['sh', 'bj'] ? 'east' : 'other'\n */\n\nexport type ExpressionErrorCode =\n | 'unexpected-character'\n | 'unterminated-string'\n | 'unexpected-token'\n | 'unexpected-end'\n | 'unknown-name'\n | 'assignment'\n | 'too-deep'\n | 'too-long'\n\n/** Where and why an expression does not parse. `start` and `end` are offsets into the source. */\nexport class ExpressionError extends Error {\n readonly code: ExpressionErrorCode\n readonly start: number\n readonly end: number\n\n constructor(code: ExpressionErrorCode, message: string, start: number, end = start + 1) {\n super(message)\n this.name = 'ExpressionError'\n this.code = code\n this.start = start\n this.end = end\n }\n}\n\n/** The syntax tree of an expression. Each node keeps its place in the source. */\nexport type ExpressionNode =\n | { kind: 'literal'; value: string | number | boolean | null; start: number; end: number }\n | { kind: 'list'; items: ExpressionNode[]; start: number; end: number }\n | { kind: 'name'; name: 'values' | 'value' | 'item'; start: number; end: number }\n | { kind: 'member'; object: ExpressionNode; key: ExpressionNode; start: number; end: number }\n | { kind: 'call'; name: string; args: ExpressionNode[]; start: number; end: number }\n | { kind: 'unary'; operator: '!' | '-'; argument: ExpressionNode; start: number; end: number }\n | {\n kind: 'binary'\n operator: BinaryOperator\n left: ExpressionNode\n right: ExpressionNode\n start: number\n end: number\n }\n | {\n kind: 'condition'\n test: ExpressionNode\n whenTrue: ExpressionNode\n whenFalse: ExpressionNode\n start: number\n end: number\n }\n\nexport type BinaryOperator =\n '||' | '&&' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'in' | '+' | '-' | '*' | '/' | '%'\n\n/** A function expressions can call. It gets the values of its arguments and should not change them. */\nexport type FormFunction = (...args: any[]) => unknown\n\n/** What an expression reads when it runs. */\nexport interface ExpressionScope {\n /** The whole form. Defaults to an empty one. */\n values?: Record<string, unknown>\n /** The value of the field the expression belongs to. */\n value?: unknown\n /** Inside a list, the item the field belongs to. */\n item?: unknown\n /** Functions besides the built-in ones; one of the same name replaces the built-in function. */\n functions?: Readonly<Record<string, FormFunction>>\n}\n\nexport interface CompiledExpression {\n readonly source: string\n readonly ast: ExpressionNode\n /**\n * The paths under `values` the expression reads, e.g. `['price', 'contact.phone']`.\n * An empty string means it reads a key that is only known when it runs,\n * so it may read anything.\n */\n readonly fields: readonly string[]\n /** The paths under `item` it reads, like `fields` for `values`. */\n readonly itemFields: readonly string[]\n /** Whether it reads `value`. */\n readonly readsValue: boolean\n /** The functions it calls. */\n readonly calls: readonly string[]\n /** Run it. A function that throws makes the result `null`. */\n evaluate: (scope: ExpressionScope) => unknown\n}\n\nconst MAX_LENGTH = 4000\nconst MAX_DEPTH = 64\n\n// ---------------------------------------------------------------------------\n// Tokens\n\ntype Token =\n | { type: 'number'; value: number; start: number; end: number }\n | { type: 'string'; value: string; start: number; end: number }\n | { type: 'name'; value: string; start: number; end: number }\n | { type: 'punct'; value: string; start: number; end: number }\n | { type: 'end'; value: ''; start: number; end: number }\n\nconst PUNCTUATION = [\n '==',\n '!=',\n '<=',\n '>=',\n '&&',\n '||',\n '(',\n ')',\n '[',\n ']',\n ',',\n '.',\n '?',\n ':',\n '!',\n '<',\n '>',\n '+',\n '-',\n '*',\n '/',\n '%',\n]\n\nconst isDigit = (char: string) => char >= '0' && char <= '9'\nconst isNameStart = (char: string) => /[A-Za-z_$À-￿]/.test(char)\nconst isNamePart = (char: string) => isNameStart(char) || isDigit(char)\n\nconst ESCAPES: Record<string, string> = {\n n: '\\n',\n t: '\\t',\n r: '\\r',\n b: '\\b',\n f: '\\f',\n v: '\\v',\n 0: '\\0',\n}\n\nfunction tokenize(source: string): Token[] {\n const tokens: Token[] = []\n let i = 0\n while (i < source.length) {\n const char = source[i]!\n if (/\\s/.test(char)) {\n i++\n continue\n }\n const start = i\n if (isDigit(char) || (char === '.' && isDigit(source[i + 1] ?? ''))) {\n const match = /^(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?/.exec(source.slice(i))!\n i += match[0].length\n tokens.push({ type: 'number', value: Number(match[0]), start, end: i })\n continue\n }\n if (char === \"'\" || char === '\"') {\n let value = ''\n i++\n for (;;) {\n if (i >= source.length)\n throw new ExpressionError(\n 'unterminated-string',\n 'The text is missing its closing quote',\n start,\n i,\n )\n const next = source[i]!\n if (next === char) {\n i++\n break\n }\n if (next === '\\\\') {\n const escaped = source[i + 1]\n if (escaped === undefined) {\n i++\n continue\n }\n if (escaped === 'u' && /^[\\da-fA-F]{4}$/.test(source.slice(i + 2, i + 6))) {\n value += String.fromCharCode(parseInt(source.slice(i + 2, i + 6), 16))\n i += 6\n continue\n }\n value += ESCAPES[escaped] ?? escaped\n i += 2\n continue\n }\n value += next\n i++\n }\n tokens.push({ type: 'string', value, start, end: i })\n continue\n }\n if (isNameStart(char)) {\n while (i < source.length && isNamePart(source[i]!)) i++\n tokens.push({ type: 'name', value: source.slice(start, i), start, end: i })\n continue\n }\n const punct = PUNCTUATION.find((p) => source.startsWith(p, i))\n if (punct) {\n i += punct.length\n tokens.push({ type: 'punct', value: punct, start, end: i })\n continue\n }\n if (char === '=')\n throw new ExpressionError('assignment', 'Use == to compare two values', start, start + 1)\n if (char === '&' || char === '|')\n throw new ExpressionError(\n 'unexpected-character',\n `Use ${char}${char} for ${char === '&' ? 'and' : 'or'}`,\n start,\n start + 1,\n )\n throw new ExpressionError(\n 'unexpected-character',\n `Unexpected character \"${char}\"`,\n start,\n start + 1,\n )\n }\n tokens.push({ type: 'end', value: '', start: source.length, end: source.length })\n return tokens\n}\n\n// ---------------------------------------------------------------------------\n// Parser: precedence climbs from `?:` to member access.\n\nconst BINARY_LEVELS: BinaryOperator[][] = [\n ['||'],\n ['&&'],\n ['==', '!='],\n ['<', '<=', '>', '>=', 'in'],\n ['+', '-'],\n ['*', '/', '%'],\n]\n\n/** Parse an expression. Throws an `ExpressionError` that says where it went wrong. */\nexport function parseExpression(source: string): ExpressionNode {\n if (source.length > MAX_LENGTH)\n throw new ExpressionError(\n 'too-long',\n `An expression may be at most ${MAX_LENGTH} characters`,\n 0,\n source.length,\n )\n const tokens = tokenize(source)\n let index = 0\n let depth = 0\n\n const peek = () => tokens[index]!\n const next = () => tokens[index++]!\n const isPunct = (value: string) => peek().type === 'punct' && peek().value === value\n const isOperator = (value: string) =>\n value === 'in' ? peek().type === 'name' && peek().value === 'in' : isPunct(value)\n\n function fail(token: Token): never {\n if (token.type === 'end')\n throw new ExpressionError(\n 'unexpected-end',\n 'The expression ends too early',\n token.start,\n token.end,\n )\n throw new ExpressionError(\n 'unexpected-token',\n `Unexpected \"${source.slice(token.start, token.end)}\"`,\n token.start,\n token.end,\n )\n }\n\n function expect(value: string) {\n if (!isPunct(value)) fail(peek())\n return next()\n }\n\n function enter(token: Token) {\n if (++depth > MAX_DEPTH)\n throw new ExpressionError(\n 'too-deep',\n 'The expression nests too deeply',\n token.start,\n token.end,\n )\n }\n\n function condition(): ExpressionNode {\n enter(peek())\n const test = binary(0)\n let result = test\n if (isPunct('?')) {\n next()\n const whenTrue = condition()\n expect(':')\n const whenFalse = condition()\n result = {\n kind: 'condition',\n test,\n whenTrue,\n whenFalse,\n start: test.start,\n end: whenFalse.end,\n }\n }\n depth--\n return result\n }\n\n function binary(level: number): ExpressionNode {\n if (level === BINARY_LEVELS.length) return unary()\n let left = binary(level + 1)\n for (;;) {\n const operator = BINARY_LEVELS[level]!.find(isOperator)\n if (!operator) return left\n next()\n const right = binary(level + 1)\n left = { kind: 'binary', operator, left, right, start: left.start, end: right.end }\n }\n }\n\n function unary(): ExpressionNode {\n const token = peek()\n if (token.type === 'punct' && (token.value === '!' || token.value === '-')) {\n next()\n enter(token)\n const argument = unary()\n depth--\n return {\n kind: 'unary',\n operator: token.value,\n argument,\n start: token.start,\n end: argument.end,\n }\n }\n return member()\n }\n\n function member(): ExpressionNode {\n let node = primary()\n for (;;) {\n if (isPunct('.')) {\n next()\n const token = next()\n if (token.type !== 'name') fail(token)\n node = {\n kind: 'member',\n object: node,\n key: { kind: 'literal', value: token.value, start: token.start, end: token.end },\n start: node.start,\n end: token.end,\n }\n } else if (isPunct('[')) {\n next()\n const key = condition()\n const close = expect(']')\n node = { kind: 'member', object: node, key, start: node.start, end: close.end }\n } else return node\n }\n }\n\n function list(open: Token): ExpressionNode[] {\n const closing = open.value === '(' ? ')' : ']'\n const items: ExpressionNode[] = []\n if (isPunct(closing)) return items\n for (;;) {\n items.push(condition())\n if (!isPunct(',')) return items\n next()\n if (isPunct(closing)) return items\n }\n }\n\n function primary(): ExpressionNode {\n const token = next()\n switch (token.type) {\n case 'number':\n case 'string':\n return { kind: 'literal', value: token.value, start: token.start, end: token.end }\n case 'name': {\n const name = token.value\n if (isPunct('(')) {\n const open = next()\n const args = list(open)\n const close = expect(')')\n return { kind: 'call', name, args, start: token.start, end: close.end }\n }\n if (name === 'true' || name === 'false')\n return { kind: 'literal', value: name === 'true', start: token.start, end: token.end }\n if (name === 'null')\n return { kind: 'literal', value: null, start: token.start, end: token.end }\n if (name === 'values' || name === 'value' || name === 'item')\n return { kind: 'name', name, start: token.start, end: token.end }\n throw new ExpressionError(\n 'unknown-name',\n `Unknown name \"${name}\"; read fields with values.${name}, or item.${name} inside a list`,\n token.start,\n token.end,\n )\n }\n case 'punct':\n if (token.value === '(') {\n const inner = condition()\n expect(')')\n return inner\n }\n if (token.value === '[') {\n const items = list(token)\n const close = expect(']')\n return { kind: 'list', items, start: token.start, end: close.end }\n }\n return fail(token)\n default:\n return fail(token)\n }\n }\n\n const root = condition()\n if (peek().type !== 'end') fail(peek())\n return root\n}\n\n// ---------------------------------------------------------------------------\n// Values\n\n/** Whether a value counts as true: everything but `false`, `null`, `0`, `NaN`, `''` and `[]`. */\nexport function truthy(value: unknown): boolean {\n if (Array.isArray(value)) return value.length > 0\n return Boolean(value)\n}\n\n/** Whether a value is empty: `null`, blank text, `[]` or `{}`. */\nexport function isEmptyValue(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'string') return value.trim() === ''\n if (Array.isArray(value)) return value.length === 0\n if (typeof value === 'object' && !(value instanceof Date)) return Object.keys(value).length === 0\n return false\n}\n\nconst hasOwn = (object: object, key: string) => Object.prototype.hasOwnProperty.call(object, key)\nconst BLOCKED = new Set(['__proto__', 'prototype', 'constructor'])\n\n/** `object[key]`, for the object's own data only; `null` when there is none. */\nfunction read(object: unknown, key: unknown): unknown {\n if (object === null || object === undefined) return null\n if (typeof key === 'number' && !Number.isInteger(key)) return null\n const name = String(key)\n if (BLOCKED.has(name)) return null\n if (typeof object === 'string') {\n if (name === 'length') return object.length\n return /^\\d+$/.test(name) ? (object[Number(name)] ?? null) : null\n }\n if (typeof object !== 'object') return null\n // Read before checking, so a reactive object tracks the key even while it is missing.\n const value = (object as Record<string, unknown>)[name]\n if (value === undefined || !hasOwn(object, name)) return null\n return value\n}\n\n/** Equal by value: lists and objects compare their items, `null` equals a missing value. */\nexport function equalValues(a: unknown, b: unknown): boolean {\n if (a === undefined) a = null\n if (b === undefined) b = null\n if (a === b) return true\n if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false\n if (Array.isArray(a) !== Array.isArray(b)) return false\n if (Array.isArray(a)) {\n const other = b as unknown[]\n return a.length === other.length && a.every((item, i) => equalValues(item, other[i]))\n }\n if (a instanceof Date || b instanceof Date)\n return a instanceof Date && b instanceof Date && a.getTime() === b.getTime()\n const keysA = Object.keys(a)\n const keysB = Object.keys(b)\n return (\n keysA.length === keysB.length &&\n keysA.every((key) => hasOwn(b, key) && equalValues((a as any)[key], (b as any)[key]))\n )\n}\n\n/** A number from a number, numeric text, a boolean or nothing (`0`); `NaN` otherwise. */\nfunction toNumber(value: unknown): number {\n if (typeof value === 'number') return value\n if (value === null || value === undefined) return 0\n if (typeof value === 'boolean') return Number(value)\n if (typeof value === 'string') return value.trim() === '' ? 0 : Number(value)\n return Number.NaN\n}\n\nconst finite = (value: number) => (Number.isFinite(value) ? value : null)\n\n/**\n * A result of arithmetic, rid of the binary rounding noise that decimals\n * pick up (`0.1 * 3` is `0.3`, not `0.30000000000000004`), or `null`\n * when it is not a finite number.\n */\nconst tidy = (value: number) => {\n if (!Number.isFinite(value)) return null\n return Number.isInteger(value) ? value : Number(value.toPrecision(15))\n}\n\n/** Nothing, or blank text: what an untouched field holds. */\nconst blank = (value: unknown) =>\n value === null || value === undefined || (typeof value === 'string' && value.trim() === '')\nconst text = (value: unknown) => (value === null || value === undefined ? '' : String(value))\n\n/** Text that reads as a number, e.g. what a text input holds. */\nconst numericText = (value: unknown) =>\n typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))\n\nfunction compare(a: unknown, b: unknown): number | null {\n if (typeof a === 'number' && numericText(b)) b = Number(b)\n else if (typeof b === 'number' && numericText(a)) a = Number(a)\n if (typeof a === 'number' && typeof b === 'number') return Number.isNaN(a - b) ? null : a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n return null\n}\n\n/**\n * `==` in an expression: equal by value, and a number equals text that\n * reads as the same number, as `<` and `*` read it. A text input holding\n * `3` equals `3`.\n */\nfunction same(a: unknown, b: unknown): boolean {\n if (typeof a === 'number' && numericText(b)) return a === Number(b)\n if (typeof b === 'number' && numericText(a)) return Number(a) === b\n return equalValues(a, b)\n}\n\nfunction contains(collection: unknown, item: unknown): boolean {\n if (Array.isArray(collection)) return collection.some((entry) => same(entry, item))\n if (typeof collection === 'string')\n return item !== null && item !== undefined && collection.includes(text(item))\n if (collection !== null && typeof collection === 'object') {\n const key = text(item)\n return !BLOCKED.has(key) && hasOwn(collection, key)\n }\n return false\n}\n\nfunction arithmetic(operator: BinaryOperator, a: unknown, b: unknown): unknown {\n if (\n operator === '+' &&\n (typeof a === 'string' || typeof b === 'string') &&\n !(blank(a) && blank(b))\n )\n return text(a) + text(b)\n // A formula over fields not filled in yet stays empty instead of showing\n // 0: a product with an unknown factor is unknown, and a sum is only\n // unknown when every term is. One empty term of a sum counts as 0.\n if (operator === '+' || operator === '-' ? blank(a) && blank(b) : blank(a) || blank(b))\n return null\n if (operator === '+') return tidy(toNumber(a) + toNumber(b))\n const x = toNumber(a)\n const y = toNumber(b)\n switch (operator) {\n case '-':\n return tidy(x - y)\n case '*':\n return tidy(x * y)\n case '/':\n return tidy(x / y)\n default:\n return tidy(x % y)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Built-in functions\n\nconst pad = (n: number) => String(n).padStart(2, '0')\nconst formatDay = (date: Date) =>\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`\n\n/** A day as a count of days since 1970, read from `YYYY-MM-DD…` text, a `Date` or a timestamp. */\nfunction dayNumber(value: unknown): number | null {\n let date: Date | null = null\n if (value instanceof Date) date = value\n else if (typeof value === 'number') date = new Date(value)\n else if (typeof value === 'string') {\n const match = /^(\\d{4})-(\\d{1,2})-(\\d{1,2})/.exec(value.trim())\n if (match) return Date.UTC(+match[1]!, +match[2]! - 1, +match[3]!) / 86_400_000\n return null\n }\n if (!date || Number.isNaN(date.getTime())) return null\n return Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / 86_400_000\n}\n\n/** The numbers among the arguments, lists flattened one level. */\nfunction numbersOf(args: unknown[]): number[] {\n const result: number[] = []\n let list: unknown[] = args\n if (Array.isArray(args[0]) && args.length === 1) list = args[0]\n // `sum(values.items, 'amount')`: one key of every item of a list.\n else if (Array.isArray(args[0]) && args.length === 2 && typeof args[1] === 'string') {\n const key = args[1]\n list = args[0].map((item) => read(item, key))\n }\n for (const arg of list) {\n if (arg === null || arg === undefined || arg === '') continue\n const number = toNumber(arg)\n if (!Number.isNaN(number)) result.push(number)\n }\n return result\n}\n\n/**\n * The functions every expression can call:\n *\n * - `len(x)`: length of text, number of items or of keys; `0` for nothing.\n * - `empty(x)`: whether `x` is `null`, blank text, `[]` or `{}`.\n * - `number(x)`: `x` as a number, or `null` when it is not one.\n * - `sum(list)`, `min(list)`, `max(list)`: over a list, or over the arguments;\n * `sum(list, 'key')` over one key of every item.\n * - `round(x, digits?)`, `abs(x)`.\n * - `today(offset?)`: today, or `offset` days from today, as `YYYY-MM-DD`.\n * - `days(from, to)`: whole days from one date to the other.\n * - `contains(list or text, x)`: like `x in list`.\n */\nexport const builtinFunctions: Readonly<Record<string, FormFunction>> = Object.freeze({\n len(value: unknown) {\n if (typeof value === 'string' || Array.isArray(value)) return value.length\n if (value !== null && typeof value === 'object') return Object.keys(value).length\n return 0\n },\n empty: isEmptyValue,\n number(value: unknown) {\n if (value === null || value === undefined || value === '') return null\n return finite(toNumber(value))\n },\n sum: (...args: unknown[]) => tidy(numbersOf(args).reduce((total, n) => total + n, 0)),\n min(...args: unknown[]) {\n const numbers = numbersOf(args)\n return numbers.length ? Math.min(...numbers) : null\n },\n max(...args: unknown[]) {\n const numbers = numbersOf(args)\n return numbers.length ? Math.max(...numbers) : null\n },\n round(value: unknown, digits: unknown = 0) {\n if (blank(value)) return null\n const number = toNumber(value)\n const places = Math.max(0, Math.min(10, Math.trunc(toNumber(digits))))\n if (!Number.isFinite(number)) return null\n const sign = number < 0 ? -1 : 1\n // Shift by exponent text so 1.005 rounds to 1.01 like it reads.\n const shifted = Math.round(Number(`${Math.abs(number)}e${places}`))\n return sign * Number(`${shifted}e-${places}`)\n },\n abs: (value: unknown) => (blank(value) ? null : finite(Math.abs(toNumber(value)))),\n today(offset: unknown = 0) {\n const date = new Date()\n date.setDate(date.getDate() + Math.trunc(toNumber(offset)))\n return formatDay(date)\n },\n days(from: unknown, to: unknown) {\n const a = dayNumber(from)\n const b = dayNumber(to)\n return a === null || b === null ? null : Math.round(b - a)\n },\n contains: (collection: unknown, item: unknown) => contains(collection, item),\n})\n\n// ---------------------------------------------------------------------------\n// Compiling: the tree becomes nested closures, so running an expression\n// walks no tree and allocates little.\n\ntype Run = (scope: ExpressionScope) => unknown\n\nclass CallError extends Error {}\n\nfunction compileNode(node: ExpressionNode): Run {\n switch (node.kind) {\n case 'literal': {\n const value = node.value\n return () => value\n }\n case 'list': {\n const items = node.items.map(compileNode)\n return (scope) => items.map((item) => item(scope))\n }\n case 'name':\n if (node.name === 'values') return (scope) => scope.values ?? {}\n if (node.name === 'item') return (scope) => scope.item ?? null\n return (scope) => scope.value ?? null\n case 'member': {\n const object = compileNode(node.object)\n if (node.key.kind === 'literal') {\n const key = node.key.value\n return (scope) => read(object(scope), key)\n }\n const key = compileNode(node.key)\n return (scope) => read(object(scope), key(scope))\n }\n case 'call': {\n const name = node.name\n const args = node.args.map(compileNode)\n return (scope) => {\n const fn =\n (scope.functions && hasOwn(scope.functions, name) && scope.functions[name]) ||\n (hasOwn(builtinFunctions, name) ? builtinFunctions[name] : undefined)\n if (!fn) throw new CallError(`Unknown function \"${name}\"`)\n const result = fn(...args.map((arg) => arg(scope)))\n return result === undefined ? null : result\n }\n }\n case 'unary': {\n const argument = compileNode(node.argument)\n return node.operator === '!'\n ? (scope) => !truthy(argument(scope))\n : (scope) => finite(-toNumber(argument(scope)))\n }\n case 'condition': {\n const test = compileNode(node.test)\n const whenTrue = compileNode(node.whenTrue)\n const whenFalse = compileNode(node.whenFalse)\n return (scope) => (truthy(test(scope)) ? whenTrue(scope) : whenFalse(scope))\n }\n case 'binary': {\n const left = compileNode(node.left)\n const right = compileNode(node.right)\n switch (node.operator) {\n case '&&':\n return (scope) => {\n const value = left(scope)\n return truthy(value) ? right(scope) : value\n }\n case '||':\n return (scope) => {\n const value = left(scope)\n return truthy(value) ? value : right(scope)\n }\n case '==':\n return (scope) => same(left(scope), right(scope))\n case '!=':\n return (scope) => !same(left(scope), right(scope))\n case '<':\n return (scope) => (compare(left(scope), right(scope)) ?? Number.NaN) < 0\n case '<=':\n return (scope) => (compare(left(scope), right(scope)) ?? Number.NaN) <= 0\n case '>':\n return (scope) => (compare(left(scope), right(scope)) ?? Number.NaN) > 0\n case '>=':\n return (scope) => (compare(left(scope), right(scope)) ?? Number.NaN) >= 0\n case 'in':\n return (scope) => contains(right(scope), left(scope))\n default: {\n const operator = node.operator\n return (scope) => arithmetic(operator, left(scope), right(scope))\n }\n }\n }\n }\n}\n\n/**\n * The `values` path a member chain reads, e.g. `contact.phone`, or\n * `undefined` when the chain does not start at `values`. A key computed\n * when it runs stops the path there: `values.rows[i].name` reads `rows`,\n * and `values[key]` reads everything (`''`).\n */\nexport function pathOf(\n node: ExpressionNode,\n root: 'values' | 'item' = 'values',\n): string | undefined {\n const keys: string[] = []\n let current = node\n while (current.kind === 'member') {\n const key = current.key\n if (key.kind === 'literal' && key.value !== null) keys.unshift(String(key.value))\n else keys.length = 0\n current = current.object\n }\n if (current.kind !== 'name' || current.name !== root) return undefined\n return keys.join('.')\n}\n\n/** The computed keys along a member chain, e.g. `i` in `values.rows[i]`. */\nexport function computedKeys(node: ExpressionNode): ExpressionNode[] {\n const keys: ExpressionNode[] = []\n for (let current = node; current.kind === 'member'; current = current.object)\n if (current.key.kind !== 'literal') keys.push(current.key)\n return keys\n}\n\n/** A path inside another one adds nothing: `a` covers `a.b`; `''` covers all. */\nfunction outermost(paths: Set<string>) {\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts a fresh array\n const sorted = [...paths].sort()\n if (sorted.includes('')) return ['']\n return sorted.filter(\n (path) => !sorted.some((other) => other !== path && path.startsWith(`${other}.`)),\n )\n}\n\nfunction analyse(root: ExpressionNode) {\n const fields = new Set<string>()\n const itemFields = new Set<string>()\n const calls = new Set<string>()\n let readsValue = false\n\n function visit(node: ExpressionNode) {\n switch (node.kind) {\n case 'name':\n if (node.name === 'value') readsValue = true\n else if (node.name === 'item') itemFields.add('')\n else fields.add('')\n return\n case 'member': {\n const path = pathOf(node)\n if (path !== undefined) {\n fields.add(path)\n computedKeys(node).forEach(visit)\n return\n }\n const itemPath = pathOf(node, 'item')\n if (itemPath !== undefined) {\n itemFields.add(itemPath)\n computedKeys(node).forEach(visit)\n return\n }\n visit(node.object)\n visit(node.key)\n return\n }\n case 'call':\n calls.add(node.name)\n node.args.forEach(visit)\n return\n case 'list':\n node.items.forEach(visit)\n return\n case 'unary':\n visit(node.argument)\n return\n case 'binary':\n visit(node.left)\n visit(node.right)\n return\n case 'condition':\n visit(node.test)\n visit(node.whenTrue)\n visit(node.whenFalse)\n return\n case 'literal':\n }\n }\n visit(root)\n return {\n fields: outermost(fields),\n itemFields: outermost(itemFields),\n calls: [...calls],\n readsValue,\n }\n}\n\nconst warned = new Set<string>()\nfunction warnOnce(key: string, message: string) {\n if (warned.has(key)) return\n warned.add(key)\n console.warn(`[@dou-ui/form-builder] ${message}`)\n}\n\nconst compiled = new Map<string, CompiledExpression | ExpressionError>()\nconst CACHE_SIZE = 2000\n\n/**\n * Parse an expression once and get a function that runs it. Results are\n * cached by source text, so every field with the same expression shares\n * one. Throws an `ExpressionError` when it does not parse.\n */\nexport function compileExpression(source: string): CompiledExpression {\n let result = compiled.get(source)\n if (!result) {\n try {\n const ast = parseExpression(source)\n const run = compileNode(ast)\n const { fields, itemFields, calls, readsValue } = analyse(ast)\n result = {\n source,\n ast,\n fields,\n itemFields,\n calls,\n readsValue,\n evaluate(scope) {\n try {\n const value = run(scope)\n return value === undefined ? null : value\n } catch (error) {\n warnOnce(\n `${source}\\u0000${(error as Error).message}`,\n `form expression \\`${source}\\` failed: ${(error as Error).message}; it gives null`,\n )\n return null\n }\n },\n }\n } catch (error) {\n if (!(error instanceof ExpressionError)) throw error\n result = error\n }\n if (compiled.size >= CACHE_SIZE) compiled.clear()\n compiled.set(source, result)\n }\n if (result instanceof ExpressionError) throw result\n return result\n}\n\n/**\n * Like `compileExpression`, but warns once and gives `undefined` for an\n * expression that does not parse, for code that renders whatever it is given.\n */\nexport function tryCompileExpression(source: string): CompiledExpression | undefined {\n try {\n return compileExpression(source)\n } catch (error) {\n const { message, start } = error as ExpressionError\n warnOnce(\n `parse\\u0000${source}`,\n `form expression \\`${source}\\` does not parse: ${message} (at ${start}); it is ignored`,\n )\n return undefined\n }\n}\n\n/** Parse and run an expression in one go. */\nexport function evaluateExpression(source: string, scope: ExpressionScope): unknown {\n return compileExpression(source).evaluate(scope)\n}\n\nexport interface ExpressionProblem {\n /** What is wrong; an editor shows its own words for each code. */\n code: ExpressionErrorCode | 'unknown-field' | 'unknown-function'\n /** An English description, for logs. */\n message: string\n /** The source text at fault, e.g. the field path or function name, for the editor's message. */\n text: string\n start: number\n end: number\n}\n\n/**\n * The problems of an expression, for an editor to point at: a syntax\n * error, or a field or function that does not exist. `fields` are the\n * names of the form's fields; a path inside one (`contact.phone` for a\n * field `contact`) or around one (`contact` for `contact.phone`) counts.\n */\nexport interface CheckExpressionOptions {\n /** The fields `values` can read; reading another is a problem. */\n fields?: readonly string[]\n /** The fields of a list item that `item` can read; an empty list where there is no item. */\n itemFields?: readonly string[]\n /** Functions besides the built-in ones. */\n functions?: readonly string[]\n}\n\nexport function checkExpression(\n source: string,\n options: CheckExpressionOptions = {},\n): ExpressionProblem[] {\n let ast: ExpressionNode\n try {\n ast = compileExpression(source).ast\n } catch (error) {\n const { code, message, start, end } = error as ExpressionError\n return [{ code, message, text: source.slice(start, end), start, end }]\n }\n const problems: ExpressionProblem[] = []\n const known = options.fields\n const functions = new Set([...Object.keys(builtinFunctions), ...(options.functions ?? [])])\n\n function visit(node: ExpressionNode) {\n if (node.kind === 'member') {\n const path = pathOf(node)\n if (path !== undefined) {\n if (path !== '' && known && !known.some((name) => related(name, path)))\n problems.push({\n code: 'unknown-field',\n message: `There is no field \"${path}\"`,\n text: path,\n start: node.start,\n end: node.end,\n })\n computedKeys(node).forEach(visit)\n return\n }\n const itemPath = pathOf(node, 'item')\n if (itemPath !== undefined) {\n const own = options.itemFields\n if (itemPath !== '' && own && !own.some((name) => related(name, itemPath)))\n problems.push({\n code: 'unknown-field',\n message: `There is no field \"item.${itemPath}\"`,\n text: `item.${itemPath}`,\n start: node.start,\n end: node.end,\n })\n computedKeys(node).forEach(visit)\n return\n }\n visit(node.object)\n visit(node.key)\n return\n }\n if (node.kind === 'call') {\n if (!functions.has(node.name))\n problems.push({\n code: 'unknown-function',\n message: `There is no function \"${node.name}\"`,\n text: node.name,\n start: node.start,\n end: node.start + node.name.length,\n })\n node.args.forEach(visit)\n return\n }\n if (node.kind === 'name' && node.name === 'item' && options.itemFields?.length === 0)\n problems.push({\n code: 'unknown-field',\n message: 'There is no list item here to read',\n text: 'item',\n start: node.start,\n end: node.end,\n })\n if (node.kind === 'list') node.items.forEach(visit)\n else if (node.kind === 'unary') visit(node.argument)\n else if (node.kind === 'binary') {\n visit(node.left)\n visit(node.right)\n } else if (node.kind === 'condition') {\n visit(node.test)\n visit(node.whenTrue)\n visit(node.whenFalse)\n }\n }\n visit(ast)\n return problems\n}\n\n/** Whether a read of `path` can see the field `name`: the same path, or one inside the other. */\nexport function related(name: string, path: string): boolean {\n return path === '' || name === path || name.startsWith(`${path}.`) || path.startsWith(`${name}.`)\n}\n\n/** Today as `YYYY-MM-DD`, in local time. */\nexport const formatToday = () => formatDay(new Date())\n"],"mappings":"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"}
@@ -0,0 +1,242 @@
1
+ import { cloneValue, getPath, isField, isList, setPath, valueNodes } from "./document.js";
2
+ import { computedKeys, equalValues, pathOf, related, tryCompileExpression } from "./expression.js";
3
+ // src/core/formulas.ts
4
+ /**
5
+ * The fields whose formulas read one another in a circle, each circle as
6
+ * the names on it, e.g. `[['total', 'tax']]`. A formula reading its own
7
+ * field is a circle of one. The items' fields are named with their list's,
8
+ * e.g. `rows.total`; a circle can go through the document's formulas and
9
+ * the items' alike. Pass the form's `functions` (or their names) when it
10
+ * has its own `sum`, `min`, `max`, `len` or `empty`, which may read any key of the items.
11
+ */
12
+ function findFormulaCycles(document, functions) {
13
+ return analyseFormulas(document, functions).cycles.map((cycle) => cycle.map(({ label }) => label));
14
+ }
15
+ /**
16
+ * Every formula of a document, the items' included, as one set: a
17
+ * formula reading a list reads all its items' formulas, and an item's
18
+ * formula reading `values` reads the document's.
19
+ */
20
+ function analyseFormulas(document, functions) {
21
+ const root = collect(document.nodes, "");
22
+ const all = [];
23
+ const scopes = /* @__PURE__ */ new Map();
24
+ const walk = (scope) => {
25
+ for (const formula of scope.formulas) {
26
+ all.push(formula);
27
+ scopes.set(formula, scope);
28
+ }
29
+ for (const { scope: inner } of scope.lists) walk(inner);
30
+ };
31
+ walk(root);
32
+ const index = new Map(all.map((formula, i) => [formula, i]));
33
+ const everyFormula = (scope) => [...scope.formulas, ...scope.lists.flatMap(({ scope: inner }) => everyFormula(inner))];
34
+ const reads = (scope, path, key) => [...scope.formulas.filter(({ name }) => related(name, path)), ...scope.lists.filter(({ node }) => node.name !== void 0 && related(node.name, path)).flatMap(({ node, scope: inner }) => {
35
+ if (key === void 0 || node.name !== path) return everyFormula(inner);
36
+ return key === null ? [] : reads(inner, key);
37
+ })];
38
+ const edges = all.map((formula) => {
39
+ const scope = scopes.get(formula);
40
+ const targets = /* @__PURE__ */ new Set();
41
+ for (const read of readsOf(formula.expression.ast, functions)) if (read.root === "value") targets.add(formula);
42
+ else if (read.root === "values") for (const t of reads(root, read.path, read.key)) targets.add(t);
43
+ else if (scope !== root) for (const t of reads(scope, read.path, read.key)) targets.add(t);
44
+ return [...targets].map((target) => index.get(target));
45
+ });
46
+ const order = Array.from({ length: all.length }, () => -1);
47
+ const low = [];
48
+ const stack = [];
49
+ const onStack = /* @__PURE__ */ new Set();
50
+ const groups = [];
51
+ let counter = 0;
52
+ function connect(at) {
53
+ order[at] = low[at] = counter++;
54
+ stack.push(at);
55
+ onStack.add(at);
56
+ for (const next of edges[at]) if (order[next] === -1) {
57
+ connect(next);
58
+ low[at] = Math.min(low[at], low[next]);
59
+ } else if (onStack.has(next)) low[at] = Math.min(low[at], order[next]);
60
+ if (low[at] !== order[at]) return;
61
+ const group = [];
62
+ let member;
63
+ do {
64
+ member = stack.pop();
65
+ onStack.delete(member);
66
+ group.unshift(member);
67
+ } while (member !== at);
68
+ if (group.length > 1 || edges[at].includes(at)) groups.push(group);
69
+ }
70
+ for (let i = 0; i < all.length; i++) if (order[i] === -1) connect(i);
71
+ groups.sort((a, b) => a[0] - b[0]);
72
+ const blocked = /* @__PURE__ */ new Set();
73
+ const cycles = groups.map((group) => group.map((i) => {
74
+ const { node, label } = all[i];
75
+ blocked.add(node.id);
76
+ return {
77
+ node,
78
+ label
79
+ };
80
+ }));
81
+ const runnable = (scope) => scope.formulas.filter(({ node }) => !blocked.has(node.id)).map(({ name, expression }) => ({
82
+ name,
83
+ expression
84
+ }));
85
+ const items = /* @__PURE__ */ new Map();
86
+ const fill = (scope) => {
87
+ for (const { node, scope: inner } of scope.lists) {
88
+ items.set(node, runnable(inner));
89
+ fill(inner);
90
+ }
91
+ };
92
+ fill(root);
93
+ return {
94
+ formulas: runnable(root),
95
+ items,
96
+ cycles,
97
+ blocked
98
+ };
99
+ }
100
+ /** The functions that take a list and one key of its items, e.g. `sum(values.rows, 'total')`. */
101
+ var keyed = /* @__PURE__ */ new Set([
102
+ "sum",
103
+ "min",
104
+ "max"
105
+ ]);
106
+ /** The functions that read only how many items a list has, e.g. `len(values.rows)`. */
107
+ var counting = /* @__PURE__ */ new Set(["len", "empty"]);
108
+ /** What an expression reads, like its `fields` and `itemFields`, with the keys aggregates read. */
109
+ function readsOf(ast, functions) {
110
+ const own = (name) => Array.isArray(functions) ? functions.includes(name) : !!functions && Object.hasOwn(functions, name);
111
+ const result = [];
112
+ const member = (node, key) => {
113
+ for (const root of ["values", "item"]) {
114
+ const path = pathOf(node, root);
115
+ if (path === void 0) continue;
116
+ result.push({
117
+ root,
118
+ path,
119
+ key
120
+ });
121
+ computedKeys(node).forEach(visit);
122
+ return true;
123
+ }
124
+ return false;
125
+ };
126
+ function visit(node) {
127
+ switch (node.kind) {
128
+ case "name":
129
+ result.push({
130
+ root: node.name,
131
+ path: ""
132
+ });
133
+ return;
134
+ case "member":
135
+ if (!member(node)) {
136
+ visit(node.object);
137
+ visit(node.key);
138
+ }
139
+ return;
140
+ case "call": {
141
+ const [list, key, ...rest] = node.args;
142
+ if (keyed.has(node.name) && !own(node.name) && list?.kind === "member" && key?.kind === "literal" && typeof key.value === "string" && rest.length === 0 && member(list, key.value)) return;
143
+ if (counting.has(node.name) && !own(node.name) && list?.kind === "member" && key === void 0 && member(list, null)) return;
144
+ node.args.forEach(visit);
145
+ return;
146
+ }
147
+ case "list":
148
+ node.items.forEach(visit);
149
+ return;
150
+ case "unary":
151
+ visit(node.argument);
152
+ return;
153
+ case "binary":
154
+ visit(node.left);
155
+ visit(node.right);
156
+ return;
157
+ case "condition":
158
+ visit(node.test);
159
+ visit(node.whenTrue);
160
+ visit(node.whenFalse);
161
+ return;
162
+ }
163
+ }
164
+ visit(ast);
165
+ return result;
166
+ }
167
+ function collect(nodes, prefix) {
168
+ const scope = {
169
+ formulas: [],
170
+ lists: []
171
+ };
172
+ for (const node of valueNodes(nodes)) {
173
+ if (isList(node)) {
174
+ scope.lists.push({
175
+ node,
176
+ scope: collect(node.children, `${prefix}${node.name}.`)
177
+ });
178
+ continue;
179
+ }
180
+ if (!isField(node) || !node.formula) continue;
181
+ const expression = tryCompileExpression(node.formula);
182
+ if (expression) scope.formulas.push({
183
+ node,
184
+ name: node.name,
185
+ label: prefix + node.name,
186
+ expression
187
+ });
188
+ }
189
+ return scope;
190
+ }
191
+ /**
192
+ * Works out every formula of the form at once, the items' included, and
193
+ * writes the results into `values`, going over them again until nothing
194
+ * changes. Formulas on a circle are left out. The renderer's watchers keep
195
+ * results up to date in the page; on the server, where watchers run only
196
+ * once, this gives the same values the page will have, so a formula reading
197
+ * the items' formulas is not left behind.
198
+ */
199
+ function settleFormulas(document, values, functions, analysis = analyseFormulas(document, functions)) {
200
+ const top = analysis.formulas;
201
+ for (let pass = 0; pass <= top.length; pass++) {
202
+ let changed = settleItems(document.nodes, values, values, functions, analysis.items);
203
+ for (const { name, expression } of top) changed = write(values, name, expression.evaluate({
204
+ values,
205
+ value: null,
206
+ functions
207
+ })) || changed;
208
+ if (!changed) return;
209
+ }
210
+ }
211
+ function settleItems(nodes, target, values, functions, items) {
212
+ let changed = false;
213
+ for (const node of valueNodes(nodes)) {
214
+ if (!isList(node)) continue;
215
+ const rows = getPath(target, node.name);
216
+ if (!Array.isArray(rows)) continue;
217
+ const formulas = items.get(node) ?? [];
218
+ for (const item of rows) {
219
+ if (item === null || typeof item !== "object") continue;
220
+ const row = item;
221
+ for (let pass = 0; pass <= formulas.length; pass++) {
222
+ let again = settleItems(node.children, row, values, functions, items);
223
+ for (const { name, expression } of formulas) again = write(row, name, expression.evaluate({
224
+ values,
225
+ item: row,
226
+ functions
227
+ })) || again;
228
+ if (!again) break;
229
+ changed = true;
230
+ }
231
+ }
232
+ }
233
+ return changed;
234
+ }
235
+ function write(target, name, result) {
236
+ if (equalValues(result, getPath(target, name))) return false;
237
+ setPath(target, name, cloneValue(result));
238
+ return true;
239
+ }
240
+ export { analyseFormulas, findFormulaCycles, settleFormulas };
241
+
242
+ //# sourceMappingURL=formulas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formulas.js","names":[],"sources":["../../src/core/formulas.ts"],"sourcesContent":["import { cloneValue, getPath, isField, isList, setPath, valueNodes } from './document'\nimport { computedKeys, equalValues, pathOf, related, tryCompileExpression } from './expression'\nimport type { CompiledExpression, ExpressionNode, FormFunction } from './expression'\nimport type { FormDocument, FormFieldNode, FormListNode, FormNode } from './types'\n\nexport interface FormulaField {\n name: string\n expression: CompiledExpression\n}\n\n/** A formula field and where it is: the document's, or the items' of a list. */\ninterface Formula extends FormulaField {\n node: FormFieldNode\n /** The name shown for it, e.g. `total`, or `rows.total` for the items' field. */\n label: string\n}\n\n/** The formulas of the document, or of a list's items, and the lists among them. */\ninterface Scope {\n formulas: Formula[]\n lists: { node: FormListNode; scope: Scope }[]\n}\n\n/** Every formula of a document, worked out once. */\nexport interface FormulaAnalysis {\n /** The document's formulas that can run, in document order. */\n formulas: FormulaField[]\n /** The item formulas that can run, per list. */\n items: Map<FormListNode, FormulaField[]>\n /** The circles, each as the formulas on it. */\n cycles: { node: FormFieldNode; label: string }[][]\n /** The ids of the fields on a circle, which are not worked out. */\n blocked: Set<string>\n}\n\n/**\n * The fields whose formulas read one another in a circle, each circle as\n * the names on it, e.g. `[['total', 'tax']]`. A formula reading its own\n * field is a circle of one. The items' fields are named with their list's,\n * e.g. `rows.total`; a circle can go through the document's formulas and\n * the items' alike. Pass the form's `functions` (or their names) when it\n * has its own `sum`, `min`, `max`, `len` or `empty`, which may read any key of the items.\n */\nexport function findFormulaCycles(\n document: FormDocument,\n functions?: Readonly<Record<string, FormFunction>> | readonly string[],\n): string[][] {\n return analyseFormulas(document, functions).cycles.map((cycle) => cycle.map(({ label }) => label))\n}\n\n/**\n * Every formula of a document, the items' included, as one set: a\n * formula reading a list reads all its items' formulas, and an item's\n * formula reading `values` reads the document's.\n */\nexport function analyseFormulas(\n document: FormDocument,\n functions?: Readonly<Record<string, FormFunction>> | readonly string[],\n): FormulaAnalysis {\n const root = collect(document.nodes, '')\n const all: Formula[] = []\n const scopes = new Map<Formula, Scope>()\n const walk = (scope: Scope) => {\n for (const formula of scope.formulas) {\n all.push(formula)\n scopes.set(formula, scope)\n }\n for (const { scope: inner } of scope.lists) walk(inner)\n }\n walk(root)\n const index = new Map(all.map((formula, i) => [formula, i]))\n\n const everyFormula = (scope: Scope): Formula[] => [\n ...scope.formulas,\n ...scope.lists.flatMap(({ scope: inner }) => everyFormula(inner)),\n ]\n // What reading `path` in a scope reads: its formulas there, and all of a\n // list's. With a `key`, as in `sum(values.rows, 'total')`, only that key\n // of the list's items; with `key` `null`, as in `len(values.rows)`, none\n // of them, only how many there are.\n const reads = (scope: Scope, path: string, key?: string | null): Formula[] => [\n ...scope.formulas.filter(({ name }) => related(name, path)),\n ...scope.lists\n .filter(({ node }) => node.name !== undefined && related(node.name, path))\n .flatMap(({ node, scope: inner }) => {\n if (key === undefined || node.name !== path) return everyFormula(inner)\n return key === null ? [] : reads(inner, key)\n }),\n ]\n const edges = all.map((formula) => {\n const scope = scopes.get(formula)!\n const targets = new Set<Formula>()\n for (const read of readsOf(formula.expression.ast, functions)) {\n if (read.root === 'value') targets.add(formula)\n else if (read.root === 'values')\n for (const t of reads(root, read.path, read.key)) targets.add(t)\n else if (scope !== root) for (const t of reads(scope, read.path, read.key)) targets.add(t)\n }\n return [...targets].map((target) => index.get(target)!)\n })\n\n // Tarjan's way: every strongly connected group of more than one, or one\n // that reads itself, is a circle.\n const order: number[] = Array.from({ length: all.length }, () => -1)\n const low: number[] = []\n const stack: number[] = []\n const onStack = new Set<number>()\n const groups: number[][] = []\n let counter = 0\n function connect(at: number) {\n order[at] = low[at] = counter++\n stack.push(at)\n onStack.add(at)\n for (const next of edges[at]!) {\n if (order[next] === -1) {\n connect(next)\n low[at] = Math.min(low[at]!, low[next]!)\n } else if (onStack.has(next)) low[at] = Math.min(low[at]!, order[next]!)\n }\n if (low[at] !== order[at]) return\n const group: number[] = []\n let member: number\n do {\n member = stack.pop()!\n onStack.delete(member)\n group.unshift(member)\n } while (member !== at)\n if (group.length > 1 || edges[at]!.includes(at)) groups.push(group)\n }\n for (let i = 0; i < all.length; i++) if (order[i] === -1) connect(i)\n groups.sort((a, b) => a[0]! - b[0]!)\n\n const blocked = new Set<string>()\n const cycles = groups.map((group) =>\n group.map((i) => {\n const { node, label } = all[i]!\n blocked.add(node.id)\n return { node, label }\n }),\n )\n const runnable = (scope: Scope): FormulaField[] =>\n scope.formulas\n .filter(({ node }) => !blocked.has(node.id))\n .map(({ name, expression }) => ({ name, expression }))\n const items = new Map<FormListNode, FormulaField[]>()\n const fill = (scope: Scope) => {\n for (const { node, scope: inner } of scope.lists) {\n items.set(node, runnable(inner))\n fill(inner)\n }\n }\n fill(root)\n return { formulas: runnable(root), items, cycles, blocked }\n}\n\n/** The functions that take a list and one key of its items, e.g. `sum(values.rows, 'total')`. */\nconst keyed = new Set(['sum', 'min', 'max'])\n/** The functions that read only how many items a list has, e.g. `len(values.rows)`. */\nconst counting = new Set(['len', 'empty'])\n\ninterface Read {\n root: 'values' | 'item' | 'value'\n path: string\n /** The key of every item an aggregate reads, or `null` for none, only the count. */\n key?: string | null\n}\n\n/** What an expression reads, like its `fields` and `itemFields`, with the keys aggregates read. */\nfunction readsOf(\n ast: ExpressionNode,\n functions: Readonly<Record<string, FormFunction>> | readonly string[] | undefined,\n): Read[] {\n // A function of the form's own by the same name is called instead, and may read anything.\n const own = (name: string) =>\n Array.isArray(functions)\n ? functions.includes(name)\n : !!functions && Object.hasOwn(functions, name)\n const result: Read[] = []\n const member = (node: ExpressionNode, key?: string | null) => {\n for (const root of ['values', 'item'] as const) {\n const path = pathOf(node, root)\n if (path === undefined) continue\n result.push({ root, path, key })\n computedKeys(node).forEach(visit)\n return true\n }\n return false\n }\n function visit(node: ExpressionNode) {\n switch (node.kind) {\n case 'name':\n result.push({ root: node.name, path: '' })\n return\n case 'member':\n if (!member(node)) {\n visit(node.object)\n visit(node.key)\n }\n return\n case 'call': {\n const [list, key, ...rest] = node.args\n if (\n keyed.has(node.name) &&\n !own(node.name) &&\n list?.kind === 'member' &&\n key?.kind === 'literal' &&\n typeof key.value === 'string' &&\n rest.length === 0 &&\n member(list, key.value)\n )\n return\n if (\n counting.has(node.name) &&\n !own(node.name) &&\n list?.kind === 'member' &&\n key === undefined &&\n member(list, null)\n )\n return\n node.args.forEach(visit)\n return\n }\n case 'list':\n node.items.forEach(visit)\n return\n case 'unary':\n visit(node.argument)\n return\n case 'binary':\n visit(node.left)\n visit(node.right)\n return\n case 'condition':\n visit(node.test)\n visit(node.whenTrue)\n visit(node.whenFalse)\n return\n case 'literal':\n }\n }\n visit(ast)\n return result\n}\n\nfunction collect(nodes: readonly FormNode[], prefix: string): Scope {\n const scope: Scope = { formulas: [], lists: [] }\n for (const node of valueNodes(nodes)) {\n if (isList(node)) {\n scope.lists.push({ node, scope: collect(node.children, `${prefix}${node.name}.`) })\n continue\n }\n if (!isField(node) || !node.formula) continue\n const expression = tryCompileExpression(node.formula)\n if (expression)\n scope.formulas.push({ node, name: node.name!, label: prefix + node.name!, expression })\n }\n return scope\n}\n\n/**\n * Works out every formula of the form at once, the items' included, and\n * writes the results into `values`, going over them again until nothing\n * changes. Formulas on a circle are left out. The renderer's watchers keep\n * results up to date in the page; on the server, where watchers run only\n * once, this gives the same values the page will have, so a formula reading\n * the items' formulas is not left behind.\n */\nexport function settleFormulas(\n document: FormDocument,\n values: Record<string, unknown>,\n functions?: Readonly<Record<string, FormFunction>>,\n analysis: FormulaAnalysis = analyseFormulas(document, functions),\n) {\n const top = analysis.formulas\n // Each pass settles at least one more formula in a chain, so this many always do.\n for (let pass = 0; pass <= top.length; pass++) {\n let changed = settleItems(document.nodes, values, values, functions, analysis.items)\n for (const { name, expression } of top)\n changed =\n write(values, name, expression.evaluate({ values, value: null, functions })) || changed\n if (!changed) return\n }\n}\n\nfunction settleItems(\n nodes: readonly FormNode[],\n target: Record<string, unknown>,\n values: Record<string, unknown>,\n functions: Readonly<Record<string, FormFunction>> | undefined,\n items: ReadonlyMap<FormListNode, FormulaField[]>,\n) {\n let changed = false\n for (const node of valueNodes(nodes)) {\n if (!isList(node)) continue\n const rows = getPath(target, node.name)\n if (!Array.isArray(rows)) continue\n const formulas = items.get(node) ?? []\n for (const item of rows as unknown[]) {\n if (item === null || typeof item !== 'object') continue\n const row = item as Record<string, unknown>\n for (let pass = 0; pass <= formulas.length; pass++) {\n let again = settleItems(node.children, row, values, functions, items)\n for (const { name, expression } of formulas)\n again = write(row, name, expression.evaluate({ values, item: row, functions })) || again\n if (!again) break\n changed = true\n }\n }\n }\n return changed\n}\n\nfunction write(target: Record<string, unknown>, name: string, result: unknown) {\n if (equalValues(result, getPath(target, name))) return false\n setPath(target, name, cloneValue(result))\n return true\n}\n"],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import { isEmptyValue, truthy, tryCompileExpression } from "./expression.js";
2
+ // src/core/rules.ts
3
+ var patterns = /* @__PURE__ */ new Map();
4
+ /** The compiled pattern, shared by every rule with the same source; `null` when it does not compile. */
5
+ function compile(source, flags = "") {
6
+ const key = `${flags}/${source}`;
7
+ let pattern = patterns.get(key);
8
+ if (pattern === void 0) {
9
+ try {
10
+ pattern = new RegExp(source, flags.replace(/[gy]/g, ""));
11
+ } catch {
12
+ pattern = null;
13
+ console.warn(`[@dou-ui/form-builder] form rule pattern /${source}/${flags} does not compile; it is ignored`);
14
+ }
15
+ patterns.set(key, pattern);
16
+ }
17
+ return pattern;
18
+ }
19
+ var warnedValidators = /* @__PURE__ */ new Set();
20
+ function warnValidator(name) {
21
+ if (process.env.NODE_ENV === "production" || warnedValidators.has(name)) return;
22
+ warnedValidators.add(name);
23
+ console.warn(`[@dou-ui/form-builder] there is no validator "${name}" in \`validators\`; the rule passes`);
24
+ }
25
+ /** One rule as data, as the form's own rule. */
26
+ function toFormRule(spec, options = {}) {
27
+ const { pattern, flags, check, validator, ...rest } = spec;
28
+ const { functions, validators, item, path } = options;
29
+ const rule = rest;
30
+ if (pattern !== void 0) {
31
+ const compiled = compile(pattern, flags);
32
+ if (compiled) rule.pattern = compiled;
33
+ }
34
+ if (check !== void 0) {
35
+ const expression = tryCompileExpression(check);
36
+ if (expression) rule.validator = (value, model) => isEmptyValue(value) || truthy(expression.evaluate({
37
+ values: model,
38
+ value,
39
+ item: item?.(),
40
+ functions
41
+ }));
42
+ } else if (validator !== void 0) rule.validator = (value, model) => {
43
+ if (isEmptyValue(value)) return true;
44
+ const run = validators?.[validator];
45
+ if (!run) {
46
+ warnValidator(validator);
47
+ return true;
48
+ }
49
+ return run(value, {
50
+ values: model,
51
+ item: item?.() ?? null,
52
+ path: path?.() ?? ""
53
+ });
54
+ };
55
+ return rule;
56
+ }
57
+ var cache = /* @__PURE__ */ new WeakMap();
58
+ /**
59
+ * The rules of a field node, `required` first. Cached per node while its
60
+ * `rules`, whether it is required and the other options stay the same, so
61
+ * re-renders hand the form item the same array.
62
+ */
63
+ function rulesOf(node, options = {}) {
64
+ const required = options.required ?? node.required ?? false;
65
+ const { functions, validators, item, path } = options;
66
+ const cached = cache.get(node);
67
+ if (cached && cached.specs === node.rules && cached.required === required && cached.functions === functions && cached.validators === validators && cached.item === item && cached.path === path) return cached.rules;
68
+ const rules = (node.rules ?? []).map((spec) => toFormRule(spec, options));
69
+ if (required && !rules.some((rule) => rule.required)) rules.unshift({ required: true });
70
+ cache.set(node, {
71
+ specs: node.rules,
72
+ required,
73
+ functions,
74
+ validators,
75
+ item,
76
+ path,
77
+ rules
78
+ });
79
+ return rules;
80
+ }
81
+ export { rulesOf, toFormRule };
82
+
83
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","names":[],"sources":["../../src/core/rules.ts"],"sourcesContent":["import type { FormRule } from 'dou-ui'\nimport { isEmptyValue, truthy, tryCompileExpression } from './expression'\nimport type { FormFunction } from './expression'\nimport type { FormFieldNode, FormListNode, FormRuleSpec, FormValidator } from './types'\n\nconst patterns = new Map<string, RegExp | null>()\n\n/** The compiled pattern, shared by every rule with the same source; `null` when it does not compile. */\nfunction compile(source: string, flags = ''): RegExp | null {\n const key = `${flags}/${source}`\n let pattern = patterns.get(key)\n if (pattern === undefined) {\n try {\n pattern = new RegExp(source, flags.replace(/[gy]/g, ''))\n } catch {\n pattern = null\n console.warn(\n `[@dou-ui/form-builder] form rule pattern /${source}/${flags} does not compile; it is ignored`,\n )\n }\n patterns.set(key, pattern)\n }\n return pattern\n}\n\nconst warnedValidators = new Set<string>()\nfunction warnValidator(name: string) {\n if (process.env.NODE_ENV === 'production' || warnedValidators.has(name)) return\n warnedValidators.add(name)\n console.warn(\n `[@dou-ui/form-builder] there is no validator \"${name}\" in \\`validators\\`; the rule passes`,\n )\n}\n\n/** One rule as data, as the form's own rule. */\nexport function toFormRule(spec: FormRuleSpec, options: RulesOptions = {}): FormRule {\n const { pattern, flags, check, validator, ...rest } = spec\n const { functions, validators, item, path } = options\n const rule: FormRule = rest\n if (pattern !== undefined) {\n const compiled = compile(pattern, flags)\n if (compiled) rule.pattern = compiled\n }\n if (check !== undefined) {\n const expression = tryCompileExpression(check)\n if (expression)\n rule.validator = (value, model) =>\n isEmptyValue(value) ||\n truthy(expression.evaluate({ values: model, value, item: item?.(), functions }))\n } else if (validator !== undefined)\n rule.validator = (value, model) => {\n if (isEmptyValue(value)) return true\n const run = validators?.[validator]\n if (!run) {\n warnValidator(validator)\n return true\n }\n return run(value, { values: model, item: item?.() ?? null, path: path?.() ?? '' })\n }\n return rule\n}\n\nexport interface RulesOptions {\n /** Whether the field is required, when that is decided elsewhere. Defaults to the node's `required`. */\n required?: boolean\n /** What `check` expressions can call besides the built-in functions. */\n functions?: Readonly<Record<string, FormFunction>>\n /** The checks a rule's `validator` names. */\n validators?: Readonly<Record<string, FormValidator>>\n /** Inside a list, the item the field belongs to, read as `item` by `check`. */\n item?: () => unknown\n /** The field's path in the form, handed to validators. */\n path?: () => string\n}\n\ninterface Cached {\n specs: FormRuleSpec[] | undefined\n required: boolean\n functions: RulesOptions['functions']\n validators: RulesOptions['validators']\n item: RulesOptions['item']\n path: RulesOptions['path']\n rules: FormRule[]\n}\n\nconst cache = new WeakMap<object, Cached>()\n\n/**\n * The rules of a field node, `required` first. Cached per node while its\n * `rules`, whether it is required and the other options stay the same, so\n * re-renders hand the form item the same array.\n */\nexport function rulesOf(\n node: FormFieldNode | FormListNode,\n options: RulesOptions = {},\n): FormRule[] {\n const required = options.required ?? node.required ?? false\n const { functions, validators, item, path } = options\n const cached = cache.get(node)\n if (\n cached &&\n cached.specs === node.rules &&\n cached.required === required &&\n cached.functions === functions &&\n cached.validators === validators &&\n cached.item === item &&\n cached.path === path\n )\n return cached.rules\n const rules = (node.rules ?? []).map((spec) => toFormRule(spec, options))\n if (required && !rules.some((rule) => rule.required)) rules.unshift({ required: true })\n cache.set(node, { specs: node.rules, required, functions, validators, item, path, rules })\n return rules\n}\n"],"mappings":";;AAKA,IAAM,2BAAW,IAAI,IAA2B;;AAGhD,SAAS,QAAQ,QAAgB,QAAQ,IAAmB;CAC1D,MAAM,MAAM,GAAG,MAAM,GAAG;CACxB,IAAI,UAAU,SAAS,IAAI,GAAG;CAC9B,IAAI,YAAY,KAAA,GAAW;EACzB,IAAI;GACF,UAAU,IAAI,OAAO,QAAQ,MAAM,QAAQ,SAAS,EAAE,CAAC;EACzD,QAAQ;GACN,UAAU;GACV,QAAQ,KACN,6CAA6C,OAAO,GAAG,MAAM,iCAC/D;EACF;EACA,SAAS,IAAI,KAAK,OAAO;CAC3B;CACA,OAAO;AACT;AAEA,IAAM,mCAAmB,IAAI,IAAY;AACzC,SAAS,cAAc,MAAc;CACnC,IAAA,QAAA,IAAA,aAA6B,gBAAgB,iBAAiB,IAAI,IAAI,GAAG;CACzE,iBAAiB,IAAI,IAAI;CACzB,QAAQ,KACN,iDAAiD,KAAK,qCACxD;AACF;;AAGA,SAAgB,WAAW,MAAoB,UAAwB,CAAC,GAAa;CACnF,MAAM,EAAE,SAAS,OAAO,OAAO,WAAW,GAAG,SAAS;CACtD,MAAM,EAAE,WAAW,YAAY,MAAM,SAAS;CAC9C,MAAM,OAAiB;CACvB,IAAI,YAAY,KAAA,GAAW;EACzB,MAAM,WAAW,QAAQ,SAAS,KAAK;EACvC,IAAI,UAAU,KAAK,UAAU;CAC/B;CACA,IAAI,UAAU,KAAA,GAAW;EACvB,MAAM,aAAa,qBAAqB,KAAK;EAC7C,IAAI,YACF,KAAK,aAAa,OAAO,UACvB,aAAa,KAAK,KAClB,OAAO,WAAW,SAAS;GAAE,QAAQ;GAAO;GAAO,MAAM,OAAO;GAAG;EAAU,CAAC,CAAC;CACrF,OAAO,IAAI,cAAc,KAAA,GACvB,KAAK,aAAa,OAAO,UAAU;EACjC,IAAI,aAAa,KAAK,GAAG,OAAO;EAChC,MAAM,MAAM,aAAa;EACzB,IAAI,CAAC,KAAK;GACR,cAAc,SAAS;GACvB,OAAO;EACT;EACA,OAAO,IAAI,OAAO;GAAE,QAAQ;GAAO,MAAM,OAAO,KAAK;GAAM,MAAM,OAAO,KAAK;EAAG,CAAC;CACnF;CACF,OAAO;AACT;AAyBA,IAAM,wBAAQ,IAAI,QAAwB;;;;;;AAO1C,SAAgB,QACd,MACA,UAAwB,CAAC,GACb;CACZ,MAAM,WAAW,QAAQ,YAAY,KAAK,YAAY;CACtD,MAAM,EAAE,WAAW,YAAY,MAAM,SAAS;CAC9C,MAAM,SAAS,MAAM,IAAI,IAAI;CAC7B,IACE,UACA,OAAO,UAAU,KAAK,SACtB,OAAO,aAAa,YACpB,OAAO,cAAc,aACrB,OAAO,eAAe,cACtB,OAAO,SAAS,QAChB,OAAO,SAAS,MAEhB,OAAO,OAAO;CAChB,MAAM,SAAS,KAAK,SAAS,CAAC,EAAA,CAAG,KAAK,SAAS,WAAW,MAAM,OAAO,CAAC;CACxE,IAAI,YAAY,CAAC,MAAM,MAAM,SAAS,KAAK,QAAQ,GAAG,MAAM,QAAQ,EAAE,UAAU,KAAK,CAAC;CACtF,MAAM,IAAI,MAAM;EAAE,OAAO,KAAK;EAAO;EAAU;EAAW;EAAY;EAAM;EAAM;CAAM,CAAC;CACzF,OAAO;AACT"}
@@ -0,0 +1,80 @@
1
+ import { cloneValue, getPath, isContainer, isList, setPath, unsetPath, valueNodes } from "./document.js";
2
+ import { truthy, tryCompileExpression } from "./expression.js";
3
+ import { fieldProps } from "../fields/define.js";
4
+ // src/core/values.ts
5
+ /** The value a field starts with: its own default, else its type's empty value. */
6
+ function startValue(type, node) {
7
+ if (node.default !== void 0) return cloneValue(node.default);
8
+ return type.empty ? type.empty(fieldProps(type, node)) : null;
9
+ }
10
+ /** The items a list starts with: its default, else `min` empty items. */
11
+ function startItems(list, types) {
12
+ if (list.default !== void 0) return cloneValue(list.default);
13
+ return Array.from({ length: Math.max(0, list.min ?? 0) }, () => emptyItem(list, types));
14
+ }
15
+ /** A new item of a list, every field at its starting value. */
16
+ function emptyItem(list, types) {
17
+ const item = {};
18
+ fillValues(list.children, item, types);
19
+ return item;
20
+ }
21
+ /**
22
+ * Give every field of `nodes` that `target` has no value for its starting
23
+ * value, the items of lists included. Values already there stay.
24
+ */
25
+ function fillValues(nodes, target, types) {
26
+ for (const node of valueNodes(nodes)) {
27
+ if (isList(node)) {
28
+ let items = getPath(target, node.name);
29
+ if (!Array.isArray(items)) {
30
+ setPath(target, node.name, startItems(node, types));
31
+ items = getPath(target, node.name);
32
+ }
33
+ for (const item of items) if (item !== null && typeof item === "object") fillValues(node.children, item, types);
34
+ continue;
35
+ }
36
+ const type = types.get(node.type);
37
+ if (!type || type.kind === "display") continue;
38
+ if (getPath(target, node.name) === void 0) setPath(target, node.name, startValue(type, node));
39
+ }
40
+ }
41
+ /**
42
+ * A copy of the values without the ones of hidden fields: a field, group,
43
+ * page or list whose `visibleWhen` is false leaves out everything it
44
+ * holds, item by item inside lists. This is what the renderer submits.
45
+ */
46
+ function visibleValues(document, model, options = {}) {
47
+ const copy = cloneValue(model);
48
+ const shown = (node, prefix, item) => {
49
+ if (!node.visibleWhen) return true;
50
+ const expression = tryCompileExpression(node.visibleWhen);
51
+ if (!expression) return true;
52
+ const name = isList(node) || !isContainer(node) ? node.name : void 0;
53
+ return truthy(expression.evaluate({
54
+ values: model,
55
+ value: name ? getPath(model, prefix + name) : null,
56
+ item,
57
+ functions: options.functions
58
+ }));
59
+ };
60
+ const drop = (nodes, prefix) => {
61
+ for (const node of valueNodes(nodes)) unsetPath(copy, prefix + node.name);
62
+ };
63
+ const visit = (nodes, prefix, item) => {
64
+ for (const node of nodes) {
65
+ if (!shown(node, prefix, item)) {
66
+ drop([node], prefix);
67
+ continue;
68
+ }
69
+ if (isList(node)) {
70
+ const items = getPath(model, prefix + node.name);
71
+ if (Array.isArray(items)) items.forEach((entry, index) => visit(node.children, `${prefix}${node.name}.${index}.`, entry));
72
+ } else if (isContainer(node)) visit(node.children, prefix, item);
73
+ }
74
+ };
75
+ visit(document.nodes, "", null);
76
+ return copy;
77
+ }
78
+ export { emptyItem, fillValues, startValue, visibleValues };
79
+
80
+ //# sourceMappingURL=values.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"values.js","names":[],"sources":["../../src/core/values.ts"],"sourcesContent":["import {\n cloneValue,\n getPath,\n isContainer,\n isList,\n setPath,\n unsetPath,\n valueNodes,\n} from './document'\nimport { truthy, tryCompileExpression } from './expression'\nimport type { FormFunction } from './expression'\nimport type { FormDocument, FormFieldNode, FormListNode, FormNode } from './types'\nimport { fieldProps } from '../fields/define'\nimport type { FormFieldType } from '../fields/define'\n\n/** The value a field starts with: its own default, else its type's empty value. */\nexport function startValue(type: FormFieldType, node: FormFieldNode): unknown {\n if (node.default !== undefined) return cloneValue(node.default)\n return type.empty ? type.empty(fieldProps(type, node)) : null\n}\n\n/** The items a list starts with: its default, else `min` empty items. */\nfunction startItems(list: FormListNode, types: Map<string, FormFieldType>) {\n if (list.default !== undefined) return cloneValue(list.default)\n return Array.from({ length: Math.max(0, list.min ?? 0) }, () => emptyItem(list, types))\n}\n\n/** A new item of a list, every field at its starting value. */\nexport function emptyItem(\n list: FormListNode,\n types: Map<string, FormFieldType>,\n): Record<string, unknown> {\n const item: Record<string, unknown> = {}\n fillValues(list.children, item, types)\n return item\n}\n\n/**\n * Give every field of `nodes` that `target` has no value for its starting\n * value, the items of lists included. Values already there stay.\n */\nexport function fillValues(\n nodes: readonly FormNode[],\n target: Record<string, unknown>,\n types: Map<string, FormFieldType>,\n) {\n for (const node of valueNodes(nodes)) {\n if (isList(node)) {\n let items = getPath(target, node.name)\n if (!Array.isArray(items)) {\n setPath(target, node.name, startItems(node, types))\n items = getPath(target, node.name)\n }\n for (const item of items as unknown[])\n if (item !== null && typeof item === 'object')\n fillValues(node.children, item as Record<string, unknown>, types)\n continue\n }\n const type = types.get(node.type)\n if (!type || type.kind === 'display') continue\n if (getPath(target, node.name!) === undefined)\n setPath(target, node.name!, startValue(type, node))\n }\n}\n\nexport interface VisibleValuesOptions {\n /** Functions the expressions can call besides the built-in ones. */\n functions?: Record<string, FormFunction>\n}\n\n/**\n * A copy of the values without the ones of hidden fields: a field, group,\n * page or list whose `visibleWhen` is false leaves out everything it\n * holds, item by item inside lists. This is what the renderer submits.\n */\nexport function visibleValues(\n document: FormDocument,\n model: Record<string, unknown>,\n options: VisibleValuesOptions = {},\n): Record<string, unknown> {\n const copy = cloneValue(model)\n const shown = (node: FormNode, prefix: string, item: unknown) => {\n if (!node.visibleWhen) return true\n const expression = tryCompileExpression(node.visibleWhen)\n if (!expression) return true\n const name = isList(node) || !isContainer(node) ? (node as { name?: string }).name : undefined\n return truthy(\n expression.evaluate({\n values: model,\n value: name ? getPath(model, prefix + name) : null,\n item,\n functions: options.functions,\n }),\n )\n }\n const drop = (nodes: readonly FormNode[], prefix: string) => {\n for (const node of valueNodes(nodes)) unsetPath(copy, prefix + node.name!)\n }\n const visit = (nodes: readonly FormNode[], prefix: string, item: unknown) => {\n for (const node of nodes) {\n if (!shown(node, prefix, item)) {\n drop([node], prefix)\n continue\n }\n if (isList(node)) {\n const items = getPath(model, prefix + node.name)\n if (Array.isArray(items))\n items.forEach((entry, index) =>\n visit(node.children, `${prefix}${node.name}.${index}.`, entry),\n )\n } else if (isContainer(node)) visit(node.children, prefix, item)\n }\n }\n visit(document.nodes, '', null)\n return copy\n}\n"],"mappings":";;;;;AAgBA,SAAgB,WAAW,MAAqB,MAA8B;CAC5E,IAAI,KAAK,YAAY,KAAA,GAAW,OAAO,WAAW,KAAK,OAAO;CAC9D,OAAO,KAAK,QAAQ,KAAK,MAAM,WAAW,MAAM,IAAI,CAAC,IAAI;AAC3D;;AAGA,SAAS,WAAW,MAAoB,OAAmC;CACzE,IAAI,KAAK,YAAY,KAAA,GAAW,OAAO,WAAW,KAAK,OAAO;CAC9D,OAAO,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,KAAK,OAAO,CAAC,EAAE,SAAS,UAAU,MAAM,KAAK,CAAC;AACxF;;AAGA,SAAgB,UACd,MACA,OACyB;CACzB,MAAM,OAAgC,CAAC;CACvC,WAAW,KAAK,UAAU,MAAM,KAAK;CACrC,OAAO;AACT;;;;;AAMA,SAAgB,WACd,OACA,QACA,OACA;CACA,KAAK,MAAM,QAAQ,WAAW,KAAK,GAAG;EACpC,IAAI,OAAO,IAAI,GAAG;GAChB,IAAI,QAAQ,QAAQ,QAAQ,KAAK,IAAI;GACrC,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG;IACzB,QAAQ,QAAQ,KAAK,MAAM,WAAW,MAAM,KAAK,CAAC;IAClD,QAAQ,QAAQ,QAAQ,KAAK,IAAI;GACnC;GACA,KAAK,MAAM,QAAQ,OACjB,IAAI,SAAS,QAAQ,OAAO,SAAS,UACnC,WAAW,KAAK,UAAU,MAAiC,KAAK;GACpE;EACF;EACA,MAAM,OAAO,MAAM,IAAI,KAAK,IAAI;EAChC,IAAI,CAAC,QAAQ,KAAK,SAAS,WAAW;EACtC,IAAI,QAAQ,QAAQ,KAAK,IAAK,MAAM,KAAA,GAClC,QAAQ,QAAQ,KAAK,MAAO,WAAW,MAAM,IAAI,CAAC;CACtD;AACF;;;;;;AAYA,SAAgB,cACd,UACA,OACA,UAAgC,CAAC,GACR;CACzB,MAAM,OAAO,WAAW,KAAK;CAC7B,MAAM,SAAS,MAAgB,QAAgB,SAAkB;EAC/D,IAAI,CAAC,KAAK,aAAa,OAAO;EAC9B,MAAM,aAAa,qBAAqB,KAAK,WAAW;EACxD,IAAI,CAAC,YAAY,OAAO;EACxB,MAAM,OAAO,OAAO,IAAI,KAAK,CAAC,YAAY,IAAI,IAAK,KAA2B,OAAO,KAAA;EACrF,OAAO,OACL,WAAW,SAAS;GAClB,QAAQ;GACR,OAAO,OAAO,QAAQ,OAAO,SAAS,IAAI,IAAI;GAC9C;GACA,WAAW,QAAQ;EACrB,CAAC,CACH;CACF;CACA,MAAM,QAAQ,OAA4B,WAAmB;EAC3D,KAAK,MAAM,QAAQ,WAAW,KAAK,GAAG,UAAU,MAAM,SAAS,KAAK,IAAK;CAC3E;CACA,MAAM,SAAS,OAA4B,QAAgB,SAAkB;EAC3E,KAAK,MAAM,QAAQ,OAAO;GACxB,IAAI,CAAC,MAAM,MAAM,QAAQ,IAAI,GAAG;IAC9B,KAAK,CAAC,IAAI,GAAG,MAAM;IACnB;GACF;GACA,IAAI,OAAO,IAAI,GAAG;IAChB,MAAM,QAAQ,QAAQ,OAAO,SAAS,KAAK,IAAI;IAC/C,IAAI,MAAM,QAAQ,KAAK,GACrB,MAAM,SAAS,OAAO,UACpB,MAAM,KAAK,UAAU,GAAG,SAAS,KAAK,KAAK,GAAG,MAAM,IAAI,KAAK,CAC/D;GACJ,OAAO,IAAI,YAAY,IAAI,GAAG,MAAM,KAAK,UAAU,QAAQ,IAAI;EACjE;CACF;CACA,MAAM,SAAS,OAAO,IAAI,IAAI;CAC9B,OAAO;AACT"}