form0-core 0.3.1 → 0.4.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 (60) hide show
  1. package/README.md +75 -0
  2. package/package.json +5 -2
  3. package/src/authoring/form-authoring.js +124 -13
  4. package/src/builtins/builtin-definitions.js +320 -0
  5. package/src/builtins/choice/choicelabel.js +2 -17
  6. package/src/builtins/choice/choicelabels.js +2 -17
  7. package/src/builtins/choice/choicevalue.js +2 -20
  8. package/src/builtins/choice/choicevalues.js +2 -17
  9. package/src/builtins/choice/hasother.js +2 -20
  10. package/src/builtins/choice/other.js +2 -20
  11. package/src/builtins/control/eval.js +78 -31
  12. package/src/builtins/control/setresult.js +29 -17
  13. package/src/builtins/date/dateadd.js +24 -0
  14. package/src/builtins/date/days.js +22 -0
  15. package/src/builtins/event/control/off.js +3 -27
  16. package/src/builtins/event/control/on.js +3 -19
  17. package/src/builtins/event/event-operations-collector.js +33 -9
  18. package/src/builtins/event/field/setvalue.js +3 -32
  19. package/src/builtins/event/ui/alert.js +3 -11
  20. package/src/builtins/logical/and.js +2 -17
  21. package/src/builtins/logical/array.js +2 -35
  22. package/src/builtins/logical/count.js +2 -20
  23. package/src/builtins/logical/counta.js +2 -20
  24. package/src/builtins/logical/countblank.js +2 -20
  25. package/src/builtins/logical/if.js +2 -20
  26. package/src/builtins/logical/or.js +2 -17
  27. package/src/builtins/math/abs.js +2 -20
  28. package/src/builtins/math/ceiling.js +2 -20
  29. package/src/builtins/math/cos.js +2 -20
  30. package/src/builtins/math/round.js +2 -23
  31. package/src/builtins/math/sin.js +2 -20
  32. package/src/builtins/registry.js +23 -40
  33. package/src/builtins/schema/datanames.js +9 -20
  34. package/src/builtins/schema/form.js +2 -20
  35. package/src/builtins/string/upper.js +2 -20
  36. package/src/engine/calculation-diagnostics.js +107 -0
  37. package/src/engine/calculation.js +65 -32
  38. package/src/engine/evaluator.js +56 -18
  39. package/src/engine/event-operation-scope.js +61 -0
  40. package/src/engine/event-source.js +164 -0
  41. package/src/engine/events.js +105 -19
  42. package/src/engine/form-engine.js +61 -16
  43. package/src/engine/warning-system.js +7 -5
  44. package/src/schema/field-specs.js +2 -2
  45. package/src/security/validation.js +10 -8
  46. package/src/utilities/calculation-editor.js +42 -6
  47. package/src/utilities/date-utils.js +72 -0
  48. package/src/builtins/docs/choice/choicelabel.mdx +0 -110
  49. package/src/builtins/docs/choice/choicelabels.mdx +0 -230
  50. package/src/builtins/docs/choice/choicevalue.mdx +0 -109
  51. package/src/builtins/docs/choice/choicevalues.mdx +0 -196
  52. package/src/builtins/docs/choice/hasother.mdx +0 -133
  53. package/src/builtins/docs/choice/other.mdx +0 -150
  54. package/src/builtins/docs/control/setresult.mdx +0 -203
  55. package/src/builtins/docs/event/field/setvalue.mdx +0 -239
  56. package/src/builtins/docs/index.mdx +0 -234
  57. package/src/builtins/docs/logical/and.mdx +0 -141
  58. package/src/builtins/docs/logical/if.mdx +0 -94
  59. package/src/builtins/docs/logical/or.mdx +0 -191
  60. package/src/builtins/docs/overview.mdx +0 -258
@@ -1,29 +1,11 @@
1
- import { BUILTIN_CONTEXTS, defineBuiltinMetadata } from '../builtin-metadata.js';
1
+ export { CHOICEVALUE_METADATA } from '../builtin-definitions.js';
2
2
 
