@rippling/rippling-sdk 0.2.0-alpha.37 → 0.2.0-alpha.39
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/examples/sdui/sdui-crud-todo/sdui-crud-todo.ts +3 -3
- package/examples/sdui/sdui-showcase/sdui-showcase.ts +6 -11
- package/examples/sdui/sdui-tab-multi-view/sdui-tab-multi-view.ts +14 -24
- package/examples/sdui/vote/vote.ts +7 -11
- package/lib/manifest/app.d.mts +11 -1
- package/lib/manifest/app.d.mts.map +1 -1
- package/lib/manifest/app.d.ts +11 -1
- package/lib/manifest/app.d.ts.map +1 -1
- package/lib/manifest/app.js +42 -1
- package/lib/manifest/app.js.map +1 -1
- package/lib/manifest/app.mjs +42 -1
- package/lib/manifest/app.mjs.map +1 -1
- package/lib/manifest/category.d.mts +14 -3
- package/lib/manifest/category.d.mts.map +1 -1
- package/lib/manifest/category.d.ts +14 -3
- package/lib/manifest/category.d.ts.map +1 -1
- package/lib/manifest/category.js +25 -3
- package/lib/manifest/category.js.map +1 -1
- package/lib/manifest/category.mjs +25 -3
- package/lib/manifest/category.mjs.map +1 -1
- package/lib/manifest/custom-object-field-section.d.mts +13 -0
- package/lib/manifest/custom-object-field-section.d.mts.map +1 -1
- package/lib/manifest/custom-object-field-section.d.ts +13 -0
- package/lib/manifest/custom-object-field-section.d.ts.map +1 -1
- package/lib/manifest/custom-object-field-section.js +22 -1
- package/lib/manifest/custom-object-field-section.js.map +1 -1
- package/lib/manifest/custom-object-field-section.mjs +22 -1
- package/lib/manifest/custom-object-field-section.mjs.map +1 -1
- package/lib/manifest/custom-object-page-layout.d.mts +23 -0
- package/lib/manifest/custom-object-page-layout.d.mts.map +1 -1
- package/lib/manifest/custom-object-page-layout.d.ts +23 -0
- package/lib/manifest/custom-object-page-layout.d.ts.map +1 -1
- package/lib/manifest/custom-object-page-layout.js +45 -2
- package/lib/manifest/custom-object-page-layout.js.map +1 -1
- package/lib/manifest/custom-object-page-layout.mjs +45 -2
- package/lib/manifest/custom-object-page-layout.mjs.map +1 -1
- package/lib/manifest/custom-object.d.mts +41 -2
- package/lib/manifest/custom-object.d.mts.map +1 -1
- package/lib/manifest/custom-object.d.ts +41 -2
- package/lib/manifest/custom-object.d.ts.map +1 -1
- package/lib/manifest/custom-object.js +70 -3
- package/lib/manifest/custom-object.js.map +1 -1
- package/lib/manifest/custom-object.mjs +70 -3
- package/lib/manifest/custom-object.mjs.map +1 -1
- package/lib/manifest/existing-manifest-context.d.mts +87 -0
- package/lib/manifest/existing-manifest-context.d.mts.map +1 -0
- package/lib/manifest/existing-manifest-context.d.ts +87 -0
- package/lib/manifest/existing-manifest-context.d.ts.map +1 -0
- package/lib/manifest/existing-manifest-context.js +511 -0
- package/lib/manifest/existing-manifest-context.js.map +1 -0
- package/lib/manifest/existing-manifest-context.mjs +506 -0
- package/lib/manifest/existing-manifest-context.mjs.map +1 -0
- package/lib/manifest/field.d.mts +44 -2
- package/lib/manifest/field.d.mts.map +1 -1
- package/lib/manifest/field.d.ts +44 -2
- package/lib/manifest/field.d.ts.map +1 -1
- package/lib/manifest/field.js +134 -3
- package/lib/manifest/field.js.map +1 -1
- package/lib/manifest/field.mjs +134 -3
- package/lib/manifest/field.mjs.map +1 -1
- package/lib/manifest/index.d.mts +8 -7
- package/lib/manifest/index.d.mts.map +1 -1
- package/lib/manifest/index.d.ts +8 -7
- package/lib/manifest/index.d.ts.map +1 -1
- package/lib/manifest/index.js +3 -1
- package/lib/manifest/index.js.map +1 -1
- package/lib/manifest/index.mjs +5 -4
- package/lib/manifest/index.mjs.map +1 -1
- package/lib/manifest/list-view.d.mts +17 -0
- package/lib/manifest/list-view.d.mts.map +1 -1
- package/lib/manifest/list-view.d.ts +17 -0
- package/lib/manifest/list-view.d.ts.map +1 -1
- package/lib/manifest/list-view.js +38 -1
- package/lib/manifest/list-view.js.map +1 -1
- package/lib/manifest/list-view.mjs +38 -1
- package/lib/manifest/list-view.mjs.map +1 -1
- package/lib/manifest/manifest-builder.d.mts +60 -7
- package/lib/manifest/manifest-builder.d.mts.map +1 -1
- package/lib/manifest/manifest-builder.d.ts +60 -7
- package/lib/manifest/manifest-builder.d.ts.map +1 -1
- package/lib/manifest/manifest-builder.js +62 -4
- package/lib/manifest/manifest-builder.js.map +1 -1
- package/lib/manifest/manifest-builder.mjs +62 -4
- package/lib/manifest/manifest-builder.mjs.map +1 -1
- package/lib/manifest/manifest-function.d.mts +18 -3
- package/lib/manifest/manifest-function.d.mts.map +1 -1
- package/lib/manifest/manifest-function.d.ts +18 -3
- package/lib/manifest/manifest-function.d.ts.map +1 -1
- package/lib/manifest/manifest-function.js +44 -3
- package/lib/manifest/manifest-function.js.map +1 -1
- package/lib/manifest/manifest-function.mjs +44 -3
- package/lib/manifest/manifest-function.mjs.map +1 -1
- package/lib/manifest/rule.d.mts +17 -0
- package/lib/manifest/rule.d.mts.map +1 -1
- package/lib/manifest/rule.d.ts +17 -0
- package/lib/manifest/rule.d.ts.map +1 -1
- package/lib/manifest/rule.js +45 -1
- package/lib/manifest/rule.js.map +1 -1
- package/lib/manifest/rule.mjs +45 -1
- package/lib/manifest/rule.mjs.map +1 -1
- package/lib/manifest/sdui-page.d.mts +10 -1
- package/lib/manifest/sdui-page.d.mts.map +1 -1
- package/lib/manifest/sdui-page.d.ts +10 -1
- package/lib/manifest/sdui-page.d.ts.map +1 -1
- package/lib/manifest/sdui-page.js +28 -1
- package/lib/manifest/sdui-page.js.map +1 -1
- package/lib/manifest/sdui-page.mjs +28 -1
- package/lib/manifest/sdui-page.mjs.map +1 -1
- package/lib/manifest/validation.d.mts +17 -0
- package/lib/manifest/validation.d.mts.map +1 -1
- package/lib/manifest/validation.d.ts +17 -0
- package/lib/manifest/validation.d.ts.map +1 -1
- package/lib/manifest/validation.js +31 -1
- package/lib/manifest/validation.js.map +1 -1
- package/lib/manifest/validation.mjs +31 -1
- package/lib/manifest/validation.mjs.map +1 -1
- package/lib/sdui/actions.d.mts.map +1 -1
- package/lib/sdui/actions.d.ts.map +1 -1
- package/lib/sdui/actions.js +23 -13
- package/lib/sdui/actions.js.map +1 -1
- package/lib/sdui/actions.mjs +23 -13
- package/lib/sdui/actions.mjs.map +1 -1
- package/lib/sdui/component.d.mts +28 -11
- package/lib/sdui/component.d.mts.map +1 -1
- package/lib/sdui/component.d.ts +28 -11
- package/lib/sdui/component.d.ts.map +1 -1
- package/lib/sdui/component.js +69 -36
- package/lib/sdui/component.js.map +1 -1
- package/lib/sdui/component.mjs +69 -36
- package/lib/sdui/component.mjs.map +1 -1
- package/lib/sdui/components.d.mts +1141 -132
- package/lib/sdui/components.d.mts.map +1 -1
- package/lib/sdui/components.d.ts +1141 -132
- package/lib/sdui/components.d.ts.map +1 -1
- package/lib/sdui/components.js +834 -206
- package/lib/sdui/components.js.map +1 -1
- package/lib/sdui/components.mjs +830 -203
- package/lib/sdui/components.mjs.map +1 -1
- package/lib/sdui/directives.d.mts.map +1 -1
- package/lib/sdui/directives.d.ts.map +1 -1
- package/lib/sdui/directives.js +4 -1
- package/lib/sdui/directives.js.map +1 -1
- package/lib/sdui/directives.mjs +4 -1
- package/lib/sdui/directives.mjs.map +1 -1
- package/lib/sdui/index.d.mts +314 -261
- package/lib/sdui/index.d.mts.map +1 -1
- package/lib/sdui/index.d.ts +314 -261
- package/lib/sdui/index.d.ts.map +1 -1
- package/lib/sdui/index.js +255 -204
- package/lib/sdui/index.js.map +1 -1
- package/lib/sdui/index.mjs +128 -103
- package/lib/sdui/index.mjs.map +1 -1
- package/lib/sdui/propTypes.d.mts +6330 -0
- package/lib/sdui/propTypes.d.mts.map +1 -0
- package/lib/sdui/propTypes.d.ts +6330 -0
- package/lib/sdui/propTypes.d.ts.map +1 -0
- package/lib/sdui/propTypes.js +14 -0
- package/lib/sdui/propTypes.js.map +1 -0
- package/lib/sdui/propTypes.mjs +13 -0
- package/lib/sdui/propTypes.mjs.map +1 -0
- package/lib/sdui/render.d.mts +1 -1
- package/lib/sdui/render.d.mts.map +1 -1
- package/lib/sdui/render.d.ts +1 -1
- package/lib/sdui/render.d.ts.map +1 -1
- package/lib/sdui/render.js +11 -3
- package/lib/sdui/render.js.map +1 -1
- package/lib/sdui/render.mjs +11 -3
- package/lib/sdui/render.mjs.map +1 -1
- package/lib/sdui/state.d.mts.map +1 -1
- package/lib/sdui/state.d.ts.map +1 -1
- package/lib/sdui/state.js +3 -1
- package/lib/sdui/state.js.map +1 -1
- package/lib/sdui/state.mjs +3 -1
- package/lib/sdui/state.mjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/manifest/app.ts +46 -2
- package/src/lib/manifest/category.ts +35 -7
- package/src/lib/manifest/custom-object-field-section.ts +37 -1
- package/src/lib/manifest/custom-object-page-layout.ts +67 -2
- package/src/lib/manifest/custom-object.ts +88 -4
- package/src/lib/manifest/existing-manifest-context.ts +673 -0
- package/src/lib/manifest/field.ts +160 -3
- package/src/lib/manifest/index.ts +39 -4
- package/src/lib/manifest/list-view.ts +50 -1
- package/src/lib/manifest/manifest-builder.ts +115 -5
- package/src/lib/manifest/manifest-function.ts +44 -4
- package/src/lib/manifest/rule.ts +60 -1
- package/src/lib/manifest/sdui-page.ts +37 -2
- package/src/lib/manifest/validation.ts +48 -1
- package/src/lib/sdui/actions.ts +23 -10
- package/src/lib/sdui/component.ts +84 -44
- package/src/lib/sdui/components.ts +1627 -212
- package/src/lib/sdui/directives.ts +4 -1
- package/src/lib/sdui/index.ts +565 -200
- package/src/lib/sdui/propTypes.ts +8470 -0
- package/src/lib/sdui/render.ts +13 -5
- package/src/lib/sdui/state.ts +8 -2
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
package/src/lib/manifest/rule.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { generateId, type TriggerType, type UpdateTriggerType } from './_helpers';
|
|
2
2
|
import type { CustomObject } from './custom-object';
|
|
3
|
+
import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context';
|
|
3
4
|
import type { Field } from './field';
|
|
5
|
+
import type { ManifestComponentDeletion } from './manifest-builder';
|
|
6
|
+
|
|
7
|
+
export interface RuleDeletionIdentifier {
|
|
8
|
+
flowId: string;
|
|
9
|
+
modelRqlName: string;
|
|
10
|
+
}
|
|
4
11
|
|
|
5
12
|
/**
|
|
6
13
|
* A single action performed when a {@link Rule} fires.
|
|
@@ -79,6 +86,10 @@ export interface RuleProps {
|
|
|
79
86
|
actions?: RuleAction[];
|
|
80
87
|
}
|
|
81
88
|
|
|
89
|
+
export interface RuleLoadFromExistingOptions extends ExistingManifestContextOptions {
|
|
90
|
+
customObjectApiName?: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
82
93
|
/** SELECT / RADIO rule actions use list `fixed_value` (Rippling rule builder). */
|
|
83
94
|
function fixedValueToWire(targetField: Field, value: any): any {
|
|
84
95
|
const ft = targetField.getFieldType();
|
|
@@ -103,6 +114,10 @@ function actionToDict(action: RuleAction): Record<string, any> {
|
|
|
103
114
|
return out;
|
|
104
115
|
}
|
|
105
116
|
|
|
117
|
+
function hasOwn(value: Record<string, any>, key: string): boolean {
|
|
118
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
119
|
+
}
|
|
120
|
+
|
|
106
121
|
/**
|
|
107
122
|
* Defines a record-triggered automation rule and registers it with the manifest.
|
|
108
123
|
*
|
|
@@ -122,6 +137,16 @@ function actionToDict(action: RuleAction): Record<string, any> {
|
|
|
122
137
|
* ```
|
|
123
138
|
*/
|
|
124
139
|
export class Rule {
|
|
140
|
+
static readonly componentType = 'CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW' as const;
|
|
141
|
+
|
|
142
|
+
static toDeletionIdentifier(identifier: RuleDeletionIdentifier): ManifestComponentDeletion {
|
|
143
|
+
return {
|
|
144
|
+
type: Rule.componentType,
|
|
145
|
+
flow_id: identifier.flowId,
|
|
146
|
+
model_rql_name: identifier.modelRqlName,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
125
150
|
private readonly _flowId: string;
|
|
126
151
|
private readonly _customObjectApiName: string;
|
|
127
152
|
private readonly _flowName: string;
|
|
@@ -154,6 +179,40 @@ export class Rule {
|
|
|
154
179
|
customObject._register(this);
|
|
155
180
|
}
|
|
156
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Loads this record-triggered rule from the active existing-manifest JSON context.
|
|
184
|
+
*/
|
|
185
|
+
static loadFromExisting(flowId: string, options: RuleLoadFromExistingOptions = {}): Rule {
|
|
186
|
+
return getExistingManifestContext(options).loadRule(flowId, options);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** @internal Hydrates a record-triggered rule from existing manifest wire JSON. */
|
|
190
|
+
static _fromExistingComponent(
|
|
191
|
+
customObject: CustomObject,
|
|
192
|
+
component: Record<string, any>,
|
|
193
|
+
resolveField: (apiName: string) => Field,
|
|
194
|
+
): Rule {
|
|
195
|
+
const props: RuleProps = {
|
|
196
|
+
flowId: component['flow_id'],
|
|
197
|
+
flowName: component['flow_name'] ?? component['flow_id'],
|
|
198
|
+
triggerType: component['trigger_type'],
|
|
199
|
+
actions: (component['actions'] ?? []).map((action: Record<string, any>): RuleAction => {
|
|
200
|
+
const out: RuleAction = {
|
|
201
|
+
targetField: resolveField(action['target_field']),
|
|
202
|
+
};
|
|
203
|
+
if (hasOwn(action, 'fixed_value')) out.fixedValue = action['fixed_value'];
|
|
204
|
+
if (hasOwn(action, 'rql_formula')) out.rqlFormula = action['rql_formula'];
|
|
205
|
+
return out;
|
|
206
|
+
}),
|
|
207
|
+
};
|
|
208
|
+
if (component['description'] !== undefined) props.description = component['description'];
|
|
209
|
+
if (component['rql_condition'] !== undefined) props.rqlCondition = component['rql_condition'];
|
|
210
|
+
if (component['update_trigger_type'] !== undefined) {
|
|
211
|
+
props.updateTriggerType = component['update_trigger_type'];
|
|
212
|
+
}
|
|
213
|
+
return new Rule(customObject, props);
|
|
214
|
+
}
|
|
215
|
+
|
|
157
216
|
/**
|
|
158
217
|
* Returns the stable identifier for this rule (e.g. `'gym_default_enrollment_status'`).
|
|
159
218
|
*/
|
|
@@ -176,7 +235,7 @@ export class Rule {
|
|
|
176
235
|
*/
|
|
177
236
|
toDict(): Record<string, any> {
|
|
178
237
|
const component: Record<string, any> = {
|
|
179
|
-
type:
|
|
238
|
+
type: Rule.componentType,
|
|
180
239
|
model_rql_name: this._customObjectApiName,
|
|
181
240
|
flow_id: this._flowId,
|
|
182
241
|
flow_name: this._flowName,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context';
|
|
1
2
|
import type { ManifestFunction } from './manifest-function';
|
|
2
|
-
import type { ManifestScope } from './manifest-builder';
|
|
3
|
+
import type { ManifestComponentDeletion, ManifestScope } from './manifest-builder';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Initialization properties for {@link SduiPage}.
|
|
@@ -59,6 +60,15 @@ export interface SduiPageProps {
|
|
|
59
60
|
* @see {@link App} — hosts this page as a tab via `pages`.
|
|
60
61
|
*/
|
|
61
62
|
export class SduiPage {
|
|
63
|
+
static readonly componentType = 'SDUI_PAGE' as const;
|
|
64
|
+
|
|
65
|
+
static toDeletionIdentifier(sduiPageId: string): ManifestComponentDeletion {
|
|
66
|
+
return {
|
|
67
|
+
type: SduiPage.componentType,
|
|
68
|
+
sdui_page_id: sduiPageId,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
62
72
|
private readonly _sduiPageId: string;
|
|
63
73
|
private readonly _name: string;
|
|
64
74
|
private readonly _functionApiName: string;
|
|
@@ -80,6 +90,31 @@ export class SduiPage {
|
|
|
80
90
|
scope._register(this);
|
|
81
91
|
}
|
|
82
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Loads this SDUI page from the active existing-manifest JSON context.
|
|
95
|
+
*/
|
|
96
|
+
static loadFromExisting(sduiPageId: string, options: ExistingManifestContextOptions = {}): SduiPage {
|
|
97
|
+
return getExistingManifestContext(options).loadSduiPage(sduiPageId);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** @internal Hydrates an SDUI page from existing manifest wire JSON. */
|
|
101
|
+
static _fromExistingComponent(
|
|
102
|
+
scope: ManifestScope,
|
|
103
|
+
component: Record<string, any>,
|
|
104
|
+
resolveFunction: (apiName: string) => ManifestFunction | string,
|
|
105
|
+
): SduiPage {
|
|
106
|
+
const functionApiName = component['function_api_name'];
|
|
107
|
+
const props: SduiPageProps = {
|
|
108
|
+
sduiPageId: component['sdui_page_id'],
|
|
109
|
+
name: component['name'] ?? component['sdui_page_id'],
|
|
110
|
+
function: typeof functionApiName === 'string' ? resolveFunction(functionApiName) : functionApiName,
|
|
111
|
+
};
|
|
112
|
+
if (component['description'] !== undefined) {
|
|
113
|
+
props.description = component['description'];
|
|
114
|
+
}
|
|
115
|
+
return new SduiPage(scope, props);
|
|
116
|
+
}
|
|
117
|
+
|
|
83
118
|
/**
|
|
84
119
|
* Returns the unique page identifier (e.g. `'gym_today_schedule'`).
|
|
85
120
|
*/
|
|
@@ -108,7 +143,7 @@ export class SduiPage {
|
|
|
108
143
|
*/
|
|
109
144
|
toDict(): Record<string, any> {
|
|
110
145
|
return {
|
|
111
|
-
type:
|
|
146
|
+
type: SduiPage.componentType,
|
|
112
147
|
name: this._name,
|
|
113
148
|
sdui_page_id: this._sduiPageId,
|
|
114
149
|
function_api_name: this._functionApiName,
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { generateId, type ValidationLevel, type ValidationState } from './_helpers';
|
|
2
2
|
import type { CustomObject } from './custom-object';
|
|
3
|
+
import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context';
|
|
3
4
|
import type { Field } from './field';
|
|
5
|
+
import type { ManifestComponentDeletion } from './manifest-builder';
|
|
6
|
+
|
|
7
|
+
export interface ValidationDeletionIdentifier {
|
|
8
|
+
ruleId: string;
|
|
9
|
+
modelRqlName: string;
|
|
10
|
+
}
|
|
4
11
|
|
|
5
12
|
/**
|
|
6
13
|
* Initialization properties for {@link Validation}.
|
|
@@ -59,6 +66,10 @@ export interface ValidationProps {
|
|
|
59
66
|
fieldRef?: Field;
|
|
60
67
|
}
|
|
61
68
|
|
|
69
|
+
export interface ValidationLoadFromExistingOptions extends ExistingManifestContextOptions {
|
|
70
|
+
customObjectApiName?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
62
73
|
/**
|
|
63
74
|
* Defines a validation rule and registers it with the manifest.
|
|
64
75
|
*
|
|
@@ -85,6 +96,16 @@ export interface ValidationProps {
|
|
|
85
96
|
* ```
|
|
86
97
|
*/
|
|
87
98
|
export class Validation {
|
|
99
|
+
static readonly componentType = 'CUSTOM_OBJECT_VALIDATION_RULE' as const;
|
|
100
|
+
|
|
101
|
+
static toDeletionIdentifier(identifier: ValidationDeletionIdentifier): ManifestComponentDeletion {
|
|
102
|
+
return {
|
|
103
|
+
type: Validation.componentType,
|
|
104
|
+
rule_id: identifier.ruleId,
|
|
105
|
+
model_rql_name: identifier.modelRqlName,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
88
109
|
private readonly _ruleId: string;
|
|
89
110
|
private readonly _customObjectApiName: string;
|
|
90
111
|
private readonly _displayName: string;
|
|
@@ -112,6 +133,32 @@ export class Validation {
|
|
|
112
133
|
customObject._register(this);
|
|
113
134
|
}
|
|
114
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Loads this validation rule from the active existing-manifest JSON context.
|
|
138
|
+
*/
|
|
139
|
+
static loadFromExisting(ruleId: string, options: ValidationLoadFromExistingOptions = {}): Validation {
|
|
140
|
+
return getExistingManifestContext(options).loadValidation(ruleId, options);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** @internal Hydrates a validation rule from existing manifest wire JSON. */
|
|
144
|
+
static _fromExistingComponent(
|
|
145
|
+
customObject: CustomObject,
|
|
146
|
+
component: Record<string, any>,
|
|
147
|
+
resolveField: (apiName: string) => Field,
|
|
148
|
+
): Validation {
|
|
149
|
+
const props: ValidationProps = {
|
|
150
|
+
ruleId: component['rule_id'],
|
|
151
|
+
displayName: component['display_name'] ?? component['rule_id'],
|
|
152
|
+
rqlFormula: component['rql_formula'] ?? '',
|
|
153
|
+
errorMessage: component['custom_error_message'] ?? '',
|
|
154
|
+
state: component['state'],
|
|
155
|
+
level: component['level'],
|
|
156
|
+
ruleDescription: component['rule_description'] ?? '',
|
|
157
|
+
};
|
|
158
|
+
if (component['field_rql_name'] != null) props.fieldRef = resolveField(component['field_rql_name']);
|
|
159
|
+
return new Validation(customObject, props);
|
|
160
|
+
}
|
|
161
|
+
|
|
115
162
|
/**
|
|
116
163
|
* Returns the stable identifier for this validation rule (e.g. `'gym_session_end_after_start'`).
|
|
117
164
|
*/
|
|
@@ -134,7 +181,7 @@ export class Validation {
|
|
|
134
181
|
*/
|
|
135
182
|
toDict(): Record<string, any> {
|
|
136
183
|
const component: Record<string, any> = {
|
|
137
|
-
type:
|
|
184
|
+
type: Validation.componentType,
|
|
138
185
|
model_rql_name: this._customObjectApiName,
|
|
139
186
|
rule_id: this._ruleId,
|
|
140
187
|
state: this._state,
|
package/src/lib/sdui/actions.ts
CHANGED
|
@@ -12,15 +12,19 @@ export function set(ref: unknown, value: unknown): ActionBinding {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function push(ref: unknown, value: unknown, opts?: { clear?: unknown }): ActionBinding {
|
|
15
|
-
const params: Record<string, unknown> = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const params: Record<string, unknown> = {
|
|
16
|
+
statePath: extractPath(ref),
|
|
17
|
+
value,
|
|
18
|
+
...(opts?.clear ? { clearStatePath: extractPath(opts.clear) } : {}),
|
|
19
|
+
};
|
|
19
20
|
return { action: 'pushState', params };
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export function remove(ref: unknown, index: unknown): ActionBinding {
|
|
23
|
-
return {
|
|
24
|
+
return {
|
|
25
|
+
action: 'removeState',
|
|
26
|
+
params: { statePath: extractPath(ref), index },
|
|
27
|
+
};
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export function callFunction(
|
|
@@ -29,11 +33,20 @@ export function callFunction(
|
|
|
29
33
|
opts?: Record<string, unknown>,
|
|
30
34
|
): ActionBinding {
|
|
31
35
|
const { responsePath, onSuccess, onError, ...rest } = opts || {};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
// Spread rest FIRST so explicit functionId/action always win if opts
|
|
37
|
+
// happens to carry colliding keys.
|
|
38
|
+
const params: Record<string, unknown> = {
|
|
39
|
+
...rest,
|
|
40
|
+
functionId,
|
|
41
|
+
action,
|
|
42
|
+
...(responsePath !== undefined ? { responsePath } : {}),
|
|
43
|
+
};
|
|
44
|
+
const binding: ActionBinding = {
|
|
45
|
+
action: 'triggerFunction',
|
|
46
|
+
params,
|
|
47
|
+
...(onSuccess ? { onSuccess: onSuccess as ActionBinding } : {}),
|
|
48
|
+
...(onError ? { onError: onError as ActionBinding } : {}),
|
|
49
|
+
};
|
|
37
50
|
return binding;
|
|
38
51
|
}
|
|
39
52
|
|
|
@@ -1,25 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* component() — creates a stateful component boundary.
|
|
3
3
|
*
|
|
4
|
-
* Provides ctx.
|
|
5
|
-
*
|
|
4
|
+
* Provides ctx.localState (scoped local state slot) and ctx.onMount (an
|
|
5
|
+
* action that fires once when the component mounts on the client). The
|
|
6
|
+
* runtime handles state setup and mount firing; consumers don't need to
|
|
7
|
+
* know how the wrapping is implemented.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: these are NOT React hooks. The factory runs ONCE at spec-build
|
|
10
|
+
* time, producing a JSON description of the UI. `localState` returns a
|
|
11
|
+
* path-builder proxy (not a [value, setter] tuple); `onMount` registers
|
|
12
|
+
* a JSON action that fires on first client render (not an effect that
|
|
13
|
+
* re-runs).
|
|
6
14
|
*
|
|
7
15
|
* Usage:
|
|
8
16
|
* function MembersGrid(props) {
|
|
9
17
|
* return component((ctx) => {
|
|
10
|
-
* const local = ctx.
|
|
11
|
-
* ctx.
|
|
12
|
-
* return { element: DataGrid({ ... })
|
|
18
|
+
* const local = ctx.localState({ data: [], loading: true })
|
|
19
|
+
* ctx.onMount(fetchAction)
|
|
20
|
+
* return { element: DataGrid({ ... }) }
|
|
13
21
|
* })
|
|
14
22
|
* }
|
|
15
23
|
*/
|
|
16
24
|
|
|
17
|
-
import type { SpecNode } from './components';
|
|
18
25
|
import type { ActionBinding } from './actions';
|
|
26
|
+
import type { SpecNode } from './components';
|
|
19
27
|
|
|
20
28
|
let _idCounter = 0;
|
|
29
|
+
// Counter-only IDs (no Date.now()) so the same factory body produces the
|
|
30
|
+
// same paths across runs. The counter is monotonic per process; calling
|
|
31
|
+
// `_resetIdCounter()` (tests only) brings it back to 0 so snapshot tests
|
|
32
|
+
// stay stable.
|
|
21
33
|
function generateId(): string {
|
|
22
|
-
|
|
34
|
+
_idCounter += 1;
|
|
35
|
+
return `auto_${_idCounter.toString(36)}`;
|
|
23
36
|
}
|
|
24
37
|
|
|
25
38
|
// Reset counter (for testing)
|
|
@@ -37,15 +50,25 @@ export interface StateScope {
|
|
|
37
50
|
}
|
|
38
51
|
|
|
39
52
|
interface ComponentContext {
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Declare a scoped local state slot. Returns a path-builder proxy whose
|
|
55
|
+
* property accesses produce `{ $state: '/path' }` references for reading
|
|
56
|
+
* and `.$bind` for two-way binding. Not a React hook — runs once when
|
|
57
|
+
* the factory is invoked, never returns `[value, setter]`.
|
|
58
|
+
*/
|
|
59
|
+
localState<T extends Record<string, unknown>>(initial: T): StateScope;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Register a JSON action to fire once when the component mounts on the
|
|
63
|
+
* client. Not a React effect — does not re-run, has no dependency
|
|
64
|
+
* tracking. Pass an `ActionBinding` (e.g. from `set(...)`, `callFunction(...)`)
|
|
65
|
+
* or an array of them.
|
|
66
|
+
*/
|
|
67
|
+
onMount(action: ActionBinding | ActionBinding[]): void;
|
|
44
68
|
}
|
|
45
69
|
|
|
46
70
|
interface ComponentResult {
|
|
47
71
|
element: SpecNode;
|
|
48
|
-
[key: string]: any; // exposed actions like refresh, reset, etc.
|
|
49
72
|
}
|
|
50
73
|
|
|
51
74
|
function createScopeProxy(basePath: string): StateScope {
|
|
@@ -65,58 +88,75 @@ function createScopeProxy(basePath: string): StateScope {
|
|
|
65
88
|
}
|
|
66
89
|
|
|
67
90
|
export function component(factory: (ctx: ComponentContext) => ComponentResult): ComponentResult {
|
|
68
|
-
|
|
69
|
-
|
|
91
|
+
const states: { path: string; initial: unknown }[] = [];
|
|
92
|
+
const mountActions: (ActionBinding | ActionBinding[])[] = [];
|
|
70
93
|
|
|
71
94
|
const ctx: ComponentContext = {
|
|
72
|
-
|
|
95
|
+
localState<T extends Record<string, unknown>>(initial: T): StateScope {
|
|
73
96
|
const path = `/${generateId()}`;
|
|
74
|
-
|
|
97
|
+
states.push({ path, initial });
|
|
75
98
|
return createScopeProxy(path);
|
|
76
99
|
},
|
|
77
|
-
|
|
78
|
-
|
|
100
|
+
onMount(action: ActionBinding | ActionBinding[]) {
|
|
101
|
+
mountActions.push(action);
|
|
79
102
|
},
|
|
80
103
|
};
|
|
81
104
|
|
|
82
105
|
const result = factory(ctx);
|
|
83
106
|
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
const capturedMount = mountAction;
|
|
87
|
-
|
|
88
|
-
// If no local state and no effects, return as-is
|
|
89
|
-
if (!capturedState && !capturedMount) {
|
|
107
|
+
// No state, no effects → return as-is.
|
|
108
|
+
if (states.length === 0 && mountActions.length === 0) {
|
|
90
109
|
return result;
|
|
91
110
|
}
|
|
92
111
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
providerProps['onMountAction'] = capturedMount;
|
|
112
|
+
// Collapse zero/one/many mount actions for the single onMountAction slot
|
|
113
|
+
// on the innermost wrapper.
|
|
114
|
+
let combinedMount: ActionBinding | ActionBinding[] | undefined;
|
|
115
|
+
if (mountActions.length === 1) {
|
|
116
|
+
combinedMount = mountActions[0];
|
|
117
|
+
} else if (mountActions.length > 1) {
|
|
118
|
+
combinedMount = mountActions.flatMap((a) => (Array.isArray(a) ? a : [a]));
|
|
101
119
|
}
|
|
102
120
|
|
|
103
|
-
|
|
104
|
-
type: '_LocalStateProvider',
|
|
105
|
-
props: providerProps,
|
|
106
|
-
children: [result.element],
|
|
107
|
-
};
|
|
121
|
+
let wrapped: SpecNode = result.element;
|
|
108
122
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
123
|
+
if (states.length === 0) {
|
|
124
|
+
// Mount-only: a single state-provider wrapper carries onMountAction.
|
|
125
|
+
wrapped = {
|
|
126
|
+
type: '_LocalStateProvider',
|
|
127
|
+
props: combinedMount ? { onMountAction: combinedMount } : {},
|
|
128
|
+
children: [wrapped],
|
|
115
129
|
};
|
|
130
|
+
} else {
|
|
131
|
+
// One state-provider wrapper per localState() call, nested
|
|
132
|
+
// innermost-first. Mount actions attach to the innermost wrapper so
|
|
133
|
+
// every state slot is initialized before any action fires.
|
|
134
|
+
for (let i = states.length - 1; i >= 0; i--) {
|
|
135
|
+
const state = states[i];
|
|
136
|
+
if (state) {
|
|
137
|
+
const isInnermost = i === states.length - 1;
|
|
138
|
+
const props: Record<string, unknown> = {
|
|
139
|
+
statePath: state.path,
|
|
140
|
+
initialValue: state.initial,
|
|
141
|
+
...(isInnermost && combinedMount ? { onMountAction: combinedMount } : {}),
|
|
142
|
+
};
|
|
143
|
+
const wrapper: SpecNode = {
|
|
144
|
+
type: '_LocalStateProvider',
|
|
145
|
+
props,
|
|
146
|
+
children: [wrapped],
|
|
147
|
+
};
|
|
148
|
+
// render() picks up this marker to populate spec.state.
|
|
149
|
+
(wrapper as any)._localStateRegistration = {
|
|
150
|
+
path: state.path,
|
|
151
|
+
initial: state.initial,
|
|
152
|
+
};
|
|
153
|
+
wrapped = wrapper;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
116
156
|
}
|
|
117
157
|
|
|
118
158
|
return {
|
|
119
159
|
...result,
|
|
120
|
-
element:
|
|
160
|
+
element: wrapped,
|
|
121
161
|
};
|
|
122
162
|
}
|