@servicetitan/dte-unlayer 0.154.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.
Files changed (60) hide show
  1. package/dist/display-conditions/ConditionRow.d.ts.map +1 -1
  2. package/dist/display-conditions/ConditionRow.js +111 -95
  3. package/dist/display-conditions/ConditionRow.js.map +1 -1
  4. package/dist/display-conditions/DisplayConditionModal.d.ts +3 -3
  5. package/dist/display-conditions/DisplayConditionModal.d.ts.map +1 -1
  6. package/dist/display-conditions/DisplayConditionModal.js +8 -95
  7. package/dist/display-conditions/DisplayConditionModal.js.map +1 -1
  8. package/dist/display-conditions/useConditionForms.d.ts +22 -0
  9. package/dist/display-conditions/useConditionForms.d.ts.map +1 -0
  10. package/dist/display-conditions/useConditionForms.js +110 -0
  11. package/dist/display-conditions/useConditionForms.js.map +1 -0
  12. package/dist/e-sign-offset/ESignDateSignedOffsetModal.d.ts +11 -0
  13. package/dist/e-sign-offset/ESignDateSignedOffsetModal.d.ts.map +1 -0
  14. package/dist/e-sign-offset/ESignDateSignedOffsetModal.js +192 -0
  15. package/dist/e-sign-offset/ESignDateSignedOffsetModal.js.map +1 -0
  16. package/dist/e-sign-offset/ESignOffsetRuleRow.d.ts +20 -0
  17. package/dist/e-sign-offset/ESignOffsetRuleRow.d.ts.map +1 -0
  18. package/dist/e-sign-offset/ESignOffsetRuleRow.js +87 -0
  19. package/dist/e-sign-offset/ESignOffsetRuleRow.js.map +1 -0
  20. package/dist/e-sign-offset/eSignOffsetController.d.ts +10 -0
  21. package/dist/e-sign-offset/eSignOffsetController.d.ts.map +1 -0
  22. package/dist/e-sign-offset/eSignOffsetController.js +27 -0
  23. package/dist/e-sign-offset/eSignOffsetController.js.map +1 -0
  24. package/dist/editor-core-source.d.ts +2 -2
  25. package/dist/editor-core-source.d.ts.map +1 -1
  26. package/dist/editor-core-source.js +2 -2
  27. package/dist/editor-core-source.js.map +1 -1
  28. package/dist/editor.d.ts.map +1 -1
  29. package/dist/editor.js +8 -0
  30. package/dist/editor.js.map +1 -1
  31. package/dist/shared/const.d.ts +30 -0
  32. package/dist/shared/const.d.ts.map +1 -1
  33. package/dist/shared/const.js +5 -0
  34. package/dist/shared/const.js.map +1 -1
  35. package/dist/shared/e-sign-offset.d.ts +21 -0
  36. package/dist/shared/e-sign-offset.d.ts.map +1 -0
  37. package/dist/shared/e-sign-offset.js +130 -0
  38. package/dist/shared/e-sign-offset.js.map +1 -0
  39. package/dist/store.d.ts +1 -0
  40. package/dist/store.d.ts.map +1 -1
  41. package/dist/store.js +30 -3
  42. package/dist/store.js.map +1 -1
  43. package/dist/tools.d.ts +6 -0
  44. package/dist/tools.d.ts.map +1 -1
  45. package/dist/tools.js +21 -1
  46. package/dist/tools.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/__tests__/e-sign-offset.test.ts +281 -0
  49. package/src/display-conditions/ConditionRow.tsx +86 -81
  50. package/src/display-conditions/DisplayConditionModal.tsx +12 -116
  51. package/src/display-conditions/useConditionForms.ts +146 -0
  52. package/src/e-sign-offset/ESignDateSignedOffsetModal.tsx +197 -0
  53. package/src/e-sign-offset/ESignOffsetRuleRow.tsx +101 -0
  54. package/src/e-sign-offset/eSignOffsetController.ts +39 -0
  55. package/src/editor-core-source.ts +2 -2
  56. package/src/editor.tsx +9 -0
  57. package/src/shared/const.ts +37 -0
  58. package/src/shared/e-sign-offset.ts +173 -0
  59. package/src/store.ts +45 -4
  60. package/src/tools.ts +36 -1
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
  });
@@ -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
+ };
package/src/store.ts CHANGED
@@ -1,8 +1,15 @@
1
+ import { emitESignOffsetRequest } from './e-sign-offset/eSignOffsetController';
1
2
  import { loadScript } from './loadScript';
2
3
  import { logUnlayerError } from './log';
3
4
  import { toUnlayerMergeTags, type UnlayerMergeTagsConfig } from './merge-tags';
4
5
  import { defaultImageValidation } from './shared/configs';
5
- import type { UnlayerEditorTwin, UnlayerEventConfig, UnlayerEventRegister } from './shared/const';
6
+ import type {
7
+ UnlayerEditorTwin,
8
+ UnlayerEventConfig,
9
+ UnlayerEventESignOffset,
10
+ UnlayerEventRegister,
11
+ } from './shared/const';
12
+ import { normalizeESignOffsetSettings } from './shared/e-sign-offset';
6
13
  import { unlayerSupportedFonts } from './shared/fonts';