3
3
  /**
4
4
  * @builtin CHOICEVALUE
5
- * @description Retrieves the currently selected choice field value, preserving the type (if the value is a number preserves the number type, otherwise string). If no selection it returns null.
5
+ * Name, signature, description, examples, and contexts: see builtin-definitions.js.
6
6
  * @param {Object} choiceField - The choice field object with choice and other arrays
7
7
  * @returns {*} The selected choice value with preserved type, or null if no selection
8
- * @example
9
- * // Get the selected city value
10
- * CHOICEVALUE($city)
11
- * @example
12
- * // Use in conditional logic
13
- * IF(CHOICEVALUE($city) === "bogota", "Welcome to Bogotá!", "Welcome!")
14
8
  */
15
- export const CHOICEVALUE_METADATA = defineBuiltinMetadata({
16
- name: 'CHOICEVALUE',
17
- category: 'choice',
18
- signature: 'CHOICEVALUE(fieldValue)',
19
- description: 'Return the selected choice value from a choice field.',
20
- examples: [
21
- 'CHOICEVALUE($city)',
22
- 'IF(CHOICEVALUE($city) === "bogota", "Welcome to Bogota!", "Welcome!")',
23
- ],
24
- contexts: [BUILTIN_CONTEXTS.CALCULATION, BUILTIN_CONTEXTS.EVENT],
25
- });
26
-
27
9
  export const CHOICEVALUE = (choiceField) => {
28
10
  // Handle null/undefined input
29
11
  if (!choiceField || typeof choiceField !== 'object') {
@@ -1,26 +1,11 @@
1
- import { BUILTIN_CONTEXTS, defineBuiltinMetadata } from '../builtin-metadata.js';
1
+ export { CHOICEVALUES_METADATA } from '../builtin-definitions.js';
2
2
 
3
3
  /**
4
4
  * @builtin CHOICEVALUES
5
- * @description Retrieves an array of all selected choice field values from a MultiChoiceField, preserving the type of each value. Returns an empty array if no selections.
5
+ * Name, signature, description, examples, and contexts: see builtin-definitions.js.
6
6
  * @param {Object} multiChoiceField - The multi choice field object with choices and other arrays
7
7
  * @returns {Array} Array of selected choice values with preserved types
8
- * @example
9
- * // Get all selected color values
10
- * CHOICEVALUES($colors)
11
- * @example
12
- * // Check if "red" is selected
13
- * CHOICEVALUES($colors).includes("red")
14
8
  */
15
- export const CHOICEVALUES_METADATA = defineBuiltinMetadata({
16
- name: 'CHOICEVALUES',
17
- category: 'choice',
18
- signature: 'CHOICEVALUES(fieldValue)',
19
- description: 'Return all selected values from a MultiChoiceField.',
20
- examples: ['CHOICEVALUES($colors)', 'CHOICEVALUES($colors).includes("red")'],
21
- contexts: [BUILTIN_CONTEXTS.CALCULATION, BUILTIN_CONTEXTS.EVENT],
22
- });
23
-
24
9
  export const CHOICEVALUES = (multiChoiceField) => {
25
10
  // Handle null/undefined input
26
11
  if (!multiChoiceField || typeof multiChoiceField !== 'object') {
@@ -1,29 +1,11 @@
1
- import { BUILTIN_CONTEXTS, defineBuiltinMetadata } from '../builtin-metadata.js';
1
+ export { HASOTHER_METADATA } from '../builtin-definitions.js';
2
2
 
3
3
  /**
4
4
  * @builtin HASOTHER
5
- * @description Returns true if user entered an other option, false otherwise. Works with both SingleChoiceField and MultiChoiceField.
5
+ * Name, signature, description, examples, and contexts: see builtin-definitions.js.
6
6
  * @param {Object} choiceField - The choice field object (SingleChoiceField with choice array or MultiChoiceField with choices array) and other array
7
7
  * @returns {boolean} True if user entered an other option, false otherwise
8
- * @example
9
- * // Check if user entered other option in single choice field
10
- * HASOTHER($city)
11
- * @example
12
- * // Check if user entered other option in multi choice field
13
- * HASOTHER($colors)
14
- * @example
15
- * // Use in conditional logic
16
- * IF(HASOTHER($city), "Custom city: " + OTHER($city), "Selected city: " + CHOICELABEL($city))
17
8
  */
18
- export const HASOTHER_METADATA = defineBuiltinMetadata({
19
- name: 'HASOTHER',
20
- category: 'choice',
21
- signature: 'HASOTHER(fieldValue)',
22
- description: 'Return true when the field has an "other" value.',
23
- examples: ['HASOTHER($city)'],
24
- contexts: [BUILTIN_CONTEXTS.CALCULATION, BUILTIN_CONTEXTS.EVENT],
25
- });
26
-
27
9
  export const HASOTHER = (choiceField) => {
28
10
  // Handle null/undefined input
29
11
  if (!choiceField || typeof choiceField !== 'object') {
@@ -1,29 +1,11 @@
1
- import { BUILTIN_CONTEXTS, defineBuiltinMetadata } from '../builtin-metadata.js';
1
+ export { OTHER_METADATA } from '../builtin-definitions.js';
2
2
 
3
3
  /**
4
4
  * @builtin OTHER
5
- * @description Retrieves the other label if user entered the other option, otherwise null. Works with both SingleChoiceField and MultiChoiceField.
5
+ * Name, signature, description, examples, and contexts: see builtin-definitions.js.
6
6
  * @param {Object} choiceField - The choice field object (SingleChoiceField with choice array or MultiChoiceField with choices array) and other array
7
7
  * @returns {string|null} The other label if user entered an other option, null otherwise
8
- * @example
9
- * // Get the other value from single choice field
10
- * OTHER($city)
11
- * @example
12
- * // Get the other value from multi choice field
13
- * OTHER($colors)
14
- * @example
15
- * // Use in conditional logic
16
- * IF(HASOTHER($city), "Custom city: " + OTHER($city), "No custom city entered")
17
8
  */
18
- export const OTHER_METADATA = defineBuiltinMetadata({
19
- name: 'OTHER',
20
- category: 'choice',
21
- signature: 'OTHER(fieldValue)',
22
- description: 'Return the "other" label from a choice field.',
23
- examples: ['OTHER($city)'],
24
- contexts: [BUILTIN_CONTEXTS.CALCULATION, BUILTIN_CONTEXTS.EVENT],
25
- });
26
-
27
9
  export const OTHER = (choiceField) => {
28
10
  // Handle null/undefined input
29
11
  if (!choiceField || typeof choiceField !== 'object') {
@@ -1,40 +1,33 @@
1
1
  import { validateExpression } from '../../security/validation.js';
2
2
  import { SAFE_SECURITY_CONFIG } from '../../security/config.js';
3
- import { BUILTIN_CONTEXTS, defineBuiltinMetadata } from '../builtin-metadata.js';
3
+ export { EVAL_METADATA } from '../builtin-definitions.js';
4
4
 
5
5
  /**
6
6
  * @builtin EVAL
7
- * @description Evaluates a dynamic expression in a controlled, secure context
7
+ * Name, signature, description, examples, and contexts: see builtin-definitions.js.
8
8
  * @param {string} expression - The dynamic expression to evaluate
9
9
  * @returns {*} The result of the evaluated expression
10
- * @example
11
- * // Dynamic field access
12
- * EVAL('$' + dynamicFieldName)
13
- * @example
14
- * // Dynamic builtin calls
15
- * EVAL('CHOICEVALUE($' + fieldVar + ')')
16
- * @example
17
- * // Computed field references
18
- * EVAL('$city' + '_suffix')
19
10
  */
20
- export const EVAL_METADATA = defineBuiltinMetadata({
21
- name: 'EVAL',
22
- category: 'control',
23
- signature: 'EVAL(expression)',
24
- description: 'Evaluate a dynamically built expression string in a restricted context.',
25
- examples: ['EVAL("$" + dynamicFieldName)'],
26
- contexts: [BUILTIN_CONTEXTS.CALCULATION, BUILTIN_CONTEXTS.EVENT],
27
- });
28
-
29
11
  // Global context for EVAL() - set during expression evaluation
30
12
  let _evalContext = null;
13
+ let _evalReporting = null;
31
14
 
32
15
  /**
33
16
  * Set the context for EVAL() during expression evaluation
34
17
  * Called internally by the expression evaluator
35
18
  */
36
- export function __setEvalContext(context) {
19
+ export function __setEvalContext(context, reporting = null) {
20
+ const previousContext = _evalContext;
21
+ const previousReporting = _evalReporting;
37
22
  _evalContext = context;
23
+ _evalReporting = reporting;
24
+ let restored = false;
25
+ return () => {
26
+ if (restored) return;
27
+ restored = true;
28
+ _evalContext = previousContext;
29
+ _evalReporting = previousReporting;
30
+ };
38
31
  }
39
32
 
40
33
  /**
@@ -43,24 +36,54 @@ export function __setEvalContext(context) {
43
36
  */
44
37
  export function __clearEvalContext() {
45
38
  _evalContext = null;
39
+ _evalReporting = null;
40
+ }
41
+
42
+ function reportFailure(code, phase, message, consoleArgs, context = {}) {
43
+ _evalReporting?.onDiagnostic?.({
44
+ code,
45
+ phase,
46
+ message,
47
+ context: { source: 'EVAL', ...context },
48
+ });
49
+ if (!_evalReporting?.suppressConsole) console.warn(...consoleArgs);
50
+ }
51
+
52
+ function logDebug(...args) {
53
+ if (!_evalReporting?.suppressConsole) console.log(...args);
54
+ }
55
+
56
+ function referenceContext(reference) {
57
+ // Legacy EVAL also treats strings starting with '$' as reference lookups.
58
+ // Do not copy a whole expression into diagnostic metadata in that case.
59
+ return /^\$[a-zA-Z_][a-zA-Z0-9_]*$/.test(reference)
60
+ ? { referencedFieldName: reference.slice(1) }
61
+ : {};
46
62
  }
47
63
 
48
64
  export const EVAL = (expression) => {
49
65
  // Step 1: Basic validation
50
66
  if (typeof expression !== 'string') {
51
- console.warn('[form0] EVAL() requires a string expression');
67
+ reportFailure('eval_invalid_argument', 'validation', 'EVAL() requires a string expression', [
68
+ '[form0] EVAL() requires a string expression',
69
+ ]);
52
70
  return null;
53
71
  }
54
72
 
55
73
  if (!expression || expression.trim() === '') {
56
- console.warn('[form0] EVAL() requires a non-empty expression');
74
+ reportFailure('eval_invalid_argument', 'validation', 'EVAL() requires a non-empty expression', [
75
+ '[form0] EVAL() requires a non-empty expression',
76
+ ]);
57
77
  return null;
58
78
  }
59
79
 
60
80
  // Step 2: Security validation for EVAL() expressions
61
81
  const securityValidation = validateEvalExpression(expression);
62
82
  if (!securityValidation.valid) {
63
- console.warn('[form0] EVAL() security validation failed:', securityValidation.reason);
83
+ reportFailure('expression_validation_failed', 'validation', securityValidation.reason, [
84
+ '[form0] EVAL() security validation failed:',
85
+ securityValidation.reason,
86
+ ]);
64
87
  return null;
65
88
  }
66
89
 
@@ -147,12 +170,13 @@ function validateEvalExpression(expression) {
147
170
  * @returns {*} The evaluation result
148
171
  */
149
172
  function evaluateHybridExpression(expression, formContext) {
173
+ let phase = 'runtime';
150
174
  try {
151
175
  // Step 3a: Handle simple string literals and field references directly
152
176
  if (isSimpleStringLiteral(expression)) {
153
177
  // Simple string literal - return as is
154
178
  const result = expression;
155
- console.log('[form0] EVAL() simple string:', expression, '→', result);
179
+ logDebug('[form0] EVAL() simple string:', expression, '→', result);
156
180
  return result;
157
181
  }
158
182
 
@@ -160,10 +184,16 @@ function evaluateHybridExpression(expression, formContext) {
160
184
  // Field reference - resolve from context
161
185
  const fieldValue = formContext?.[expression];
162
186
  if (fieldValue !== undefined) {
163
- console.log('[form0] EVAL() resolved field reference:', expression, '→', fieldValue);
187
+ logDebug('[form0] EVAL() resolved field reference:', expression, '→', fieldValue);
164
188
  return fieldValue;
165
189
  } else {
166
- console.warn('[form0] EVAL() field reference not found:', expression);
190
+ reportFailure(
191
+ 'eval_reference_unavailable',
192
+ 'scope',
193
+ 'EVAL() field reference is unavailable.',
194
+ ['[form0] EVAL() field reference not found:', expression],
195
+ referenceContext(expression)
196
+ );
167
197
  return null;
168
198
  }
169
199
  }
@@ -183,7 +213,9 @@ function evaluateHybridExpression(expression, formContext) {
183
213
  const keys = Object.keys(basicContext);
184
214
  const values = Object.values(basicContext);
185
215
 
216
+ phase = 'syntax';
186
217
  const fn = new Function(...keys, `return (${expression});`);
218
+ phase = 'runtime';
187
219
  const result = fn(...values);
188
220
 
189
221
  // Step 3e: Check if result should be resolved as field reference
@@ -191,7 +223,7 @@ function evaluateHybridExpression(expression, formContext) {
191
223
  // This is a field reference - resolve it from context
192
224
  const fieldValue = formContext?.[result];
193
225
  if (fieldValue !== undefined) {
194
- console.log(
226
+ logDebug(
195
227
  '[form0] EVAL() resolved field reference:',
196
228
  expression,
197
229
  '→',
@@ -201,16 +233,31 @@ function evaluateHybridExpression(expression, formContext) {
201
233
  );
202
234
  return fieldValue;
203
235
  } else {
204
- console.warn('[form0] EVAL() field reference not found:', result);
236
+ reportFailure(
237
+ 'eval_reference_unavailable',
238
+ 'scope',
239
+ 'EVAL() field reference is unavailable.',
240
+ ['[form0] EVAL() field reference not found:', result],
241
+ referenceContext(result)
242
+ );
205
243
  return null;
206
244
  }
207
245
  }
208
246
 
209
247
  // Step 3f: Return string result for dataname references
210
- console.log('[form0] EVAL() built string:', expression, '→', result);
248
+ logDebug('[form0] EVAL() built string:', expression, '→', result);
211
249
  return result;
212
250
  } catch (error) {
213
- console.warn('[form0] EVAL() execution failed:', error.message);
251
+ reportFailure(
252
+ phase === 'syntax'
253
+ ? error instanceof SyntaxError
254
+ ? 'invalid_syntax'
255
+ : 'compilation_error'
256
+ : 'runtime_exception',
257
+ phase,
258
+ error instanceof Error && error.message ? error.message : 'Unknown EVAL() runtime error.',
259
+ ['[form0] EVAL() execution failed:', error.message]
260
+ );
214
261
  return null;
215
262
  }
216
263
  }
@@ -1,30 +1,16 @@
1
- import { BUILTIN_CONTEXTS, defineBuiltinMetadata } from '../builtin-metadata.js';
1
+ export { SETRESULT_METADATA } from '../builtin-definitions.js';
2
2
 
3
3
  // Global state for result management
4
4
  let _resultSet = false;
5
5
  let _resultValue;
6
+ let _resultScopeDepth = 0;
6
7
 
7
8
  /**
8
9
  * @builtin SETRESULT
9
- * @description Sets a result value that can be consumed by the form engine
10
+ * Name, signature, description, examples, and contexts: see builtin-definitions.js.
10
11
  * @param {*} value - The value to set as the result
11
12
  * @returns {*} The same value that was passed in
12
- * @example
13
- * // Set a calculation result
14
- * SETRESULT(price * quantity * (1 + taxRate))
15
- * @example
16
- * // Set a conditional result
17
- * SETRESULT(IF(isEligible, discount, 0))
18
13
  */
19
- export const SETRESULT_METADATA = defineBuiltinMetadata({
20
- name: 'SETRESULT',
21
- category: 'control',
22
- signature: 'SETRESULT(value)',
23
- description: 'Set the final return value for multiline calculations.',
24
- examples: ['SETRESULT($price * $quantity)'],
25
- contexts: [BUILTIN_CONTEXTS.CALCULATION],
26
- });
27
-
28
14
  export const SETRESULT = (value) => {
29
15
  _resultSet = true;
30
16
  _resultValue = value;
@@ -43,6 +29,32 @@ export function __consumeResult() {
43
29
  return { called, value };
44
30
  }
45
31
 
32
+ /**
33
+ * Start an isolated synchronous result scope and return its restoration function.
34
+ * Nested scopes restore the enclosing SETRESULT state; top-level scopes retain the
35
+ * legacy behavior of clearing stale state at both evaluation boundaries.
36
+ * @returns {() => void}
37
+ */
38
+ export function __beginResultScope() {
39
+ const previous = { called: _resultSet, value: _resultValue };
40
+ const restorePrevious = _resultScopeDepth > 0;
41
+ _resultScopeDepth += 1;
42
+ __resetResult();
43
+
44
+ let restored = false;
45
+ return () => {
46
+ if (restored) return;
47
+ restored = true;
48
+ _resultScopeDepth = Math.max(0, _resultScopeDepth - 1);
49
+ if (restorePrevious) {
50
+ _resultSet = previous.called;
51
+ _resultValue = previous.value;
52
+ } else {
53
+ __resetResult();
54
+ }
55
+ };
56
+ }
57
+
46
58
  /** Clear calculation result state without consuming it. */
47
59
  export function __resetResult() {
48
60
  _resultSet = false;
@@ -0,0 +1,24 @@
1
+ import { formatIsoDate, isBlankDateArgument, parseIsoDate } from '../../utilities/date-utils.js';
2
+
3
+ export { DATEADD_METADATA } from '../builtin-definitions.js';
4
+
5
+ /**
6
+ * @builtin DATEADD
7
+ * Name, signature, description, examples, and contexts: see builtin-definitions.js.
8
+ * @param {string | null} date - A YYYY-MM-DD date
9
+ * @param {number | null} days - Whole number of days to add; negative values subtract
10
+ * @returns {string | null} The resulting YYYY-MM-DD date, or null when an argument is blank
11
+ */
12
+ export const DATEADD = (date, days) => {
13
+ if (isBlankDateArgument(date) || isBlankDateArgument(days)) return null;
14
+
15
+ const dayNumber = parseIsoDate(date);
16
+ if (dayNumber === null) {
17
+ throw new Error('DATEADD requires a date in YYYY-MM-DD format');
18
+ }
19
+ if (!Number.isInteger(days)) {
20
+ throw new Error('DATEADD requires a whole number of days');
21
+ }
22
+
23
+ return formatIsoDate(dayNumber + days);
24
+ };
@@ -0,0 +1,22 @@
1
+ import { isBlankDateArgument, parseIsoDate } from '../../utilities/date-utils.js';
2
+
3
+ export { DAYS_METADATA } from '../builtin-definitions.js';
4
+
5
+ /**
6
+ * @builtin DAYS
7
+ * Name, signature, description, examples, and contexts: see builtin-definitions.js.
8
+ * @param {string | null} endDate - A YYYY-MM-DD date
9
+ * @param {string | null} startDate - A YYYY-MM-DD date
10
+ * @returns {number | null} Days from startDate to endDate, or null when an argument is blank
11
+ */
12
+ export const DAYS = (endDate, startDate) => {
13
+ if (isBlankDateArgument(endDate) || isBlankDateArgument(startDate)) return null;
14
+
15
+ const end = parseIsoDate(endDate);
16
+ const start = parseIsoDate(startDate);
17
+ if (end === null || start === null) {
18
+ throw new Error('DAYS requires dates in YYYY-MM-DD format');
19
+ }
20
+
21
+ return end - start;
22
+ };
@@ -1,9 +1,10 @@
1
1
  import { __collectEventOperation } from '../event-operations-collector.js';
2
- import { BUILTIN_CONTEXTS, defineBuiltinMetadata } from '../../builtin-metadata.js';
2
+ export { OFF_METADATA } from '../../builtin-definitions.js';
3
3
  import { isValidEventType } from '../../../engine/event-registry.js';
4
4
 
5
5
  /**
6
- * OFF builtin for form events
6
+ * @builtin OFF
7
+ * Name, signature, description, examples, and contexts: see builtin-definitions.js.
7
8
  * Removes event handlers within event context
8
9
  * Automatically collects operation for platform execution
9
10
  *
@@ -11,32 +12,7 @@ import { isValidEventType } from '../../../engine/event-registry.js';
11
12
  * @param {string|Function} [fieldKeyOrCallback] - Field key for field events, or callback for global events
12
13
  * @param {Function} [callback] - Callback function for field events (when fieldKeyOrCallback is a field key)
13
14
  * @returns {Object} Operation descriptor for platform execution (for backward compatibility)
14
- *
15
- * @example
16
- * // Remove specific global event handler
17
- * OFF('load-record', specificCallback);
18
- *
19
- * @example
20
- * // Remove all global event handlers
21
- * OFF('load-record');
22
- *
23
- * @example
24
- * // Remove specific field event handler
25
- * OFF('change', 'city', specificCallback);
26
- *
27
- * @example
28
- * // Remove all field event handlers
29
- * OFF('change', 'city');
30
15
  */
31
- export const OFF_METADATA = defineBuiltinMetadata({
32
- name: 'OFF',
33
- category: 'event',
34
- signature: 'OFF(eventType, fieldKeyOrCallback, callback)',
35
- description: 'Remove event handlers within form event code.',
36
- examples: ["OFF('change', 'city', specificCallback)"],
37
- contexts: [BUILTIN_CONTEXTS.EVENT],
38
- });
39
-
40
16
  export function OFF(eventType, fieldKeyOrCallback, callback) {
41
17
  if (typeof eventType !== 'string' || eventType.trim().length === 0) {
42
18
  console.warn('[form0] OFF() requires a non-empty event type string');
@@ -1,9 +1,10 @@
1
1
  import { __collectEventOperation } from '../event-operations-collector.js';
2
- import { BUILTIN_CONTEXTS, defineBuiltinMetadata } from '../../builtin-metadata.js';
2
+ export { ON_METADATA } from '../../builtin-definitions.js';
3
3
  import { isValidEventType } from '../../../engine/event-registry.js';
4
4
 
5
5
  /**
6
- * ON builtin for form events
6
+ * @builtin ON
7
+ * Name, signature, description, examples, and contexts: see builtin-definitions.js.
7
8
  * Registers an event handler within event context
8
9
  * Automatically collects operation for platform execution
9
10
  *
@@ -11,24 +12,7 @@ import { isValidEventType } from '../../../engine/event-registry.js';
11
12
  * @param {string|Function} fieldKeyOrCallback - Field key for field events, or callback for global events
12
13
  * @param {Function} [callback] - Callback function for field events
13
14
  * @returns {Object} Operation descriptor for platform execution (for backward compatibility)
14
- *
15
- * @example
16
- * // Register global event handler
17
- * ON('load-record', function() { ALERT('Record loaded!'); });
18
- *
19
- * @example
20
- * // Register field-specific event handler
21
- * ON('change', 'city', function(event) { ALERT('City changed!'); });
22
15
  */
23
- export const ON_METADATA = defineBuiltinMetadata({
24
- name: 'ON',
25
- category: 'event',
26
- signature: 'ON(eventType, fieldKeyOrCallback, callback)',
27
- description: 'Register an event handler within form event code.',
28
- examples: ["ON('change', 'city', function(event) { ALERT('City changed!'); })"],
29
- contexts: [BUILTIN_CONTEXTS.EVENT],
30
- });
31
-
32
16
  export function ON(eventType, fieldKeyOrCallback, callback) {
33
17
  if (typeof eventType !== 'string' || eventType.trim().length === 0) {
34
18
  console.warn('[form0] ON() requires a non-empty event type string');
@@ -1,20 +1,44 @@
1
- // Global state for event operations collection
2
- let _eventOperations = [];
1
+ let activeScope = null;
2
+ let boundScope = null;
3
3
 
4
4
  /**
5
5
  * Internal function to collect event operations
6
6
  * @param {Object} operation - The operation descriptor to collect
7
7
  */
8
8
  export function __collectEventOperation(operation) {
9
- _eventOperations.push(operation);
9
+ const scope = boundScope || activeScope;
10
+ scope?.collect(operation);
10
11
  }
11
12
 
12
13
  /**
13
- * Internal function to consume all collected event operations
14
- * @returns {Array} Array of collected operations
14
+ * Run synchronous event work with a dispatch-owned operation scope.
15
+ * @param {Object} scope - The scope receiving operations
16
+ * @param {Function} work - Synchronous work to execute
17
+ * @returns {*} The work result
15
18
  */
16
- export function __consumeEventOperations() {
17
- const operations = [..._eventOperations]; // Copy array
18
- _eventOperations = []; // Clear for next execution
19
- return operations;
19
+ export function __runWithEventOperationScope(scope, work) {
20
+ const enclosingScope = activeScope;
21
+ activeScope = scope;
22
+ try {
23
+ return work();
24
+ } finally {
25
+ activeScope = enclosingScope;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Invoke a canonical builtin with the scope it was bound to.
31
+ * @param {Object} scope - The builtin's originating scope
32
+ * @param {Function} builtin - Canonical event builtin
33
+ * @param {Array} args - Builtin arguments
34
+ * @returns {*} The builtin result
35
+ */
36
+ export function __invokeBoundEventBuiltin(scope, builtin, args) {
37
+ const enclosingScope = boundScope;
38
+ boundScope = scope;
39
+ try {
40
+ return builtin(...args);
41
+ } finally {
42
+ boundScope = enclosingScope;
43
+ }
20
44
  }
@@ -1,45 +1,16 @@
1
1
  import { __collectEventOperation } from '../event-operations-collector.js';
2
- import { BUILTIN_CONTEXTS, defineBuiltinMetadata } from '../../builtin-metadata.js';
2
+ export { SETVALUE_METADATA } from '../../builtin-definitions.js';
3
3
 
4
4
  /**
5
- * SETVALUE builtin for form events
5
+ * @builtin SETVALUE
6
+ * Name, signature, description, examples, and contexts: see builtin-definitions.js.
6
7
  * Sets a field value within event context
7
8
  * Automatically collects operation for platform execution
8
- * Supports multiple field types:
9
- * - TextField: string values
10
- * - NumericField: numeric values
11
- * - SingleChoiceField: string choice values
12
- * - MultiChoiceField: array of string choice values
13
9
  *
14
10
  * @param {string} fieldDataName - The data name of the field to set
15
11
  * @param {any} valueToSet - The value to set (string, number, array, etc.)
16
12
  * @returns {Object} Operation descriptor for platform execution (for backward compatibility)
17
- *
18
- * @example
19
- * // Set TextField value
20
- * SETVALUE('field_dataname', 'value_to_set')
21
- *
22
- * @example
23
- * // Set NumericField value
24
- * SETVALUE('field_dataname', 12)
25
- *
26
- * @example
27
- * // Set SingleChoiceField value
28
- * SETVALUE('field_dataname', 'choicefield_value_to_set')
29
- *
30
- * @example
31
- * // Set MultiChoiceField value
32
- * SETVALUE('field_dataname', ['value1', 'value2', 'value3'])
33
13
  */
34
- export const SETVALUE_METADATA = defineBuiltinMetadata({
35
- name: 'SETVALUE',
36
- category: 'event',
37
- signature: 'SETVALUE(fieldDataName, valueToSet)',
38
- description: 'Set a field value from a form event handler.',
39
- examples: ["SETVALUE('field_dataname', 'value_to_set')"],
40
- contexts: [BUILTIN_CONTEXTS.EVENT],
41
- });
42
-
43
14
  export function SETVALUE(fieldDataName, valueToSet) {
44
15
  // Create operation descriptor
45
16
  const operation = {
@@ -1,22 +1,14 @@
1
1
  import { __collectEventOperation } from '../event-operations-collector.js';
2
- import { BUILTIN_CONTEXTS, defineBuiltinMetadata } from '../../builtin-metadata.js';
2
+ export { ALERT_METADATA } from '../../builtin-definitions.js';
3
3
 
4
4
  /**
5
- * ALERT builtin for form events
5
+ * @builtin ALERT
6
+ * Name, signature, description, examples, and contexts: see builtin-definitions.js.
6
7
  * Automatically collects operation for platform execution
7
8
  * @param {string} title - The title to display in the alert
8
9
  * @param {string} message - The message to display in the alert (optional)
9
10
  * @returns {Object} Operation descriptor for platform execution (for backward compatibility)
10
11
  */
11
- export const ALERT_METADATA = defineBuiltinMetadata({
12
- name: 'ALERT',
13
- category: 'event',
14
- signature: "ALERT(title, message = '')",
15
- description: 'Display an alert from a form event handler.',
16
- examples: ["ALERT('Saved', 'The record was saved successfully.')"],
17
- contexts: [BUILTIN_CONTEXTS.EVENT],
18
- });
19
-
20
12
  export function ALERT(title, message = '') {
21
13
  // Create operation descriptor
22
14
  const operation = {