@servicetitan/dte-pdf-editor 1.20.0 → 1.22.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 (121) hide show
  1. package/dist/components/display-conditions/condition-group.d.ts +11 -0
  2. package/dist/components/display-conditions/condition-group.d.ts.map +1 -0
  3. package/dist/components/display-conditions/condition-group.js +52 -0
  4. package/dist/components/display-conditions/condition-group.js.map +1 -0
  5. package/dist/components/display-conditions/condition-groups-section.d.ts +10 -0
  6. package/dist/components/display-conditions/condition-groups-section.d.ts.map +1 -0
  7. package/dist/components/display-conditions/condition-groups-section.js +14 -0
  8. package/dist/components/display-conditions/condition-groups-section.js.map +1 -0
  9. package/dist/components/display-conditions/condition-row.d.ts +10 -0
  10. package/dist/components/display-conditions/condition-row.d.ts.map +1 -0
  11. package/dist/components/display-conditions/condition-row.js +60 -0
  12. package/dist/components/display-conditions/condition-row.js.map +1 -0
  13. package/dist/components/display-conditions/display-condition-modal.d.ts +12 -0
  14. package/dist/components/display-conditions/display-condition-modal.d.ts.map +1 -0
  15. package/dist/components/display-conditions/display-condition-modal.js +89 -0
  16. package/dist/components/display-conditions/display-condition-modal.js.map +1 -0
  17. package/dist/components/display-conditions/display-condition.d.ts +9 -0
  18. package/dist/components/display-conditions/display-condition.d.ts.map +1 -0
  19. package/dist/components/display-conditions/display-condition.js +10 -0
  20. package/dist/components/display-conditions/display-condition.js.map +1 -0
  21. package/dist/components/field-config-panel/advanced-settings.d.ts.map +1 -1
  22. package/dist/components/field-config-panel/advanced-settings.js +10 -8
  23. package/dist/components/field-config-panel/advanced-settings.js.map +1 -1
  24. package/dist/components/field-config-panel/field-config-panel.d.ts +1 -1
  25. package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
  26. package/dist/components/field-config-panel/field-config-panel.js +4 -2
  27. package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
  28. package/dist/components/field-config-panel/formula-generator.d.ts.map +1 -1
  29. package/dist/components/field-config-panel/formula-generator.js +3 -20
  30. package/dist/components/field-config-panel/formula-generator.js.map +1 -1
  31. package/dist/components/field-config-panel/formula-modal.d.ts.map +1 -1
  32. package/dist/components/field-config-panel/formula-modal.js +9 -3
  33. package/dist/components/field-config-panel/formula-modal.js.map +1 -1
  34. package/dist/components/field-config-panel/formula-workspace.d.ts.map +1 -1
  35. package/dist/components/field-config-panel/formula-workspace.js +4 -3
  36. package/dist/components/field-config-panel/formula-workspace.js.map +1 -1
  37. package/dist/components/field-config-panel/result-type-selector.d.ts.map +1 -1
  38. package/dist/components/field-config-panel/result-type-selector.js +2 -2
  39. package/dist/components/field-config-panel/result-type-selector.js.map +1 -1
  40. package/dist/components/pdf-view/pdf-view-field-container.d.ts +2 -1
  41. package/dist/components/pdf-view/pdf-view-field-container.d.ts.map +1 -1
  42. package/dist/components/pdf-view/pdf-view-field-container.js +6 -2
  43. package/dist/components/pdf-view/pdf-view-field-container.js.map +1 -1
  44. package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
  45. package/dist/components/pdf-view/pdf-view.js +1 -1
  46. package/dist/components/pdf-view/pdf-view.js.map +1 -1
  47. package/dist/constants/calculated.constants.d.ts +2 -0
  48. package/dist/constants/calculated.constants.d.ts.map +1 -0
  49. package/dist/constants/calculated.constants.js +2 -0
  50. package/dist/constants/calculated.constants.js.map +1 -0
  51. package/dist/constants/conditions.constants.d.ts +6 -0
  52. package/dist/constants/conditions.constants.d.ts.map +1 -0
  53. package/dist/constants/conditions.constants.js +17 -0
  54. package/dist/constants/conditions.constants.js.map +1 -0
  55. package/dist/constants/index.d.ts +3 -0
  56. package/dist/constants/index.d.ts.map +1 -1
  57. package/dist/constants/index.js +3 -0
  58. package/dist/constants/index.js.map +1 -1
  59. package/dist/interface/types.d.ts +78 -1
  60. package/dist/interface/types.d.ts.map +1 -1
  61. package/dist/interface/types.js +25 -0
  62. package/dist/interface/types.js.map +1 -1
  63. package/dist/utils/conditions/evaluate.utils.d.ts +6 -0
  64. package/dist/utils/conditions/evaluate.utils.d.ts.map +1 -0
  65. package/dist/utils/conditions/evaluate.utils.js +95 -0
  66. package/dist/utils/conditions/evaluate.utils.js.map +1 -0
  67. package/dist/utils/conditions/index.d.ts +3 -0
  68. package/dist/utils/conditions/index.d.ts.map +1 -0
  69. package/dist/utils/conditions/index.js +3 -0
  70. package/dist/utils/conditions/index.js.map +1 -0
  71. package/dist/utils/conditions/schema-data-points.utils.d.ts +12 -0
  72. package/dist/utils/conditions/schema-data-points.utils.d.ts.map +1 -0
  73. package/dist/utils/conditions/schema-data-points.utils.js +67 -0
  74. package/dist/utils/conditions/schema-data-points.utils.js.map +1 -0
  75. package/dist/utils/data-model/extract-fields.utils.d.ts +1 -0
  76. package/dist/utils/data-model/extract-fields.utils.d.ts.map +1 -1
  77. package/dist/utils/data-model/extract-fields.utils.js +6 -3
  78. package/dist/utils/data-model/extract-fields.utils.js.map +1 -1
  79. package/dist/utils/formula/render-formula.utils.d.ts +0 -6
  80. package/dist/utils/formula/render-formula.utils.d.ts.map +1 -1
  81. package/dist/utils/formula/render-formula.utils.js +0 -17
  82. package/dist/utils/formula/render-formula.utils.js.map +1 -1
  83. package/dist/utils/index.d.ts +2 -0
  84. package/dist/utils/index.d.ts.map +1 -1
  85. package/dist/utils/index.js +2 -0
  86. package/dist/utils/index.js.map +1 -1
  87. package/dist/utils/shared/index.d.ts +2 -0
  88. package/dist/utils/shared/index.d.ts.map +1 -0
  89. package/dist/utils/shared/index.js +2 -0
  90. package/dist/utils/shared/index.js.map +1 -0
  91. package/dist/utils/shared/number.utils.d.ts +2 -0
  92. package/dist/utils/shared/number.utils.d.ts.map +1 -0
  93. package/dist/utils/shared/number.utils.js +12 -0
  94. package/dist/utils/shared/number.utils.js.map +1 -0
  95. package/package.json +1 -1
  96. package/src/components/display-conditions/condition-group.tsx +141 -0
  97. package/src/components/display-conditions/condition-groups-section.tsx +63 -0
  98. package/src/components/display-conditions/condition-row.tsx +182 -0
  99. package/src/components/display-conditions/display-condition-modal.tsx +180 -0
  100. package/src/components/display-conditions/display-condition.tsx +41 -0
  101. package/src/components/field-config-panel/advanced-settings.tsx +42 -46
  102. package/src/components/field-config-panel/field-config-panel.tsx +12 -2
  103. package/src/components/field-config-panel/formula-generator.tsx +9 -44
  104. package/src/components/field-config-panel/formula-modal.tsx +72 -82
  105. package/src/components/field-config-panel/formula-workspace.tsx +6 -5
  106. package/src/components/field-config-panel/result-type-selector.tsx +8 -11
  107. package/src/components/pdf-view/pdf-view-field-container.tsx +11 -2
  108. package/src/components/pdf-view/pdf-view.tsx +1 -0
  109. package/src/constants/calculated.constants.ts +1 -0
  110. package/src/constants/conditions.constants.ts +26 -0
  111. package/src/constants/index.ts +3 -0
  112. package/src/interface/types.ts +64 -1
  113. package/src/styles/formula-modal.css +1 -155
  114. package/src/utils/conditions/evaluate.utils.ts +134 -0
  115. package/src/utils/conditions/index.ts +2 -0
  116. package/src/utils/conditions/schema-data-points.utils.ts +93 -0
  117. package/src/utils/data-model/extract-fields.utils.ts +12 -7
  118. package/src/utils/formula/render-formula.utils.ts +0 -19
  119. package/src/utils/index.ts +2 -0
  120. package/src/utils/shared/index.ts +1 -0
  121. package/src/utils/shared/number.utils.ts +13 -0