7
14
  import {
8
15
  FormFieldInfo,
@@ -12,7 +19,11 @@ import {
12
19
  } from './shared/forms';
13
20
  import { schemaBuildMap, schemaIsDate } from './shared/schema';
14
21
  import { unlayerToolsParseTwinKey } from './shared/tools';
15
- import { unlayerToolsDedupeInstanceIds, unlayerToolsIterate } from './tools';
22
+ import {
23
+ unlayerToolsDedupeInstanceIds,
24
+ unlayerToolsIterate,
25
+ unlayerToolsNormalizeESignOffsets,
26
+ } from './tools';
16
27
  import {
17
28
  createUnlayerEditor,
18
29
  DesignUpdatedEventType,
@@ -220,18 +231,28 @@ export class UnlayerStore {
220
231
  .map(field => field.fullKey),
221
232
  );
222
233
 
223
- this.props.eSignFieldTypes = ['Signature', 'Initials', 'Date Signed', 'Full Name'];
234
+ this.props.eSignFieldTypes = [
235
+ 'Signature',
236
+ 'Initials',
237
+ 'Date Signed',
238
+ 'Date Signed Offset',
239
+ 'Full Name',
240
+ ];
224
241
 
225
242
  this.unlayerRef = {
226
243
  loadDesign: design => {
227
244
  this.editor?.loadDesign(design);
228
245
  },
229
246
  saveDesign: cb => {
230
- this.editor?.saveDesign(cb);
247
+ this.editor?.saveDesign(design => {
248
+ unlayerToolsNormalizeESignOffsets(design);
249
+ cb(design);
250
+ });
231
251
  },
232
252
  exportHtml: cb => {
233
253
  this.editor?.exportHtml(data => {
234
254
  const result = ensureChunksFonts(data);
255
+ unlayerToolsNormalizeESignOffsets(result?.design);
235
256
  result.demoData = this.buildCalculatedFieldDemoData(result.design);
236
257
  cb(result);
237
258
  });
@@ -487,6 +508,8 @@ export class UnlayerStore {
487
508
  genericConfigMode: this.props.genericConfigMode,
488
509
  isSnapshotMode: this.props.isSnapshotMode,
489
510
  holidays: this.props.holidays,
511
+ conditionalRulesEnabled:
512
+ !!this.props.displayConditions || !!this.props.onConditionalsOpen,
490
513
  };
491
514
 
492
515
  this.sendMessage('--config', JSON.parse(JSON.stringify(configData)));
@@ -494,6 +517,8 @@ export class UnlayerStore {
494
517
  this.onFormSelectCB?.(data?.formId);
495
518
  } else if (type === '--calc-field-open') {
496
519
  this.onCalcFieldOpenCB?.(data?.usedFormIds ?? []);
520
+ } else if (type === '--esign-offset-open') {
521
+ this.openESignOffsetRules(data);
497
522
  } else if (type === '--calc-field-select') {
498
523
  if (this.onCalcFieldSelectCB) {
499
524
  this.onCalcFieldSelectCB(data?.fieldKeys ?? []);
@@ -538,6 +563,22 @@ export class UnlayerStore {
538
563
  this.onMessageCB?.(type, data);
539
564
  };
540
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
+
541
582
  private updateFormFieldSamples = (formId: number, fields: FormFieldInfo[]) => {
542
583
  fields.forEach((field, index) => {
543
584
  const normalizedFieldId = String(field.id ?? '').replaceAll('-', '');
package/src/tools.ts CHANGED
@@ -1,4 +1,11 @@
1
- import { constGenericsEditor, UnlayerToolMetaData } from './shared/const';
1
+ import {
2
+ constGenericsEditor,
3
+ E_SIGN_DATE_SIGNED_OFFSET_TYPE,
4
+ E_SIGN_DATE_SIGNED_OFFSET_VALUES_KEY,
5
+ E_SIGN_TOOL_SLUG,
6
+ UnlayerToolMetaData,
7
+ } from './shared/const';
8
+ import { isESignOffsetSettings, normalizeESignOffsetSettings } from './shared/e-sign-offset';
2
9
  import { editIconSvg } from './shared/edit-icon';
3
10
  import { generateInstanceId, INSTANCE_ID_TOOL_SLUGS } from './shared/instance-id';
4
11
  import { unlayerToolsParseUnitKey } from './shared/tools';
@@ -253,6 +260,34 @@ export const unlayerToolsDedupeInstanceIds = (
253
260
  return hasChanges;
254
261
  };
255
262
 
263
+ /**
264
+ * Gives every "Date Signed Offset" e-sign placement valid settings. Unlayer never applies an
265
+ * option's default to placements saved before the option existed, and the backend rejects a
266
+ * design whose dateSignedOffset recipient has no settings.
267
+ */
268
+ export const unlayerToolsNormalizeESignOffsets = (design: UnlayerDesignFormat): boolean => {
269
+ let hasChanges = false;
270
+
271
+ unlayerToolsIterateCustom(design, tool => {
272
+ if (
273
+ tool.slug !== E_SIGN_TOOL_SLUG ||
274
+ tool.values?.eSignFieldType !== E_SIGN_DATE_SIGNED_OFFSET_TYPE
275
+ ) {
276
+ return;
277
+ }
278
+
279
+ const settings = tool.values[E_SIGN_DATE_SIGNED_OFFSET_VALUES_KEY];
280
+
281
+ if (!isESignOffsetSettings(settings)) {
282
+ tool.values[E_SIGN_DATE_SIGNED_OFFSET_VALUES_KEY] =
283
+ normalizeESignOffsetSettings(settings);
284
+ hasChanges = true;
285
+ }
286
+ });
287
+
288
+ return hasChanges;
289
+ };
290
+
256
291
  export const unlayerToolsBuildDesign = (tools: UnlayerDesignTool[]): UnlayerDesignFormat => {
257
292
  const design = unlayerGetDefaultDesign();
258
293