@servicetitan/dte-pdf-editor 1.41.0 → 1.43.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 (120) hide show
  1. package/README.md +89 -45
  2. package/dist/components/display-conditions/condition-group.d.ts +8 -3
  3. package/dist/components/display-conditions/condition-group.d.ts.map +1 -1
  4. package/dist/components/display-conditions/condition-group.js +7 -2
  5. package/dist/components/display-conditions/condition-group.js.map +1 -1
  6. package/dist/components/display-conditions/condition-groups-section.d.ts +8 -3
  7. package/dist/components/display-conditions/condition-groups-section.d.ts.map +1 -1
  8. package/dist/components/display-conditions/condition-groups-section.js +2 -2
  9. package/dist/components/display-conditions/condition-groups-section.js.map +1 -1
  10. package/dist/components/display-conditions/condition-row.d.ts +8 -3
  11. package/dist/components/display-conditions/condition-row.d.ts.map +1 -1
  12. package/dist/components/display-conditions/condition-row.js +154 -22
  13. package/dist/components/display-conditions/condition-row.js.map +1 -1
  14. package/dist/components/display-conditions/display-condition-modal.d.ts +6 -2
  15. package/dist/components/display-conditions/display-condition-modal.d.ts.map +1 -1
  16. package/dist/components/display-conditions/display-condition-modal.js +7 -7
  17. package/dist/components/display-conditions/display-condition-modal.js.map +1 -1
  18. package/dist/components/display-conditions/display-condition.d.ts +1 -1
  19. package/dist/components/display-conditions/display-condition.d.ts.map +1 -1
  20. package/dist/components/display-conditions/display-condition.js +2 -2
  21. package/dist/components/display-conditions/display-condition.js.map +1 -1
  22. package/dist/components/field-config-panel/field-config-panel-overlay.d.ts +6 -2
  23. package/dist/components/field-config-panel/field-config-panel-overlay.d.ts.map +1 -1
  24. package/dist/components/field-config-panel/field-config-panel-overlay.js +2 -2
  25. package/dist/components/field-config-panel/field-config-panel-overlay.js.map +1 -1
  26. package/dist/components/field-config-panel/field-config-panel.d.ts +6 -2
  27. package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
  28. package/dist/components/field-config-panel/field-config-panel.js +9 -4
  29. package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
  30. package/dist/components/field-config-panel/field-sidebar.d.ts +5 -1
  31. package/dist/components/field-config-panel/field-sidebar.d.ts.map +1 -1
  32. package/dist/components/field-config-panel/field-sidebar.js +42 -8
  33. package/dist/components/field-config-panel/field-sidebar.js.map +1 -1
  34. package/dist/components/field-config-panel/formula-generator.d.ts +5 -1
  35. package/dist/components/field-config-panel/formula-generator.d.ts.map +1 -1
  36. package/dist/components/field-config-panel/formula-generator.js +2 -2
  37. package/dist/components/field-config-panel/formula-generator.js.map +1 -1
  38. package/dist/components/field-config-panel/formula-modal.d.ts +5 -1
  39. package/dist/components/field-config-panel/formula-modal.d.ts.map +1 -1
  40. package/dist/components/field-config-panel/formula-modal.js +38 -8
  41. package/dist/components/field-config-panel/formula-modal.js.map +1 -1
  42. package/dist/components/field-sidebar/field-sidebar.d.ts +6 -1
  43. package/dist/components/field-sidebar/field-sidebar.d.ts.map +1 -1
  44. package/dist/components/field-sidebar/field-sidebar.js +11 -6
  45. package/dist/components/field-sidebar/field-sidebar.js.map +1 -1
  46. package/dist/components/field-sidebar/form-fields-type-list.d.ts +13 -0
  47. package/dist/components/field-sidebar/form-fields-type-list.d.ts.map +1 -0
  48. package/dist/components/field-sidebar/form-fields-type-list.js +14 -0
  49. package/dist/components/field-sidebar/form-fields-type-list.js.map +1 -0
  50. package/dist/components/pdf-editor/pdf-editor.d.ts +4 -1
  51. package/dist/components/pdf-editor/pdf-editor.d.ts.map +1 -1
  52. package/dist/components/pdf-editor/pdf-editor.js +6 -5
  53. package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
  54. package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts.map +1 -1
  55. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js +1 -1
  56. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
  57. package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
  58. package/dist/components/pdf-view/pdf-view.js +2 -1
  59. package/dist/components/pdf-view/pdf-view.js.map +1 -1
  60. package/dist/constants/menu-group.d.ts.map +1 -1
  61. package/dist/constants/menu-group.js +2 -0
  62. package/dist/constants/menu-group.js.map +1 -1
  63. package/dist/hooks/index.d.ts +1 -0
  64. package/dist/hooks/index.d.ts.map +1 -1
  65. package/dist/hooks/index.js +1 -0
  66. package/dist/hooks/index.js.map +1 -1
  67. package/dist/hooks/useFormFields.d.ts +13 -0
  68. package/dist/hooks/useFormFields.d.ts.map +1 -0
  69. package/dist/hooks/useFormFields.js +121 -0
  70. package/dist/hooks/useFormFields.js.map +1 -0
  71. package/dist/hooks/useFormulaEditor.d.ts.map +1 -1
  72. package/dist/hooks/useFormulaEditor.js +7 -1
  73. package/dist/hooks/useFormulaEditor.js.map +1 -1
  74. package/dist/hooks/usePdfFieldDnD.d.ts.map +1 -1
  75. package/dist/hooks/usePdfFieldDnD.js +4 -0
  76. package/dist/hooks/usePdfFieldDnD.js.map +1 -1
  77. package/dist/interface/types.d.ts +35 -3
  78. package/dist/interface/types.d.ts.map +1 -1
  79. package/dist/interface/types.js +1 -0
  80. package/dist/interface/types.js.map +1 -1
  81. package/dist/utils/formula/expression.utils.d.ts +5 -2
  82. package/dist/utils/formula/expression.utils.d.ts.map +1 -1
  83. package/dist/utils/formula/expression.utils.js +8 -6
  84. package/dist/utils/formula/expression.utils.js.map +1 -1
  85. package/dist/utils/formula/form-fields.utils.d.ts +21 -0
  86. package/dist/utils/formula/form-fields.utils.d.ts.map +1 -0
  87. package/dist/utils/formula/form-fields.utils.js +101 -0
  88. package/dist/utils/formula/form-fields.utils.js.map +1 -0
  89. package/dist/utils/formula/index.d.ts +1 -0
  90. package/dist/utils/formula/index.d.ts.map +1 -1
  91. package/dist/utils/formula/index.js +1 -0
  92. package/dist/utils/formula/index.js.map +1 -1
  93. package/package.json +1 -1
  94. package/src/components/display-conditions/condition-group.tsx +32 -5
  95. package/src/components/display-conditions/condition-groups-section.tsx +24 -4
  96. package/src/components/display-conditions/condition-row.tsx +359 -80
  97. package/src/components/display-conditions/display-condition-modal.tsx +39 -10
  98. package/src/components/display-conditions/display-condition.tsx +19 -2
  99. package/src/components/field-config-panel/field-config-panel-overlay.tsx +22 -2
  100. package/src/components/field-config-panel/field-config-panel.tsx +34 -10
  101. package/src/components/field-config-panel/field-sidebar.tsx +187 -41
  102. package/src/components/field-config-panel/formula-generator.tsx +14 -0
  103. package/src/components/field-config-panel/formula-modal.tsx +62 -5
  104. package/src/components/field-sidebar/field-sidebar.tsx +35 -4
  105. package/src/components/field-sidebar/form-fields-type-list.tsx +113 -0
  106. package/src/components/pdf-editor/pdf-editor.tsx +42 -25
  107. package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +3 -1
  108. package/src/components/pdf-view/pdf-view.tsx +2 -1
  109. package/src/constants/menu-group.ts +2 -0
  110. package/src/hooks/index.ts +1 -0
  111. package/src/hooks/useFormFields.ts +157 -0
  112. package/src/hooks/useFormulaEditor.ts +7 -1
  113. package/src/hooks/usePdfFieldDnD.ts +4 -0
  114. package/src/interface/types.ts +43 -4
  115. package/src/styles/field-type-list.css +1 -0
  116. package/src/styles/formula-modal.css +66 -8
  117. package/src/styles/variables.css +4 -0
  118. package/src/utils/formula/expression.utils.ts +24 -6
  119. package/src/utils/formula/form-fields.utils.ts +165 -0
  120. package/src/utils/formula/index.ts +1 -0
