@dotcms/uve 1.2.0 → 1.2.1-next.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/index.cjs.js CHANGED
@@ -1,10 +1,596 @@
1
1
  'use strict';
2
2
 
3
3
  var _public = require('./public.cjs.js');
4
- require('@dotcms/types');
4
+ var types = require('@dotcms/types');
5
5
  require('@dotcms/types/internal');
6
6
 
7
+ /**
8
+ * Normalizes a field definition into the schema format expected by UVE.
9
+ *
10
+ * Converts developer-friendly field definitions into a normalized structure where
11
+ * all type-specific configuration properties are moved into a `config` object.
12
+ * This transformation ensures consistency in the schema format sent to UVE.
13
+ *
14
+ * **Field Type Handling:**
15
+ * - **Input fields**: Extracts `inputType`, `placeholder`, and `defaultValue` into config
16
+ * - **Dropdown fields**: Normalizes options (strings become `{ label, value }` objects),
17
+ * extracts `placeholder` and `defaultValue` into config
18
+ * - **Radio fields**: Normalizes options (preserves image properties like `imageURL`, `width`, `height`),
19
+ * extracts `defaultValue` into config
20
+ * - **Checkbox group fields**: Normalizes options and extracts `defaultValue` (as Record<string, boolean>) into config
21
+ *
22
+ * @experimental This method is experimental and may be subject to change.
23
+ *
24
+ * @param field - The field definition to normalize. Must be one of: input, dropdown, radio, or checkboxGroup
25
+ * @returns The normalized field schema with type, label, and config properties ready to be sent to UVE
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * // Input field normalization
30
+ * normalizeField({
31
+ * type: 'input',
32
+ * label: 'Font Size',
33
+ * inputType: 'number',
34
+ * defaultValue: 16,
35
+ * placeholder: 'Enter size'
36
+ * })
37
+ * // Returns: {
38
+ * // type: 'input',
39
+ * // label: 'Font Size',
40
+ * // config: { inputType: 'number', defaultValue: 16, placeholder: 'Enter size' }
41
+ * // }
42
+ *
43
+ * // Dropdown field with string options normalization
44
+ * normalizeField({
45
+ * type: 'dropdown',
46
+ * label: 'Font Family',
47
+ * options: ['Arial', 'Helvetica']
48
+ * })
49
+ * // Returns: {
50
+ * // type: 'dropdown',
51
+ * // label: 'Font Family',
52
+ * // config: { options: [{ label: 'Arial', value: 'Arial' }, { label: 'Helvetica', value: 'Helvetica' }] }
53
+ * // }
54
+ * ```
55
+ */
56
+ function normalizeField(field) {
57
+ const base = {
58
+ type: field.type,
59
+ label: field.label
60
+ };
61
+ const config = {};
62
+ // Handle type-specific properties
63
+ if (field.type === 'input') {
64
+ config.inputType = field.inputType;
65
+ config.placeholder = field.placeholder;
66
+ config.defaultValue = field.defaultValue;
67
+ }
68
+ if (field.type === 'dropdown' || field.type === 'radio') {
69
+ // Normalize options to consistent format
70
+ config.options = field.options.map(opt => typeof opt === 'string' ? {
71
+ label: opt,
72
+ value: opt
73
+ } : opt);
74
+ config.placeholder = field.type === 'dropdown' ? field.placeholder : undefined;
75
+ config.defaultValue = field.defaultValue;
76
+ // Handle radio-specific properties
77
+ if (field.type === 'radio') {
78
+ config.columns = field.columns;
79
+ }
80
+ }
81
+ if (field.type === 'checkboxGroup') {
82
+ // Normalize options to consistent format
83
+ config.options = field.options.map(opt => typeof opt === 'string' ? {
84
+ label: opt,
85
+ value: opt
86
+ } : opt);
87
+ config.defaultValue = field.defaultValue;
88
+ }
89
+ return {
90
+ ...base,
91
+ config
92
+ };
93
+ }
94
+ /**
95
+ * Normalizes a section definition into the schema format expected by UVE.
96
+ *
97
+ * Converts a section with a flat array of fields into the normalized schema format
98
+ * where fields are organized as a multi-dimensional array (array of column arrays).
99
+ * Currently, all sections are normalized to a single-column layout structure.
100
+ *
101
+ * **Normalization Process:**
102
+ * 1. Normalizes each field in the section using `normalizeField`
103
+ * 2. Wraps the normalized fields array in an outer array to create the column structure
104
+ * 3. Preserves the section title
105
+ *
106
+ * The output format always uses a multi-dimensional array structure (`fields: StyleEditorFieldSchema[][]`),
107
+ * even for single-column layouts, ensuring consistency in the UVE schema format.
108
+ *
109
+ * @experimental This method is experimental and may be subject to change.
110
+ *
111
+ * @param section - The section definition to normalize, containing a title and array of fields
112
+ * @param section.title - The section title displayed to users
113
+ * @param section.fields - Array of field definitions to normalize
114
+ * @returns The normalized section schema with fields organized as a single-column array structure
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * normalizeSection({
119
+ * title: 'Typography',
120
+ * fields: [
121
+ * { type: 'input', label: 'Font Size', inputType: 'number' },
122
+ * { type: 'dropdown', label: 'Font Family', options: ['Arial'] }
123
+ * ]
124
+ * })
125
+ * // Returns: {
126
+ * // title: 'Typography',
127
+ * // fields: [
128
+ * // [
129
+ * // { type: 'input', label: 'Font Size', config: { inputType: 'number' } },
130
+ * // { type: 'dropdown', label: 'Font Family', config: { options: [...] } }
131
+ * // ]
132
+ * // ]
133
+ * // }
134
+ * ```
135
+ */
136
+ function normalizeSection(section) {
137
+ // Determine if fields is multi-column or single column
138
+ const normalizedFields = section.fields.map(normalizeField);
139
+ return {
140
+ title: section.title,
141
+ fields: normalizedFields
142
+ };
143
+ }
144
+ /**
145
+ * Normalizes a complete form definition into the schema format expected by UVE.
146
+ *
147
+ * This is the main entry point for converting a developer-friendly form definition
148
+ * into the normalized schema structure that UVE (Universal Visual Editor) can consume.
149
+ * The normalization process transforms the entire form hierarchy:
150
+ *
151
+ * **Normalization Process:**
152
+ * 1. Preserves the `contentType` identifier
153
+ * 2. Processes each section using `normalizeSection`, which:
154
+ * - Normalizes all fields in the section using `normalizeField`
155
+ * - Organizes fields into the required multi-dimensional array structure
156
+ * 3. Returns a fully normalized schema with consistent structure across all sections
157
+ *
158
+ * The resulting schema has all field-specific properties moved into `config` objects
159
+ * and all sections using the consistent single-column array structure, regardless
160
+ * of the input format.
161
+ *
162
+ * @experimental This method is experimental and may be subject to change.
163
+ *
164
+ * @param form - The complete form definition to normalize
165
+ * @param form.contentType - The content type identifier this form is associated with
166
+ * @param form.sections - Array of section definitions, each containing a title and fields
167
+ * @returns The normalized form schema ready to be sent to UVE, with all fields and sections normalized
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * const schema = normalizeForm({
172
+ * contentType: 'my-content-type',
173
+ * sections: [
174
+ * {
175
+ * title: 'Typography',
176
+ * fields: [
177
+ * { type: 'input', label: 'Font Size', inputType: 'number', defaultValue: 16 },
178
+ * { type: 'dropdown', label: 'Font Family', options: ['Arial', 'Helvetica'] }
179
+ * ]
180
+ * },
181
+ * {
182
+ * title: 'Colors',
183
+ * fields: [
184
+ * { type: 'input', label: 'Primary Color', inputType: 'text', defaultValue: '#000000' }
185
+ * ]
186
+ * }
187
+ * ]
188
+ * });
189
+ * // Returns: {
190
+ * // contentType: 'my-content-type',
191
+ * // sections: [
192
+ * // {
193
+ * // title: 'Typography',
194
+ * // fields: [
195
+ * // [
196
+ * // { type: 'input', label: 'Font Size', config: { inputType: 'number', defaultValue: 16 } },
197
+ * // { type: 'dropdown', label: 'Font Family', config: { options: [...] } }
198
+ * // ]
199
+ * // ]
200
+ * // },
201
+ * // {
202
+ * // title: 'Colors',
203
+ * // fields: [
204
+ * // [
205
+ * // { type: 'input', label: 'Primary Color', config: { inputType: 'text', defaultValue: '#000000' } }
206
+ * // ]
207
+ * // ]
208
+ * // }
209
+ * // ]
210
+ * // }
211
+ * ```
212
+ */
213
+ function normalizeForm(form) {
214
+ return {
215
+ contentType: form.contentType,
216
+ sections: form.sections.map(normalizeSection)
217
+ };
218
+ }
7
219
 