@@ -0,0 +1,134 @@
1
+ import {
2
+ ConditionOperator,
3
+ DataModelValues,
4
+ DisplayConditionGroup,
5
+ DisplayConditionState,
6
+ VALUE_LESS_OPERATORS,
7
+ } from '../../interface/types';
8
+
9
+ function getValueAtPath(
10
+ data: DataModelValues | undefined,
11
+ path: string,
12
+ ): string | number | undefined {
13
+ if (!data || !path) {
14
+ return undefined;
15
+ }
16
+ const keys = path.split('.');
17
+ let value: unknown = data;
18
+ for (const key of keys) {
19
+ if (value === null || value === undefined || typeof value !== 'object') {
20
+ return undefined;
21
+ }
22
+ value = (value as Record<string, unknown>)[key];
23
+ }
24
+ if (value === null || value === undefined) {
25
+ return undefined;
26
+ }
27
+ if (typeof value === 'number' || typeof value === 'string') {
28
+ return value;
29
+ }
30
+ return String(value);
31
+ }
32
+
33
+ function evaluateSingleCondition(
34
+ dataPointKey: string,
35
+ operator: string,
36
+ value: string,
37
+ data: DataModelValues | undefined,
38
+ ): boolean {
39
+ const raw = getValueAtPath(data, dataPointKey);
40
+ const strVal = raw === undefined ? '' : String(raw);
41
+ const numVal = typeof raw === 'number' ? raw : Number(strVal);
42
+ const trimmedInput = value.trim();
43
+ const parsed = Number(trimmedInput);
44
+ const numInput = trimmedInput !== '' && Number.isFinite(parsed) ? parsed : 0;
45
+
46
+ switch (operator as ConditionOperator) {
47
+ case 'is_equal_to':
48
+ return strVal === trimmedInput;
49
+ case 'is_not_equal_to':
50
+ return strVal !== trimmedInput;
51
+ case 'contains':
52
+ return strVal.includes(trimmedInput);
53
+ case 'does_not_contain':
54
+ return !strVal.includes(trimmedInput);
55
+ case 'starts_with':
56
+ return strVal.startsWith(trimmedInput);
57
+ case 'ends_with':
58
+ return strVal.endsWith(trimmedInput);
59
+ case 'is_empty':
60
+ return strVal === '' || strVal.length === 0;
61
+ case 'is_not_empty':
62
+ return strVal !== '' && strVal.length > 0;
63
+ case 'num_eq':
64
+ return numVal === numInput;
65
+ case 'num_neq':
66
+ return numVal !== numInput;
67
+ case 'num_gt':
68
+ return numVal > numInput;
69
+ case 'num_lt':
70
+ return numVal < numInput;
71
+ case 'num_gte':
72
+ return numVal >= numInput;
73
+ case 'num_lte':
74
+ return numVal <= numInput;
75
+ default:
76
+ return strVal === trimmedInput;
77
+ }
78
+ }
79
+
80
+ function evaluateGroup(group: DisplayConditionGroup, data: DataModelValues | undefined): boolean {
81
+ const valid = group.conditions.filter(
82
+ c =>
83
+ c.dataPointKey &&
84
+ (VALUE_LESS_OPERATORS.includes(c.operator as ConditionOperator) ||
85
+ c.value.trim() !== ''),
86
+ );
87
+ if (valid.length === 0) {
88
+ return false;
89
+ }
90
+ let result = evaluateSingleCondition(
91
+ valid[0].dataPointKey,
92
+ valid[0].operator,
93
+ valid[0].value,
94
+ data,
95
+ );
96
+ for (let i = 1; i < valid.length; i++) {
97
+ const op = valid[i].logicalOperator === 'or' ? 'or' : 'and';
98
+ const next = evaluateSingleCondition(
99
+ valid[i].dataPointKey,
100
+ valid[i].operator,
101
+ valid[i].value,
102
+ data,
103
+ );
104
+ result = op === 'or' ? result || next : result && next;
105
+ }
106
+ return result;
107
+ }
108
+
109
+ /**
110
+ * Evaluate display condition state against data. Returns true if the field should be visible.
111
+ */
112
+ export function evaluateDisplayCondition(
113
+ state: DisplayConditionState,
114
+ data: DataModelValues | undefined,
115
+ ): boolean {
116
+ const validGroups = state.groups.filter(g =>
117
+ g.conditions.some(
118
+ c =>
119
+ c.dataPointKey &&
120
+ (VALUE_LESS_OPERATORS.includes(c.operator as ConditionOperator) ||
121
+ c.value.trim() !== ''),
122
+ ),
123
+ );
124
+ if (validGroups.length === 0) {
125
+ return true;
126
+ }
127
+ let result = evaluateGroup(validGroups[0], data);
128
+ for (let i = 1; i < validGroups.length; i++) {
129
+ const op = validGroups[i].logicalOperator === 'or' ? 'or' : 'and';
130
+ const next = evaluateGroup(validGroups[i], data);
131
+ result = op === 'or' ? result || next : result && next;
132
+ }
133
+ return state.behavior === 'hide' ? !result : result;
134
+ }
@@ -0,0 +1,2 @@
1
+ export * from './evaluate.utils';
2
+ export * from './schema-data-points.utils';
@@ -0,0 +1,93 @@
1
+ import {
2
+ DataPointOption,
3
+ FieldTypeEnum,
4
+ type PdfField,
5
+ type SchemaNode,
6
+ type SchemaObject,
7
+ } from '../../interface/types';
8
+
9
+ function isUseInConditionals(
10
+ node:
11
+ | { options?: { useInConditionals?: boolean; useInCalculatedFields?: boolean } }
12
+ | undefined,
13
+ ): boolean {
14
+ return (
15
+ node?.options?.useInConditionals === true || node?.options?.useInCalculatedFields === true
16
+ );
17
+ }
18
+
19
+ function isValueNode(
20
+ node: SchemaNode,
21
+ ): node is { type: 'string' | 'number'; title?: string; options?: object } {
22
+ return node?.type === 'string' || node?.type === 'number';
23
+ }
24
+
25
+ function walkSchema(
26
+ schema: SchemaObject | undefined,
27
+ prefix: string,
28
+ parentTitles: string[],
29
+ ): DataPointOption[] {
30
+ if (!schema?.properties) {
31
+ return [];
32
+ }
33
+ const result: DataPointOption[] = [];
34
+ for (const [key, node] of Object.entries(schema.properties)) {
35
+ const fullKey = prefix ? `${prefix}.${key}` : key;
36
+ const title = (node as { title?: string }).title ?? key;
37
+ const fullTitle = [...parentTitles, title].join(' - ');
38
+
39
+ if (node?.type === 'object') {
40
+ result.push(...walkSchema(node as SchemaObject, fullKey, [...parentTitles, title]));
41
+ } else if (isValueNode(node) && isUseInConditionals(node)) {
42
+ result.push({
43
+ fieldType: node.type === 'number' ? 'number' : 'string',
44
+ fullKey,
45
+ title: fullTitle || fullKey,
46
+ });
47
+ }
48
+ }
49
+ return result;
50
+ }
51
+
52
+ export function getSchemaDataPointOptions(schema: SchemaObject | undefined): DataPointOption[] {
53
+ const options = walkSchema(schema, '', []);
54
+ return options.sort((a, b) => a.title.localeCompare(b.title));
55
+ }
56
+
57
+ /**
58
+ * Data point options from document fields: fillable only (no calculated fields).
59
+ * Uses field.path as fullKey so condition evaluation can read from the same data object.
60
+ */
61
+ export function getDocumentFieldsDataPointOptions(
62
+ fields: PdfField[] | undefined,
63
+ ): DataPointOption[] {
64
+ if (!fields?.length) {
65
+ return [];
66
+ }
67
+ const result: DataPointOption[] = [];
68
+ for (const f of fields) {
69
+ if (!f.path || f.type !== FieldTypeEnum.fillable) {
70
+ continue;
71
+ }
72
+ const fieldType = f.subType === 'number' ? 'number' : 'string';
73
+ result.push({
74
+ fieldType,
75
+ fullKey: f.path,
76
+ title: f.label || f.path,
77
+ });
78
+ }
79
+ return result.sort((a, b) => a.title.localeCompare(b.title));
80
+ }
81
+
82
+ /**
83
+ * Combined data point options: schema (data model) + fillable fields.
84
+ */
85
+ export function getDataPointOptions(
86
+ schema: SchemaObject | undefined,
87
+ documentFields: PdfField[] | undefined,
88
+ ): DataPointOption[] {
89
+ const schemaOptions = getSchemaDataPointOptions(schema);
90
+ const documentOptions = getDocumentFieldsDataPointOptions(documentFields);
91
+ const combined = [...schemaOptions, ...documentOptions];
92
+ return combined.sort((a, b) => a.title.localeCompare(b.title));
93
+ }
@@ -4,6 +4,7 @@ import {
4
4
  FieldTypeOption,
5
5
  SchemaNode,
6
6
  SchemaObject,
7
+ SchemaSimpleDate,
7
8
  SchemaSimpleNumber,
8
9
  } from '../../interface/types';
