@astroapps/forms-core 1.2.3 → 2.0.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.
package/src/formState.ts DELETED
@@ -1,529 +0,0 @@
1
- import { FormNode, lookupDataNode } from "./formNode";
2
- import {
3
- hideDisplayOnly,
4
- SchemaDataNode,
5
- validDataNode,
6
- } from "./schemaDataNode";
7
- import {
8
- AnyControlDefinition,
9
- ControlAdornmentType,
10
- ControlDefinition,
11
- DataRenderType,
12
- DynamicPropertyType,
13
- HtmlDisplay,
14
- isActionControl,
15
- isControlDisabled,
16
- isControlReadonly,
17
- isDataControl,
18
- isDisplayControl,
19
- isHtmlDisplay,
20
- isTextDisplay,
21
- TextDisplay,
22
- } from "./controlDefinition";
23
- import { SchemaInterface } from "./schemaInterface";
24
- import { FieldOption } from "./schemaField";
25
- import {
26
- CleanupScope,
27
- Control,
28
- createScopedEffect,
29
- createSyncEffect,
30
- ensureMetaValue,
31
- getControlPath,
32
- getCurrentFields,
33
- getMetaValue,
34
- newControl,
35
- unsafeRestoreControl,
36
- updateComputedValue,
37
- } from "@astroapps/controls";
38
- import {
39
- defaultEvaluators,
40
- ExpressionEval,
41
- ExpressionEvalContext,
42
- } from "./evalExpression";
43
- import { EntityExpression } from "./entityExpression";
44
- import { createScoped, jsonPathString } from "./util";
45
- import { setupValidation } from "./validators";
46
-
47
- export interface ControlState {
48
- definition: ControlDefinition;
49
- schemaInterface: SchemaInterface;
50
- dataNode?: SchemaDataNode | undefined;
51
- display?: string;
52
- stateId?: string;
53
- style?: object;
54
- layoutStyle?: object;
55
- allowedOptions?: any[];
56
- readonly: boolean;
57
- hidden: boolean;
58
- disabled: boolean;
59
- clearHidden: boolean;
60
- variables: Record<string, any>;
61
- meta: Control<Record<string, any>>;
62
- }
63
-
64
- export interface FormContextOptions {
65
- readonly?: boolean | null;
66
- hidden?: boolean | null;
67
- disabled?: boolean | null;
68
- clearHidden?: boolean;
69
- stateKey?: string;
70
- variables?: Record<string, any>;
71
- }
72
-
73
- /**
74
- * Interface representing the form context data.
75
- */
76
- export interface FormContextData {
77
- option?: FieldOption;
78
- optionSelected?: boolean;
79
- }
80
-
81
- export interface FormState {
82
- getControlState(
83
- parent: SchemaDataNode,
84
- formNode: FormNode,
85
- context: FormContextOptions,
86
- runAsync: (af: () => void) => void,
87
- ): ControlState;
88
-
89
- cleanup(): void;
90
-
91
- evalExpression(expr: EntityExpression, context: ExpressionEvalContext): void;
92
-
93
- getExistingControlState(
94
- parent: SchemaDataNode,
95
- formNode: FormNode,
96
- stateKey?: string,
97
- ): ControlState | undefined;
98
- }
99
-
100
- const formStates: FormState[] = [];
101
-
102
- export function getControlStateId(
103
- parent: SchemaDataNode,
104
- formNode: FormNode,
105
- stateKey?: string,
106
- ): string {
107
- return parent.id + "$" + formNode.id + (stateKey ?? "");
108
- }
109
-
110
- export function createFormState(
111
- schemaInterface: SchemaInterface,
112
- evaluators: Record<string, ExpressionEval<any>> = defaultEvaluators,
113
- ): FormState {
114
- // console.log("createFormState");
115
- const controlStates = newControl<Record<string, FormContextOptions>>({});
116
-
117
- function evalExpression(
118
- e: EntityExpression,
119
- context: ExpressionEvalContext,
120
- ): void {
121
- const x = evaluators[e.type];
122
- x?.(e, context);
123
- }
124
-
125
- return {
126
- evalExpression,
127
- cleanup: () => {
128
- // console.log("Cleanup form state");
129
- controlStates.cleanup();
130
- },
131
- getExistingControlState(
132
- parent: SchemaDataNode,
133
- formNode: FormNode,
134
- stateKey?: string,
135
- ): ControlState | undefined {
136
- const stateId = getControlStateId(parent, formNode, stateKey);
137
- const control = getCurrentFields(controlStates)[stateId];
138
- if (control) {
139
- return getMetaValue<Control<ControlState>>(control, "impl")?.value;
140
- }
141
- return undefined;
142
- },
143
- getControlState(
144
- parent: SchemaDataNode,
145
- formNode: FormNode,
146
- context: FormContextOptions,
147
- runAsync: (af: () => void) => void,
148
- ): ControlState {
149
- const stateId = getControlStateId(parent, formNode, context.stateKey);
150
- const controlImpl = controlStates.fields[stateId];
151
- controlImpl.value = context;
152
- function evalExpr<A>(
153
- scope: CleanupScope,
154
- init: A,
155
- nk: Control<A>,
156
- e: EntityExpression | undefined,
157
- coerce: (t: unknown) => any,
158
- ): boolean {
159
- nk.value = init;
160
- if (e?.type) {
161
- evalExpression(e, {
162
- returnResult: (r) => {
163
- nk.value = coerce(r);
164
- },
165
- scope,
166
- dataNode: parent,
167
- variables: controlImpl.fields.variables,
168
- schemaInterface,
169
- runAsync,
170
- });
171
- return true;
172
- }
173
- return false;
174
- }
175
-
176
- return createScopedMetaValue(formNode, controlImpl, "impl", (scope) => {
177
- const cf = controlImpl.fields;
178
- const definitionOverrides = createScoped<Record<string, any>>(
179
- controlImpl,
180
- {},
181
- );
182
- const displayControl = createScoped<string | undefined>(
183
- controlImpl,
184
- undefined,
185
- );
186
-
187
- const displayOverrides = createScoped<Record<string, any>>(
188
- controlImpl,
189
- {},
190
- );
191
- const def = formNode.definition;
192
- const definition = createOverrideProxy(def, definitionOverrides);
193
- const of = definitionOverrides.fields as Record<
194
- KeysOfUnion<AnyControlDefinition>,
195
- Control<any>
196
- >;
197
-
198
- const { text, html } = displayOverrides.fields as Record<
199
- KeysOfUnion<TextDisplay | HtmlDisplay>,
200
- Control<any>
201
- >;
202
-
203
- updateComputedValue(of.displayData, () =>
204
- isDisplayControl(def)
205
- ? createOverrideProxy(def.displayData, displayOverrides)
206
- : undefined,
207
- );
208
-
209
- createScopedEffect((c) => {
210
- evalExpr(
211
- c,
212
- def.hidden,
213
- of.hidden,
214
- firstExpr(formNode, DynamicPropertyType.Visible),
215
- (r) => !r,
216
- );
217
- }, definitionOverrides);
218
-
219
- createScopedEffect((c) => {
220
- evalExpr(
221
- c,
222
- isControlReadonly(def),
223
- of.readonly,
224
- firstExpr(formNode, DynamicPropertyType.Readonly),
225
- (r) => !!r,
226
- );
227
- }, definitionOverrides);
228
-
229
- createScopedEffect((c) => {
230
- evalExpr(
231
- c,
232
- isControlDisabled(def),
233
- of.disabled,
234
- firstExpr(formNode, DynamicPropertyType.Disabled),
235
- (r) => !!r,
236
- );
237
- }, definitionOverrides);
238
-
239
- createScopedEffect((c) => {
240
- evalExpr(
241
- c,
242
- isDataControl(def) ? def.defaultValue : undefined,
243
- of.defaultValue,
244
- isDataControl(def)
245
- ? firstExpr(formNode, DynamicPropertyType.DefaultValue)
246
- : undefined,
247
- (r) => r,
248
- );
249
- }, definitionOverrides);
250
-
251
- createScopedEffect((c) => {
252
- evalExpr(
253
- c,
254
- isActionControl(def) ? def.actionData : undefined,
255
- of.actionData,
256
- isActionControl(def)
257
- ? firstExpr(formNode, DynamicPropertyType.ActionData)
258
- : undefined,
259
- (r) => r,
260
- );
261
- }, definitionOverrides);
262
-
263
- createScopedEffect((c) => {
264
- evalExpr(
265
- c,
266
- def.title,
267
- of.title,
268
- firstExpr(formNode, DynamicPropertyType.Label),
269
- coerceString,
270
- );
271
- }, definitionOverrides);
272
-
273
- const control = createScoped<ControlState>(controlImpl, {
274
- definition,
275
- dataNode: undefined,
276
- schemaInterface,
277
- disabled: false,
278
- readonly: false,
279
- clearHidden: false,
280
- hidden: false,
281
- variables: controlImpl.fields.variables.current.value ?? {},
282
- stateId,
283
- meta: newControl({}),
284
- });
285
-
286
- const {
287
- dataNode,
288
- hidden,
289
- readonly,
290
- style,
291
- layoutStyle,
292
- allowedOptions,
293
- disabled,
294
- variables,
295
- display,
296
- } = control.fields;
297
-
298
- createScopedEffect(
299
- (c) =>
300
- evalExpr(
301
- c,
302
- undefined,
303
- style,
304
- firstExpr(formNode, DynamicPropertyType.Style),
305
- coerceStyle,
306
- ),
307
- scope,
308
- );
309
-
310
- createScopedEffect(
311
- (c) =>
312
- evalExpr(
313
- c,
314
- undefined,
315
- layoutStyle,
316
- firstExpr(formNode, DynamicPropertyType.LayoutStyle),
317
- coerceStyle,
318
- ),
319
- scope,
320
- );
321
-
322
- createScopedEffect(
323
- (c) =>
324
- evalExpr(
325
- c,
326
- undefined,
327
- allowedOptions,
328
- firstExpr(formNode, DynamicPropertyType.AllowedOptions),
329
- (x) => x,
330
- ),
331
- scope,
332
- );
333
-
334
- createScopedEffect(
335
- (c) =>
336
- evalExpr(
337
- c,
338
- undefined,
339
- display,
340
- firstExpr(formNode, DynamicPropertyType.Display),
341
- coerceString,
342
- ),
343
- scope,
344
- );
345
-
346
- updateComputedValue(dataNode, () => lookupDataNode(definition, parent));
347
- updateComputedValue(
348
- hidden,
349
- () =>
350
- !!cf.hidden.value ||
351
- definition.hidden ||
352
- (dataNode.value &&
353
- (!validDataNode(dataNode.value) ||
354
- hideDisplayOnly(dataNode.value, schemaInterface, definition))),
355
- );
356
-
357
- updateComputedValue(
358
- readonly,
359
- () => !!cf.readonly.value || isControlReadonly(definition),
360
- );
361
- updateComputedValue(
362
- disabled,
363
- () => !!cf.disabled.value || of.disabled.value,
364
- );
365
-
366
- updateComputedValue(variables, () => {
367
- return controlImpl.fields.variables.value ?? {};
368
- });
369
-
370
- createSyncEffect(() => {
371
- const dn = dataNode.value;
372
- if (dn) {
373
- dn.control.disabled = disabled.value;
374
- }
375
- }, scope);
376
-
377
- createSyncEffect(() => {
378
- if (isDisplayControl(def)) {
379
- if (display.value !== undefined) {
380
- text.value = isTextDisplay(def.displayData)
381
- ? display.value
382
- : NoOverride;
383
- html.value = isHtmlDisplay(def.displayData)
384
- ? display.value
385
- : NoOverride;
386
- } else {
387
- text.value = NoOverride;
388
- html.value = NoOverride;
389
- }
390
- }
391
- }, displayOverrides);
392
-
393
- setupValidation(
394
- controlImpl,
395
- definition,
396
- dataNode,
397
- schemaInterface,
398
- parent,
399
- formNode,
400
- hidden,
401
- runAsync,
402
- );
403
-
404
- createSyncEffect(() => {
405
- const dn = dataNode.value?.control;
406
- if (dn && isDataControl(definition)) {
407
- if (definition.hidden) {
408
- if (
409
- controlImpl.fields.clearHidden.value &&
410
- !definition.dontClearHidden
411
- ) {
412
- // console.log("Clearing hidden");
413
- dn.value = undefined;
414
- }
415
- } else if (
416
- dn.value === undefined &&
417
- definition.defaultValue != null &&
418
- !definition.adornments?.some(
419
- (x) => x.type === ControlAdornmentType.Optional,
420
- ) &&
421
- definition.renderOptions?.type != DataRenderType.NullToggle
422
- ) {
423
- // console.log(
424
- // "Setting to default",
425
- // definition.defaultValue,
426
- // definition.field,
427
- // );
428
- // const [required, dcv] = isDataControl(definition)
429
- // ? [definition.required, definition.defaultValue]
430
- // : [false, undefined];
431
- // const field = ctx.dataNode?.schema.field;
432
- // return (
433
- // dcv ??
434
- // (field
435
- // ? ctx.dataNode!.elementIndex != null
436
- // ? elementValueForField(field)
437
- // : defaultValueForField(field, required)
438
- // : undefined)
439
- // );
440
-
441
- dn.value = definition.defaultValue;
442
- }
443
- }
444
- }, scope);
445
- return createOverrideProxy(control.current.value, control);
446
- });
447
- },
448
- };
449
- }
450
-
451
- function firstExpr(
452
- formNode: FormNode,
453
- property: DynamicPropertyType,
454
- ): EntityExpression | undefined {
455
- return formNode.definition.dynamic?.find(
456
- (x) => x.type === property && x.expr.type,
457
- )?.expr;
458
- }
459
-
460
- function coerceStyle(v: unknown): any {
461
- return typeof v === "object" ? v : undefined;
462
- }
463
-
464
- function coerceString(v: unknown): string {
465
- return typeof v === "string" ? v : (v?.toString() ?? "");
466
- }
467
-
468
- function createScopedMetaValue<A>(
469
- formNode: FormNode,
470
- c: Control<any>,
471
- key: string,
472
- init: (scope: CleanupScope) => A,
473
- ): A {
474
- return ensureMetaValue(c, key, () => {
475
- const holder = createScoped<A | undefined>(c, undefined, {
476
- equals: (a, b) => a === b,
477
- });
478
- const effect = createScopedEffect((c) => (holder.value = init(c)), holder);
479
- effect.run = () => {
480
- console.log(
481
- "ControlState being recreated:",
482
- effect.subscriptions.map(
483
- (x) =>
484
- `${x[1]?.mask} ${jsonPathString(getControlPath(x[0], unsafeRestoreControl(formNode.definition)))}`,
485
- ),
486
- );
487
- };
488
- return holder;
489
- }).value!;
490
- }
491
-
492
- export function createOverrideProxy<
493
- A extends object,
494
- B extends Record<string, any>,
495
- >(proxyFor: A, handlers: Control<B>): A {
496
- const overrides = getCurrentFields(handlers);
497
- const allOwn = Reflect.ownKeys(proxyFor);
498
- Reflect.ownKeys(overrides).forEach((k) => {
499
- if (!allOwn.includes(k)) allOwn.push(k);
500
- });
501
- return new Proxy(proxyFor, {
502
- get(target: A, p: string | symbol, receiver: any): any {
503
- if (Object.hasOwn(overrides, p)) {
504
- const nv = overrides[p as keyof B]!.value;
505
- if (nv !== NoOverride) return nv;
506
- }
507
- return Reflect.get(target, p, receiver);
508
- },
509
- ownKeys(target: A): ArrayLike<string | symbol> {
510
- return allOwn;
511
- },
512
- has(target: A, p: string | symbol): boolean {
513
- return Reflect.has(proxyFor, p) || Reflect.has(overrides, p);
514
- },
515
- getOwnPropertyDescriptor(target, k) {
516
- if (Object.hasOwn(overrides, k))
517
- return {
518
- enumerable: true,
519
- configurable: true,
520
- };
521
- return Reflect.getOwnPropertyDescriptor(target, k);
522
- },
523
- });
524
- }
525
-
526
- class NoValue {}
527
- const NoOverride = new NoValue();
528
-
529
- type KeysOfUnion<T> = T extends T ? keyof T : never;