-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathwidget.zod.ts
More file actions
450 lines (394 loc) · 12.4 KB
/
widget.zod.ts
File metadata and controls
450 lines (394 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
import { z } from 'zod';
import { FieldSchema } from '../data/field.zod';
import { SnakeCaseIdentifierSchema } from '../shared/identifiers.zod';
import { I18nLabelSchema, AriaPropsSchema } from './i18n.zod';
import { PerformanceConfigSchema } from './responsive.zod';
/**
* Widget Lifecycle Hooks Schema
*
* Defines lifecycle callbacks for custom widgets inspired by Web Components and React.
* These hooks allow widgets to perform initialization, cleanup, and respond to changes.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/Web_components
* @see https://react.dev/reference/react/Component#component-lifecycle
*
* @example
* ```typescript
* const widget = {
* lifecycle: {
* onMount: "console.log('Widget mounted')",
* onUpdate: "if (prevProps.value !== props.value) { updateUI() }",
* onUnmount: "cleanup()",
* onValidate: "return value.length > 0 ? null : 'Required field'"
* }
* }
* ```
*/
export const WidgetLifecycleSchema = z.object({
/**
* Called when widget is mounted/rendered for the first time
* Use for initialization, setting up event listeners, loading data, etc.
*
* @example "initializeDatePicker(); loadOptions();"
*/
onMount: z.string().optional().describe('Initialization code when widget mounts'),
/**
* Called when widget props change
* Receives previous props for comparison
*
* @example "if (prevProps.value !== props.value) { updateDisplay() }"
*/
onUpdate: z.string().optional().describe('Code to run when props change'),
/**
* Called when widget is about to be removed from DOM
* Use for cleanup, removing event listeners, canceling timers, etc.
*
* @example "destroyDatePicker(); cancelPendingRequests();"
*/
onUnmount: z.string().optional().describe('Cleanup code when widget unmounts'),
/**
* Custom validation logic for this widget
* Should return error message string if invalid, null/undefined if valid
*
* @example "return value && value.length >= 10 ? null : 'Minimum 10 characters'"
*/
onValidate: z.string().optional().describe('Custom validation logic'),
/**
* Called when widget receives focus
*
* @example "highlightField(); logFocusEvent();"
*/
onFocus: z.string().optional().describe('Code to run on focus'),
/**
* Called when widget loses focus
*
* @example "validateField(); saveFieldState();"
*/
onBlur: z.string().optional().describe('Code to run on blur'),
/**
* Called on any error in the widget
*
* @example "logError(error); showErrorNotification();"
*/
onError: z.string().optional().describe('Error handling code'),
});
export type WidgetLifecycle = z.infer<typeof WidgetLifecycleSchema>;
/**
* Widget Event Schema
*
* Defines custom events that widgets can emit, inspired by DOM Events and Lightning Web Components.
*
* @see https://developer.mozilla.org/en-US/docs/Web/Events/Creating_and_triggering_events
* @see https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.events
*
* @example
* ```typescript
* const searchEvent = {
* name: 'search',
* bubbles: true,
* cancelable: false,
* payload: {
* query: 'string',
* filters: 'object'
* }
* }
* ```
*/
export const WidgetEventSchema = z.object({
/**
* Event name
* Should be lowercase, dash-separated for consistency
*
* @example "value-change", "item-selected", "search-complete"
*/
name: z.string().describe('Event name'),
/**
* Event label for documentation
*/
label: I18nLabelSchema.optional().describe('Human-readable event label'),
/**
* Event description
*/
description: I18nLabelSchema.optional().describe('Event description and usage'),
/**
* Whether event bubbles up through the DOM hierarchy
*
* @default false
*/
bubbles: z.boolean().default(false).describe('Whether event bubbles'),
/**
* Whether event can be cancelled
*
* @default false
*/
cancelable: z.boolean().default(false).describe('Whether event is cancelable'),
/**
* Event payload schema
* Defines the data structure sent with the event
*
* @example { userId: 'string', timestamp: 'number' }
*/
payload: z.record(z.string(), z.unknown()).optional().describe('Event payload schema'),
});
export type WidgetEvent = z.infer<typeof WidgetEventSchema>;
/**
* Widget Property Definition Schema
*
* Defines the contract for widget configuration properties.
* Inspired by React PropTypes and Web Component attributes.
*
* @see https://react.dev/reference/react/Component#static-proptypes
* @see https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements
*
* @example
* ```typescript
* const widgetProps = {
* maxLength: {
* type: 'number',
* required: false,
* default: 100,
* description: 'Maximum input length'
* }
* }
* ```
*/
export const WidgetPropertySchema = z.object({
/**
* Property name
* Should be camelCase following ObjectStack conventions
*/
name: z.string().describe('Property name (camelCase)'),
/**
* Property label for UI
*/
label: I18nLabelSchema.optional().describe('Human-readable label'),
/**
* Property data type
*
* @example "string", "number", "boolean", "array", "object", "function"
*/
type: z.enum(['string', 'number', 'boolean', 'array', 'object', 'function', 'any'])
.describe('TypeScript type'),
/**
* Whether property is required
*
* @default false
*/
required: z.boolean().default(false).describe('Whether property is required'),
/**
* Default value for the property
*/
default: z.unknown().optional().describe('Default value'),
/**
* Property description
*/
description: I18nLabelSchema.optional().describe('Property description'),
/**
* Property validation schema
* Can include min/max, regex, enum values, etc.
*/
validation: z.record(z.string(), z.unknown()).optional().describe('Validation rules'),
/**
* Property category for grouping in UI
*/
category: z.string().optional().describe('Property category'),
});
export type WidgetProperty = z.infer<typeof WidgetPropertySchema>;
/**
* Widget Manifest Schema
*
* Complete definition for a custom widget including metadata, lifecycle, events, and props.
* This is used for widget registration and discovery.
*
* @example
* ```typescript
* const customWidget = {
* name: 'custom_date_picker',
* label: 'Custom Date Picker',
* version: '1.0.0',
* author: 'Company Name',
* fieldTypes: ['date', 'datetime'],
* lifecycle: { ... },
* events: [ ... ],
* properties: [ ... ]
* }
* ```
*/
/**
* Widget Source Schema
* Defines how the widget code is loaded.
*/
export const WidgetSourceSchema = z.discriminatedUnion('type', [
// NPM Registry (standard)
z.object({
type: z.literal('npm'),
packageName: z.string().describe('NPM package name'),
version: z.string().default('latest'),
exportName: z.string().optional().describe('Named export (default: default)'),
}),
// Module Federation (Remote)
z.object({
type: z.literal('remote'),
url: z.string().url().describe('Remote entry URL (.js)'),
moduleName: z.string().describe('Exposed module name'),
scope: z.string().describe('Remote scope name'),
}),
// Inline Code (Simple scripts)
z.object({
type: z.literal('inline'),
code: z.string().describe('JavaScript code body'),
}),
]);
export type WidgetSource = z.infer<typeof WidgetSourceSchema>;
export const WidgetManifestSchema = z.object({
/**
* Widget identifier (snake_case)
*/
name: SnakeCaseIdentifierSchema
.describe('Widget identifier (snake_case)'),
/**
* Human-readable widget name
*/
label: I18nLabelSchema.describe('Widget display name'),
/**
* Widget description
*/
description: I18nLabelSchema.optional().describe('Widget description'),
/**
* Widget version (semver)
*/
version: z.string().optional().describe('Widget version (semver)'),
/**
* Widget author/organization
*/
author: z.string().optional().describe('Widget author'),
/**
* Icon name or URL
*/
icon: z.string().optional().describe('Widget icon'),
/**
* Field types this widget supports
*
* @example ["text", "email", "url"]
*/
fieldTypes: z.array(z.string()).optional().describe('Supported field types'),
/**
* Widget category for organization
*/
category: z.enum(['input', 'display', 'picker', 'editor', 'custom'])
.default('custom')
.describe('Widget category'),
/**
* Widget lifecycle hooks
*/
lifecycle: WidgetLifecycleSchema.optional().describe('Lifecycle hooks'),
/**
* Custom events this widget emits
*/
events: z.array(WidgetEventSchema).optional().describe('Custom events'),
/**
* Widget configuration properties
*/
properties: z.array(WidgetPropertySchema).optional().describe('Configuration properties'),
/**
* Widget implementation
* Defines how to load the widget code
*/
implementation: WidgetSourceSchema.optional().describe('Widget implementation source'),
/**
* Widget dependencies
* External libraries or scripts needed
*/
dependencies: z.array(z.object({
name: z.string(),
version: z.string().optional(),
url: z.string().url().optional(),
})).optional().describe('Widget dependencies'),
/**
* Widget screenshots for showcase
*/
screenshots: z.array(z.string().url()).optional().describe('Screenshot URLs'),
/**
* Widget documentation URL
*/
documentation: z.string().url().optional().describe('Documentation URL'),
/**
* License information
*/
license: z.string().optional().describe('License (SPDX identifier)'),
/**
* Tags for discovery
*/
tags: z.array(z.string()).optional().describe('Tags for categorization'),
/** ARIA accessibility attributes */
aria: AriaPropsSchema.optional().describe('ARIA accessibility attributes'),
/** Performance optimization settings */
performance: PerformanceConfigSchema.optional().describe('Performance optimization settings'),
});
export type WidgetManifest = z.infer<typeof WidgetManifestSchema>;
/**
* Field Widget Props Schema
*
* This defines the contract for custom field components and plugin UI extensions.
* Third-party developers use this interface to build custom field widgets that integrate
* seamlessly with the ObjectStack UI system.
*
* @example
* // Custom widget implementation
* function CustomDatePicker(props: FieldWidgetProps) {
* const { value, onChange, readonly, required, error, field, record, options } = props;
* // Widget implementation...
* }
*/
export const FieldWidgetPropsSchema = z.object({
/**
* Current field value.
* Type depends on the field type (string, number, boolean, array, object, etc.)
*/
value: z.unknown().describe('Current field value'),
/**
* Callback function to update the field value.
* Should be called when user interaction changes the value.
*
* @param newValue - The new value to set
*/
onChange: z.function()
.input(z.tuple([z.unknown()]))
.output(z.void())
.describe('Callback to update field value'),
/**
* Whether the field is in read-only mode.
* When true, the widget should display the value but not allow editing.
*/
readonly: z.boolean().default(false).describe('Read-only mode flag'),
/**
* Whether the field is required.
* Widget should indicate required state visually and validate accordingly.
*/
required: z.boolean().default(false).describe('Required field flag'),
/**
* Validation error message to display.
* When present, widget should display the error in its UI.
*/
error: z.string().optional().describe('Validation error message'),
/**
* Complete field definition from the schema.
* Contains metadata like type, constraints, options, etc.
*/
field: FieldSchema.describe('Field schema definition'),
/**
* The complete record/document being edited.
* Useful for conditional logic and cross-field dependencies.
*/
record: z.record(z.string(), z.unknown()).optional().describe('Complete record data'),
/**
* Custom options passed to the widget.
* Can contain widget-specific configuration like themes, behaviors, etc.
*/
options: z.record(z.string(), z.unknown()).optional().describe('Custom widget options'),
});
/**
* TypeScript type for Field Widget Props
*/
export type FieldWidgetProps = z.infer<typeof FieldWidgetPropsSchema>;