@ankhorage/contracts 1.10.0 → 1.12.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/CHANGELOG.md +12 -0
- package/dist/bindings.d.ts +80 -25
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js.map +1 -1
- package/dist/types.d.ts +4 -79
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ui.d.ts +37 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js.map +1 -1
- package/package.json +1 -1
- package/src/bindings.test.ts +290 -70
- package/src/bindings.ts +101 -25
- package/src/contracts.test.ts +0 -172
- package/src/types.ts +4 -118
- package/src/ui.test.ts +169 -0
- package/src/ui.ts +53 -0
package/src/contracts.test.ts
CHANGED
|
@@ -5,8 +5,6 @@ import { COLOR_HARMONIES } from '@ankhorage/color-theory';
|
|
|
5
5
|
import { describe, expect, it } from 'bun:test';
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
-
type ActionBinding,
|
|
9
|
-
type ActionValueSource,
|
|
10
8
|
APP_CATEGORIES,
|
|
11
9
|
type AppCategory,
|
|
12
10
|
AUTH_PROVIDERS,
|
|
@@ -21,22 +19,16 @@ import {
|
|
|
21
19
|
type DbAdapter,
|
|
22
20
|
type DbAdminAdapter,
|
|
23
21
|
type DbChangeEvent,
|
|
24
|
-
type DbPersistActionBinding,
|
|
25
|
-
type DbPersistCommand,
|
|
26
22
|
type DbRealtimeAdapter,
|
|
27
23
|
DEPLOYMENT_TARGETS,
|
|
28
|
-
type EmailSendActionBinding,
|
|
29
24
|
type FormSubmitEventDto,
|
|
30
25
|
type ImageAssetSource,
|
|
31
|
-
type KnownActionBinding,
|
|
32
|
-
type KnownCommandDto,
|
|
33
26
|
NAVIGATOR_TYPES,
|
|
34
27
|
type StoragePublicUrlResult,
|
|
35
28
|
type StorageResult,
|
|
36
29
|
type StorageUploadResult,
|
|
37
30
|
type ThemeConfig,
|
|
38
31
|
type ThemeModeConfig,
|
|
39
|
-
type UiNode,
|
|
40
32
|
} from './index';
|
|
41
33
|
|
|
42
34
|
async function collectTypeScriptFiles(directory: string): Promise<string[]> {
|
|
@@ -236,55 +228,6 @@ describe('contracts', () => {
|
|
|
236
228
|
expect(JSON.parse(JSON.stringify(source))).toEqual(source);
|
|
237
229
|
});
|
|
238
230
|
|
|
239
|
-
it('serializes node event bindings for submit actions', () => {
|
|
240
|
-
const node: UiNode = {
|
|
241
|
-
id: 'contact-form',
|
|
242
|
-
type: 'Form',
|
|
243
|
-
props: {
|
|
244
|
-
title: 'Contact us',
|
|
245
|
-
},
|
|
246
|
-
events: {
|
|
247
|
-
submit: [
|
|
248
|
-
{
|
|
249
|
-
type: 'email.send',
|
|
250
|
-
payload: {
|
|
251
|
-
to: 'info@example.com',
|
|
252
|
-
subject: 'New contact message',
|
|
253
|
-
bodyFrom: 'payload.values.message',
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
type: 'db.persist',
|
|
258
|
-
payload: {
|
|
259
|
-
collection: 'contact_messages',
|
|
260
|
-
mode: 'columns',
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
|
-
],
|
|
264
|
-
},
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
expect(JSON.parse(JSON.stringify(node))).toEqual(node);
|
|
268
|
-
expect(node.events?.submit?.map((action) => action.type)).toEqual(['email.send', 'db.persist']);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it('accepts conditional node event bindings', () => {
|
|
272
|
-
const binding: ActionBinding = {
|
|
273
|
-
type: 'db.persist',
|
|
274
|
-
payload: {
|
|
275
|
-
collection: 'contact_messages',
|
|
276
|
-
},
|
|
277
|
-
when: {
|
|
278
|
-
source: 'event',
|
|
279
|
-
path: 'payload.values.email',
|
|
280
|
-
operator: 'exists',
|
|
281
|
-
},
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
expect(binding.when?.operator).toBe('exists');
|
|
285
|
-
expect(binding.payload?.collection).toBe('contact_messages');
|
|
286
|
-
});
|
|
287
|
-
|
|
288
231
|
it('serializes normalized form submit event DTOs', () => {
|
|
289
232
|
const event: FormSubmitEventDto = {
|
|
290
233
|
type: 'form.submit',
|
|
@@ -344,121 +287,6 @@ describe('contracts', () => {
|
|
|
344
287
|
expect(knownEventType).toBe('form.submit');
|
|
345
288
|
});
|
|
346
289
|
|
|
347
|
-
it('serializes provider-neutral value sources for action payloads', () => {
|
|
348
|
-
const sources: readonly ActionValueSource[] = [
|
|
349
|
-
{ source: 'event', path: 'payload.values.email' },
|
|
350
|
-
{ source: 'literal', value: 'website-contact-form' },
|
|
351
|
-
{ source: 'context', path: 'auth.user.id' },
|
|
352
|
-
{ source: 'state', path: 'forms.contact.isSubmitting' },
|
|
353
|
-
];
|
|
354
|
-
|
|
355
|
-
expect(JSON.parse(JSON.stringify(sources))).toEqual(sources);
|
|
356
|
-
expect(sources.map((source) => source.source)).toEqual([
|
|
357
|
-
'event',
|
|
358
|
-
'literal',
|
|
359
|
-
'context',
|
|
360
|
-
'state',
|
|
361
|
-
]);
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it('serializes a typed db.persist action binding', () => {
|
|
365
|
-
const binding: DbPersistActionBinding = {
|
|
366
|
-
type: 'db.persist',
|
|
367
|
-
payload: {
|
|
368
|
-
collection: 'contact_messages',
|
|
369
|
-
mode: 'columns',
|
|
370
|
-
values: [
|
|
371
|
-
{
|
|
372
|
-
field: 'firstname',
|
|
373
|
-
valueFrom: { source: 'event', path: 'payload.values.firstname' },
|
|
374
|
-
transform: 'trim',
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
field: 'message',
|
|
378
|
-
valueFrom: { source: 'event', path: 'payload.values.message' },
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
field: 'source',
|
|
382
|
-
valueFrom: { source: 'literal', value: 'website-contact-form' },
|
|
383
|
-
},
|
|
384
|
-
],
|
|
385
|
-
},
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
expect(JSON.parse(JSON.stringify(binding))).toEqual(binding);
|
|
389
|
-
expect(binding.payload.values.map((value) => value.field)).toEqual([
|
|
390
|
-
'firstname',
|
|
391
|
-
'message',
|
|
392
|
-
'source',
|
|
393
|
-
]);
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it('serializes a typed email.send action binding', () => {
|
|
397
|
-
const binding: EmailSendActionBinding = {
|
|
398
|
-
type: 'email.send',
|
|
399
|
-
payload: {
|
|
400
|
-
to: 'info@example.com',
|
|
401
|
-
subject: 'New contact message',
|
|
402
|
-
bodyFrom: { source: 'event', path: 'payload.values.message' },
|
|
403
|
-
},
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
expect(JSON.parse(JSON.stringify(binding))).toEqual(binding);
|
|
407
|
-
expect(binding.payload.bodyFrom?.source).toBe('event');
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
it('accepts typed action bindings in node event bindings', () => {
|
|
411
|
-
const persist: DbPersistActionBinding = {
|
|
412
|
-
type: 'db.persist',
|
|
413
|
-
payload: {
|
|
414
|
-
collection: 'contact_messages',
|
|
415
|
-
values: [
|
|
416
|
-
{
|
|
417
|
-
field: 'message',
|
|
418
|
-
valueFrom: { source: 'event', path: 'payload.values.message' },
|
|
419
|
-
},
|
|
420
|
-
],
|
|
421
|
-
},
|
|
422
|
-
};
|
|
423
|
-
const email: EmailSendActionBinding = {
|
|
424
|
-
type: 'email.send',
|
|
425
|
-
payload: {
|
|
426
|
-
to: 'info@example.com',
|
|
427
|
-
bodyFrom: { source: 'event', path: 'payload.values.message' },
|
|
428
|
-
},
|
|
429
|
-
};
|
|
430
|
-
const node: UiNode = {
|
|
431
|
-
id: 'contact-form',
|
|
432
|
-
type: 'Form',
|
|
433
|
-
events: {
|
|
434
|
-
submit: [persist, email],
|
|
435
|
-
},
|
|
436
|
-
};
|
|
437
|
-
const known: KnownActionBinding = persist;
|
|
438
|
-
|
|
439
|
-
expect(node.events?.submit?.map((action) => action.type)).toEqual(['db.persist', 'email.send']);
|
|
440
|
-
expect(known.type).toBe('db.persist');
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
it('serializes a normalized db.persist command DTO', () => {
|
|
444
|
-
const command: DbPersistCommand = {
|
|
445
|
-
type: 'db.persist.command',
|
|
446
|
-
payload: {
|
|
447
|
-
operation: 'insert',
|
|
448
|
-
collection: 'contact_messages',
|
|
449
|
-
mode: 'columns',
|
|
450
|
-
values: {
|
|
451
|
-
firstname: 'Fabio',
|
|
452
|
-
message: 'This is my contact message',
|
|
453
|
-
},
|
|
454
|
-
},
|
|
455
|
-
};
|
|
456
|
-
const knownCommand: KnownCommandDto = command;
|
|
457
|
-
|
|
458
|
-
expect(JSON.parse(JSON.stringify(command))).toEqual(command);
|
|
459
|
-
expect(knownCommand.type).toBe('db.persist.command');
|
|
460
|
-
});
|
|
461
|
-
|
|
462
290
|
it('accepts a provider-neutral CRUD database adapter', async () => {
|
|
463
291
|
const adapter: DbAdapter = {
|
|
464
292
|
capabilities: {
|
package/src/types.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ColorHarmony } from '@ankhorage/color-theory';
|
|
2
2
|
|
|
3
3
|
import type { AuthFlowConfig, AuthIdentifierKind, AuthSignUpField } from './auth';
|
|
4
|
-
import type {
|
|
4
|
+
import type { ComponentDataBindingRegistry } from './bindings';
|
|
5
|
+
import type { DataSourceRegistry } from './data';
|
|
5
6
|
|
|
6
7
|
export interface ThemeModeConfig {
|
|
7
8
|
primaryColor: string;
|
|
@@ -89,121 +90,6 @@ export type ManifestValue =
|
|
|
89
90
|
| readonly ManifestValue[]
|
|
90
91
|
| { readonly [key: string]: ManifestValue };
|
|
91
92
|
|
|
92
|
-
export type ActionValue = ManifestValue;
|
|
93
|
-
|
|
94
|
-
export type ActionBindingPayload = Record<string, ActionValue>;
|
|
95
|
-
|
|
96
|
-
export type ActionConditionSource = 'context' | 'event' | 'state';
|
|
97
|
-
|
|
98
|
-
export type ActionConditionOperator = 'eq' | 'exists' | 'neq' | 'notExists';
|
|
99
|
-
|
|
100
|
-
export interface ActionCondition {
|
|
101
|
-
readonly source: ActionConditionSource;
|
|
102
|
-
readonly path: string;
|
|
103
|
-
readonly operator: ActionConditionOperator;
|
|
104
|
-
readonly value?: ActionValue;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export type ActionValueSource =
|
|
108
|
-
| {
|
|
109
|
-
readonly source: 'context';
|
|
110
|
-
readonly path: string;
|
|
111
|
-
}
|
|
112
|
-
| {
|
|
113
|
-
readonly source: 'event';
|
|
114
|
-
readonly path: string;
|
|
115
|
-
}
|
|
116
|
-
| {
|
|
117
|
-
readonly source: 'literal';
|
|
118
|
-
readonly value: ActionValue;
|
|
119
|
-
}
|
|
120
|
-
| {
|
|
121
|
-
readonly source: 'state';
|
|
122
|
-
readonly path: string;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
export type ActionValueTransform = 'lowercase' | 'trim' | 'uppercase';
|
|
126
|
-
|
|
127
|
-
export interface ActionValueBinding {
|
|
128
|
-
readonly valueFrom: ActionValueSource;
|
|
129
|
-
readonly transform?: ActionValueTransform | readonly ActionValueTransform[];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export interface ActionBinding<
|
|
133
|
-
TType extends string = string,
|
|
134
|
-
TPayload extends object = ActionBindingPayload,
|
|
135
|
-
> {
|
|
136
|
-
readonly type: TType;
|
|
137
|
-
readonly payload?: TPayload;
|
|
138
|
-
readonly when?: ActionCondition;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export type DbPersistMode = 'columns' | 'json';
|
|
142
|
-
|
|
143
|
-
export interface DbPersistFieldBinding {
|
|
144
|
-
readonly field: string;
|
|
145
|
-
readonly valueFrom: ActionValueSource;
|
|
146
|
-
readonly transform?: ActionValueTransform | readonly ActionValueTransform[];
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export interface DbPersistActionPayload {
|
|
150
|
-
readonly collection: string;
|
|
151
|
-
readonly kind?: string;
|
|
152
|
-
readonly mode?: DbPersistMode;
|
|
153
|
-
readonly jsonField?: string;
|
|
154
|
-
readonly values: readonly DbPersistFieldBinding[];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export interface DbPersistActionBinding extends ActionBinding<
|
|
158
|
-
'db.persist',
|
|
159
|
-
DbPersistActionPayload
|
|
160
|
-
> {
|
|
161
|
-
readonly payload: DbPersistActionPayload;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export interface EmailSendActionPayload {
|
|
165
|
-
readonly to: string;
|
|
166
|
-
readonly subject?: string;
|
|
167
|
-
readonly body?: string;
|
|
168
|
-
readonly bodyFrom?: ActionValueSource;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export interface EmailSendActionBinding extends ActionBinding<
|
|
172
|
-
'email.send',
|
|
173
|
-
EmailSendActionPayload
|
|
174
|
-
> {
|
|
175
|
-
readonly payload: EmailSendActionPayload;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export type KnownActionBinding = DbPersistActionBinding | EmailSendActionBinding;
|
|
179
|
-
|
|
180
|
-
export type UiNodeEventBindings = Record<string, readonly ActionBinding<string, object>[]>;
|
|
181
|
-
|
|
182
|
-
export interface CommandDto<
|
|
183
|
-
TType extends string = string,
|
|
184
|
-
TPayload extends object = Record<string, ActionValue>,
|
|
185
|
-
> {
|
|
186
|
-
readonly type: TType;
|
|
187
|
-
readonly payload: TPayload;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export type DbPersistOperation = 'insert';
|
|
191
|
-
|
|
192
|
-
export type DbPersistCommandValues = Record<string, ActionValue>;
|
|
193
|
-
|
|
194
|
-
export interface DbPersistCommandPayload {
|
|
195
|
-
readonly operation: DbPersistOperation;
|
|
196
|
-
readonly collection: string;
|
|
197
|
-
readonly kind?: string;
|
|
198
|
-
readonly mode?: DbPersistMode;
|
|
199
|
-
readonly jsonField?: string;
|
|
200
|
-
readonly values: DbPersistCommandValues;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export type DbPersistCommand = CommandDto<'db.persist.command', DbPersistCommandPayload>;
|
|
204
|
-
|
|
205
|
-
export type KnownCommandDto = DbPersistCommand;
|
|
206
|
-
|
|
207
93
|
export type ComponentEventPayloadValue = ManifestValue;
|
|
208
94
|
|
|
209
95
|
export interface ComponentEventDto<
|
|
@@ -331,10 +217,8 @@ export interface UiNode {
|
|
|
331
217
|
type: string;
|
|
332
218
|
alias?: string;
|
|
333
219
|
props?: Record<string, unknown>;
|
|
334
|
-
bindings?: readonly NodePropBinding[];
|
|
335
220
|
children?: UiNode[];
|
|
336
221
|
style?: Record<string, number | string>;
|
|
337
|
-
events?: UiNodeEventBindings;
|
|
338
222
|
}
|
|
339
223
|
|
|
340
224
|
export interface ScreenSpec {
|
|
@@ -437,6 +321,8 @@ export interface AppManifest {
|
|
|
437
321
|
infra: InfraManifest;
|
|
438
322
|
navigator: NavigatorSpec;
|
|
439
323
|
screens: Record<string, ScreenSpec>;
|
|
324
|
+
dataSources?: DataSourceRegistry;
|
|
325
|
+
dataBindings?: ComponentDataBindingRegistry;
|
|
440
326
|
settings: {
|
|
441
327
|
apiBaseUrl?: string;
|
|
442
328
|
localization: {
|
package/src/ui.test.ts
CHANGED
|
@@ -113,6 +113,134 @@ describe('ui component metadata contracts', () => {
|
|
|
113
113
|
expect(meta.props.columns?.itemSchema?.map((item) => item.key)).toEqual(['id', 'label']);
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
+
it('serializes bindable prop and event metadata on component metadata', () => {
|
|
117
|
+
const meta: UiComponentMeta = {
|
|
118
|
+
name: 'Button',
|
|
119
|
+
category: 'component',
|
|
120
|
+
directManifestNode: true,
|
|
121
|
+
allowedChildren: [],
|
|
122
|
+
bindings: {
|
|
123
|
+
props: {
|
|
124
|
+
children: {
|
|
125
|
+
label: 'Label',
|
|
126
|
+
description: 'Text content rendered inside the button.',
|
|
127
|
+
acceptsFallback: true,
|
|
128
|
+
acceptsTransforms: true,
|
|
129
|
+
value: {
|
|
130
|
+
type: 'string',
|
|
131
|
+
label: 'Button label',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
disabled: {
|
|
135
|
+
label: 'Disabled',
|
|
136
|
+
value: {
|
|
137
|
+
type: 'boolean',
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
events: {
|
|
142
|
+
press: {
|
|
143
|
+
label: 'Press',
|
|
144
|
+
description: 'Runs when the button is pressed.',
|
|
145
|
+
payload: {
|
|
146
|
+
eventType: 'button.press',
|
|
147
|
+
fields: [],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
events: {
|
|
153
|
+
press: {
|
|
154
|
+
label: 'Press',
|
|
155
|
+
eventType: 'button.press',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
props: {
|
|
159
|
+
children: {
|
|
160
|
+
type: 'string',
|
|
161
|
+
category: 'Content',
|
|
162
|
+
label: 'Label',
|
|
163
|
+
},
|
|
164
|
+
disabled: {
|
|
165
|
+
type: 'boolean',
|
|
166
|
+
category: 'State',
|
|
167
|
+
label: 'Disabled',
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
assertSerializable(meta);
|
|
173
|
+
expect(meta.bindings?.props?.children?.value.type).toBe('string');
|
|
174
|
+
expect(meta.bindings?.events?.press?.payload?.eventType).toBe('button.press');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('serializes bindable collection item metadata with nested value fields', () => {
|
|
178
|
+
const meta: UiComponentMeta = {
|
|
179
|
+
name: 'List',
|
|
180
|
+
category: 'pattern',
|
|
181
|
+
directManifestNode: true,
|
|
182
|
+
allowedChildren: [],
|
|
183
|
+
bindings: {
|
|
184
|
+
props: {
|
|
185
|
+
items: {
|
|
186
|
+
label: 'Items',
|
|
187
|
+
value: {
|
|
188
|
+
type: 'array',
|
|
189
|
+
itemType: 'object',
|
|
190
|
+
fields: [
|
|
191
|
+
{
|
|
192
|
+
path: '$.id',
|
|
193
|
+
type: 'string',
|
|
194
|
+
label: 'ID',
|
|
195
|
+
required: true,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
path: '$.title',
|
|
199
|
+
type: 'string',
|
|
200
|
+
label: 'Title',
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
events: {
|
|
207
|
+
itemPress: {
|
|
208
|
+
label: 'Item press',
|
|
209
|
+
payload: {
|
|
210
|
+
eventType: 'collection.itemPress',
|
|
211
|
+
fields: [
|
|
212
|
+
{
|
|
213
|
+
path: 'payload.itemId',
|
|
214
|
+
type: 'string',
|
|
215
|
+
label: 'Item ID',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
path: 'payload.item',
|
|
219
|
+
type: 'record',
|
|
220
|
+
label: 'Item',
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
props: {
|
|
228
|
+
items: {
|
|
229
|
+
type: 'array',
|
|
230
|
+
category: 'Data',
|
|
231
|
+
label: 'Items',
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
assertSerializable(meta);
|
|
237
|
+
expect(meta.bindings?.props?.items?.value.fields?.map((field) => field.path)).toEqual([
|
|
238
|
+
'$.id',
|
|
239
|
+
'$.title',
|
|
240
|
+
]);
|
|
241
|
+
expect(meta.bindings?.events?.itemPress?.payload?.eventType).toBe('collection.itemPress');
|
|
242
|
+
});
|
|
243
|
+
|
|
116
244
|
it('serializes a component metadata registry', () => {
|
|
117
245
|
const registry: UiComponentMetaRegistry = {
|
|
118
246
|
Text: {
|
|
@@ -120,6 +248,15 @@ describe('ui component metadata contracts', () => {
|
|
|
120
248
|
category: 'component',
|
|
121
249
|
directManifestNode: true,
|
|
122
250
|
allowedChildren: [],
|
|
251
|
+
bindings: {
|
|
252
|
+
props: {
|
|
253
|
+
children: {
|
|
254
|
+
value: {
|
|
255
|
+
type: 'string',
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
},
|
|
123
260
|
props: {
|
|
124
261
|
children: {
|
|
125
262
|
type: 'string',
|
|
@@ -139,6 +276,7 @@ describe('ui component metadata contracts', () => {
|
|
|
139
276
|
|
|
140
277
|
assertSerializable(registry);
|
|
141
278
|
expect(registry.Screen?.allowedChildren).toEqual(['Text']);
|
|
279
|
+
expect(registry.Text?.bindings?.props?.children?.value.type).toBe('string');
|
|
142
280
|
});
|
|
143
281
|
|
|
144
282
|
it('serializes a component package manifest for extension packages', () => {
|
|
@@ -151,6 +289,36 @@ describe('ui component metadata contracts', () => {
|
|
|
151
289
|
category: 'component',
|
|
152
290
|
directManifestNode: true,
|
|
153
291
|
allowedChildren: [],
|
|
292
|
+
bindings: {
|
|
293
|
+
props: {
|
|
294
|
+
fen: {
|
|
295
|
+
value: {
|
|
296
|
+
type: 'string',
|
|
297
|
+
label: 'FEN',
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
events: {
|
|
302
|
+
move: {
|
|
303
|
+
label: 'Move',
|
|
304
|
+
payload: {
|
|
305
|
+
eventType: 'chess.move',
|
|
306
|
+
fields: [
|
|
307
|
+
{
|
|
308
|
+
path: 'payload.from',
|
|
309
|
+
type: 'string',
|
|
310
|
+
label: 'From square',
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
path: 'payload.to',
|
|
314
|
+
type: 'string',
|
|
315
|
+
label: 'To square',
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
154
322
|
events: {
|
|
155
323
|
move: {
|
|
156
324
|
label: 'Move',
|
|
@@ -189,5 +357,6 @@ describe('ui component metadata contracts', () => {
|
|
|
189
357
|
|
|
190
358
|
assertSerializable(manifest);
|
|
191
359
|
expect(manifest.components.Chessboard?.events?.move?.eventType).toBe('chess.move');
|
|
360
|
+
expect(manifest.components.Chessboard?.bindings?.props?.fen?.value.type).toBe('string');
|
|
192
361
|
});
|
|
193
362
|
});
|
package/src/ui.ts
CHANGED
|
@@ -85,6 +85,58 @@ export interface UiComponentEventMeta {
|
|
|
85
85
|
readonly payloadFields?: readonly UiComponentEventPayloadFieldMeta[];
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
export type UiBindableValueType =
|
|
89
|
+
| 'array'
|
|
90
|
+
| 'boolean'
|
|
91
|
+
| 'date'
|
|
92
|
+
| 'imageAsset'
|
|
93
|
+
| 'number'
|
|
94
|
+
| 'object'
|
|
95
|
+
| 'record'
|
|
96
|
+
| 'string'
|
|
97
|
+
| 'unknown';
|
|
98
|
+
|
|
99
|
+
export interface UiBindableValueFieldMeta {
|
|
100
|
+
readonly path: string;
|
|
101
|
+
readonly type: UiBindableValueType;
|
|
102
|
+
readonly label?: string;
|
|
103
|
+
readonly description?: string;
|
|
104
|
+
readonly required?: boolean;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface UiBindableValueMeta {
|
|
108
|
+
readonly type: UiBindableValueType;
|
|
109
|
+
readonly label?: string;
|
|
110
|
+
readonly description?: string;
|
|
111
|
+
readonly fields?: readonly UiBindableValueFieldMeta[];
|
|
112
|
+
readonly itemType?: UiBindableValueType;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface UiBindablePropMeta {
|
|
116
|
+
readonly value: UiBindableValueMeta;
|
|
117
|
+
readonly label?: string;
|
|
118
|
+
readonly description?: string;
|
|
119
|
+
readonly required?: boolean;
|
|
120
|
+
readonly acceptsFallback?: boolean;
|
|
121
|
+
readonly acceptsTransforms?: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface UiBindableEventPayloadMeta {
|
|
125
|
+
readonly eventType: UiComponentEventPayloadKind;
|
|
126
|
+
readonly fields?: readonly UiComponentEventPayloadFieldMeta[];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface UiBindableEventMeta {
|
|
130
|
+
readonly label?: string;
|
|
131
|
+
readonly description?: string;
|
|
132
|
+
readonly payload?: UiBindableEventPayloadMeta;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface UiComponentBindingMeta {
|
|
136
|
+
readonly props?: Readonly<Record<string, UiBindablePropMeta>>;
|
|
137
|
+
readonly events?: Readonly<Record<string, UiBindableEventMeta>>;
|
|
138
|
+
}
|
|
139
|
+
|
|
88
140
|
export interface UiComponentSlotMeta {
|
|
89
141
|
readonly label?: string;
|
|
90
142
|
readonly allowedChildren?: readonly string[];
|
|
@@ -96,6 +148,7 @@ export interface UiComponentMeta {
|
|
|
96
148
|
readonly description?: string;
|
|
97
149
|
readonly directManifestNode: boolean;
|
|
98
150
|
readonly allowedChildren: readonly string[];
|
|
151
|
+
readonly bindings?: UiComponentBindingMeta;
|
|
99
152
|
readonly blueprint?: UiComponentBlueprint;
|
|
100
153
|
readonly events?: Readonly<Record<string, UiComponentEventMeta>>;
|
|
101
154
|
readonly i18n?: UiComponentI18nMeta;
|