@@ -1,26 +1,41 @@
1
1
  import { Button, Flex, Text } from '@servicetitan/anvil2';
2
2
  import IconClose from '@servicetitan/anvil2/assets/icons/material/round/close.svg';
3
3
  import { FC } from 'react';
4
- import { PdfField, RecipientInfo, SchemaObject } from '../../interface/types';
4
+ import {
5
+ FormFieldsByFormIdI,
6
+ FormInfo,
7
+ PdfField,
8
+ RecipientInfo,
9
+ SchemaObject,
10
+ } from '../../interface/types';
5
11
  import { FieldConfigPanel } from './field-config-panel';
6
12
 
7
13
  interface FieldConfigPanelOverlayProps {
8
14
  dataModel?: SchemaObject;
9
- /** All fields on the document (e.g. for formula builder fillable section). */
10
15
  documentFields?: PdfField[];
16
+ formFieldsByFormId: FormFieldsByFormIdI;
17
+ forms: FormInfo[];
18
+ formulaLoadingFormId?: number | null;
11
19
  selectedField: PdfField;
12
20
  recipients?: RecipientInfo[];
21
+ hideConditionalLogic?: boolean;
13
22
  onFieldConfigChange(updates: Partial<PdfField>): void;
14
23
  onDeleteField(): void;
15
24
  onDeselectField?(): void;
25
+ onRequestFormFields(formId: number): void;
16
26
  }