9
10
 
@@ -11,6 +12,10 @@ function isSchemaSimpleNumber(node: SchemaNode): node is SchemaSimpleNumber {
11
12
  return node.type === 'number';
12
13
  }
13
14
 
15
+ function isSchemaSimpleDate(node: SchemaNode): node is SchemaSimpleDate {
16
+ return node.type === 'date';
17
+ }
18
+
14
19
  function useInCalculatedFields(node: SchemaNode): boolean {
15
20
  return node.options?.useInCalculatedFields === true;
16
21
  }
@@ -18,6 +23,7 @@ function useInCalculatedFields(node: SchemaNode): boolean {
18
23
  export interface ExtractGroupedFieldsOptions {
19
24
  /** When true, only include fields with SchemaFieldBaseOptions.useInCalculatedFields === true */
20
25
  onlyUseInCalculatedFields?: boolean;
26
+ onlyUseInConditionals?: boolean;
21
27
  }
22
28
 
23
29
  /**
@@ -64,13 +70,13 @@ export const extractGroupedFieldsFromDataModel = (
64
70
  const rawType = (property as { type?: string }).type;
65
71
  const isLeaf =
66
72
  property.type === 'string' ||
67
- isSchemaSimpleNumber(property as SchemaNode) ||
73
+ isSchemaSimpleNumber(property) ||
74
+ isSchemaSimpleDate(property) ||
68
75
  rawType === 'boolean';
69
- const includeField =
70
- !onlyUseInCalculatedFields || useInCalculatedFields(property as SchemaNode);
76
+ const includeField = !onlyUseInCalculatedFields || useInCalculatedFields(property);
71
77
  if (isLeaf && includeField) {
72
78
  topLevelFields.push({
73
- label: (property as SchemaNode).title ?? key,
79
+ label: property.title ?? key,
74
80
  type: FieldTypeEnum.dataModel,
75
81
  path: key,
76
82
  });
@@ -98,14 +104,13 @@ const extractFieldsRecursive = (
98
104
  ): void => {
99
105
  Object.keys(properties).forEach(fieldKey => {
100
106
  const fieldProperty = properties[fieldKey];
101
- const currentPath = basePath.includes('[]')
102
- ? `${basePath}.${fieldKey}`
103
- : `${basePath}.${fieldKey}`;
107
+ const currentPath = `${basePath}.${fieldKey}`;
104
108
 
105
109
  const rawType = (fieldProperty as { type?: string }).type;
106
110
  const isLeaf =
107
111
  fieldProperty.type === 'string' ||
108
112
  isSchemaSimpleNumber(fieldProperty) ||
113
+ isSchemaSimpleDate(fieldProperty) ||
109
114
  rawType === 'boolean';
110
115
  const includeField = !onlyUseInCalculatedFields || useInCalculatedFields(fieldProperty);
111
116
  if (isLeaf && includeField) {
@@ -1,25 +1,6 @@
1
- import { FormulaToken } from '../../interface/types';
2
1
  import { escapeHtml } from './dom.utils';
3
2
  import { tokenizeExpression } from './expression.utils';
4
3
 
5
- /**
6
- * Renders formula tokens to HTML for the read-only preview (e.g. formula box).
7
- * Uses the same token classes as the modal (dte-formula-token, dte-formula-token-field, etc.).
8
- */
9
- export function renderFormulaPreviewHtml(tokens: FormulaToken[]): string {
10
- if (!tokens?.length) {
11
- return '';
12
- }
13
- return tokens
14
- .map(token => {
15
- const type = token.type;
16
- const text = type === 'field' ? token.label : (token as { value: string }).value;
17
- const escaped = escapeHtml(text).replace(/ /g, '&nbsp;');
18
- return `<span class="dte-formula-token dte-formula-token-${type}">${escaped}</span>`;
19
- })
20
- .join('');
21
- }
22
-
23
4
  export function renderFormulaHtml(expression: string, labelMap: Map<string, string>): string {
24
5
  if (!expression.trim()) {
25
6
  return '';
@@ -4,3 +4,5 @@ export * from './data-model';
4
4
  export * from './formula';
5
5
  export * from './path';
6
6
  export * from './recipients';
7
+ export * from './conditions';
8
+ export * from './shared';
@@ -0,0 +1 @@
1
+ export * from './number.utils';
@@ -0,0 +1,13 @@
1
+ export function isValidNumber(value: string | number): boolean {
2
+ if (typeof value === 'number') {
3
+ return Number.isFinite(value);
4
+ }
5
+
6
+ const trimmed = value.trim();
7
+ if (trimmed === '') {
8
+ return false;
9
+ }
10
+
11
+ const parsed = Number(trimmed);
12
+ return Number.isFinite(parsed);
13
+ }