220
+ /**
221
+ * Helper functions for creating style editor field definitions.
222
+ *
223
+ * Provides type-safe factory functions for creating different types of form fields
224
+ * used in the style editor. Each function creates a field definition with the
225
+ * appropriate `type` property automatically set, eliminating the need to manually
226
+ * specify the type discriminator.
227
+ *
228
+ * **Available Field Types:**
229
+ * - `input`: Text or number input fields
230
+ * - `dropdown`: Single-value selection from a dropdown list
231
+ * - `radio`: Single-value selection from radio button options (supports visual options with images)
232
+ * - `checkboxGroup`: Multiple-value selection from checkbox options
233
+ *
234
+ * These factory functions ensure type safety by inferring the correct field type
235
+ * based on the configuration provided, and they automatically set the `type` property
236
+ * to match the factory function used.
237
+ *
238
+ * @experimental This API is experimental and may be subject to change.
239
+ *
240
+ * @example
241
+ * ```typescript
242
+ * const form = defineStyleEditorSchema({
243
+ * contentType: 'my-content-type',
244
+ * sections: [
245
+ * {
246
+ * title: 'Typography',
247
+ * fields: [
248
+ * styleEditorField.input({
249
+ * label: 'Font Size',
250
+ * inputType: 'number',
251
+ * defaultValue: 16
252
+ * }),
253
+ * styleEditorField.dropdown({
254
+ * label: 'Font Family',
255
+ * options: ['Arial', 'Helvetica'],
256
+ * defaultValue: 'Arial'
257
+ * }),
258
+ * styleEditorField.radio({
259
+ * label: 'Alignment',
260
+ * options: ['Left', 'Center', 'Right'],
261
+ * defaultValue: 'Left'
262
+ * })
263
+ * ]
264
+ * }
265
+ * ]
266
+ * });
267
+ * ```
268
+ */
269
+ const styleEditorField = {
270
+ /**
271
+ * Creates an input field definition with type-safe default values.
272
+ *
273
+ * Supports both text and number input types. The `defaultValue` type is
274
+ * enforced based on the `inputType` using TypeScript generics:
275
+ * - When `inputType` is `'number'`, `defaultValue` must be a `number`
276
+ * - When `inputType` is `'text'`, `defaultValue` must be a `string`
277
+ *
278
+ * This provides compile-time type checking to prevent mismatched types,
279
+ * such as passing a string when a number is expected.
280
+ *
281
+ * @experimental This method is experimental and may be subject to change.
282
+ *
283
+ * @typeParam T - The input type ('text' or 'number'), inferred from `config.inputType`
284
+ * @param config - Input field configuration
285
+ * @param config.label - The label displayed for this input field
286
+ * @param config.inputType - The type of input ('text' or 'number')
287
+ * @param config.placeholder - Optional placeholder text for the input
288
+ * @param config.defaultValue - Optional default value (type enforced based on inputType)
289
+ * @returns A complete input field definition with type 'input'
290
+ *
291
+ * @example
292
+ * ```typescript
293
+ * // Number input - defaultValue must be a number
294
+ * styleEditorField.input({
295
+ * label: 'Font Size',
296
+ * inputType: 'number',
297
+ * placeholder: 'Enter font size',
298
+ * defaultValue: 16 // ✓ Correct: number
299
+ * })
300
+ *
301
+ * // Text input - defaultValue must be a string
302
+ * styleEditorField.input({
303
+ * label: 'Font Name',
304
+ * inputType: 'text',
305
+ * placeholder: 'Enter font name',
306
+ * defaultValue: 'Arial' // ✓ Correct: string
307
+ * })
308
+ *
309
+ * // TypeScript error - type mismatch
310
+ * styleEditorField.input({
311
+ * label: 'Font Size',
312
+ * inputType: 'number',
313
+ * defaultValue: '16' // ✗ Error: Type 'string' is not assignable to type 'number'
314
+ * })
315
+ * ```
316
+ */
317
+ input: config => ({
318
+ type: 'input',
319
+ ...config
320
+ }),
321
+ /**
322
+ * Creates a dropdown field definition.
323
+ *
324
+ * Allows users to select a single value from a list of options.
325
+ * Options can be provided as simple strings or as objects with label and value.
326
+ *
327
+ * @experimental This method is experimental and may be subject to change.
328
+ *
329
+ * @param config - Dropdown field configuration (without the 'type' property)
330
+ * @param config.label - The label displayed for this dropdown field
331
+ * @param config.options - Array of options. Can be strings or objects with label and value
332
+ * @param config.defaultValue - Optional default selected value (must match one of the option values)
333
+ * @param config.placeholder - Optional placeholder text shown when no value is selected
334
+ * @returns A complete dropdown field definition with type 'dropdown'
335
+ *
336
+ * @example
337
+ * ```typescript
338
+ * // Simple string options
339
+ * styleEditorField.dropdown({
340
+ * label: 'Font Family',
341
+ * options: ['Arial', 'Helvetica', 'Times New Roman'],
342
+ * defaultValue: 'Arial',
343
+ * placeholder: 'Select a font'
344
+ * })
345
+ *
346
+ * // Object options with custom labels
347
+ * styleEditorField.dropdown({
348
+ * label: 'Theme',
349
+ * options: [
350
+ * { label: 'Light Theme', value: 'light' },
351
+ * { label: 'Dark Theme', value: 'dark' }
352
+ * ],
353
+ * defaultValue: 'light'
354
+ * })
355
+ * ```
356
+ */
357
+ dropdown: config => ({
358
+ type: 'dropdown',
359
+ ...config
360
+ }),
361
+ /**
362
+ * Creates a radio button field definition.
363
+ *
364
+ * Allows users to select a single option from a list. Supports visual
365
+ * options with background images for enhanced UI. Options can be provided
366
+ * as simple strings or as objects with label, value, and optional image properties.
367
+ *
368
+ * **Layout Options:**
369
+ * - `columns: 1` (default): Single column list layout
370
+ * - `columns: 2`: Two-column grid layout, ideal for visual options with images
371
+ *
372
+ * @experimental This method is experimental and may be subject to change.
373
+ *
374
+ * @param config - Radio field configuration (without the 'type' property)
375
+ * @param config.label - The label displayed for this radio group
376
+ * @param config.options - Array of options. Can be strings or objects with label, value, and optional imageURL, width, height
377
+ * @param config.defaultValue - Optional default selected value (must match one of the option values)
378
+ * @param config.columns - Optional number of columns (1 or 2). Defaults to 1 (single column)
379
+ * @returns A complete radio field definition with type 'radio'
380
+ *
381
+ * @example
382
+ * ```typescript
383
+ * // Simple string options (single column)
384
+ * styleEditorField.radio({
385
+ * label: 'Alignment',
386
+ * options: ['Left', 'Center', 'Right'],
387
+ * defaultValue: 'Left'
388
+ * })
389
+ *
390
+ * // Two-column grid layout with images
391
+ * styleEditorField.radio({
392
+ * label: 'Layout',
393
+ * columns: 2,
394
+ * options: [
395
+ * {
396
+ * label: 'Left',
397
+ * value: 'left',
398
+ * imageURL: 'https://example.com/layout-left.png',
399
+ * width: 80,
400
+ * height: 50
401
+ * },
402
+ * {
403
+ * label: 'Right',
404
+ * value: 'right',
405
+ * imageURL: 'https://example.com/layout-right.png',
406
+ * width: 80,
407
+ * height: 50
408
+ * },
409
+ * { label: 'Center', value: 'center' },
410
+ * { label: 'Overlap', value: 'overlap' }
411
+ * ],
412
+ * defaultValue: 'right'
413
+ * })
414
+ * ```
415
+ */
416
+ radio: config => ({
417
+ type: 'radio',
418
+ ...config
419
+ }),
420
+ /**
421
+ * Creates a checkbox group field definition.
422
+ *
423
+ * Allows users to select multiple options simultaneously. Each option
424
+ * can be independently checked or unchecked. The defaultValue is a
425
+ * record mapping option values to their boolean checked state.
426
+ *
427
+ * @experimental This method is experimental and may be subject to change.
428
+ *
429
+ * @param config - Checkbox group field configuration (without the 'type' property)
430
+ * @param config.label - The label displayed for this checkbox group
431
+ * @param config.options - Array of options. Can be strings or objects with label and value
432
+ * @param config.defaultValue - Optional default checked state as a record of option values to boolean
433
+ * @returns A complete checkbox group field definition with type 'checkboxGroup'
434
+ *
435
+ * @example
436
+ * ```typescript
437
+ * styleEditorField.checkboxGroup({
438
+ * label: 'Text Decoration',
439
+ * options: [
440
+ * { label: 'Underline', value: 'underline' },
441
+ * { label: 'Overline', value: 'overline' },
442
+ * { label: 'Line Through', value: 'line-through' }
443
+ * ],
444
+ * defaultValue: {
445
+ * 'underline': true,
446
+ * 'overline': false,
447
+ * 'line-through': false
448
+ * }
449
+ * })
450
+ * ```
451
+ */
452
+ checkboxGroup: config => ({
453
+ type: 'checkboxGroup',
454
+ ...config
455
+ })
456
+ };
457
+ /**
458
+ * Normalizes and validates a style editor form definition.
459
+ *
460
+ * Converts the developer-friendly form structure into the schema format
461
+ * expected by UVE (Universal Visual Editor). This function processes the
462
+ * form definition and transforms it into the normalized schema format where:
463
+ *
464
+ * - All field-specific properties are moved into `config` objects
465
+ * - String options are normalized to `{ label, value }` objects
466
+ * - Sections are organized into the multi-dimensional array structure required by UVE
467
+ *
468
+ * The normalization process ensures consistency and type safety in the schema
469
+ * format sent to UVE. After normalization, use `registerStyleEditorSchemas`
470
+ * to register the schema with the UVE editor.
471
+ *
472
+ * @experimental This method is experimental and may be subject to change.
473
+ *
474
+ * @param form - The style editor form definition containing contentType and sections
475
+ * @param form.contentType - The content type identifier for this form
476
+ * @param form.sections - Array of sections, each containing a title and fields array
477
+ * @returns The normalized form schema ready to be sent to UVE
478
+ *
479
+ * @example
480
+ * ```typescript
481
+ * const formSchema = defineStyleEditorSchema({
482
+ * contentType: 'my-content-type',
483
+ * sections: [
484
+ * {
485
+ * title: 'Typography',
486
+ * fields: [
487
+ * styleEditorField.input({
488
+ * label: 'Font Size',
489
+ * inputType: 'number',
490
+ * defaultValue: 16
491
+ * }),
492
+ * styleEditorField.dropdown({
493
+ * label: 'Font Family',
494
+ * options: ['Arial', 'Helvetica'],
495
+ * defaultValue: 'Arial'
496
+ * })
497
+ * ]
498
+ * },
499
+ * {
500
+ * title: 'Colors',
501
+ * fields: [
502
+ * styleEditorField.input({
503
+ * label: 'Primary Color',
504
+ * inputType: 'text',
505
+ * defaultValue: '#000000'
506
+ * }),
507
+ * styleEditorField.input({
508
+ * label: 'Secondary Color',
509
+ * inputType: 'text',
510
+ * defaultValue: '#FFFFFF'
511
+ * })
512
+ * ]
513
+ * }
514
+ * ]
515
+ * });
516
+ *
517
+ * // Register the schema with UVE
518
+ * registerStyleEditorSchemas([formSchema]);
519
+ * ```
520
+ */
521
+ function defineStyleEditorSchema(form) {
522
+ return normalizeForm(form);
523
+ }
524
+ /**
525
+ * Registers style editor form schemas with the UVE editor.
526
+ *
527
+ * Sends normalized style editor schemas to the UVE (Universal Visual Editor)
528
+ * for registration. The schemas must be normalized using `defineStyleEditorSchema`
529
+ * before being passed to this function.
530
+ *
531
+ * **Behavior:**
532
+ * - Only registers schemas when UVE is in EDIT mode
533
+ * - Validates that each schema has a `contentType` property
534
+ * - Skips schemas without `contentType` and logs a warning
535
+ * - Sends the validated schemas to UVE via the `REGISTER_STYLE_SCHEMAS` action
536
+ *
537
+ * **Note:** This function will silently return early if UVE is not in EDIT mode,
538
+ * so it's safe to call even when the editor is not active.
539
+ *
540
+ * @experimental This method is experimental and may be subject to change.
541
+ *
542
+ * @param schemas - Array of normalized style editor form schemas to register with UVE
543
+ * @returns void - This function does not return a value
544
+ *
545
+ * @example
546
+ * ```typescript
547
+ * // Create and normalize a form schema
548
+ * const formSchema = defineStyleEditorSchema({
549
+ * contentType: 'my-content-type',
550
+ * sections: [
551
+ * {
552
+ * title: 'Typography',
553
+ * fields: [
554
+ * styleEditorField.input({
555
+ * label: 'Font Size',
556
+ * inputType: 'number',
557
+ * defaultValue: 16
558
+ * })
559
+ * ]
560
+ * }
561
+ * ]
562
+ * });
563
+ *
564
+ * // Register the schema with UVE
565
+ * registerStyleEditorSchemas([formSchema]);
566
+ *
567
+ * // Register multiple schemas at once
568
+ * const schema1 = defineStyleEditorSchema({ ... });
569
+ * const schema2 = defineStyleEditorSchema({ ... });
570
+ * registerStyleEditorSchemas([schema1, schema2]);
571
+ * ```
572
+ */
573
+ function registerStyleEditorSchemas(schemas) {
574
+ const {
575
+ mode
576
+ } = _public.getUVEState() || {};
577
+ if (!mode || mode !== types.UVE_MODE.EDIT) {
578
+ return;
579
+ }
580
+ const validatedSchemas = schemas.filter((schema, index) => {
581
+ if (!schema.contentType) {
582
+ console.warn(`[registerStyleEditorSchemas] Skipping schema with index [${index}] for not having a contentType`);
583
+ return false;
584
+ }
585
+ return true;
586
+ });
587
+ _public.sendMessageToUVE({
588
+ action: types.DotCMSUVEAction.REGISTER_STYLE_SCHEMAS,
589
+ payload: {
590
+ schemas: validatedSchemas
591
+ }
592
+ });
593
+ }
8
594
 
9
595
  exports.createUVESubscription = _public.createUVESubscription;
10
596
  exports.editContentlet = _public.editContentlet;
@@ -16,3 +602,6 @@ exports.isAnalyticsActive = _public.isAnalyticsActive;
16
602
  exports.reorderMenu = _public.reorderMenu;
17
603
  exports.sendMessageToUVE = _public.sendMessageToUVE;
18
604
  exports.updateNavigation = _public.updateNavigation;
605
+ exports.defineStyleEditorSchema = defineStyleEditorSchema;
606
+ exports.registerStyleEditorSchemas = registerStyleEditorSchemas;
607
+ exports.styleEditorField = styleEditorField;