@servicetitan/dte-unlayer 0.153.0 → 0.155.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/display-conditions/ConditionRow.d.ts.map +1 -1
- package/dist/display-conditions/ConditionRow.js +111 -95
- package/dist/display-conditions/ConditionRow.js.map +1 -1
- package/dist/display-conditions/DisplayConditionModal.d.ts +3 -3
- package/dist/display-conditions/DisplayConditionModal.d.ts.map +1 -1
- package/dist/display-conditions/DisplayConditionModal.js +8 -95
- package/dist/display-conditions/DisplayConditionModal.js.map +1 -1
- package/dist/display-conditions/useConditionForms.d.ts +22 -0
- package/dist/display-conditions/useConditionForms.d.ts.map +1 -0
- package/dist/display-conditions/useConditionForms.js +110 -0
- package/dist/display-conditions/useConditionForms.js.map +1 -0
- package/dist/e-sign-offset/ESignDateSignedOffsetModal.d.ts +11 -0
- package/dist/e-sign-offset/ESignDateSignedOffsetModal.d.ts.map +1 -0
- package/dist/e-sign-offset/ESignDateSignedOffsetModal.js +192 -0
- package/dist/e-sign-offset/ESignDateSignedOffsetModal.js.map +1 -0
- package/dist/e-sign-offset/ESignOffsetRuleRow.d.ts +20 -0
- package/dist/e-sign-offset/ESignOffsetRuleRow.d.ts.map +1 -0
- package/dist/e-sign-offset/ESignOffsetRuleRow.js +87 -0
- package/dist/e-sign-offset/ESignOffsetRuleRow.js.map +1 -0
- package/dist/e-sign-offset/eSignOffsetController.d.ts +10 -0
- package/dist/e-sign-offset/eSignOffsetController.d.ts.map +1 -0
- package/dist/e-sign-offset/eSignOffsetController.js +27 -0
- package/dist/e-sign-offset/eSignOffsetController.js.map +1 -0
- 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/editor.d.ts.map +1 -1
- package/dist/editor.js +8 -0
- package/dist/editor.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/merge-tags.d.ts +6 -10
- package/dist/merge-tags.d.ts.map +1 -1
- package/dist/merge-tags.js +35 -44
- package/dist/merge-tags.js.map +1 -1
- package/dist/shared/const.d.ts +30 -0
- package/dist/shared/const.d.ts.map +1 -1
- package/dist/shared/const.js +5 -0
- package/dist/shared/const.js.map +1 -1
- package/dist/shared/e-sign-offset.d.ts +21 -0
- package/dist/shared/e-sign-offset.d.ts.map +1 -0
- package/dist/shared/e-sign-offset.js +130 -0
- package/dist/shared/e-sign-offset.js.map +1 -0
- package/dist/shared/instance-id.d.ts +13 -0
- package/dist/shared/instance-id.d.ts.map +1 -0
- package/dist/shared/instance-id.js +22 -0
- package/dist/shared/instance-id.js.map +1 -0
- package/dist/store.d.ts +15 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +80 -5
- package/dist/store.js.map +1 -1
- package/dist/tools.d.ts +18 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +68 -1
- package/dist/tools.js.map +1 -1
- package/dist/unlayer-interface.d.ts +11 -1
- package/dist/unlayer-interface.d.ts.map +1 -1
- package/dist/unlayer-interface.js.map +1 -1
- package/dist/unlayer.d.ts.map +1 -1
- package/dist/unlayer.js +5 -3
- package/dist/unlayer.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/e-sign-offset.test.ts +281 -0
- package/src/__tests__/merge-tags.test.ts +98 -76
- package/src/__tests__/store.merge-tags.test.ts +186 -0
- package/src/display-conditions/ConditionRow.tsx +86 -81
- package/src/display-conditions/DisplayConditionModal.tsx +12 -116
- package/src/display-conditions/useConditionForms.ts +146 -0
- package/src/e-sign-offset/ESignDateSignedOffsetModal.tsx +197 -0
- package/src/e-sign-offset/ESignOffsetRuleRow.tsx +101 -0
- package/src/e-sign-offset/eSignOffsetController.ts +39 -0
- package/src/editor-core-source.ts +2 -2
- package/src/editor.tsx +9 -0
- package/src/index.ts +1 -2
- package/src/merge-tags.ts +41 -60
- package/src/shared/const.ts +37 -0
- package/src/shared/e-sign-offset.ts +173 -0
- package/src/shared/instance-id.ts +23 -0
- package/src/store.ts +106 -5
- package/src/tools.ts +87 -1
- package/src/unlayer-interface.tsx +11 -1
- package/src/unlayer.tsx +5 -3
package/src/editor.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
useState,
|
|
9
9
|
} from 'react';
|
|
10
10
|
import { DisplayConditionModal } from './display-conditions/DisplayConditionModal';
|
|
11
|
+
import { ESignDateSignedOffsetModal } from './e-sign-offset/ESignDateSignedOffsetModal';
|
|
11
12
|
import { getErrorMessage } from './log';
|
|
12
13
|
import { UnlayerEditorTwin } from './shared/const';
|
|
13
14
|
import { UnlayerStore, UnlayerDesignChangeInfo } from './store';
|
|
@@ -146,6 +147,14 @@ export const UnlayerEditor = forwardRef<UnlayerRef, UnlayerEditorProps>((props,
|
|
|
146
147
|
onConditionFormSelect={props.opts.onConditionFormSelect}
|
|
147
148
|
/>
|
|
148
149
|
)}
|
|
150
|
+
{!!props.opts.eSignRecipients?.length && (
|
|
151
|
+
<ESignDateSignedOffsetModal
|
|
152
|
+
portalContainer={props.opts.displayConditionModalPortalContainer}
|
|
153
|
+
schema={props.opts.schema}
|
|
154
|
+
onConditionalsOpen={props.opts.onConditionalsOpen}
|
|
155
|
+
onConditionFormSelect={props.opts.onConditionFormSelect}
|
|
156
|
+
/>
|
|
157
|
+
)}
|
|
149
158
|
</div>
|
|
150
159
|
);
|
|
151
160
|
});
|
package/src/index.ts
CHANGED
|
@@ -3,13 +3,12 @@ export * from './tools';
|
|
|
3
3
|
export * from './api-core';
|
|
4
4
|
export * from './api-custom-tools';
|
|
5
5
|
export * from './unlayer-interface';
|
|
6
|
-
export { isUnlayerMergeTagGroup,
|
|
6
|
+
export { isUnlayerMergeTagGroup, toUnlayerMergeTags } from './merge-tags';
|
|
7
7
|
export type {
|
|
8
8
|
ToUnlayerMergeTagsOptions,
|
|
9
9
|
UnlayerMergeTagGroup,
|
|
10
10
|
UnlayerMergeTagLeaf,
|
|
11
11
|
UnlayerMergeTagsConfig,
|
|
12
|
-
UnlayerMergeTagsEditor,
|
|
13
12
|
} from './merge-tags';
|
|
14
13
|
export * from './loadScript';
|
|
15
14
|
export * from './shared/const';
|
package/src/merge-tags.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { logUnlayerError } from './log';
|
|
2
1
|
import { UnlayerEditorMergeTagInfo } from './unlayer-interface';
|
|
3
2
|
|
|
4
3
|
export interface UnlayerMergeTagLeaf {
|
|
@@ -8,7 +7,8 @@ export interface UnlayerMergeTagLeaf {
|
|
|
8
7
|
|
|
9
8
|
export interface UnlayerMergeTagGroup {
|
|
10
9
|
name: string;
|
|
11
|
-
|
|
10
|
+
/** Nested submenu: leaves and/or further groups (Unlayer supports arbitrary depth). */
|
|
11
|
+
mergeTags: UnlayerMergeTagsConfig;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export type UnlayerMergeTagsConfig = Record<string, UnlayerMergeTagLeaf | UnlayerMergeTagGroup>;
|
|
@@ -21,11 +21,6 @@ export interface ToUnlayerMergeTagsOptions {
|
|
|
21
21
|
groups?: boolean;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export interface UnlayerMergeTagsEditor {
|
|
25
|
-
addEventListener(event: string, cb: (arg: any) => void): void;
|
|
26
|
-
setMergeTags(tags: UnlayerMergeTagsConfig): void;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
24
|
export function isUnlayerMergeTagGroup(
|
|
30
25
|
entry: UnlayerMergeTagLeaf | UnlayerMergeTagGroup,
|
|
31
26
|
): entry is UnlayerMergeTagGroup {
|
|
@@ -42,6 +37,43 @@ const toMergeTagKey = (value: string) => {
|
|
|
42
37
|
return key || value;
|
|
43
38
|
};
|
|
44
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Normalizes the host `group` into the submenu path the tag belongs to:
|
|
42
|
+
* `'Custom Fields'` → `['Custom Fields']`, `['Forms', 'Intake']` → `['Forms', 'Intake']`.
|
|
43
|
+
* Blank segments are dropped; an empty result means the tag stays at the top level.
|
|
44
|
+
*/
|
|
45
|
+
const toGroupPath = (group: UnlayerEditorMergeTagInfo['group']): string[] => {
|
|
46
|
+
const segments = Array.isArray(group) ? group : group === undefined ? [] : [group];
|
|
47
|
+
|
|
48
|
+
return segments.map(segment => segment.trim()).filter(Boolean);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Returns the `mergeTags` record of the (possibly nested) group at `path`, creating groups on the way. */
|
|
52
|
+
const ensureGroup = (root: UnlayerMergeTagsConfig, path: string[]) => {
|
|
53
|
+
let current = root;
|
|
54
|
+
|
|
55
|
+
for (const groupName of path) {
|
|
56
|
+
const groupKey = toMergeTagKey(groupName);
|
|
57
|
+
const existing = current[groupKey];
|
|
58
|
+
|
|
59
|
+
if (existing && isUnlayerMergeTagGroup(existing)) {
|
|
60
|
+
current = existing.mergeTags;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const created: UnlayerMergeTagGroup = { name: groupName, mergeTags: {} };
|
|
65
|
+
current[groupKey] = created;
|
|
66
|
+
current = created.mergeTags;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return current;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Builds Unlayer's `mergeTags` config. With `groups` enabled (default) tags nest under the
|
|
74
|
+
* submenu path in `group`; `UnlayerStore` applies that shape once the editor is ready, since
|
|
75
|
+
* nested groups at `createEditor` time prevent DTE custom tools from loading.
|
|
76
|
+
*/
|
|
45
77
|
export const toUnlayerMergeTags = (
|
|
46
78
|
tags: UnlayerEditorMergeTagInfo[] | undefined,
|
|
47
79
|
options?: ToUnlayerMergeTagsOptions,
|
|
@@ -58,61 +90,10 @@ export const toUnlayerMergeTags = (
|
|
|
58
90
|
value: tag.propertyPath,
|
|
59
91
|
};
|
|
60
92
|
const tagKey = toMergeTagKey(tag.id);
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
if (!groupName) {
|
|
64
|
-
out[tagKey] = mergeTag;
|
|
65
|
-
return out;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const groupKey = toMergeTagKey(groupName);
|
|
69
|
-
const existing = out[groupKey];
|
|
70
|
-
|
|
71
|
-
if (existing && isUnlayerMergeTagGroup(existing)) {
|
|
72
|
-
existing.mergeTags[tagKey] = mergeTag;
|
|
73
|
-
return out;
|
|
74
|
-
}
|
|
93
|
+
const groupPath = groupsEnabled ? toGroupPath(tag.group) : [];
|
|
75
94
|
|
|
76
|
-
out[
|
|
77
|
-
name: groupName,
|
|
78
|
-
mergeTags: {
|
|
79
|
-
[tagKey]: mergeTag,
|
|
80
|
-
},
|
|
81
|
-
};
|
|
95
|
+
ensureGroup(out, groupPath)[tagKey] = mergeTag;
|
|
82
96
|
|
|
83
97
|
return out;
|
|
84
98
|
}, {});
|
|
85
99
|
};
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Nested Unlayer groups passed to createEditor prevent DTE custom tools from
|
|
89
|
-
* loading in the host app. Apply the 0.151 flat shape at init, then upgrade to
|
|
90
|
-
* grouped submenus once the editor (and customJS tools) are ready.
|
|
91
|
-
*/
|
|
92
|
-
export const scheduleUnlayerMergeTags = (
|
|
93
|
-
editor: UnlayerMergeTagsEditor,
|
|
94
|
-
tags: UnlayerEditorMergeTagInfo[] | undefined,
|
|
95
|
-
) => {
|
|
96
|
-
const grouped = toUnlayerMergeTags(tags);
|
|
97
|
-
|
|
98
|
-
if (!grouped) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
let isApplied = false;
|
|
103
|
-
const apply = () => {
|
|
104
|
-
if (isApplied) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
editor.setMergeTags(grouped);
|
|
110
|
-
isApplied = true;
|
|
111
|
-
} catch (error: unknown) {
|
|
112
|
-
logUnlayerError('Failed to apply grouped merge tags', error);
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
editor.addEventListener('editor:ready', apply);
|
|
117
|
-
editor.addEventListener('design:loaded', apply);
|
|
118
|
-
};
|
package/src/shared/const.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DisplayConditionState } from '../display-conditions/types';
|
|
1
2
|
import { SchemaObject } from './schema';
|
|
2
3
|
|
|
3
4
|
export const constGenericsEditor = {
|
|
@@ -12,6 +13,41 @@ export interface UnlayerEditorESignRecipientInfo {
|
|
|
12
13
|
displayName: string;
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
export const E_SIGN_TOOL_SLUG = 'e-sign';
|
|
17
|
+
export const E_SIGN_DATE_SIGNED_OFFSET_TYPE = 'dateSignedOffset';
|
|
18
|
+
/** Must match the backend `EsignFieldSubTypes` registry key read from the e-sign tool values. */
|
|
19
|
+
export const E_SIGN_DATE_SIGNED_OFFSET_VALUES_KEY = 'eSignDateSignedOffset';
|
|
20
|
+
export const MAX_E_SIGN_OFFSET_DAYS = 365;
|
|
21
|
+
/** Counts the trailing unconditional rule too. */
|
|
22
|
+
export const MAX_E_SIGN_OFFSET_RULES = 5;
|
|
23
|
+
|
|
24
|
+
export interface ESignDateSignedOffsetRule {
|
|
25
|
+
days: number;
|
|
26
|
+
/** Always `behavior: 'show'` with exactly one condition; the backend rejects anything else. */
|
|
27
|
+
condition: DisplayConditionState;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ESignDateSignedOffsetFinalRule {
|
|
31
|
+
days: number;
|
|
32
|
+
condition?: never;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Ordered if / else-if / else chain: the first rule whose condition matches supplies the offset,
|
|
37
|
+
* and the last rule is always the unconditional fallback.
|
|
38
|
+
*/
|
|
39
|
+
export interface ESignDateSignedOffsetSettings {
|
|
40
|
+
rules:
|
|
41
|
+
| [ESignDateSignedOffsetFinalRule]
|
|
42
|
+
| [...ESignDateSignedOffsetRule[], ESignDateSignedOffsetFinalRule];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Payload of both `--esign-offset-open` (iframe to parent) and `--esign-offset-result` (back). */
|
|
46
|
+
export interface UnlayerEventESignOffset {
|
|
47
|
+
requestId: string;
|
|
48
|
+
value: ESignDateSignedOffsetSettings;
|
|
49
|
+
}
|
|
50
|
+
|
|
15
51
|
export interface UnlayerEditorUnit {
|
|
16
52
|
id: string;
|
|
17
53
|
generic: string;
|
|
@@ -33,6 +69,7 @@ export interface UnlayerEventConfig {
|
|
|
33
69
|
isSnapshotMode?: boolean;
|
|
34
70
|
units?: UnlayerEditorUnit[];
|
|
35
71
|
holidays?: string[];
|
|
72
|
+
conditionalRulesEnabled?: boolean;
|
|
36
73
|
}
|
|
37
74
|
|
|
38
75
|
export interface UnlayerEventRegister {
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { defaultGroup } from '../display-conditions/constants';
|
|
2
|
+
import {
|
|
3
|
+
CONDITION_OPERATORS,
|
|
4
|
+
type DataPointOption,
|
|
5
|
+
type DisplayConditionState,
|
|
6
|
+
type SingleCondition,
|
|
7
|
+
VALUE_LESS_OPERATORS,
|
|
8
|
+
} from '../display-conditions/types';
|
|
9
|
+
import {
|
|
10
|
+
type ESignDateSignedOffsetFinalRule,
|
|
11
|
+
type ESignDateSignedOffsetRule,
|
|
12
|
+
type ESignDateSignedOffsetSettings,
|
|
13
|
+
MAX_E_SIGN_OFFSET_DAYS,
|
|
14
|
+
MAX_E_SIGN_OFFSET_RULES,
|
|
15
|
+
} from './const';
|
|
16
|
+
|
|
17
|
+
const NUMERIC_VALUE_RE = /^-?(?:\d+\.?\d*|\.\d+)$/;
|
|
18
|
+
|
|
19
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
20
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isConditionState(value: unknown): value is DisplayConditionState {
|
|
24
|
+
return isRecord(value) && Array.isArray(value.groups) && value.groups.length > 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isConditionedRule(
|
|
28
|
+
rule: ESignDateSignedOffsetRule | ESignDateSignedOffsetFinalRule,
|
|
29
|
+
): rule is ESignDateSignedOffsetRule {
|
|
30
|
+
return rule.condition !== undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const clampESignOffsetDays = (days: unknown) => {
|
|
34
|
+
if (typeof days !== 'number' || !Number.isFinite(days)) {
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return Math.max(Math.min(MAX_E_SIGN_OFFSET_DAYS, Math.trunc(days)), 0);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const createESignOffsetSettings = (days = 0): ESignDateSignedOffsetSettings => {
|
|
42
|
+
return { rules: [{ days: clampESignOffsetDays(days) }] };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const createESignOffsetRule = (): ESignDateSignedOffsetRule => {
|
|
46
|
+
return { condition: { behavior: 'show', groups: [defaultGroup()] }, days: 0 };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const buildESignOffsetSettings = (
|
|
50
|
+
conditionedRules: ESignDateSignedOffsetRule[],
|
|
51
|
+
finalDays: number,
|
|
52
|
+
): ESignDateSignedOffsetSettings => {
|
|
53
|
+
return {
|
|
54
|
+
rules: [
|
|
55
|
+
...conditionedRules.map(rule => ({ ...rule, days: clampESignOffsetDays(rule.days) })),
|
|
56
|
+
{ days: clampESignOffsetDays(finalDays) },
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const splitESignOffsetSettings = (settings: ESignDateSignedOffsetSettings) => {
|
|
62
|
+
const rules: readonly (ESignDateSignedOffsetRule | ESignDateSignedOffsetFinalRule)[] =
|
|
63
|
+
settings.rules;
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
conditionedRules: rules.filter(isConditionedRule),
|
|
67
|
+
finalDays: rules[rules.length - 1]?.days ?? 0,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function isESignOffsetSettings(value: unknown): value is ESignDateSignedOffsetSettings {
|
|
72
|
+
if (!isRecord(value) || !Array.isArray(value.rules)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const rules: unknown[] = value.rules;
|
|
77
|
+
|
|
78
|
+
if (rules.length === 0 || rules.length > MAX_E_SIGN_OFFSET_RULES) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return rules.every((rule, index) => {
|
|
83
|
+
if (!isRecord(rule) || clampESignOffsetDays(rule.days) !== rule.days) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return index === rules.length - 1
|
|
88
|
+
? rule.condition === undefined
|
|
89
|
+
: isConditionState(rule.condition);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Coerces whatever is stored on the tool (missing, legacy or hand-edited) into a valid chain. */
|
|
94
|
+
export const normalizeESignOffsetSettings = (value: unknown): ESignDateSignedOffsetSettings => {
|
|
95
|
+
if (isESignOffsetSettings(value)) {
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!isRecord(value) || !Array.isArray(value.rules)) {
|
|
100
|
+
return createESignOffsetSettings();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const rules: unknown[] = value.rules;
|
|
104
|
+
const lastRule = rules[rules.length - 1];
|
|
105
|
+
const conditionedRules = rules
|
|
106
|
+
.slice(0, -1)
|
|
107
|
+
.flatMap(rule =>
|
|
108
|
+
isRecord(rule) && isConditionState(rule.condition)
|
|
109
|
+
? [{ condition: rule.condition, days: clampESignOffsetDays(rule.days) }]
|
|
110
|
+
: [],
|
|
111
|
+
)
|
|
112
|
+
.slice(0, MAX_E_SIGN_OFFSET_RULES - 1);
|
|
113
|
+
|
|
114
|
+
return buildESignOffsetSettings(
|
|
115
|
+
conditionedRules,
|
|
116
|
+
isRecord(lastRule) ? clampESignOffsetDays(lastRule.days) : 0,
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const canAddESignOffsetRule = (rules: ESignDateSignedOffsetRule[]) => {
|
|
121
|
+
return rules.length < MAX_E_SIGN_OFFSET_RULES - 1;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const addESignOffsetRule = (rules: ESignDateSignedOffsetRule[]) => {
|
|
125
|
+
return canAddESignOffsetRule(rules) ? [...rules, createESignOffsetRule()] : rules;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const updateESignOffsetRule = (
|
|
129
|
+
rules: ESignDateSignedOffsetRule[],
|
|
130
|
+
index: number,
|
|
131
|
+
nextRule: ESignDateSignedOffsetRule,
|
|
132
|
+
) => {
|
|
133
|
+
return rules.map((rule, ruleIndex) => (ruleIndex === index ? nextRule : rule));
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const removeESignOffsetRule = (rules: ESignDateSignedOffsetRule[], index: number) => {
|
|
137
|
+
return rules.filter((_, ruleIndex) => ruleIndex !== index);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const getESignOffsetRuleCondition = (rule: ESignDateSignedOffsetRule) => {
|
|
141
|
+
return rule.condition.groups[0]?.conditions[0];
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const isCompleteESignOffsetCondition = (
|
|
145
|
+
condition: SingleCondition | undefined,
|
|
146
|
+
fieldType: DataPointOption['fieldType'],
|
|
147
|
+
) => {
|
|
148
|
+
if (!condition?.dataPointKey) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (VALUE_LESS_OPERATORS.includes(condition.operator)) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const value = condition.value.trim();
|
|
157
|
+
|
|
158
|
+
return value !== '' && (fieldType !== 'number' || NUMERIC_VALUE_RE.test(value));
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const getESignOffsetConditionText = (condition: SingleCondition, dataPointTitle: string) => {
|
|
162
|
+
const operatorLabel = (
|
|
163
|
+
CONDITION_OPERATORS.find(operator => operator.value === condition.operator)?.label ??
|
|
164
|
+
condition.operator
|
|
165
|
+
)
|
|
166
|
+
.split(' (')[0]
|
|
167
|
+
.replace('==', '=')
|
|
168
|
+
.toLowerCase();
|
|
169
|
+
|
|
170
|
+
return VALUE_LESS_OPERATORS.includes(condition.operator)
|
|
171
|
+
? `${dataPointTitle} ${operatorLabel}`
|
|
172
|
+
: `${dataPointTitle} ${operatorLabel} ${condition.value}`;
|
|
173
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom tools whose `values.id` is a per-instance GUID that must stay unique
|
|
3
|
+
* within a design. When such a tool is duplicated (content or row duplicate),
|
|
4
|
+
* Unlayer copies the values as-is, so the host regenerates the id of the copy.
|
|
5
|
+
*/
|
|
6
|
+
export const INSTANCE_ID_TOOL_SLUGS = ['embedded-image'];
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generates an RFC 4122 v4 GUID (`3fa85f64-5717-4562-b3fc-2c963f66afa6`) with the
|
|
10
|
+
* platform `crypto.randomUUID()`. It is only exposed in secure contexts (https /
|
|
11
|
+
* localhost); when missing the error is logged and no id is produced.
|
|
12
|
+
*/
|
|
13
|
+
export const generateInstanceId = (): string | undefined => {
|
|
14
|
+
if (typeof globalThis.crypto?.randomUUID !== 'function') {
|
|
15
|
+
// eslint-disable-next-line no-console
|
|
16
|
+
console.error(
|
|
17
|
+
'crypto.randomUUID is not available (insecure context?), cannot generate an instance id',
|
|
18
|
+
);
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return globalThis.crypto.randomUUID();
|
|
23
|
+
};
|
package/src/store.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import { emitESignOffsetRequest } from './e-sign-offset/eSignOffsetController';
|
|
1
2
|
import { loadScript } from './loadScript';
|
|
2
3
|
import { logUnlayerError } from './log';
|
|
4
|
+
import { toUnlayerMergeTags, type UnlayerMergeTagsConfig } from './merge-tags';
|
|
3
5
|
import { defaultImageValidation } from './shared/configs';
|
|
4
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
UnlayerEditorTwin,
|
|
8
|
+
UnlayerEventConfig,
|
|
9
|
+
UnlayerEventESignOffset,
|
|
10
|
+
UnlayerEventRegister,
|
|
11
|
+
} from './shared/const';
|
|
12
|
+
import { normalizeESignOffsetSettings } from './shared/e-sign-offset';
|
|
5
13
|
import { unlayerSupportedFonts } from './shared/fonts';
|
|
6
14
|
import {
|
|
7
15
|
FormFieldInfo,
|
|
@@ -11,14 +19,23 @@ import {
|
|
|
11
19
|
} from './shared/forms';
|
|
12
20
|
import { schemaBuildMap, schemaIsDate } from './shared/schema';
|
|
13
21
|
import { unlayerToolsParseTwinKey } from './shared/tools';
|
|
14
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
unlayerToolsDedupeInstanceIds,
|
|
24
|
+
unlayerToolsIterate,
|
|
25
|
+
unlayerToolsNormalizeESignOffsets,
|
|
26
|
+
} from './tools';
|
|
15
27
|
import {
|
|
16
28
|
createUnlayerEditor,
|
|
17
29
|
DesignUpdatedEventType,
|
|
18
30
|
EventDesignUpdated,
|
|
19
31
|
Unlayer,
|
|
20
32
|
} from './unlayer';
|
|
21
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
CreateUnlayerEditorProps,
|
|
35
|
+
UnlayerDesignFormat,
|
|
36
|
+
UnlayerEditorMergeTagInfo,
|
|
37
|
+
UnlayerRef,
|
|
38
|
+
} from './unlayer-interface';
|
|
22
39
|
|
|
23
40
|
const defaultScriptUrl = 'https://editor.unlayer.com/embed.js?2';
|
|
24
41
|
const UNLAYER_TOOLS_READY_TIMEOUT_MS = 30000;
|
|
@@ -191,6 +208,9 @@ export class UnlayerStore {
|
|
|
191
208
|
private toolsReadyWatchdogId?: number;
|
|
192
209
|
private formFieldsByFormId: Record<number, FormFieldInfo[]> = {};
|
|
193
210
|
private formFieldSamples: Record<string, unknown> = {};
|
|
211
|
+
private isEditorReady = false;
|
|
212
|
+
private isMergeTagsApplied = false;
|
|
213
|
+
private mergeTagsConfig?: UnlayerMergeTagsConfig;
|
|
194
214
|
|
|
195
215
|
private onMessageCB?: (type: string, data: any) => void;
|
|
196
216
|
private onChangeCB?: (info: UnlayerDesignChangeInfo) => void;
|
|
@@ -211,18 +231,28 @@ export class UnlayerStore {
|
|
|
211
231
|
.map(field => field.fullKey),
|
|
212
232
|
);
|
|
213
233
|
|
|
214
|
-
this.props.eSignFieldTypes = [
|
|
234
|
+
this.props.eSignFieldTypes = [
|
|
235
|
+
'Signature',
|
|
236
|
+
'Initials',
|
|
237
|
+
'Date Signed',
|
|
238
|
+
'Date Signed Offset',
|
|
239
|
+
'Full Name',
|
|
240
|
+
];
|
|
215
241
|
|
|
216
242
|
this.unlayerRef = {
|
|
217
243
|
loadDesign: design => {
|
|
218
244
|
this.editor?.loadDesign(design);
|
|
219
245
|
},
|
|
220
246
|
saveDesign: cb => {
|
|
221
|
-
this.editor?.saveDesign(
|
|
247
|
+
this.editor?.saveDesign(design => {
|
|
248
|
+
unlayerToolsNormalizeESignOffsets(design);
|
|
249
|
+
cb(design);
|
|
250
|
+
});
|
|
222
251
|
},
|
|
223
252
|
exportHtml: cb => {
|
|
224
253
|
this.editor?.exportHtml(data => {
|
|
225
254
|
const result = ensureChunksFonts(data);
|
|
255
|
+
unlayerToolsNormalizeESignOffsets(result?.design);
|
|
226
256
|
result.demoData = this.buildCalculatedFieldDemoData(result.design);
|
|
227
257
|
cb(result);
|
|
228
258
|
});
|
|
@@ -236,6 +266,9 @@ export class UnlayerStore {
|
|
|
236
266
|
sendCalcFieldLabels: labels => {
|
|
237
267
|
this.sendCalcFieldLabels(labels);
|
|
238
268
|
},
|
|
269
|
+
setMergeTags: tags => {
|
|
270
|
+
this.setMergeTags(tags);
|
|
271
|
+
},
|
|
239
272
|
};
|
|
240
273
|
}
|
|
241
274
|
|
|
@@ -256,6 +289,9 @@ export class UnlayerStore {
|
|
|
256
289
|
}
|
|
257
290
|
|
|
258
291
|
this.editor = createUnlayerEditor(container, this.props);
|
|
292
|
+
// The host may already have pushed a newer set through `setMergeTags`; keep it.
|
|
293
|
+
this.mergeTagsConfig ??= toUnlayerMergeTags(this.props.mergeTags);
|
|
294
|
+
this.editor.addEventListener('editor:ready', this.onEditorReady);
|
|
259
295
|
this.editor.addEventListener('design:loaded', this.onDesignLoaded);
|
|
260
296
|
this.editor.addEventListener('design:updated', this.onDesignUpdated);
|
|
261
297
|
this.editor.registerCallback('image', this.uploadImage);
|
|
@@ -280,6 +316,7 @@ export class UnlayerStore {
|
|
|
280
316
|
window.removeEventListener('message', this.onPostMessage);
|
|
281
317
|
|
|
282
318
|
if (this.editor) {
|
|
319
|
+
this.editor.removeEventListener('editor:ready', this.onEditorReady);
|
|
283
320
|
this.editor.removeEventListener('design:loaded', this.onDesignLoaded);
|
|
284
321
|
this.editor.removeEventListener('design:updated', this.onDesignUpdated);
|
|
285
322
|
this.editor.unregisterCallback('image', this.uploadImage);
|
|
@@ -355,7 +392,46 @@ export class UnlayerStore {
|
|
|
355
392
|
this.sendMessage('--calc-field-labels', { labels });
|
|
356
393
|
};
|
|
357
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Replaces the editor's merge tag menu. Nested groups passed to `createEditor` stop DTE
|
|
397
|
+
* custom tools from registering, so init only carries the flat shape and the grouped
|
|
398
|
+
* config is applied here once the editor is ready. A call before that (including the
|
|
399
|
+
* initial props) is parked; a later call replaces it. A failed apply is retried on the
|
|
400
|
+
* next `design:loaded`.
|
|
401
|
+
*/
|
|
402
|
+
setMergeTags = (tags: UnlayerEditorMergeTagInfo[] | undefined) => {
|
|
403
|
+
this.mergeTagsConfig = toUnlayerMergeTags(tags);
|
|
404
|
+
this.isMergeTagsApplied = false;
|
|
405
|
+
this.applyMergeTags();
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
private applyMergeTags = () => {
|
|
409
|
+
if (
|
|
410
|
+
!this.editor ||
|
|
411
|
+
!this.isEditorReady ||
|
|
412
|
+
this.isMergeTagsApplied ||
|
|
413
|
+
!this.mergeTagsConfig
|
|
414
|
+
) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
try {
|
|
419
|
+
this.editor.setMergeTags(this.mergeTagsConfig);
|
|
420
|
+
this.isMergeTagsApplied = true;
|
|
421
|
+
} catch (error: unknown) {
|
|
422
|
+
logUnlayerError('Failed to apply merge tags', error);
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
private onEditorReady = () => {
|
|
427
|
+
this.isEditorReady = true;
|
|
428
|
+
this.applyMergeTags();
|
|
429
|
+
};
|
|
430
|
+
|
|
358
431
|
private onDesignLoaded = () => {
|
|
432
|
+
// A loaded design means the editor is up even if `editor:ready` never reached us.
|
|
433
|
+
this.onEditorReady();
|
|
434
|
+
|
|
359
435
|
if (!this.hasDesign) {
|
|
360
436
|
this.hasDesign = true;
|
|
361
437
|
this.editor?.setBodyValues({
|
|
@@ -391,6 +467,11 @@ export class UnlayerStore {
|
|
|
391
467
|
}
|
|
392
468
|
});
|
|
393
469
|
|
|
470
|
+
// Duplicated contents/rows carry the original's instance GUIDs; give copies new ones.
|
|
471
|
+
if (unlayerToolsDedupeInstanceIds(data.design, event.item?.values?._meta?.htmlID)) {
|
|
472
|
+
hasChanges = true;
|
|
473
|
+
}
|
|
474
|
+
|
|
394
475
|
if (hasChanges) {
|
|
395
476
|
this.setDesign(data.design);
|
|
396
477
|
}
|
|
@@ -427,6 +508,8 @@ export class UnlayerStore {
|
|
|
427
508
|
genericConfigMode: this.props.genericConfigMode,
|
|
428
509
|
isSnapshotMode: this.props.isSnapshotMode,
|
|
429
510
|
holidays: this.props.holidays,
|
|
511
|
+
conditionalRulesEnabled:
|
|
512
|
+
!!this.props.displayConditions || !!this.props.onConditionalsOpen,
|
|
430
513
|
};
|
|
431
514
|
|
|
432
515
|
this.sendMessage('--config', JSON.parse(JSON.stringify(configData)));
|
|
@@ -434,6 +517,8 @@ export class UnlayerStore {
|
|
|
434
517
|
this.onFormSelectCB?.(data?.formId);
|
|
435
518
|
} else if (type === '--calc-field-open') {
|
|
436
519
|
this.onCalcFieldOpenCB?.(data?.usedFormIds ?? []);
|
|
520
|
+
} else if (type === '--esign-offset-open') {
|
|
521
|
+
this.openESignOffsetRules(data);
|
|
437
522
|
} else if (type === '--calc-field-select') {
|
|
438
523
|
if (this.onCalcFieldSelectCB) {
|
|
439
524
|
this.onCalcFieldSelectCB(data?.fieldKeys ?? []);
|
|
@@ -478,6 +563,22 @@ export class UnlayerStore {
|
|
|
478
563
|
this.onMessageCB?.(type, data);
|
|
479
564
|
};
|
|
480
565
|
|
|
566
|
+
private openESignOffsetRules = (data?: Partial<UnlayerEventESignOffset>) => {
|
|
567
|
+
const requestId = data?.requestId;
|
|
568
|
+
|
|
569
|
+
if (!requestId) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
emitESignOffsetRequest({
|
|
574
|
+
value: normalizeESignOffsetSettings(data.value),
|
|
575
|
+
done: value => {
|
|
576
|
+
const result: UnlayerEventESignOffset = { requestId, value };
|
|
577
|
+
this.sendMessage('--esign-offset-result', JSON.parse(JSON.stringify(result)));
|
|
578
|
+
},
|
|
579
|
+
});
|
|
580
|
+
};
|
|
581
|
+
|
|
481
582
|
private updateFormFieldSamples = (formId: number, fields: FormFieldInfo[]) => {
|
|
482
583
|
fields.forEach((field, index) => {
|
|
483
584
|
const normalizedFieldId = String(field.id ?? '').replaceAll('-', '');
|