@servicetitan/dte-unlayer 0.131.0 → 0.132.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/dist/editor-core-source.d.ts +2 -2
- package/dist/editor-core-source.d.ts.map +1 -1
- package/dist/editor-core-source.js +2 -2
- package/dist/editor-core-source.js.map +1 -1
- package/dist/shared/forms.d.ts +4 -0
- package/dist/shared/forms.d.ts.map +1 -1
- package/dist/shared/forms.js +16 -0
- package/dist/shared/forms.js.map +1 -1
- package/dist/store.d.ts +8 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +145 -2
- package/dist/store.js.map +1 -1
- package/dist/unlayer-interface.d.ts +1 -0
- package/dist/unlayer-interface.d.ts.map +1 -1
- package/dist/unlayer-interface.js.map +1 -1
- package/dist/unlayer.d.ts +1 -0
- package/dist/unlayer.d.ts.map +1 -1
- package/dist/unlayer.js.map +1 -1
- package/package.json +1 -1
- package/src/editor-core-source.ts +2 -2
- package/src/shared/forms.ts +25 -0
- package/src/store.ts +192 -8
- package/src/unlayer-interface.tsx +1 -0
- package/src/unlayer.tsx +1 -0
package/src/shared/forms.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface FormFieldInfo {
|
|
|
7
7
|
id: string;
|
|
8
8
|
header: string;
|
|
9
9
|
itemType: string;
|
|
10
|
+
sampleData?: unknown;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export const enum FormItemType {
|
|
@@ -93,3 +94,27 @@ export const getConditionalFieldTypeFromFormItemType = (
|
|
|
93
94
|
}
|
|
94
95
|
return 'string';
|
|
95
96
|
};
|
|
97
|
+
|
|
98
|
+
const DEFAULT_FORM_NUMBER_SAMPLE = 44;
|
|
99
|
+
const DEFAULT_FORM_DATE_SAMPLE = '2026-01-15';
|
|
100
|
+
|
|
101
|
+
export const getFormFieldSampleValue = (
|
|
102
|
+
field: Pick<FormFieldInfo, 'itemType' | 'sampleData'> & { header?: string },
|
|
103
|
+
index = 0,
|
|
104
|
+
): number | string => {
|
|
105
|
+
if (typeof field.sampleData === 'number' || typeof field.sampleData === 'string') {
|
|
106
|
+
return field.sampleData;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (field.itemType === FormItemType.Number) {
|
|
110
|
+
return DEFAULT_FORM_NUMBER_SAMPLE + index;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (field.itemType === FormItemType.Date) {
|
|
114
|
+
const [year, month, day] = DEFAULT_FORM_DATE_SAMPLE.split('-');
|
|
115
|
+
const dayNumber = Number(day) + (index % 10);
|
|
116
|
+
return `${year}-${month}-${String(dayNumber).padStart(2, '0')}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return field.header ? `${field.header} value` : 'sample';
|
|
120
|
+
};
|
package/src/store.ts
CHANGED
|
@@ -2,7 +2,13 @@ import { loadScript } from './loadScript';
|
|
|
2
2
|
import { defaultImageValidation } from './shared/configs';
|
|
3
3
|
import { UnlayerEditorTwin, UnlayerEventConfig, UnlayerEventRegister } from './shared/const';
|
|
4
4
|
import { unlayerSupportedFonts } from './shared/fonts';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
FormFieldInfo,
|
|
7
|
+
FormInfo,
|
|
8
|
+
getFormFieldSampleValue,
|
|
9
|
+
parseFormFieldKey,
|
|
10
|
+
} from './shared/forms';
|
|
11
|
+
import { schemaBuildMap, schemaIsDate } from './shared/schema';
|
|
6
12
|
import { unlayerToolsParseTwinKey } from './shared/tools';
|
|
7
13
|
import { unlayerToolsIterate } from './tools';
|
|
8
14
|
import {
|
|
@@ -16,11 +22,7 @@ import { CreateUnlayerEditorProps, UnlayerDesignFormat, UnlayerRef } from './unl
|
|
|
16
22
|
const defaultScriptUrl = 'https://editor.unlayer.com/embed.js?2';
|
|
17
23
|
|
|
18
24
|
const normalizeFontToken = (font: string) =>
|
|
19
|
-
font
|
|
20
|
-
.replace(/["']/g, '')
|
|
21
|
-
.split(',')[0]
|
|
22
|
-
.trim()
|
|
23
|
-
.toLowerCase();
|
|
25
|
+
font.replace(/["']/g, '').split(',')[0].trim().toLowerCase();
|
|
24
26
|
|
|
25
27
|
const getDesignFontTokens = (value: any, out: Set<string>) => {
|
|
26
28
|
if (!value || typeof value !== 'object') {
|
|
@@ -77,7 +79,7 @@ const ensureChunksFonts = (data: any) => {
|
|
|
77
79
|
const labelToken = normalizeFontToken(font.label);
|
|
78
80
|
const valueToken = normalizeFontToken(font.value);
|
|
79
81
|
const isUsedInDesign = usedFontTokens.has(labelToken) || usedFontTokens.has(valueToken);
|
|
80
|
-
const isUsedInHtml =
|
|
82
|
+
const isUsedInHtml = [labelToken, valueToken].some(token => html.includes(token));
|
|
81
83
|
|
|
82
84
|
if ((isUsedInDesign || isUsedInHtml) && !existingLabels.has(font.label)) {
|
|
83
85
|
existingFonts.push(font);
|
|
@@ -89,6 +91,69 @@ const ensureChunksFonts = (data: any) => {
|
|
|
89
91
|
return data;
|
|
90
92
|
};
|
|
91
93
|
|
|
94
|
+
const FORM_FIELD_DATA_POINT_PREFIX = 'FORM_FIELD_';
|
|
95
|
+
|
|
96
|
+
const getByPath = (data: unknown, path: string): unknown => {
|
|
97
|
+
if (!data || typeof data !== 'object') {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return path.split('.').reduce<unknown>((current, part) => {
|
|
102
|
+
if (!current || typeof current !== 'object') {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return (current as Record<string, unknown>)[part];
|
|
107
|
+
}, data);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
111
|
+
!!value && typeof value === 'object' && !Array.isArray(value);
|
|
112
|
+
|
|
113
|
+
const deepMerge = (...sources: unknown[]): Record<string, unknown> => {
|
|
114
|
+
const out: Record<string, unknown> = {};
|
|
115
|
+
|
|
116
|
+
for (const source of sources) {
|
|
117
|
+
if (!isPlainObject(source)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const [key, value] of Object.entries(source)) {
|
|
122
|
+
if (isPlainObject(value) && isPlainObject(out[key])) {
|
|
123
|
+
out[key] = deepMerge(out[key], value);
|
|
124
|
+
} else {
|
|
125
|
+
out[key] = value;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return out;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const setByPath = (target: Record<string, unknown>, path: string, value: unknown) => {
|
|
134
|
+
const parts = path.split('.');
|
|
135
|
+
if (!parts.length) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let node: Record<string, unknown> = target;
|
|
140
|
+
|
|
141
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
142
|
+
const key = parts[i];
|
|
143
|
+
if (!isPlainObject(node[key])) {
|
|
144
|
+
node[key] = {};
|
|
145
|
+
}
|
|
146
|
+
node = node[key] as Record<string, unknown>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
node[parts[parts.length - 1]] = value;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const tokenizeFormulaFields = (expression: string): string[] => {
|
|
153
|
+
const regex = /[A-Za-z_][A-Za-z0-9_.]*/g;
|
|
154
|
+
return expression.match(regex) ?? [];
|
|
155
|
+
};
|
|
156
|
+
|
|
92
157
|
export interface UnlayerDesignChangeInfo {
|
|
93
158
|
isToolsListChanged: boolean;
|
|
94
159
|
}
|
|
@@ -112,11 +177,15 @@ const eventsNotAddingTools: DesignUpdatedEventType[] = [
|
|
|
112
177
|
|
|
113
178
|
export class UnlayerStore {
|
|
114
179
|
readonly unlayerRef: UnlayerRef;
|
|
180
|
+
private readonly schemaDummyData: Record<string, unknown>;
|
|
181
|
+
private readonly dateSchemaFieldKeys: Set<string>;
|
|
115
182
|
|
|
116
183
|
private editor: Unlayer | undefined;
|
|
117
184
|
private isInit = false;
|
|
118
185
|
private iframe?: HTMLIFrameElement;
|
|
119
186
|
private hasDesign = false;
|
|
187
|
+
private formFieldsByFormId: Record<number, FormFieldInfo[]> = {};
|
|
188
|
+
private formFieldSamples: Record<string, unknown> = {};
|
|
120
189
|
|
|
121
190
|
private onMessageCB?: (type: string, data: any) => void;
|
|
122
191
|
private onChangeCB?: (info: UnlayerDesignChangeInfo) => void;
|
|
@@ -129,6 +198,14 @@ export class UnlayerStore {
|
|
|
129
198
|
private onCalcFieldSelectCB?: (fieldKeys: string[]) => void;
|
|
130
199
|
|
|
131
200
|
constructor(readonly props: CreateUnlayerEditorProps) {
|
|
201
|
+
const schemaData = this.props.schema ? schemaBuildMap(this.props.schema) : undefined;
|
|
202
|
+
this.schemaDummyData = (schemaData?.dummyData ?? {}) as Record<string, unknown>;
|
|
203
|
+
this.dateSchemaFieldKeys = new Set(
|
|
204
|
+
Object.values(schemaData?.map ?? {})
|
|
205
|
+
.filter(field => field.isValue && schemaIsDate(field.node))
|
|
206
|
+
.map(field => field.fullKey),
|
|
207
|
+
);
|
|
208
|
+
|
|
132
209
|
this.props.eSignFieldTypes = ['Signature', 'Initials', 'Date Signed', 'Full Name'];
|
|
133
210
|
|
|
134
211
|
this.unlayerRef = {
|
|
@@ -140,7 +217,9 @@ export class UnlayerStore {
|
|
|
140
217
|
},
|
|
141
218
|
exportHtml: cb => {
|
|
142
219
|
this.editor?.exportHtml(data => {
|
|
143
|
-
|
|
220
|
+
const result = ensureChunksFonts(data);
|
|
221
|
+
result.demoData = this.buildCalculatedFieldDemoData(result.design);
|
|
222
|
+
cb(result);
|
|
144
223
|
});
|
|
145
224
|
},
|
|
146
225
|
sendFormList: forms => {
|
|
@@ -244,6 +323,8 @@ export class UnlayerStore {
|
|
|
244
323
|
};
|
|
245
324
|
|
|
246
325
|
sendFormFields = (formId: number, fields: FormFieldInfo[]) => {
|
|
326
|
+
this.formFieldsByFormId[formId] = fields;
|
|
327
|
+
this.updateFormFieldSamples(formId, fields);
|
|
247
328
|
this.sendMessage('--form-fields', { formId, fields });
|
|
248
329
|
};
|
|
249
330
|
|
|
@@ -371,6 +452,109 @@ export class UnlayerStore {
|
|
|
371
452
|
this.onMessageCB?.(type, data);
|
|
372
453
|
};
|
|
373
454
|
|
|
455
|
+
private updateFormFieldSamples = (formId: number, fields: FormFieldInfo[]) => {
|
|
456
|
+
fields.forEach((field, index) => {
|
|
457
|
+
const normalizedFieldId = String(field.id ?? '').replaceAll('-', '');
|
|
458
|
+
if (!normalizedFieldId) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const sampleValue = getFormFieldSampleValue(field, index);
|
|
463
|
+
setByPath(
|
|
464
|
+
this.formFieldSamples,
|
|
465
|
+
`__submission_fields.${formId}.${normalizedFieldId}`,
|
|
466
|
+
sampleValue,
|
|
467
|
+
);
|
|
468
|
+
this.formFieldSamples[`${FORM_FIELD_DATA_POINT_PREFIX}${normalizedFieldId}`] =
|
|
469
|
+
sampleValue;
|
|
470
|
+
});
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
private collectCalculatedFieldKeys = (design?: UnlayerDesignFormat): Set<string> => {
|
|
474
|
+
const keys = new Set<string>();
|
|
475
|
+
|
|
476
|
+
if (!design) {
|
|
477
|
+
return keys;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
unlayerToolsIterate(design, tool => {
|
|
481
|
+
const calc = tool.values?.calculation;
|
|
482
|
+
if (!calc || typeof calc !== 'object') {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (typeof calc.expression === 'string') {
|
|
487
|
+
for (const key of tokenizeFormulaFields(calc.expression)) {
|
|
488
|
+
keys.add(key);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (calc.fieldLabels && typeof calc.fieldLabels === 'object') {
|
|
493
|
+
Object.keys(calc.fieldLabels).forEach(key => keys.add(key));
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
return keys;
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
private resolveDemoValue = (
|
|
501
|
+
key: string,
|
|
502
|
+
fallbackIndex: number,
|
|
503
|
+
sourceData: Record<string, unknown>,
|
|
504
|
+
): unknown => {
|
|
505
|
+
const parsedFormField = parseFormFieldKey(key);
|
|
506
|
+
if (parsedFormField) {
|
|
507
|
+
const normalizedFieldId = parsedFormField.fieldId;
|
|
508
|
+
const formFields = this.formFieldsByFormId[parsedFormField.formId] ?? [];
|
|
509
|
+
const fieldIndex = formFields.findIndex(
|
|
510
|
+
field => String(field.id ?? '').replaceAll('-', '') === normalizedFieldId,
|
|
511
|
+
);
|
|
512
|
+
const field = fieldIndex >= 0 ? formFields[fieldIndex] : undefined;
|
|
513
|
+
const sample = field
|
|
514
|
+
? getFormFieldSampleValue(field, fieldIndex)
|
|
515
|
+
: getByPath(
|
|
516
|
+
this.formFieldSamples,
|
|
517
|
+
`__submission_fields.${parsedFormField.formId}.${normalizedFieldId}`,
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
if (sample !== undefined) {
|
|
521
|
+
return sample;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return this.dateSchemaFieldKeys.has(key) ? '2026-01-15' : 44 + fallbackIndex;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const existing = getByPath(sourceData, key);
|
|
528
|
+
if (existing !== undefined) {
|
|
529
|
+
return existing;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (this.dateSchemaFieldKeys.has(key)) {
|
|
533
|
+
return '2026-01-15';
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return 44 + fallbackIndex;
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
private buildCalculatedFieldDemoData = (
|
|
540
|
+
design?: UnlayerDesignFormat,
|
|
541
|
+
): Record<string, unknown> => {
|
|
542
|
+
const demoData: Record<string, unknown> = {};
|
|
543
|
+
const usedKeys = Array.from(this.collectCalculatedFieldKeys(design));
|
|
544
|
+
const sourceData = deepMerge(
|
|
545
|
+
this.schemaDummyData,
|
|
546
|
+
this.props.dummyData,
|
|
547
|
+
this.formFieldSamples,
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
usedKeys.forEach((key, index) => {
|
|
551
|
+
const value = this.resolveDemoValue(key, index, sourceData);
|
|
552
|
+
setByPath(demoData, key, value);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
return demoData;
|
|
556
|
+
};
|
|
557
|
+
|
|
374
558
|
private sendMessage = (type: string, data?: any) => {
|
|
375
559
|
this.iframe?.contentWindow?.postMessage(
|
|
376
560
|
{
|