@aiaiai-pt/design-system 0.44.1 → 0.46.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/components/ActionFormRenderer.svelte +446 -48
- package/components/ActionFormRenderer.svelte.d.ts +15 -0
- package/components/DateRangePicker.svelte.d.ts +1 -1
- package/components/JsonEditor.svelte +152 -0
- package/components/JsonEditor.svelte.d.ts +44 -0
- package/components/LayoutStackedDefault.svelte +36 -3
- package/components/LayoutStackedDefault.svelte.d.ts +1 -1
- package/components/MoneyInput.svelte +219 -0
- package/components/MoneyInput.svelte.d.ts +52 -0
- package/components/MultiSelectCombobox.svelte +140 -0
- package/components/MultiSelectCombobox.svelte.d.ts +62 -0
- package/components/Tag.svelte +4 -0
- package/components/action-form-renderer-layouts.d.ts +20 -0
- package/components/action-form-renderer-layouts.ts +40 -3
- package/components/action-form-renderer-payload.d.ts +12 -0
- package/components/action-form-renderer-payload.ts +46 -3
- package/components/action-form-renderer-relationships.d.ts +20 -0
- package/components/action-form-renderer-relationships.ts +77 -0
- package/components/action-form-renderer-widgets.d.ts +39 -0
- package/components/action-form-renderer-widgets.ts +127 -0
- package/components/action-form-visibility.d.ts +48 -0
- package/components/action-form-visibility.ts +171 -0
- package/components/index.d.ts +4 -0
- package/components/index.js +11 -0
- package/components/json-editor-contract.d.ts +11 -0
- package/components/json-editor-contract.ts +33 -0
- package/package.json +17 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side `visible_when` evaluation for action-form sections (#634 S4).
|
|
3
|
+
*
|
|
4
|
+
* The action's server-compiled contract round-trips an OPTIONAL
|
|
5
|
+
* `visible_when` predicate per section (the platform's Foundry-framed
|
|
6
|
+
* ActionPrecondition shape — conditions are data, one vocabulary). The
|
|
7
|
+
* server owns ENFORCEMENT (submission gates); this module owns the pure
|
|
8
|
+
* PRESENTATION concern: live show/hide of a section as the operator types.
|
|
9
|
+
*
|
|
10
|
+
* The operator set is a deliberate SUBSET of the server evaluator
|
|
11
|
+
* (`validation.py::_evaluate_operator`) — the comparisons a form value bag
|
|
12
|
+
* can answer locally. Two deliberate divergences, both UI-safe:
|
|
13
|
+
*
|
|
14
|
+
* - Blank form values (`""`, null, undefined) are treated as ABSENT for
|
|
15
|
+
* `is_null` / `not_null` — an untouched text input means "no value yet",
|
|
16
|
+
* so `{field: X, operator: not_null}` reads "shown once X has a value"
|
|
17
|
+
* (the spec's canonical example).
|
|
18
|
+
* - An operator this subset doesn't know evaluates to VISIBLE (fail-open).
|
|
19
|
+
* Hiding inputs on an unknown operator would make fields unreachable;
|
|
20
|
+
* the server's gates still enforce whatever the condition meant.
|
|
21
|
+
*/
|
|
22
|
+
export interface VisibleWhenCondition {
|
|
23
|
+
field?: string;
|
|
24
|
+
operator?: string;
|
|
25
|
+
value?: unknown;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}
|
|
28
|
+
/** Blank = the form has no value for the field yet. `false` and `0` are
|
|
29
|
+
* real values; only null / undefined / empty-string count as blank. */
|
|
30
|
+
export declare function isBlankFormValue(value: unknown): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Evaluate one `visible_when` condition against the live form value bag.
|
|
33
|
+
* Returns whether the section should be VISIBLE.
|
|
34
|
+
*/
|
|
35
|
+
export declare function evaluateVisibleWhen(condition: VisibleWhenCondition | null | undefined, values: Record<string, unknown>): boolean;
|
|
36
|
+
/** A section shape carrying an optional predicate. Reads BOTH spellings so
|
|
37
|
+
* hosts can pass either the renderer's mapped sections (`visibleWhen`) or
|
|
38
|
+
* raw contract sections (`visible_when`) without an adapter. `unknown` on
|
|
39
|
+
* purpose: hosts carry their own concrete precondition types; the evaluator
|
|
40
|
+
* reads the predicate defensively. */
|
|
41
|
+
export interface ConditionalSection {
|
|
42
|
+
visibleWhen?: unknown;
|
|
43
|
+
visible_when?: unknown;
|
|
44
|
+
}
|
|
45
|
+
/** Whether a section is visible given the live value bag. Sections with no
|
|
46
|
+
* predicate are always visible. Accepts any object shape (fold sections
|
|
47
|
+
* carry no predicate at all) — the predicate is read defensively. */
|
|
48
|
+
export declare function sectionVisible(section: object | null | undefined, values: Record<string, unknown>): boolean;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side `visible_when` evaluation for action-form sections (#634 S4).
|
|
3
|
+
*
|
|
4
|
+
* The action's server-compiled contract round-trips an OPTIONAL
|
|
5
|
+
* `visible_when` predicate per section (the platform's Foundry-framed
|
|
6
|
+
* ActionPrecondition shape — conditions are data, one vocabulary). The
|
|
7
|
+
* server owns ENFORCEMENT (submission gates); this module owns the pure
|
|
8
|
+
* PRESENTATION concern: live show/hide of a section as the operator types.
|
|
9
|
+
*
|
|
10
|
+
* The operator set is a deliberate SUBSET of the server evaluator
|
|
11
|
+
* (`validation.py::_evaluate_operator`) — the comparisons a form value bag
|
|
12
|
+
* can answer locally. Two deliberate divergences, both UI-safe:
|
|
13
|
+
*
|
|
14
|
+
* - Blank form values (`""`, null, undefined) are treated as ABSENT for
|
|
15
|
+
* `is_null` / `not_null` — an untouched text input means "no value yet",
|
|
16
|
+
* so `{field: X, operator: not_null}` reads "shown once X has a value"
|
|
17
|
+
* (the spec's canonical example).
|
|
18
|
+
* - An operator this subset doesn't know evaluates to VISIBLE (fail-open).
|
|
19
|
+
* Hiding inputs on an unknown operator would make fields unreachable;
|
|
20
|
+
* the server's gates still enforce whatever the condition meant.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export interface VisibleWhenCondition {
|
|
24
|
+
field?: string;
|
|
25
|
+
operator?: string;
|
|
26
|
+
value?: unknown;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Blank = the form has no value for the field yet. `false` and `0` are
|
|
31
|
+
* real values; only null / undefined / empty-string count as blank. */
|
|
32
|
+
export function isBlankFormValue(value: unknown): boolean {
|
|
33
|
+
return value === undefined || value === null || value === "";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function asComparableNumber(value: unknown): number | null {
|
|
37
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
38
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
39
|
+
const parsed = Number(value);
|
|
40
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function numericCompare(
|
|
46
|
+
operator: "gt" | "lt" | "gte" | "lte",
|
|
47
|
+
actual: unknown,
|
|
48
|
+
expected: unknown,
|
|
49
|
+
): boolean {
|
|
50
|
+
// Mirrors the server: a missing actual is VACUOUSLY satisfied for the
|
|
51
|
+
// ordering operators (the gate that owns refusal runs server-side).
|
|
52
|
+
if (isBlankFormValue(actual)) return true;
|
|
53
|
+
const left = asComparableNumber(actual);
|
|
54
|
+
const right = asComparableNumber(expected);
|
|
55
|
+
if (left === null || right === null) {
|
|
56
|
+
// Fall back to string comparison only when both sides are strings
|
|
57
|
+
// (e.g. ISO dates order lexicographically); otherwise fail open.
|
|
58
|
+
if (typeof actual === "string" && typeof expected === "string") {
|
|
59
|
+
if (operator === "gt") return actual > expected;
|
|
60
|
+
if (operator === "lt") return actual < expected;
|
|
61
|
+
if (operator === "gte") return actual >= expected;
|
|
62
|
+
return actual <= expected;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (operator === "gt") return left > right;
|
|
67
|
+
if (operator === "lt") return left < right;
|
|
68
|
+
if (operator === "gte") return left >= right;
|
|
69
|
+
return left <= right;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function looseEquals(actual: unknown, expected: unknown): boolean {
|
|
73
|
+
if (actual === expected) return true;
|
|
74
|
+
// Form controls carry strings; sheets author typed literals. "3" == 3 and
|
|
75
|
+
// "true" == true are the same answer from the operator's point of view.
|
|
76
|
+
if (typeof expected === "boolean") return String(actual) === String(expected);
|
|
77
|
+
const leftNumber = asComparableNumber(actual);
|
|
78
|
+
const rightNumber = asComparableNumber(expected);
|
|
79
|
+
if (leftNumber !== null && rightNumber !== null)
|
|
80
|
+
return leftNumber === rightNumber;
|
|
81
|
+
return String(actual ?? "") === String(expected ?? "");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function stringMatch(
|
|
85
|
+
mode: "contains" | "starts" | "ends",
|
|
86
|
+
actual: unknown,
|
|
87
|
+
expected: unknown,
|
|
88
|
+
): boolean {
|
|
89
|
+
if (isBlankFormValue(actual) || isBlankFormValue(expected)) return false;
|
|
90
|
+
const haystack = String(actual).toLowerCase();
|
|
91
|
+
const needle = String(expected).toLowerCase();
|
|
92
|
+
if (mode === "contains") return haystack.includes(needle);
|
|
93
|
+
if (mode === "starts") return haystack.startsWith(needle);
|
|
94
|
+
return haystack.endsWith(needle);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Evaluate one `visible_when` condition against the live form value bag.
|
|
99
|
+
* Returns whether the section should be VISIBLE.
|
|
100
|
+
*/
|
|
101
|
+
export function evaluateVisibleWhen(
|
|
102
|
+
condition: VisibleWhenCondition | null | undefined,
|
|
103
|
+
values: Record<string, unknown>,
|
|
104
|
+
): boolean {
|
|
105
|
+
if (!condition || typeof condition !== "object") return true;
|
|
106
|
+
const field = typeof condition.field === "string" ? condition.field : "";
|
|
107
|
+
const operator =
|
|
108
|
+
typeof condition.operator === "string" ? condition.operator : "";
|
|
109
|
+
if (!field || !operator) return true;
|
|
110
|
+
const actual = values[field];
|
|
111
|
+
const expected = condition.value;
|
|
112
|
+
|
|
113
|
+
switch (operator) {
|
|
114
|
+
case "eq":
|
|
115
|
+
return looseEquals(actual, expected);
|
|
116
|
+
case "ne":
|
|
117
|
+
return !looseEquals(actual, expected);
|
|
118
|
+
case "gt":
|
|
119
|
+
case "lt":
|
|
120
|
+
case "gte":
|
|
121
|
+
case "lte":
|
|
122
|
+
return numericCompare(operator, actual, expected);
|
|
123
|
+
case "in":
|
|
124
|
+
return Array.isArray(expected)
|
|
125
|
+
? expected.some((entry) => looseEquals(actual, entry))
|
|
126
|
+
: false;
|
|
127
|
+
case "not_in":
|
|
128
|
+
return Array.isArray(expected)
|
|
129
|
+
? !expected.some((entry) => looseEquals(actual, entry))
|
|
130
|
+
: true;
|
|
131
|
+
case "is_null":
|
|
132
|
+
return isBlankFormValue(actual);
|
|
133
|
+
case "not_null":
|
|
134
|
+
return !isBlankFormValue(actual);
|
|
135
|
+
case "contains":
|
|
136
|
+
return stringMatch("contains", actual, expected);
|
|
137
|
+
case "starts_with":
|
|
138
|
+
return stringMatch("starts", actual, expected);
|
|
139
|
+
case "ends_with":
|
|
140
|
+
return stringMatch("ends", actual, expected);
|
|
141
|
+
default:
|
|
142
|
+
// Unknown operator → fail-open VISIBLE (see module doc).
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** A section shape carrying an optional predicate. Reads BOTH spellings so
|
|
148
|
+
* hosts can pass either the renderer's mapped sections (`visibleWhen`) or
|
|
149
|
+
* raw contract sections (`visible_when`) without an adapter. `unknown` on
|
|
150
|
+
* purpose: hosts carry their own concrete precondition types; the evaluator
|
|
151
|
+
* reads the predicate defensively. */
|
|
152
|
+
export interface ConditionalSection {
|
|
153
|
+
visibleWhen?: unknown;
|
|
154
|
+
visible_when?: unknown;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Whether a section is visible given the live value bag. Sections with no
|
|
158
|
+
* predicate are always visible. Accepts any object shape (fold sections
|
|
159
|
+
* carry no predicate at all) — the predicate is read defensively. */
|
|
160
|
+
export function sectionVisible(
|
|
161
|
+
section: object | null | undefined,
|
|
162
|
+
values: Record<string, unknown>,
|
|
163
|
+
): boolean {
|
|
164
|
+
if (!section) return true;
|
|
165
|
+
const bag = section as ConditionalSection;
|
|
166
|
+
const predicate =
|
|
167
|
+
(bag.visibleWhen as VisibleWhenCondition | null | undefined) ??
|
|
168
|
+
(bag.visible_when as VisibleWhenCondition | null | undefined) ??
|
|
169
|
+
null;
|
|
170
|
+
return evaluateVisibleWhen(predicate, values);
|
|
171
|
+
}
|
package/components/index.d.ts
CHANGED
|
@@ -12,6 +12,9 @@ export { default as Checkbox } from "./Checkbox.svelte";
|
|
|
12
12
|
export { default as Label } from "./Label.svelte";
|
|
13
13
|
export { default as FileUpload } from "./FileUpload.svelte";
|
|
14
14
|
export { default as FileUploadItem } from "./FileUploadItem.svelte";
|
|
15
|
+
export { default as MultiSelectCombobox } from "./MultiSelectCombobox.svelte";
|
|
16
|
+
export { default as MoneyInput } from "./MoneyInput.svelte";
|
|
17
|
+
export { default as JsonEditor } from "./JsonEditor.svelte";
|
|
15
18
|
export { default as Separator } from "./Separator.svelte";
|
|
16
19
|
export { default as Progress } from "./Progress.svelte";
|
|
17
20
|
export { default as List } from "./List.svelte";
|
|
@@ -106,3 +109,4 @@ export { default as GeoSearch } from "./GeoSearch.svelte";
|
|
|
106
109
|
export { default as Calendar } from "./Calendar.svelte";
|
|
107
110
|
export { default as ActionFormRenderer } from "./ActionFormRenderer.svelte";
|
|
108
111
|
export { serializeValueSource, parseValueSource } from "./ValueSourcePicker.helpers.js";
|
|
112
|
+
export { evaluateVisibleWhen, isBlankFormValue, sectionVisible } from "./action-form-visibility";
|
package/components/index.js
CHANGED
|
@@ -25,6 +25,9 @@ export { default as Checkbox } from "./Checkbox.svelte";
|
|
|
25
25
|
export { default as Label } from "./Label.svelte";
|
|
26
26
|
export { default as FileUpload } from "./FileUpload.svelte";
|
|
27
27
|
export { default as FileUploadItem } from "./FileUploadItem.svelte";
|
|
28
|
+
export { default as MultiSelectCombobox } from "./MultiSelectCombobox.svelte";
|
|
29
|
+
export { default as MoneyInput } from "./MoneyInput.svelte";
|
|
30
|
+
export { default as JsonEditor } from "./JsonEditor.svelte";
|
|
28
31
|
|
|
29
32
|
// Layout
|
|
30
33
|
export { default as Separator } from "./Separator.svelte";
|
|
@@ -173,3 +176,11 @@ export { default as Calendar } from "./Calendar.svelte";
|
|
|
173
176
|
|
|
174
177
|
// Action form renderer (placement-aware; admin preview + portal runtime, #73)
|
|
175
178
|
export { default as ActionFormRenderer } from "./ActionFormRenderer.svelte";
|
|
179
|
+
// #634 S4 — pure client-side `visible_when` evaluation (section show/hide).
|
|
180
|
+
// Exported at the root so host step-models (wizard rails) share the exact
|
|
181
|
+
// evaluator the renderer applies internally.
|
|
182
|
+
export {
|
|
183
|
+
evaluateVisibleWhen,
|
|
184
|
+
isBlankFormValue,
|
|
185
|
+
sectionVisible,
|
|
186
|
+
} from "./action-form-visibility";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type JsonDraftResult = {
|
|
2
|
+
ok: true;
|
|
3
|
+
value: unknown;
|
|
4
|
+
} | {
|
|
5
|
+
ok: false;
|
|
6
|
+
error: string;
|
|
7
|
+
};
|
|
8
|
+
/** Pretty-print a value into the editor. Null/undefined → empty editor. */
|
|
9
|
+
export declare function formatJsonValue(value: unknown): string;
|
|
10
|
+
/** Evaluate the operator's raw text. */
|
|
11
|
+
export declare function evaluateJsonDraft(text: string): JsonDraftResult;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// #38 (atelier#669 V1) — the JsonEditor's parse/emit contract, pure.
|
|
2
|
+
// Invalid text NEVER silently drops or half-parses: evaluation either
|
|
3
|
+
// yields `{ok: true, value}` (emit) or `{ok: false, error}` (surface the
|
|
4
|
+
// parse error, block submit via the renderer's error seam, emit NOTHING).
|
|
5
|
+
// Empty and `{}` are distinct: empty text is `{ok: true, value: null}`.
|
|
6
|
+
|
|
7
|
+
export type JsonDraftResult =
|
|
8
|
+
{ ok: true; value: unknown } | { ok: false; error: string };
|
|
9
|
+
|
|
10
|
+
/** Pretty-print a value into the editor. Null/undefined → empty editor. */
|
|
11
|
+
export function formatJsonValue(value: unknown): string {
|
|
12
|
+
if (value === null || value === undefined) return "";
|
|
13
|
+
try {
|
|
14
|
+
return JSON.stringify(value, null, 2) ?? "";
|
|
15
|
+
} catch {
|
|
16
|
+
// Circular or non-serializable host value — start the editor empty
|
|
17
|
+
// rather than exploding the form.
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Evaluate the operator's raw text. */
|
|
23
|
+
export function evaluateJsonDraft(text: string): JsonDraftResult {
|
|
24
|
+
const trimmed = text.trim();
|
|
25
|
+
if (!trimmed) return { ok: true, value: null };
|
|
26
|
+
try {
|
|
27
|
+
return { ok: true, value: JSON.parse(trimmed) };
|
|
28
|
+
} catch (parseError) {
|
|
29
|
+
const detail =
|
|
30
|
+
parseError instanceof Error ? parseError.message : String(parseError);
|
|
31
|
+
return { ok: false, error: `Invalid JSON: ${detail}` };
|
|
32
|
+
}
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiaiai-pt/design-system",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"description": "Design system tokens and Svelte components for aiaiai products",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,6 +31,22 @@
|
|
|
31
31
|
"svelte": "./components/index.js",
|
|
32
32
|
"default": "./components/index.js"
|
|
33
33
|
},
|
|
34
|
+
"./components/action-form-visibility": {
|
|
35
|
+
"types": "./components/action-form-visibility.d.ts",
|
|
36
|
+
"default": "./components/action-form-visibility.ts"
|
|
37
|
+
},
|
|
38
|
+
"./components/action-form-renderer-relationships": {
|
|
39
|
+
"types": "./components/action-form-renderer-relationships.d.ts",
|
|
40
|
+
"default": "./components/action-form-renderer-relationships.ts"
|
|
41
|
+
},
|
|
42
|
+
"./components/action-form-renderer-widgets": {
|
|
43
|
+
"types": "./components/action-form-renderer-widgets.d.ts",
|
|
44
|
+
"default": "./components/action-form-renderer-widgets.ts"
|
|
45
|
+
},
|
|
46
|
+
"./components/json-editor-contract": {
|
|
47
|
+
"types": "./components/json-editor-contract.d.ts",
|
|
48
|
+
"default": "./components/json-editor-contract.ts"
|
|
49
|
+
},
|
|
34
50
|
"./components/*.svelte": {
|
|
35
51
|
"types": "./components/*.svelte.d.ts",
|
|
36
52
|
"svelte": "./components/*.svelte",
|