bank20baht-ui 0.0.8 → 0.0.10
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/README.md +15 -0
- package/dist/components/baht-form.d.ts +9 -1
- package/dist/components/baht-workflow.d.ts +12 -1
- package/dist/core/flags.d.ts +19 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/types.d.ts +12 -0
- package/dist/custom-elements.json +80 -0
- package/dist/engine.js +168 -137
- package/dist/index.js +473 -442
- package/dist/react.d.ts +3 -1
- package/dist/vscode.html-custom-data.json +1 -1
- package/dist/web-types.json +12 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,6 +93,21 @@ dep changes, options refetch and the dependent value clears — cascading down c
|
|
|
93
93
|
`compare` checks against a sibling field (confirm email = `op: 'eq'`), numeric/date-aware,
|
|
94
94
|
skips while either side is empty. Works inside repeater rows and on the server.
|
|
95
95
|
|
|
96
|
+
### Feature flags — deploy dark, flip a JSON file
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
// schema: gate a field / workflow step / rule
|
|
100
|
+
{ order: 9, type: 'text', key: 'promoCode', flag: 'promo' } // '!promo' = the old path
|
|
101
|
+
|
|
102
|
+
// host: flags from one static file — edit it, features flip
|
|
103
|
+
el.flags = await fetch('/flags.json').then((r) => r.json()); // { "promo": true }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Flag-off means **not in the schema**: no render, no validation (a dark `required` can't
|
|
107
|
+
block), nothing in the payload. Missing keys count as off, so new features stay dark until
|
|
108
|
+
the file turns them on. Server side: `validateSubmission(applyFlags(schema, flags), data)`
|
|
109
|
+
with the same JSON — `bank20baht-validator` re-exports `applyFlags`.
|
|
110
|
+
|
|
96
111
|
### Drafts / autosave (host pattern)
|
|
97
112
|
|
|
98
113
|
The library is controlled — autosave is three lines in your app:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TemplateResult } from 'lit';
|
|
2
2
|
import { BahtElement } from './base.js';
|
|
3
|
-
import { DerivedFieldState, FormDefinition, Registry, ValidationError } from '../core/types.js';
|
|
3
|
+
import { DerivedFieldState, FeatureFlags, FormDefinition, Registry, ValidationError } from '../core/types.js';
|
|
4
4
|
export interface BahtFormOptions {
|
|
5
5
|
validateOn?: 'change' | 'blur' | 'submit';
|
|
6
6
|
}
|
|
@@ -23,6 +23,11 @@ export declare class BahtForm extends BahtElement {
|
|
|
23
23
|
* the complete schema, views never change the submit payload.
|
|
24
24
|
*/
|
|
25
25
|
view?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Feature-flag values (spec 01 §12) — fields whose `flag` is off behave as
|
|
28
|
+
* if they were never in the schema: no render, no validation, no payload.
|
|
29
|
+
*/
|
|
30
|
+
flags: FeatureFlags;
|
|
26
31
|
/** Edits the user made since the host last set `value` (controlled overlay). */
|
|
27
32
|
private edits;
|
|
28
33
|
private errors;
|
|
@@ -36,6 +41,9 @@ export declare class BahtForm extends BahtElement {
|
|
|
36
41
|
private dynamicOptions;
|
|
37
42
|
private optionsSnapshots;
|
|
38
43
|
private optionsEpoch;
|
|
44
|
+
/** applyFlags result, memoized on (form, flags) identity. */
|
|
45
|
+
private flagCache;
|
|
46
|
+
private get effectiveForm();
|
|
39
47
|
protected willUpdate(changed: Map<PropertyKey, unknown>): void;
|
|
40
48
|
/**
|
|
41
49
|
* Refetch `optionsFrom` options whose dep snapshot changed. Sync fetchers
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TemplateResult } from 'lit';
|
|
2
2
|
import { BahtElement } from './base.js';
|
|
3
|
-
import { NestedData, Registry, WorkflowDefinition } from '../core/types.js';
|
|
3
|
+
import { FeatureFlags, NestedData, Registry, WorkflowDefinition } from '../core/types.js';
|
|
4
4
|
/**
|
|
5
5
|
* <baht-workflow> — the optional brain (spec 03). Wraps the current form in an
|
|
6
6
|
* internal <baht-form>, evaluates rules on every change, walks the visible forms
|
|
@@ -21,12 +21,17 @@ export declare class BahtWorkflow extends BahtElement {
|
|
|
21
21
|
submitLabel?: string;
|
|
22
22
|
backLabel?: string;
|
|
23
23
|
showBack: boolean;
|
|
24
|
+
/** Feature-flag values (spec 01 §12): flag-off steps/fields/rules vanish. */
|
|
25
|
+
flags: FeatureFlags;
|
|
24
26
|
private data;
|
|
25
27
|
private derived;
|
|
26
28
|
private current;
|
|
27
29
|
private approvalError;
|
|
28
30
|
private history;
|
|
29
31
|
get currentFormName(): string | null;
|
|
32
|
+
/** applyFlags result, memoized on (workflow, flags) identity. */
|
|
33
|
+
private flagCache;
|
|
34
|
+
private get effectiveWorkflow();
|
|
30
35
|
protected willUpdate(changed: Map<PropertyKey, unknown>): void;
|
|
31
36
|
private reevaluate;
|
|
32
37
|
/** Forms in ascending order whose derived visibility is true. */
|
|
@@ -36,6 +41,12 @@ export declare class BahtWorkflow extends BahtElement {
|
|
|
36
41
|
private fieldStatesFor;
|
|
37
42
|
private get formEl();
|
|
38
43
|
getData(): NestedData;
|
|
44
|
+
/**
|
|
45
|
+
* Host/store write into one form's data — same path a user edit takes
|
|
46
|
+
* (rules reevaluate, `baht-data-change` fires) but WITHOUT the step reset
|
|
47
|
+
* that re-feeding the `value` prop causes.
|
|
48
|
+
*/
|
|
49
|
+
setValue(formName: string, patch: Record<string, unknown>): void;
|
|
39
50
|
next(): void;
|
|
40
51
|
/** Advance past the current form: submit+complete on the last one, else step forward. */
|
|
41
52
|
private advance;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FeatureFlags, FormDefinition, WorkflowDefinition } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Is a `flag` expression on (spec 01 §12)?
|
|
4
|
+
* - no flag -> always on
|
|
5
|
+
* - 'name' -> on only while flags.name is true
|
|
6
|
+
* - '!name' -> on only while flags.name is NOT true
|
|
7
|
+
* A missing key counts as false — new features stay dark until the flags
|
|
8
|
+
* file explicitly turns them on.
|
|
9
|
+
*/
|
|
10
|
+
export declare function flagEnabled(flag: string | undefined, flags: FeatureFlags): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Resolve every `flag` in a definition against the current flag values:
|
|
13
|
+
* flag-off fields/steps/rules come back REMOVED, as if never authored —
|
|
14
|
+
* they don't render, don't validate, and never reach the payload. Run the
|
|
15
|
+
* same call server-side (bank20baht-validator re-exports it) with the same
|
|
16
|
+
* flags JSON so both sides agree. Returns the input untouched when nothing
|
|
17
|
+
* in it carries a flag.
|
|
18
|
+
*/
|
|
19
|
+
export declare function applyFlags<T extends FormDefinition | WorkflowDefinition>(schema: T, flags?: FeatureFlags): T;
|
package/dist/core/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { defaultValue } from './defaults.js';
|
|
|
3
3
|
export { approvalFields } from './approval.js';
|
|
4
4
|
export { validateField } from './validators.js';
|
|
5
5
|
export { resolveViewFields } from './views.js';
|
|
6
|
+
export { applyFlags, flagEnabled } from './flags.js';
|
|
6
7
|
export { setLocale, t } from './i18n.js';
|
|
7
8
|
export type { Locale, MessageCatalog, MessageKey } from './i18n.js';
|
|
8
9
|
export { evaluate } from './engine/evaluate.js';
|
package/dist/core/types.d.ts
CHANGED
|
@@ -54,6 +54,12 @@ export interface FieldDescriptor {
|
|
|
54
54
|
maxSize?: number;
|
|
55
55
|
text?: string;
|
|
56
56
|
variant?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Feature flag gating this field (spec 01 §12): shown only while the flag
|
|
59
|
+
* is on ('name') or off ('!name'). Flag-off = as if the field were never in
|
|
60
|
+
* the schema — not rendered, not validated, absent from the payload.
|
|
61
|
+
*/
|
|
62
|
+
flag?: string;
|
|
57
63
|
fields?: FieldDescriptor[];
|
|
58
64
|
minRows?: number;
|
|
59
65
|
maxRows?: number;
|
|
@@ -108,6 +114,8 @@ export interface WorkflowForm extends FormDefinition {
|
|
|
108
114
|
approval?: ApprovalConfig;
|
|
109
115
|
/** Name of one of this form's `views` to render for this step (display only). */
|
|
110
116
|
view?: string;
|
|
117
|
+
/** Feature flag gating this whole step (spec 01 §12). */
|
|
118
|
+
flag?: string;
|
|
111
119
|
}
|
|
112
120
|
export type ConditionOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn' | 'contains' | 'notContains' | 'startsWith' | 'endsWith' | 'isEmpty' | 'isNotEmpty';
|
|
113
121
|
/** Leaf: compare `formName.fieldKey` against a constant or another field. */
|
|
@@ -160,7 +168,11 @@ export interface Rule {
|
|
|
160
168
|
when?: Condition;
|
|
161
169
|
then?: Action[];
|
|
162
170
|
else?: Action[];
|
|
171
|
+
/** Feature flag gating this rule (spec 01 §12). */
|
|
172
|
+
flag?: string;
|
|
163
173
|
}
|
|
174
|
+
/** Flag values — typically fetched from one static JSON file (spec 01 §12). */
|
|
175
|
+
export type FeatureFlags = Record<string, boolean>;
|
|
164
176
|
/** A Workflow definition — consumed by <baht-workflow> (spec 00 §4.2). */
|
|
165
177
|
export interface WorkflowDefinition {
|
|
166
178
|
workflowId: string;
|
|
@@ -60,6 +60,15 @@
|
|
|
60
60
|
"description": "Name of a view from `form.views`. Display-only: when set, only that view's\nfields are rendered and validated (the user can only fix what they see),\nbut getData() still returns the FULL form — the server always validates\nthe complete schema, views never change the submit payload.",
|
|
61
61
|
"attribute": "view"
|
|
62
62
|
},
|
|
63
|
+
{
|
|
64
|
+
"kind": "field",
|
|
65
|
+
"name": "flags",
|
|
66
|
+
"type": {
|
|
67
|
+
"text": "FeatureFlags"
|
|
68
|
+
},
|
|
69
|
+
"default": "{}",
|
|
70
|
+
"description": "Feature-flag values (spec 01 §12) — fields whose `flag` is off behave as\nif they were never in the schema: no render, no validation, no payload."
|
|
71
|
+
},
|
|
63
72
|
{
|
|
64
73
|
"kind": "field",
|
|
65
74
|
"name": "edits",
|
|
@@ -105,6 +114,25 @@
|
|
|
105
114
|
"privacy": "private",
|
|
106
115
|
"default": "new Map<string, number>()"
|
|
107
116
|
},
|
|
117
|
+
{
|
|
118
|
+
"kind": "field",
|
|
119
|
+
"name": "flagCache",
|
|
120
|
+
"type": {
|
|
121
|
+
"text": "{ form?: FormDefinition; flags?: FeatureFlags; out?: FormDefinition }"
|
|
122
|
+
},
|
|
123
|
+
"privacy": "private",
|
|
124
|
+
"default": "{}",
|
|
125
|
+
"description": "applyFlags result, memoized on (form, flags) identity."
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"kind": "field",
|
|
129
|
+
"name": "effectiveForm",
|
|
130
|
+
"type": {
|
|
131
|
+
"text": "FormDefinition"
|
|
132
|
+
},
|
|
133
|
+
"privacy": "private",
|
|
134
|
+
"readonly": true
|
|
135
|
+
},
|
|
108
136
|
{
|
|
109
137
|
"kind": "method",
|
|
110
138
|
"name": "syncDynamicOptions",
|
|
@@ -401,6 +429,15 @@
|
|
|
401
429
|
"default": "true",
|
|
402
430
|
"attribute": "show-back"
|
|
403
431
|
},
|
|
432
|
+
{
|
|
433
|
+
"kind": "field",
|
|
434
|
+
"name": "flags",
|
|
435
|
+
"type": {
|
|
436
|
+
"text": "FeatureFlags"
|
|
437
|
+
},
|
|
438
|
+
"default": "{}",
|
|
439
|
+
"description": "Feature-flag values (spec 01 §12): flag-off steps/fields/rules vanish."
|
|
440
|
+
},
|
|
404
441
|
{
|
|
405
442
|
"kind": "field",
|
|
406
443
|
"name": "data",
|
|
@@ -454,6 +491,25 @@
|
|
|
454
491
|
},
|
|
455
492
|
"readonly": true
|
|
456
493
|
},
|
|
494
|
+
{
|
|
495
|
+
"kind": "field",
|
|
496
|
+
"name": "flagCache",
|
|
497
|
+
"type": {
|
|
498
|
+
"text": "{\n workflow?: WorkflowDefinition;\n flags?: FeatureFlags;\n out?: WorkflowDefinition;\n }"
|
|
499
|
+
},
|
|
500
|
+
"privacy": "private",
|
|
501
|
+
"default": "{}",
|
|
502
|
+
"description": "applyFlags result, memoized on (workflow, flags) identity."
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
"kind": "field",
|
|
506
|
+
"name": "effectiveWorkflow",
|
|
507
|
+
"type": {
|
|
508
|
+
"text": "WorkflowDefinition"
|
|
509
|
+
},
|
|
510
|
+
"privacy": "private",
|
|
511
|
+
"readonly": true
|
|
512
|
+
},
|
|
457
513
|
{
|
|
458
514
|
"kind": "method",
|
|
459
515
|
"name": "reevaluate",
|
|
@@ -528,6 +584,30 @@
|
|
|
528
584
|
}
|
|
529
585
|
}
|
|
530
586
|
},
|
|
587
|
+
{
|
|
588
|
+
"kind": "method",
|
|
589
|
+
"name": "setValue",
|
|
590
|
+
"return": {
|
|
591
|
+
"type": {
|
|
592
|
+
"text": "void"
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
"parameters": [
|
|
596
|
+
{
|
|
597
|
+
"name": "formName",
|
|
598
|
+
"type": {
|
|
599
|
+
"text": "string"
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
"name": "patch",
|
|
604
|
+
"type": {
|
|
605
|
+
"text": "Record<string, unknown>"
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
],
|
|
609
|
+
"description": "Host/store write into one form's data — same path a user edit takes\n(rules reevaluate, `baht-data-change` fires) but WITHOUT the step reset\nthat re-feeding the `value` prop causes."
|
|
610
|
+
},
|
|
531
611
|
{
|
|
532
612
|
"kind": "method",
|
|
533
613
|
"name": "next",
|