17
27
 
18
28
  export const FieldConfigPanelOverlay: FC<FieldConfigPanelOverlayProps> = ({
19
29
  dataModel,
20
30
  documentFields = [],
31
+ formFieldsByFormId,
32
+ forms,
33
+ formulaLoadingFormId,
34
+ hideConditionalLogic,
21
35
  onDeleteField,
22
36
  onDeselectField,
23
37
  onFieldConfigChange,
38
+ onRequestFormFields,
24
39
  recipients,
25
40
  selectedField,
26
41
  }) => {
@@ -47,9 +62,14 @@ export const FieldConfigPanelOverlay: FC<FieldConfigPanelOverlayProps> = ({
47
62
  dataModel={dataModel}
48
63
  documentFields={documentFields}
49
64
  field={selectedField}
65
+ formFieldsByFormId={formFieldsByFormId}
66
+ forms={forms}
67
+ formulaLoadingFormId={formulaLoadingFormId}
50
68
  recipients={recipients}
51
69
  onFieldConfigChange={onFieldConfigChange}
52
70
  onDeleteField={onDeleteField}
71
+ hideConditionalLogic={hideConditionalLogic}
72
+ onRequestFormFields={onRequestFormFields}
53
73
  />
54
74
  </div>
55
75
  </Flex>
@@ -4,6 +4,8 @@ import { E_SIGN_FIELD_TYPE_OPTIONS } from '../../constants';
4
4
  import {
5
5
  ESignFieldType,
6
6
  FieldTypeEnum,
7
+ FormFieldsByFormIdI,
8
+ FormInfo,
7
9
  PdfField,
8
10
  RecipientInfo,
9
11
  SchemaObject,
@@ -16,19 +18,28 @@ import { TableConfigs } from './table-configs';
16
18
  interface FieldConfigPanelProps {
17
19
  field: PdfField;
18
20
  dataModel?: SchemaObject;
19
- /** All fields on the document (e.g., for a formula builder fillable section). */
20
21
  documentFields?: PdfField[];
22
+ formFieldsByFormId?: FormFieldsByFormIdI;
23
+ forms: FormInfo[];
24
+ formulaLoadingFormId?: number | null;
21
25
  recipients?: RecipientInfo[];
26
+ hideConditionalLogic?: boolean;
22
27
  onDeleteField(): void;
23
28
  onFieldConfigChange(updates: Partial<PdfField>): void;
29
+ onRequestFormFields(formId: number): void;
24
30
  }
25
31
 
26
32
  export const FieldConfigPanel: FC<FieldConfigPanelProps> = ({
27
33
  dataModel,
28
34
  documentFields = [],
29
35
  field,
36
+ formFieldsByFormId,
37
+ forms,
38
+ formulaLoadingFormId,
39
+ hideConditionalLogic = false,
30
40
  onDeleteField,
31
41
  onFieldConfigChange,
42
+ onRequestFormFields,
32
43
  recipients = [],
33
44
  }) => {
34
45
  const recipientOptions = useMemo(
@@ -77,9 +88,12 @@ export const FieldConfigPanel: FC<FieldConfigPanelProps> = ({
77
88
 
78
89
  return (
79
90
  <Flex direction="column" gap="4">
80
- {[FieldTypeEnum.fillable, FieldTypeEnum.eSign, FieldTypeEnum.calculated].includes(
81
- field.type,
82
- ) && (
91
+ {[
92
+ FieldTypeEnum.fillable,
93
+ FieldTypeEnum.eSign,
94
+ FieldTypeEnum.calculated,
95
+ FieldTypeEnum.forms,
96
+ ].includes(field.type) && (
83
97
  <TextField
84
98
  required={field.type === FieldTypeEnum.fillable}
85
99
  label="Label"
@@ -152,14 +166,18 @@ export const FieldConfigPanel: FC<FieldConfigPanelProps> = ({
152
166
  <FormulaGenerator
153
167
  dataModel={dataModel}
154
168
  documentFields={documentFields}
169
+ formFieldsByFormId={formFieldsByFormId}
170
+ forms={forms}
155
171
  formula={field.formula}
156
172
  formulaFormat={field.formulaFormat}
173
+ formulaLoadingFormId={formulaLoadingFormId}
157
174
  onFormulaChange={(formula, formulaFormat) =>
158
175
  onFieldConfigChange({
159
176
  formula,
160
177
  formulaFormat,
161
178
  })
162
179
  }
180
+ onRequestFormFields={onRequestFormFields}
163
181
  />
164
182
  )}
165
183
 
@@ -167,12 +185,18 @@ export const FieldConfigPanel: FC<FieldConfigPanelProps> = ({
167
185
  <TableConfigs field={field} onFieldConfigChange={onFieldConfigChange} />
168
186
  )}
169
187
 
170
- <DisplayCondition
171
- onSave={onFieldConfigChange}
172
- documentFields={documentFields}
173
- schema={dataModel}
174
- initialState={field.displayCondition ?? undefined}
175
- />
188
+ {!hideConditionalLogic && (
189
+ <DisplayCondition
190
+ onSave={onFieldConfigChange}
191
+ documentFields={documentFields}
192
+ formFieldsByFormId={formFieldsByFormId}
193
+ formFieldsLoadingFormId={formulaLoadingFormId}
194
+ forms={forms}
195
+ initialState={field.displayCondition ?? undefined}
196
+ onRequestFormFields={onRequestFormFields}
197
+ schema={dataModel}
198
+ />
199
+ )}
176
200
 
177
201
  <Textarea
178
202
  label="Description"
@@ -1,6 +1,9 @@
1
- import { Text } from '@servicetitan/anvil2';
2
- import { FC } from 'react';
3
- import { FieldTypeOption } from '../../interface/types';
1
+ import { Button, Flex, Text } from '@servicetitan/anvil2';
2
+ import IconArrowBack from '@servicetitan/anvil2/assets/icons/material/round/keyboard_arrow_left.svg';
3
+ import IconArrowForward from '@servicetitan/anvil2/assets/icons/material/round/keyboard_arrow_right.svg';
4
+ import { FC, Fragment, useMemo, useState } from 'react';
5
+ import { FieldTypeOption, FormFieldsByFormIdI, FormInfo } from '../../interface/types';
6
+ import { formFieldInfosToCalculationOptions, parseFormFieldKey } from '../../utils';
4
7
 
5
8
  interface FieldSidebarProps {
6
9
  fillableOptions: FieldTypeOption[];
@@ -9,34 +12,157 @@ interface FieldSidebarProps {
9
12
  onHover: (path: string) => void;
10
13
  onSelect: (path: string) => void;
11
14
  selectedPaths: Set<string>;
15
+ forms: FormInfo[];
16
+ formFieldsByFormId?: FormFieldsByFormIdI;
17
+ formulaLoadingFormId?: number | null;
18
+ onRequestFormFields(formId: number): void;
12
19
  }
13
20
 
14
21
  export const FieldSidebar: FC<FieldSidebarProps> = ({
15
22
  fillableOptions,
23
+ formFieldsByFormId = {},
24
+ forms,
25
+ formulaLoadingFormId = null,
16
26
  highlightElementPath,
17
27
  mergeTagOptions,
18
28
  onHover,
29
+ onRequestFormFields,
19
30
  onSelect,
20
31
  selectedPaths,
21
32
  }) => {
33
+ const [selectedFormId, setSelectedFormId] = useState<number | null>(null);
34
+
35
+ const onFormClick = (formId: number) => {
36
+ setSelectedFormId(formId);
37
+ const loaded = formFieldsByFormId[formId];
38
+ if (!loaded?.length) {
39
+ onRequestFormFields(formId);
40
+ }
41
+ };
42
+
43
+ const isFormFieldsLoading = selectedFormId != null && formulaLoadingFormId === selectedFormId;
44
+
45
+ const selectedForm = useMemo(
46
+ () => (forms ?? []).find(f => f.id === selectedFormId) ?? null,
47
+ [selectedFormId, forms],
48
+ );
49
+
50
+ const calcOptions = useMemo(() => {
51
+ return selectedFormId === null
52
+ ? []
53
+ : formFieldInfosToCalculationOptions(
54
+ selectedFormId,
55
+ formFieldsByFormId[selectedFormId] ?? [],
56
+ selectedForm?.name ?? '',
57
+ );
58
+ }, [selectedFormId, formFieldsByFormId, selectedForm?.name]);
59
+
60
+ const formIdsReferencedInFormula = useMemo(() => {
61
+ const ids = new Set<number>();
62
+ for (const path of selectedPaths) {
63
+ const parsed = parseFormFieldKey(path);
64
+ if (parsed) {
65
+ ids.add(parsed.formId);
66
+ }
67
+ }
68
+ return ids;
69
+ }, [selectedPaths]);
70
+
22
71
  return (
23
72
  <div className="dte-formula-sidebar">
24
- <FieldOptionList
25
- title="Fillable fields"
26
- options={fillableOptions}
27
- highlightElementPath={highlightElementPath}
28
- selectedPaths={selectedPaths}
29
- onHover={onHover}
30
- onSelect={onSelect}
31
- />
32
- <FieldOptionList
33
- title="Merge tags"
34
- options={mergeTagOptions}
35
- highlightElementPath={highlightElementPath}
36
- selectedPaths={selectedPaths}
37
- onHover={onHover}
38
- onSelect={onSelect}
39
- />
73
+ <div className="dte-formula-sidebar-slide-outer">
74
+ <div
75
+ className={`dte-formula-sidebar-slide-track${selectedFormId ? ' --detail' : ''}`}
76
+ >
77
+ <div className="dte-formula-sidebar-slide-panel">
78
+ {forms.length > 0 && (
79
+ <div className="dte-formula-sidebar-forms-heading">
80
+ <Text variant="headline" el="h6" size="small">
81
+ Forms
82
+ </Text>
83
+ <ul
84
+ className="dte-formula-field-list"
85
+ role="listbox"
86
+ aria-label="Forms"
87
+ >
88
+ {forms.map(form => (
89
+ <li
90
+ key={form.id}
91
+ role="option"
92
+ className={`dte-formula-field-list-item${formIdsReferencedInFormula.has(form.id) ? ' --selected' : ''}`}
93
+ onMouseDown={e => {
94
+ e.preventDefault();
95
+ onFormClick(form.id);
96
+ }}
97
+ >
98
+ <Flex
99
+ justifyContent="space-between"
100
+ alignItems="center"
101
+ >
102
+ <span
103
+ className="dte-text-ellipsis"
104
+ title={form.name}
105
+ >
106
+ {form.name}
107
+ </span>
108
+ <IconArrowForward />
109
+ </Flex>
110
+ </li>
111
+ ))}
112
+ </ul>
113
+ </div>
114
+ )}
115
+ <FieldOptionList
116
+ title="Fillable fields"
117
+ options={fillableOptions}
118
+ highlightElementPath={highlightElementPath}
119
+ selectedPaths={selectedPaths}
120
+ onHover={onHover}
121
+ onSelect={onSelect}
122
+ />
123
+ <FieldOptionList
124
+ title="Merge tags"
125
+ options={mergeTagOptions}
126
+ highlightElementPath={highlightElementPath}
127
+ selectedPaths={selectedPaths}
128
+ onHover={onHover}
129
+ onSelect={onSelect}
130
+ />
131
+ </div>
132
+ <div className="dte-formula-sidebar-slide-panel --form-fields">
133
+ {selectedFormId ? (
134
+ <Fragment>
135
+ <Button
136
+ style={{ width: '100%' }}
137
+ appearance="ghost"
138
+ size="small"
139
+ icon={IconArrowBack}
140
+ title={selectedForm?.name ?? 'Form fields'}
141
+ onClick={() => setSelectedFormId(null)}
142
+ >
143
+ <Text
144
+ size="medium"
145
+ className="dte-text-ellipsis"
146
+ style={{ fontWeight: 'bold', width: '100%' }}
147
+ >
148
+ {selectedForm?.name ?? 'Form fields'}
149
+ </Text>
150
+ </Button>
151
+
152
+ <FieldOptionList
153
+ title=""
154
+ isLoading={isFormFieldsLoading}
155
+ options={calcOptions}
156
+ highlightElementPath={highlightElementPath}
157
+ selectedPaths={selectedPaths}
158
+ onHover={onHover}
159
+ onSelect={onSelect}
160
+ />
161
+ </Fragment>
162
+ ) : null}
163
+ </div>
164
+ </div>
165
+ </div>
40
166
  </div>
41
167
  );
42
168
  };
@@ -48,10 +174,12 @@ interface FieldOptionListProps {
48
174
  options: FieldTypeOption[];
49
175
  selectedPaths: Set<string>;
50
176
  title: string;
177
+ isLoading?: boolean;
51
178
  }
52
179
 
53
180
  const FieldOptionList: FC<FieldOptionListProps> = ({
54
181
  highlightElementPath,
182
+ isLoading,
55
183
  onHover,
56
184
  onSelect,
57
185
  options,
@@ -64,28 +192,46 @@ const FieldOptionList: FC<FieldOptionListProps> = ({
64
192
 
65
193
  return (
66
194
  <div>
67
- <Text variant="headline" el="h6" size="small">
68
- {title}
69
- </Text>
70
- <ul className="dte-formula-field-list" role="listbox" aria-label={title}>
71
- {options.map(opt => (
72
- <li
73
- key={`${opt.path}-${opt.label}`}
74
- role="option"
75
- aria-selected={opt.path === highlightElementPath}
76
- className={`dte-formula-field-list-item ${opt.path === highlightElementPath ? '--highlight' : ''} ${selectedPaths.has(opt.path ?? '') ? '--selected' : ''}`}
77
- onMouseEnter={() => opt.path && onHover(opt.path)}
78
- onMouseDown={e => {
79
- e.preventDefault();
80
- if (opt.path) {
81
- onSelect(opt.path);
82
- }
83
- }}
84
- >
85
- {opt.label ?? opt.path}
86
- </li>
87
- ))}
88
- </ul>
195
+ {title ? (
196
+ <Text variant="headline" el="h6" size="small">
197
+ {title}
198
+ </Text>
199
+ ) : null}
200
+ {isLoading ? (
201
+ <Text size="small" subdued variant="body">
202
+ Loading fields…
203
+ </Text>
204
+ ) : options.length === 0 ? (
205
+ <Text size="small" subdued variant="body">
206
+ No fields on this form.
207
+ </Text>
208
+ ) : (
209
+ <ul
210
+ className="dte-formula-field-list"
211
+ role="listbox"
212
+ aria-label={title || 'Fields'}
213
+ >
214
+ {options.map(opt => (
215
+ <li
216
+ key={`${opt.path}-${opt.label}`}
217
+ role="option"
218
+ aria-selected={opt.path === highlightElementPath}
219
+ className={`dte-formula-field-list-item ${opt.path === highlightElementPath ? '--highlight' : ''} ${selectedPaths.has(opt.path ?? '') ? '--selected' : ''}`}
220
+ onMouseEnter={() => opt.path && onHover(opt.path)}
221
+ onMouseDown={e => {
222
+ e.preventDefault();
223
+ if (opt.path) {
224
+ onSelect(opt.path);
225
+ }
226
+ }}
227
+ >
228
+ <span className="dte-text-ellipsis" title={opt.label ?? opt.path}>
229
+ {opt.label ?? opt.path}
230
+ </span>
231
+ </li>
232
+ ))}
233
+ </ul>
234
+ )}
89
235
  </div>
90
236
  );
91
237
  };
@@ -5,6 +5,8 @@ import {
5
5
  CalculatedFieldFormat,
6
6
  FieldTypeEnum,
7
7
  FieldTypeOption,
8
+ FormFieldsByFormIdI,
9
+ FormInfo,
8
10
  PdfField,
9
11
  PdfFieldSubType,
10
12
  SchemaObject,
@@ -16,9 +18,13 @@ import { FormulaModal } from './formula-modal';
16
18
  export interface FormulaGeneratorProps {
17
19
  dataModel?: SchemaObject;
18
20
  documentFields?: PdfField[];
21
+ formFieldsByFormId?: FormFieldsByFormIdI;
22
+ forms: FormInfo[];
19
23
  formula?: StructuredFormula;
20
24
  formulaFormat?: CalculatedFieldFormat;
25
+ formulaLoadingFormId?: number | null;
21
26
  onFormulaChange?(formula: StructuredFormula, format: CalculatedFieldFormat): void;
27
+ onRequestFormFields(formId: number): void;
22
28
  }
23
29
 
24
30
  function getFillableFilteredBuySubTypeFields(
@@ -39,9 +45,13 @@ function getFillableFilteredBuySubTypeFields(
39
45
  export const FormulaGenerator: FC<FormulaGeneratorProps> = ({
40
46
  dataModel,
41
47
  documentFields = [],
48
+ formFieldsByFormId,
49
+ forms,
42
50
  formula,
43
51
  formulaFormat,
52
+ formulaLoadingFormId,
44
53
  onFormulaChange,
54
+ onRequestFormFields,
45
55
  }) => {
46
56
  const [modalOpen, setModalOpen] = useState(false);
47
57
  const dataModelGroups = useMemo(
@@ -79,7 +89,11 @@ export const FormulaGenerator: FC<FormulaGeneratorProps> = ({
79
89
  initialFormat={formulaFormat}
80
90
  dataModelGroups={dataModelGroups}
81
91
  fillableFieldsFromDocument={fillableFieldsFromDocument}
92
+ formFieldsByFormId={formFieldsByFormId}
93
+ forms={forms}
94
+ formulaLoadingFormId={formulaLoadingFormId}
82
95
  onClose={() => setModalOpen(false)}
96
+ onRequestFormFields={onRequestFormFields}
83
97
  onSave={handleSave}
84
98
  />
85
99
  )}
@@ -12,9 +12,12 @@ import {
12
12
  CalculatedFieldFormat,
13
13
  DataModelFieldGroup,
14
14
  FieldTypeOption,
15
+ FormFieldsByFormIdI,
16
+ FormInfo,
15
17
  StructuredFormula,
16
18
  } from '../../interface/types';
17
19
  import {
20
+ formFieldInfosToCalculationOptions,
18
21
  parseExpression,
19
22
  tokenizeExpression,
20
23
  tokensToExpression,
@@ -48,18 +51,41 @@ export interface FormulaModalProps {
48
51
  initialFormat?: CalculatedFieldFormat;
49
52
  dataModelGroups: DataModelFieldGroup[];
50
53
  fillableFieldsFromDocument?: FieldTypeOption[];
54
+ formFieldsByFormId?: FormFieldsByFormIdI;
55
+ forms: FormInfo[];
56
+ formulaLoadingFormId?: number | null;
51
57
  onClose(): void;
58
+ onRequestFormFields(formId: number): void;
52
59
  onSave(formula: StructuredFormula, format: CalculatedFieldFormat): void;
53
60
  }
54
61
 
55
62
  export const FormulaModal: FC<FormulaModalProps> = ({
56
63
  dataModelGroups,
57
64
  fillableFieldsFromDocument = [],
65
+ formFieldsByFormId = {},
66
+ forms,
67
+ formulaLoadingFormId,
58
68
  initialFormat,
59
69
  initialFormula,
60
70
  onClose,
71
+ onRequestFormFields,
61
72
  onSave,
62
73
  }) => {
74
+ const formCalculationOptions = useMemo((): FieldTypeOption[] => {
75
+ if (!forms?.length) {
76
+ return [];
77
+ }
78
+ const out: FieldTypeOption[] = [];
79
+ for (const form of forms) {
80
+ const list = formFieldsByFormId[form.id];
81
+ if (!list?.length) {
82
+ continue;
83
+ }
84
+ out.push(...formFieldInfosToCalculationOptions(form.id, list, form.name));
85
+ }
86
+ return out;
87
+ }, [forms, formFieldsByFormId]);
88
+
63
89
  const validPaths = useMemo(() => {
64
90
  const set = getValidPathsSet(dataModelGroups);
65
91
  for (const f of fillableFieldsFromDocument) {
@@ -67,8 +93,13 @@ export const FormulaModal: FC<FormulaModalProps> = ({
67
93
  set.add(f.path);
68
94
  }
69
95
  }
96
+ for (const f of formCalculationOptions) {
97
+ if (f.path) {
98
+ set.add(f.path);
99
+ }
100
+ }
70
101
  return set;
71
- }, [dataModelGroups, fillableFieldsFromDocument]);
102
+ }, [dataModelGroups, fillableFieldsFromDocument, formCalculationOptions]);
72
103
  const allFields = useMemo(() => getAllFields(dataModelGroups), [dataModelGroups]);
73
104
  const pathToLabel = useMemo(() => {
74
105
  const m = new Map<string, string>();
@@ -82,8 +113,13 @@ export const FormulaModal: FC<FormulaModalProps> = ({
82
113
  m.set(f.path, f.label ?? f.path);
83
114
  }
84
115
  }
116
+ for (const f of formCalculationOptions) {
117
+ if (f.path) {
118
+ m.set(f.path, f.label ?? f.path);
119
+ }
120
+ }
85
121
  return m;
86
- }, [allFields, fillableFieldsFromDocument]);
122
+ }, [allFields, fillableFieldsFromDocument, formCalculationOptions]);
87
123
 
88
124
  const knownDateFields = useMemo(() => {
89
125
  const set = new Set<string>();
@@ -97,8 +133,13 @@ export const FormulaModal: FC<FormulaModalProps> = ({
97
133
  set.add(f.path);
98
134
  }
99
135
  }
136
+ for (const f of formCalculationOptions) {
137
+ if (f.fieldType === 'date' && f.path) {
138
+ set.add(f.path);
139
+ }
140
+ }
100
141
  return set;
101
- }, [allFields, fillableFieldsFromDocument]);
142
+ }, [allFields, fillableFieldsFromDocument, formCalculationOptions]);
102
143
 
103
144
  const currentExpression = useMemo(() => tokensToExpression(initialFormula), [initialFormula]);
104
145
 
@@ -123,9 +164,21 @@ export const FormulaModal: FC<FormulaModalProps> = ({
123
164
  }
124
165
  }, [formulaEditor.expressionHasDateFields, format.resultType]);
125
166
 
167
+ const formParseContext = useMemo(
168
+ (): { forms: FormInfo[]; formFieldsByFormId: FormFieldsByFormIdI } | undefined =>
169
+ forms.length > 0 ? { forms, formFieldsByFormId } : undefined,
170
+ [forms, formFieldsByFormId],
171
+ );
172
+
126
173
  const parsedTokens = useMemo(
127
- () => parseExpression(formulaEditor.draftExpression, validPaths, knownDateFields),
128
- [formulaEditor.draftExpression, validPaths, knownDateFields],
174
+ () =>
175
+ parseExpression(
176
+ formulaEditor.draftExpression,
177
+ validPaths,
178
+ knownDateFields,
179
+ formParseContext,
180
+ ),
181
+ [formulaEditor.draftExpression, validPaths, knownDateFields, formParseContext],
129
182
  );
130
183
 
131
184
  const formulaValidation = useMemo(
@@ -174,9 +227,13 @@ export const FormulaModal: FC<FormulaModalProps> = ({
174
227
  >
175
228
  <FieldSidebar
176
229
  fillableOptions={fillableFieldsFromDocument}
230
+ formFieldsByFormId={formFieldsByFormId}
231
+ forms={forms}
232
+ formulaLoadingFormId={formulaLoadingFormId}
177
233
  mergeTagOptions={allFields}
178
234
  highlightElementPath={highlightElementPath}
179
235
  onHover={setHighlightElementPath}
236
+ onRequestFormFields={onRequestFormFields}
180
237
  onSelect={formulaEditor.insertField}
181
238
  selectedPaths={formulaEditor.selectedFieldPaths}
182
239
  />