@developer_tribe/react-builder 1.2.44-test.1 → 1.2.44-test.2
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/dist/attributes-editor/Field.d.ts +3 -1
- package/dist/attributes-editor/attributesEditorModelTypes.d.ts +3 -0
- package/dist/attributes-editor/useAttributesEditorModel.d.ts +1 -1
- package/dist/build-components/FormSubmitButton/FormSubmitButtonProps.generated.d.ts +8 -3
- package/dist/build-components/GlobalProvider/globalProviderUtils.d.ts +4 -13
- package/dist/build-components/GlobalProvider/useGlobalProviderLogic.d.ts +15 -0
- package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +8 -3
- package/dist/build-components/SystemButton/SystemButtonProps.generated.d.ts +8 -3
- package/dist/build-components/SystemButton/usePlacementButtonEvents.d.ts +9 -2
- package/dist/build-components/patterns.generated.d.ts +15 -9
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.web.cjs.js +3 -3
- package/dist/index.web.cjs.js.map +1 -1
- package/dist/index.web.esm.js +3 -3
- package/dist/index.web.esm.js.map +1 -1
- package/dist/utils/nodeTree.d.ts +18 -0
- package/package.json +1 -1
- package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +68 -4
- package/src/assets/meta.json +1 -1
- package/src/assets/samples/global-onboard-flow.json +7 -1
- package/src/assets/samples/terms-and-privacy-no-form.json +1 -1
- package/src/assets/samples/terms-and-privacy.json +1 -1
- package/src/attributes-editor/AttributesEditorView.tsx +3 -0
- package/src/attributes-editor/Field.tsx +91 -2
- package/src/attributes-editor/attributesEditorModelTypes.ts +3 -0
- package/src/attributes-editor/useAttributesEditorModel.ts +8 -0
- package/src/build-components/FormCheckbox/FormCheckbox.tsx +3 -1
- package/src/build-components/FormSubmitButton/FormSubmitButton.tsx +3 -0
- package/src/build-components/FormSubmitButton/FormSubmitButtonProps.generated.ts +26 -3
- package/src/build-components/GlobalProvider/GlobalProvider.tsx +4 -144
- package/src/build-components/GlobalProvider/globalProviderUtils.ts +79 -38
- package/src/build-components/GlobalProvider/useGlobalNavigation.ts +0 -5
- package/src/build-components/GlobalProvider/useGlobalProviderLogic.ts +172 -0
- package/src/build-components/OnboardButton/OnboardButton.tsx +3 -0
- package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +26 -3
- package/src/build-components/OnboardButton/pattern.json +5 -3
- package/src/build-components/SystemButton/SystemButton.tsx +3 -0
- package/src/build-components/SystemButton/SystemButtonProps.generated.ts +26 -3
- package/src/build-components/SystemButton/pattern.json +5 -3
- package/src/build-components/SystemButton/usePlacementButtonEvents.ts +22 -9
- package/src/build-components/patterns.generated.ts +45 -9
- package/src/components/AttributesEditorPanel.tsx +1 -0
- package/src/patterns/event-constants.json +19 -0
- package/src/utils/nodeTree.ts +115 -0
package/dist/utils/nodeTree.d.ts
CHANGED
|
@@ -3,3 +3,21 @@ export declare function deleteNodeFromTree(root: Node, target: Node): Node;
|
|
|
3
3
|
export declare function isNodeRecord(node: Node): node is NodeData;
|
|
4
4
|
export declare function nodeHasChild(parent: NodeData, potentialChild: Node): boolean;
|
|
5
5
|
export declare function findNodeByKey(root: Node, key?: string): Node | null;
|
|
6
|
+
export type ProjectOptions = {
|
|
7
|
+
pageKeys: string[];
|
|
8
|
+
conditionKeys: string[];
|
|
9
|
+
placementKeys: string[];
|
|
10
|
+
formFieldNames: string[];
|
|
11
|
+
eventTypes: string[];
|
|
12
|
+
validationTypes: string[];
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Traverses the entire node tree to dynamically discover project-level metadata
|
|
16
|
+
* (page keys, condition keys, form field names, etc.) and merges them with
|
|
17
|
+
* central constants from event-constants.json.
|
|
18
|
+
*
|
|
19
|
+
* This enables the Attributes Editor to provide a complete list of valid options
|
|
20
|
+
* for dropdown fields, combining predefined standards with actually used keys
|
|
21
|
+
* in the current design.
|
|
22
|
+
*/
|
|
23
|
+
export declare function collectProjectMetadata(root: Node): ProjectOptions;
|
package/package.json
CHANGED
|
@@ -4,6 +4,52 @@ import { formatWithPrettier } from './formatWithPrettier.js';
|
|
|
4
4
|
import { fail } from './fail.js';
|
|
5
5
|
import { validateExistingComponentTsx } from './validateExistingComponentTsx.js';
|
|
6
6
|
|
|
7
|
+
// ── Event Constants ($ref resolution) ────────────────────────────────────────
|
|
8
|
+
// Loads src/patterns/event-constants.json once and resolves "$ref:key" strings
|
|
9
|
+
// in pattern.json `types` blocks to their corresponding arrays.
|
|
10
|
+
|
|
11
|
+
let _eventConstants = null;
|
|
12
|
+
async function loadEventConstants(paths) {
|
|
13
|
+
if (_eventConstants) return _eventConstants;
|
|
14
|
+
const constantsPath = path.join(paths.PATTERNS_ROOT, 'event-constants.json');
|
|
15
|
+
const exists = await fs
|
|
16
|
+
.stat(constantsPath)
|
|
17
|
+
.then(() => true)
|
|
18
|
+
.catch(() => false);
|
|
19
|
+
if (!exists) {
|
|
20
|
+
_eventConstants = {};
|
|
21
|
+
return _eventConstants;
|
|
22
|
+
}
|
|
23
|
+
const raw = await fs.readFile(constantsPath, 'utf8');
|
|
24
|
+
_eventConstants = JSON.parse(raw);
|
|
25
|
+
return _eventConstants;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolves "$ref:key" tokens in a pattern.json `types` block.
|
|
30
|
+
* e.g. "$ref:event-constants.permissionTypes" → ["notification", "camera", ...]
|
|
31
|
+
*/
|
|
32
|
+
function resolveRefs(data, constants) {
|
|
33
|
+
if (!data || typeof data !== 'object') return data;
|
|
34
|
+
// Deep clone to avoid mutating the original
|
|
35
|
+
const result = Array.isArray(data) ? [...data] : { ...data };
|
|
36
|
+
for (const key of Object.keys(result)) {
|
|
37
|
+
const val = result[key];
|
|
38
|
+
if (typeof val === 'string' && val.startsWith('$ref:')) {
|
|
39
|
+
// Format: "$ref:event-constants.permissionTypes"
|
|
40
|
+
const refPath = val.slice('$ref:'.length);
|
|
41
|
+
const [fileName, fieldName] = refPath.split('.');
|
|
42
|
+
const source = fileName === 'event-constants' ? constants : null;
|
|
43
|
+
if (source && fieldName && Array.isArray(source[fieldName])) {
|
|
44
|
+
result[key] = source[fieldName];
|
|
45
|
+
}
|
|
46
|
+
} else if (val && typeof val === 'object') {
|
|
47
|
+
result[key] = resolveRefs(val, constants);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
7
53
|
// single-use: get all entries with filtering
|
|
8
54
|
async function getAllEntriesInComponentsRoot(paths) {
|
|
9
55
|
const { COMPONENTS_ROOT } = paths;
|
|
@@ -62,7 +108,7 @@ async function ensureOtherTsExists(paths) {
|
|
|
62
108
|
}
|
|
63
109
|
|
|
64
110
|
// single-use: validate pattern.json
|
|
65
|
-
async function validatePatternJson(componentDir, componentName) {
|
|
111
|
+
async function validatePatternJson(componentDir, componentName, paths) {
|
|
66
112
|
const patternPath = path.join(componentDir, 'pattern.json');
|
|
67
113
|
const exists = await fs
|
|
68
114
|
.stat(patternPath)
|
|
@@ -85,6 +131,12 @@ async function validatePatternJson(componentDir, componentName) {
|
|
|
85
131
|
return fail(`Failed to read JSON at ${patternPath}: ${reason}`);
|
|
86
132
|
}
|
|
87
133
|
|
|
134
|
+
// Resolve $ref tokens (e.g. in types blocks) before validation
|
|
135
|
+
if (paths) {
|
|
136
|
+
const constants = await loadEventConstants(paths);
|
|
137
|
+
data = resolveRefs(data, constants);
|
|
138
|
+
}
|
|
139
|
+
|
|
88
140
|
if (typeof data.schemaVersion !== 'number') {
|
|
89
141
|
return fail(
|
|
90
142
|
`[${componentName}] pattern.json -> 'schemaVersion' must be a number`
|
|
@@ -299,6 +351,11 @@ async function validatePatternJson(componentDir, componentName) {
|
|
|
299
351
|
try {
|
|
300
352
|
const parentContent = await fs.readFile(parentPatternPath, 'utf8');
|
|
301
353
|
parentData = JSON.parse(parentContent);
|
|
354
|
+
// Resolve $ref tokens in parent data too (e.g. SystemButton has $ref in types)
|
|
355
|
+
if (paths) {
|
|
356
|
+
const constants = await loadEventConstants(paths);
|
|
357
|
+
parentData = resolveRefs(parentData, constants);
|
|
358
|
+
}
|
|
302
359
|
} catch (error) {
|
|
303
360
|
const reason =
|
|
304
361
|
error instanceof SyntaxError
|
|
@@ -531,8 +588,12 @@ async function validatePatternJson(componentDir, componentName) {
|
|
|
531
588
|
}
|
|
532
589
|
|
|
533
590
|
// single-use: component-level validation aggregator
|
|
534
|
-
async function validateComponent(componentDir, componentName) {
|
|
535
|
-
const patternJson = await validatePatternJson(
|
|
591
|
+
async function validateComponent(componentDir, componentName, paths) {
|
|
592
|
+
const patternJson = await validatePatternJson(
|
|
593
|
+
componentDir,
|
|
594
|
+
componentName,
|
|
595
|
+
paths
|
|
596
|
+
);
|
|
536
597
|
await validateExistingComponentTsx(componentDir, componentName);
|
|
537
598
|
return { componentDir, componentName, patternJson };
|
|
538
599
|
}
|
|
@@ -541,11 +602,14 @@ export async function validateAllComponentsOrThrow(paths) {
|
|
|
541
602
|
const dirents = await getAllEntriesInComponentsRoot(paths);
|
|
542
603
|
ensureAllEntriesAreFolders(dirents, paths);
|
|
543
604
|
|
|
605
|
+
// Pre-load event constants once for $ref resolution across all components
|
|
606
|
+
await loadEventConstants(paths);
|
|
607
|
+
|
|
544
608
|
const validated = [];
|
|
545
609
|
for (const dirent of dirents) {
|
|
546
610
|
const componentName = dirent.name;
|
|
547
611
|
const componentDir = path.join(paths.COMPONENTS_ROOT, componentName);
|
|
548
|
-
const result = await validateComponent(componentDir, componentName);
|
|
612
|
+
const result = await validateComponent(componentDir, componentName, paths);
|
|
549
613
|
validated.push(result);
|
|
550
614
|
}
|
|
551
615
|
// Ensure fallback renderer exists for unknown component types
|
package/src/assets/meta.json
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"children": {
|
|
19
19
|
"type": "GlobalProvider",
|
|
20
|
+
"key": "main-global-onboard-flow",
|
|
20
21
|
"attributes": {
|
|
21
22
|
"initialPage": "terms",
|
|
22
23
|
"persistProgress": true,
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
"description": "Header",
|
|
46
47
|
"styles": {
|
|
47
48
|
"padding": 16,
|
|
48
|
-
"alignItems": "
|
|
49
|
+
"alignItems": "stretch",
|
|
49
50
|
"borderBottomWidth": 1,
|
|
50
51
|
"borderBottomColor": "THEME_COLORS.LINE",
|
|
51
52
|
"backgroundColor": "THEME_COLORS.BACKGROUND"
|
|
@@ -127,6 +128,11 @@
|
|
|
127
128
|
"validationRequired": true,
|
|
128
129
|
"disableIfUnvalidated": true,
|
|
129
130
|
"events": [
|
|
131
|
+
{
|
|
132
|
+
"type": "SetCondition",
|
|
133
|
+
"conditionKey": "termsAccepted",
|
|
134
|
+
"value": true
|
|
135
|
+
},
|
|
130
136
|
{
|
|
131
137
|
"type": "Navigate",
|
|
132
138
|
"placementKey": "onboard"
|
|
@@ -81,6 +81,7 @@ export function AttributesEditorView(props: AttributesEditorViewProps) {
|
|
|
81
81
|
loadedFonts,
|
|
82
82
|
markFontLoaded,
|
|
83
83
|
addError,
|
|
84
|
+
projectOptions,
|
|
84
85
|
} = props;
|
|
85
86
|
|
|
86
87
|
const headerSection = (
|
|
@@ -277,6 +278,7 @@ export function AttributesEditorView(props: AttributesEditorViewProps) {
|
|
|
277
278
|
viewAttributes={viewAttributes}
|
|
278
279
|
label={isBoolean ? label : undefined}
|
|
279
280
|
preferredScale={preferredScale}
|
|
281
|
+
projectOptions={projectOptions}
|
|
280
282
|
/>
|
|
281
283
|
)}
|
|
282
284
|
</div>
|
|
@@ -355,6 +357,7 @@ export function AttributesEditorView(props: AttributesEditorViewProps) {
|
|
|
355
357
|
viewAttributes={viewAttributes}
|
|
356
358
|
label={isBoolean ? label : undefined}
|
|
357
359
|
preferredScale={preferredScale}
|
|
360
|
+
projectOptions={projectOptions}
|
|
358
361
|
/>
|
|
359
362
|
)}
|
|
360
363
|
</div>
|
|
@@ -12,6 +12,7 @@ import { Checkbox } from '../components/Checkbox';
|
|
|
12
12
|
import { LayoutPreviewPicker } from './LayoutPreviewPicker';
|
|
13
13
|
import { SizeField, type PreferredScale } from './SizeField';
|
|
14
14
|
import { LayoutContext, LayoutFieldName, isBooleanFieldType } from './types';
|
|
15
|
+
import type { ProjectOptions } from '../utils/nodeTree';
|
|
15
16
|
|
|
16
17
|
type FieldProps = {
|
|
17
18
|
name: string;
|
|
@@ -24,6 +25,7 @@ type FieldProps = {
|
|
|
24
25
|
viewAttributes?: Partial<ViewPropsGenerated['attributes']>;
|
|
25
26
|
label?: React.ReactNode;
|
|
26
27
|
preferredScale?: PreferredScale;
|
|
28
|
+
projectOptions?: ProjectOptions;
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
const layoutFieldNames: LayoutFieldName[] = [
|
|
@@ -47,6 +49,7 @@ export function Field({
|
|
|
47
49
|
viewAttributes,
|
|
48
50
|
label,
|
|
49
51
|
preferredScale,
|
|
52
|
+
projectOptions,
|
|
50
53
|
}: FieldProps) {
|
|
51
54
|
if (Array.isArray(type)) {
|
|
52
55
|
if (isLayoutField(name)) {
|
|
@@ -64,6 +67,11 @@ export function Field({
|
|
|
64
67
|
/>
|
|
65
68
|
);
|
|
66
69
|
}
|
|
70
|
+
const options =
|
|
71
|
+
name === 'type'
|
|
72
|
+
? Array.from(new Set([...type, ...(projectOptions?.eventTypes ?? [])]))
|
|
73
|
+
: type;
|
|
74
|
+
|
|
67
75
|
return (
|
|
68
76
|
<select
|
|
69
77
|
value={value ?? ''}
|
|
@@ -71,7 +79,7 @@ export function Field({
|
|
|
71
79
|
className="input"
|
|
72
80
|
>
|
|
73
81
|
<option value="">(none)</option>
|
|
74
|
-
{
|
|
82
|
+
{options.map((opt) => (
|
|
75
83
|
<option key={opt} value={opt}>
|
|
76
84
|
{opt}
|
|
77
85
|
</option>
|
|
@@ -117,6 +125,54 @@ export function Field({
|
|
|
117
125
|
);
|
|
118
126
|
case 'string':
|
|
119
127
|
case 'description':
|
|
128
|
+
if (name === 'validation') {
|
|
129
|
+
const options = projectOptions?.validationTypes ?? [];
|
|
130
|
+
return (
|
|
131
|
+
<select
|
|
132
|
+
value={itemValue ?? ''}
|
|
133
|
+
onChange={(e) => {
|
|
134
|
+
const next = [...arr];
|
|
135
|
+
next[idx] =
|
|
136
|
+
e.target.value === '' ? undefined : e.target.value;
|
|
137
|
+
onChange(next);
|
|
138
|
+
}}
|
|
139
|
+
className="input"
|
|
140
|
+
style={{ flex: 1 }}
|
|
141
|
+
>
|
|
142
|
+
<option value="">(none)</option>
|
|
143
|
+
{options.map((opt) => (
|
|
144
|
+
<option key={opt} value={opt}>
|
|
145
|
+
{opt}
|
|
146
|
+
</option>
|
|
147
|
+
))}
|
|
148
|
+
</select>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (name === 'type') {
|
|
152
|
+
const options = projectOptions?.eventTypes ?? [];
|
|
153
|
+
if (options.length > 0) {
|
|
154
|
+
return (
|
|
155
|
+
<select
|
|
156
|
+
value={itemValue ?? ''}
|
|
157
|
+
onChange={(e) => {
|
|
158
|
+
const next = [...arr];
|
|
159
|
+
next[idx] =
|
|
160
|
+
e.target.value === '' ? undefined : e.target.value;
|
|
161
|
+
onChange(next);
|
|
162
|
+
}}
|
|
163
|
+
className="input"
|
|
164
|
+
style={{ flex: 1 }}
|
|
165
|
+
>
|
|
166
|
+
<option value="">(none)</option>
|
|
167
|
+
{options.map((opt) => (
|
|
168
|
+
<option key={opt} value={opt}>
|
|
169
|
+
{opt}
|
|
170
|
+
</option>
|
|
171
|
+
))}
|
|
172
|
+
</select>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
120
176
|
return (
|
|
121
177
|
<input
|
|
122
178
|
type="text"
|
|
@@ -244,6 +300,7 @@ export function Field({
|
|
|
244
300
|
layoutContext={layoutContext}
|
|
245
301
|
viewAttributes={viewAttributes}
|
|
246
302
|
label={fieldName}
|
|
303
|
+
projectOptions={projectOptions}
|
|
247
304
|
/>
|
|
248
305
|
</div>
|
|
249
306
|
);
|
|
@@ -265,6 +322,7 @@ export function Field({
|
|
|
265
322
|
projectColors={projectColors}
|
|
266
323
|
layoutContext={layoutContext}
|
|
267
324
|
viewAttributes={viewAttributes}
|
|
325
|
+
projectOptions={projectOptions}
|
|
268
326
|
/>
|
|
269
327
|
</React.Fragment>
|
|
270
328
|
);
|
|
@@ -369,6 +427,7 @@ export function Field({
|
|
|
369
427
|
layoutContext={layoutContext}
|
|
370
428
|
viewAttributes={viewAttributes}
|
|
371
429
|
label={fieldName}
|
|
430
|
+
projectOptions={projectOptions}
|
|
372
431
|
/>
|
|
373
432
|
</div>
|
|
374
433
|
);
|
|
@@ -388,6 +447,7 @@ export function Field({
|
|
|
388
447
|
projectColors={projectColors}
|
|
389
448
|
layoutContext={layoutContext}
|
|
390
449
|
viewAttributes={viewAttributes}
|
|
450
|
+
projectOptions={projectOptions}
|
|
391
451
|
/>
|
|
392
452
|
</React.Fragment>
|
|
393
453
|
);
|
|
@@ -496,7 +556,35 @@ export function Field({
|
|
|
496
556
|
/>
|
|
497
557
|
);
|
|
498
558
|
case 'string':
|
|
499
|
-
case 'description':
|
|
559
|
+
case 'description': {
|
|
560
|
+
let options: string[] = [];
|
|
561
|
+
if (name === 'navigate_to' || name === 'pageKey') {
|
|
562
|
+
options = projectOptions?.pageKeys ?? [];
|
|
563
|
+
} else if (name === 'conditionKey') {
|
|
564
|
+
options = projectOptions?.conditionKeys ?? [];
|
|
565
|
+
} else if (name === 'placementKey') {
|
|
566
|
+
options = projectOptions?.placementKeys ?? [];
|
|
567
|
+
} else if (name === 'name' && componentType?.startsWith('Form')) {
|
|
568
|
+
options = projectOptions?.formFieldNames ?? [];
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (options.length > 0) {
|
|
572
|
+
return (
|
|
573
|
+
<select
|
|
574
|
+
value={value ?? ''}
|
|
575
|
+
onChange={(e) => onChange(e.target.value || undefined)}
|
|
576
|
+
className="input"
|
|
577
|
+
>
|
|
578
|
+
<option value="">(none)</option>
|
|
579
|
+
{options.map((opt) => (
|
|
580
|
+
<option key={opt} value={opt}>
|
|
581
|
+
{opt}
|
|
582
|
+
</option>
|
|
583
|
+
))}
|
|
584
|
+
</select>
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
500
588
|
return (
|
|
501
589
|
<input
|
|
502
590
|
type="text"
|
|
@@ -507,6 +595,7 @@ export function Field({
|
|
|
507
595
|
className="input"
|
|
508
596
|
/>
|
|
509
597
|
);
|
|
598
|
+
}
|
|
510
599
|
case 'title':
|
|
511
600
|
return (
|
|
512
601
|
<input
|
|
@@ -3,6 +3,7 @@ import type { ProjectColors } from '../types/Project';
|
|
|
3
3
|
import type { ViewPropsGenerated } from '../build-components/View/ViewProps.generated';
|
|
4
4
|
import type { Fonts } from '../types/Fonts';
|
|
5
5
|
import type { LayoutContext, SchemaEntry } from './types';
|
|
6
|
+
import type { ProjectOptions } from '../utils/nodeTree';
|
|
6
7
|
|
|
7
8
|
export type TabId = 'style' | 'container' | 'other';
|
|
8
9
|
|
|
@@ -10,6 +11,7 @@ export type AttributesEditorProps = {
|
|
|
10
11
|
node: Node;
|
|
11
12
|
onChange: (next: Node) => void;
|
|
12
13
|
projectColors?: ProjectColors;
|
|
14
|
+
projectRoot?: Node;
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
export type AttributesEditorTabConfig = {
|
|
@@ -60,6 +62,7 @@ export type AttributesEditorModel = {
|
|
|
60
62
|
projectColorsForPicker?: ProjectColors;
|
|
61
63
|
viewAttributes?: Partial<ViewPropsGenerated['attributes']>;
|
|
62
64
|
layoutContext: LayoutContext;
|
|
65
|
+
projectOptions: ProjectOptions;
|
|
63
66
|
getAttributeValue: (name: string) => unknown;
|
|
64
67
|
handleAttributeChange: (name: string, val: unknown) => void;
|
|
65
68
|
handleChildrenChange: (val: string) => void;
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from '../utils/patterns';
|
|
14
14
|
import { useRenderStore } from '../store';
|
|
15
15
|
import type { LayoutContext, SchemaEntry } from './types';
|
|
16
|
+
import { collectProjectMetadata } from '../utils/nodeTree';
|
|
16
17
|
import {
|
|
17
18
|
buildAttributesEditorEntries,
|
|
18
19
|
findLegacyFlatStyleKeys,
|
|
@@ -36,6 +37,7 @@ export function useAttributesEditorModel({
|
|
|
36
37
|
node,
|
|
37
38
|
onChange,
|
|
38
39
|
projectColors,
|
|
40
|
+
projectRoot,
|
|
39
41
|
}: AttributesEditorProps): AttributesEditorModel {
|
|
40
42
|
const isInvalidNode = !node || isNodeString(node);
|
|
41
43
|
// Memoize baseData to prevent it from changing on every render
|
|
@@ -141,6 +143,11 @@ export function useAttributesEditorModel({
|
|
|
141
143
|
[projectColors],
|
|
142
144
|
);
|
|
143
145
|
|
|
146
|
+
const projectOptions = useMemo(
|
|
147
|
+
() => collectProjectMetadata(projectRoot || baseData),
|
|
148
|
+
[projectRoot, baseData],
|
|
149
|
+
);
|
|
150
|
+
|
|
144
151
|
const viewAttributes = useMemo<
|
|
145
152
|
Partial<ViewPropsGenerated['attributes']> | undefined
|
|
146
153
|
>(
|
|
@@ -428,6 +435,7 @@ export function useAttributesEditorModel({
|
|
|
428
435
|
projectColorsForPicker,
|
|
429
436
|
viewAttributes,
|
|
430
437
|
layoutContext,
|
|
438
|
+
projectOptions,
|
|
431
439
|
getAttributeValue,
|
|
432
440
|
handleAttributeChange,
|
|
433
441
|
handleChildrenChange,
|
|
@@ -70,11 +70,13 @@ function validationArrayToRules(validation: string[] | undefined): {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/** FormCheckbox wraps Checkbox with RHF; UI and styling come from Checkbox. */
|
|
73
|
+
// eslint-disable-next-line react/prop-types
|
|
73
74
|
export function FormCheckbox({ node }: FormCheckboxComponentProps) {
|
|
74
75
|
useLogRender('FormCheckbox');
|
|
75
76
|
node = useNode(node);
|
|
76
77
|
const ctx = useContext(formContext);
|
|
77
|
-
|
|
78
|
+
// eslint-disable-next-line react/prop-types
|
|
79
|
+
const attrs = node?.attributes;
|
|
78
80
|
const name = attrs?.name as string | undefined;
|
|
79
81
|
const validation = attrs?.validation as string[] | undefined;
|
|
80
82
|
|
|
@@ -7,6 +7,7 @@ import { useLogRender } from '../../utils/useLogRender';
|
|
|
7
7
|
import { useMockOSContext, useMockPermission } from '../../mockOS';
|
|
8
8
|
import { usePlacementButtonEvents } from '../SystemButton/usePlacementButtonEvents';
|
|
9
9
|
import { useGlobalNavigation } from '../GlobalProvider/useGlobalNavigation';
|
|
10
|
+
import { useGlobalContext } from '../GlobalProvider/GlobalContext';
|
|
10
11
|
|
|
11
12
|
export function FormSubmitButton({ node }: FormSubmitButtonComponentProps) {
|
|
12
13
|
useLogRender('FormSubmitButton');
|
|
@@ -15,6 +16,7 @@ export function FormSubmitButton({ node }: FormSubmitButtonComponentProps) {
|
|
|
15
16
|
const context = useMockOSContext();
|
|
16
17
|
const mockPermissionManager = useMockPermission(context);
|
|
17
18
|
const globalNavigate = useGlobalNavigation();
|
|
19
|
+
const globalCtx = useGlobalContext();
|
|
18
20
|
const attrs = node.attributes;
|
|
19
21
|
|
|
20
22
|
const runPlacementEvents = usePlacementButtonEvents(attrs?.events, {
|
|
@@ -22,6 +24,7 @@ export function FormSubmitButton({ node }: FormSubmitButtonComponentProps) {
|
|
|
22
24
|
requestPermission: (permission) =>
|
|
23
25
|
mockPermissionManager.requestPermission(permission),
|
|
24
26
|
globalNavigate,
|
|
27
|
+
setCondition: globalCtx?.setCondition,
|
|
25
28
|
});
|
|
26
29
|
|
|
27
30
|
const validationRequired =
|
|
@@ -2,7 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
import type { NodeData } from '../../types/Node';
|
|
4
4
|
|
|
5
|
-
export type TypeOptionType =
|
|
5
|
+
export type TypeOptionType =
|
|
6
|
+
| 'Permission'
|
|
7
|
+
| 'Navigate'
|
|
8
|
+
| 'Placement'
|
|
9
|
+
| 'SetCondition';
|
|
10
|
+
export type PermissionOptionType =
|
|
11
|
+
| 'notification'
|
|
12
|
+
| 'camera'
|
|
13
|
+
| 'microphone'
|
|
14
|
+
| 'location'
|
|
15
|
+
| 'photos'
|
|
16
|
+
| 'contacts'
|
|
17
|
+
| 'att'
|
|
18
|
+
| 'rating'
|
|
19
|
+
| 'GDPR';
|
|
20
|
+
export type PlacementKeyOptionType =
|
|
21
|
+
| 'terms'
|
|
22
|
+
| 'onboard'
|
|
23
|
+
| 'paywall'
|
|
24
|
+
| 'subscription'
|
|
25
|
+
| 'home';
|
|
26
|
+
export type ConditionKeyOptionType = 'termsAccepted';
|
|
6
27
|
export type FlexDirectionOptionType = 'row' | 'column';
|
|
7
28
|
export type FlexWrapOptionType = 'nowrap' | 'wrap' | 'wrap-reverse';
|
|
8
29
|
export type AlignItemsOptionType =
|
|
@@ -22,10 +43,12 @@ export type PositionOptionType = 'relative' | 'absolute';
|
|
|
22
43
|
|
|
23
44
|
export interface EventObjectGenerated {
|
|
24
45
|
type?: TypeOptionType;
|
|
25
|
-
permission?:
|
|
46
|
+
permission?: PermissionOptionType;
|
|
26
47
|
navigate_to?: string;
|
|
27
48
|
targetIndex?: number;
|
|
28
|
-
placementKey?:
|
|
49
|
+
placementKey?: PlacementKeyOptionType;
|
|
50
|
+
conditionKey?: ConditionKeyOptionType;
|
|
51
|
+
value?: boolean;
|
|
29
52
|
}
|
|
30
53
|
|
|
31
54
|
export interface FormSubmitButtonStyleGenerated {
|