@servicetitan/dte-pdf-editor 1.92.1 → 1.92.3

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 (43) hide show
  1. package/dist/components/field-config-panel/field-sidebar.d.ts.map +1 -1
  2. package/dist/components/field-config-panel/field-sidebar.js +8 -4
  3. package/dist/components/field-config-panel/field-sidebar.js.map +1 -1
  4. package/dist/components/field-sidebar/data-model-field-type-list.d.ts +15 -1
  5. package/dist/components/field-sidebar/data-model-field-type-list.d.ts.map +1 -1
  6. package/dist/components/field-sidebar/data-model-field-type-list.js +54 -10
  7. package/dist/components/field-sidebar/data-model-field-type-list.js.map +1 -1
  8. package/dist/components/field-sidebar/field-sidebar.d.ts +7 -0
  9. package/dist/components/field-sidebar/field-sidebar.d.ts.map +1 -1
  10. package/dist/components/field-sidebar/field-sidebar.js +8 -11
  11. package/dist/components/field-sidebar/field-sidebar.js.map +1 -1
  12. package/dist/components/field-sidebar/form-fields-type-list.d.ts +28 -5
  13. package/dist/components/field-sidebar/form-fields-type-list.d.ts.map +1 -1
  14. package/dist/components/field-sidebar/form-fields-type-list.js +27 -6
  15. package/dist/components/field-sidebar/form-fields-type-list.js.map +1 -1
  16. package/dist/components/pdf-editor/pdf-editor.d.ts.map +1 -1
  17. package/dist/components/pdf-editor/pdf-editor.js +3 -2
  18. package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
  19. package/dist/components/pdf-view/pdf-view.js +1 -1
  20. package/dist/components/pdf-view/pdf-view.js.map +1 -1
  21. package/dist/constants/menu-group.d.ts +5 -0
  22. package/dist/constants/menu-group.d.ts.map +1 -1
  23. package/dist/constants/menu-group.js +5 -2
  24. package/dist/constants/menu-group.js.map +1 -1
  25. package/dist/hooks/useFormFields.d.ts +3 -1
  26. package/dist/hooks/useFormFields.d.ts.map +1 -1
  27. package/dist/hooks/useFormFields.js +20 -3
  28. package/dist/hooks/useFormFields.js.map +1 -1
  29. package/dist/styles/field-type.css +5 -0
  30. package/dist/styles/formula-modal.css +17 -0
  31. package/dist/styles/pdf-canvas.css +5 -5
  32. package/package.json +1 -1
  33. package/src/components/field-config-panel/field-sidebar.tsx +24 -8
  34. package/src/components/field-sidebar/data-model-field-type-list.tsx +143 -29
  35. package/src/components/field-sidebar/field-sidebar.tsx +19 -18
  36. package/src/components/field-sidebar/form-fields-type-list.tsx +137 -95
  37. package/src/components/pdf-editor/pdf-editor.tsx +4 -3
  38. package/src/components/pdf-view/pdf-view.tsx +1 -1
  39. package/src/constants/menu-group.ts +5 -2
  40. package/src/hooks/useFormFields.ts +24 -2
  41. package/src/styles/field-type.css +5 -0
  42. package/src/styles/formula-modal.css +17 -0
  43. package/src/styles/pdf-canvas.css +5 -5
@@ -1,10 +1,14 @@
1
1
  import IconBorderColor from '@servicetitan/anvil2/assets/icons/material/round/border_color.svg';
2
2
  import IconCalculate from '@servicetitan/anvil2/assets/icons/material/round/calculate.svg';
3
- import IconFactCheck from '@servicetitan/anvil2/assets/icons/material/round/fact_check.svg';
4
3
  import IconPhotoSizeSelectSmall from '@servicetitan/anvil2/assets/icons/material/round/photo_size_select_small.svg';
5
4
  import IconCommercial from '@servicetitan/anvil2/assets/icons/st/commercial.svg';
6
5
  import IconEstimate from '@servicetitan/anvil2/assets/icons/st/estimate.svg';
7
6
  import { FieldTypeEnum } from '../interface/types';
7
+ /**
8
+ * Sidebar tabs. Forms (`FieldTypeEnum.forms`) has no tab of its own: form fields are offered as a
9
+ * group inside Merge Tags, next to the data model groups. `hideFields: [FieldTypeEnum.forms]` still
10
+ * hides that group.
11
+ */
8
12
  export const menuGroups = [
9
13
  { svgIcon: IconEstimate, label: 'Merge Tags', key: FieldTypeEnum.dataModel },
10
14
  { svgIcon: IconBorderColor, label: 'E-Sign', key: FieldTypeEnum.eSign },
@@ -23,6 +27,5 @@ export const menuGroups = [
23
27
  label: 'Generic Fields',
24
28
  key: FieldTypeEnum.generic,
25
29
  },
26
- { svgIcon: IconFactCheck, label: 'Forms', key: FieldTypeEnum.forms },
27
30
  ];
28
31
  //# sourceMappingURL=menu-group.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"menu-group.js","sourceRoot":"","sources":["../../src/constants/menu-group.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mEAAmE,CAAC;AAChG,OAAO,aAAa,MAAM,gEAAgE,CAAC;AAC3F,OAAO,aAAa,MAAM,iEAAiE,CAAC;AAC5F,OAAO,wBAAwB,MAAM,8EAA8E,CAAC;AACpH,OAAO,cAAc,MAAM,qDAAqD,CAAC;AACjF,OAAO,YAAY,MAAM,mDAAmD,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAQnD,MAAM,CAAC,MAAM,UAAU,GAAqB;IACxC,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,CAAC,SAAS,EAAE;IAC5E,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,CAAC,KAAK,EAAE;IACvE;QACI,OAAO,EAAE,wBAAwB;QACjC,KAAK,EAAE,iBAAiB;QACxB,GAAG,EAAE,aAAa,CAAC,QAAQ;KAC9B;IACD;QACI,OAAO,EAAE,aAAa;QACtB,KAAK,EAAE,mBAAmB;QAC1B,GAAG,EAAE,aAAa,CAAC,UAAU;KAChC;IACD;QACI,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,gBAAgB;QACvB,GAAG,EAAE,aAAa,CAAC,OAAO;KAC7B;IACD,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,CAAC,KAAK,EAAE;CACvE,CAAC"}
1
+ {"version":3,"file":"menu-group.js","sourceRoot":"","sources":["../../src/constants/menu-group.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mEAAmE,CAAC;AAChG,OAAO,aAAa,MAAM,gEAAgE,CAAC;AAC3F,OAAO,wBAAwB,MAAM,8EAA8E,CAAC;AACpH,OAAO,cAAc,MAAM,qDAAqD,CAAC;AACjF,OAAO,YAAY,MAAM,mDAAmD,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAQnD;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAqB;IACxC,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,CAAC,SAAS,EAAE;IAC5E,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,CAAC,KAAK,EAAE;IACvE;QACI,OAAO,EAAE,wBAAwB;QACjC,KAAK,EAAE,iBAAiB;QACxB,GAAG,EAAE,aAAa,CAAC,QAAQ;KAC9B;IACD;QACI,OAAO,EAAE,aAAa;QACtB,KAAK,EAAE,mBAAmB;QAC1B,GAAG,EAAE,aAAa,CAAC,UAAU;KAChC;IACD;QACI,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,gBAAgB;QACvB,GAAG,EAAE,aAAa,CAAC,OAAO;KAC7B;CACJ,CAAC"}
@@ -1,6 +1,8 @@
1
1
  import { type FieldTypeOption, type FormFieldsByFormIdI, type FormInfo, type PdfField } from '../interface/types';
2
2
  interface UseFormFieldsResult {
3
3
  isLoading: boolean;
4
+ isLoadingForms: boolean;
5
+ formsWithFields: FormInfo[];
4
6
  formFieldsByFormId: FormFieldsByFormIdI;
5
7
  formulaLoadingFormId: null | number;
6
8
  formFieldOptions: FieldTypeOption[];
@@ -8,6 +10,6 @@ interface UseFormFieldsResult {
8
10
  handleSelectForm: (form: FormInfo | null) => void;
9
11
  requestFormFieldsById: (formId: number) => void;
10
12
  }
11
- export declare const useFormFields: (fields: PdfField[], onFormSelect?: (formIds: number[], sendFormFields: (formFieldsByFormId: FormFieldsByFormIdI) => void) => void) => UseFormFieldsResult;
13
+ export declare const useFormFields: (fields: PdfField[], onFormSelect?: (formIds: number[], sendFormFields: (formFieldsByFormId: FormFieldsByFormIdI) => void) => void, forms?: FormInfo[]) => UseFormFieldsResult;
12
14
  export {};
13
15
  //# sourceMappingURL=useFormFields.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useFormFields.d.ts","sourceRoot":"","sources":["../../src/hooks/useFormFields.ts"],"names":[],"mappings":"AACA,OAAO,EAEH,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,QAAQ,EACb,KAAK,QAAQ,EAChB,MAAM,oBAAoB,CAAC;AAG5B,UAAU,mBAAmB;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,kBAAkB,EAAE,mBAAmB,CAAC;IACxC,oBAAoB,EAAE,IAAI,GAAG,MAAM,CAAC;IACpC,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,YAAY,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;IAClD,qBAAqB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD;AAED,eAAO,MAAM,aAAa,GACtB,QAAQ,QAAQ,EAAE,EAClB,eAAe,CACX,OAAO,EAAE,MAAM,EAAE,EACjB,cAAc,EAAE,CAAC,kBAAkB,EAAE,mBAAmB,KAAK,IAAI,KAChE,IAAI,KACV,mBAkIF,CAAC"}
1
+ {"version":3,"file":"useFormFields.d.ts","sourceRoot":"","sources":["../../src/hooks/useFormFields.ts"],"names":[],"mappings":"AACA,OAAO,EAEH,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,QAAQ,EACb,KAAK,QAAQ,EAChB,MAAM,oBAAoB,CAAC;AAG5B,UAAU,mBAAmB;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,QAAQ,EAAE,CAAC;IAC5B,kBAAkB,EAAE,mBAAmB,CAAC;IACxC,oBAAoB,EAAE,IAAI,GAAG,MAAM,CAAC;IACpC,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,YAAY,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;IAClD,qBAAqB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD;AAID,eAAO,MAAM,aAAa,GACtB,QAAQ,QAAQ,EAAE,EAClB,eAAe,CACX,OAAO,EAAE,MAAM,EAAE,EACjB,cAAc,EAAE,CAAC,kBAAkB,EAAE,mBAAmB,KAAK,IAAI,KAChE,IAAI,EACT,QAAO,QAAQ,EAAgB,KAChC,mBAmJF,CAAC"}
@@ -1,7 +1,8 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { FieldTypeEnum, } from '../interface/types';
3
3
  import { formFieldInfoToFieldTypeOption } from '../utils';
4
- export const useFormFields = (fields, onFormSelect) => {
4
+ const EMPTY_FORMS = [];
5
+ export const useFormFields = (fields, onFormSelect, forms = EMPTY_FORMS) => {
5
6
  const [formFieldsByFormId, setFormFieldsByFormId] = useState({});
6
7
  const [formulaLoadingFormId, setFormulaLoadingFormId] = useState(null);
7
8
  const [selectedForm, setSelectedForm] = useState(null);
@@ -12,7 +13,13 @@ export const useFormFields = (fields, onFormSelect) => {
12
13
  if (!onFormSelect) {
13
14
  return;
14
15
  }
15
- const initialFormIds = new Set();
16
+ /*
17
+ * Every form is asked for up front, not just the ones a formula already references: a form
18
+ * with no fields has nothing to offer and is dropped from the list (`formsWithFields`), and
19
+ * that cannot be decided without its fields. They go out as one batched call, and the reply
20
+ * is cached, so opening a form afterwards costs nothing.
21
+ */
22
+ const initialFormIds = new Set(forms.map(form => form.id));
16
23
  fields.forEach(field => {
17
24
  var _a;
18
25
  if (field.type === FieldTypeEnum.calculated) {
@@ -60,7 +67,7 @@ export const useFormFields = (fields, onFormSelect) => {
60
67
  cancelled = true;
61
68
  cancelAnimationFrame(rafId);
62
69
  };
63
- }, [fields, onFormSelect]);
70
+ }, [fields, forms, onFormSelect]);
64
71
  const formFieldOptions = useMemo(() => {
65
72
  if (!selectedForm) {
66
73
  return [];
@@ -107,9 +114,19 @@ export const useFormFields = (fields, onFormSelect) => {
107
114
  setFormulaLoadingFormId(null);
108
115
  });
109
116
  }, [formFieldsByFormId, onFormSelect]);
117
+ /*
118
+ * A form is offered only once it is known to have something to drag out of it. Until its reply
119
+ * lands it is neither listed nor counted as empty — `isLoadingForms` stands in for it.
120
+ */
121
+ const formsWithFields = useMemo(() => {
122
+ return forms.filter(form => { var _a; return !!((_a = formFieldsByFormId[form.id]) === null || _a === void 0 ? void 0 : _a.length); });
123
+ }, [forms, formFieldsByFormId]);
110
124
  const isLoading = selectedForm != null && formFieldsLoadingFormIds[selectedForm.id];
125
+ const isLoadingForms = forms.some(form => !!formFieldsLoadingFormIds[form.id]);
111
126
  return {
112
127
  isLoading,
128
+ isLoadingForms,
129
+ formsWithFields,
113
130
  formFieldsByFormId,
114
131
  formulaLoadingFormId,
115
132
  formFieldOptions,
@@ -1 +1 @@
1
- {"version":3,"file":"useFormFields.js","sourceRoot":"","sources":["../../src/hooks/useFormFields.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useFormFields.js","sourceRoot":"","sources":["../../src/hooks/useFormFields.ts"],"names":[],"mappings":"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"}
@@ -16,6 +16,11 @@
16
16
  box-shadow: 0 0 2px 0 var(--border-color-active);
17
17
  }
18
18
 
19
+ .dte-field-type-item.--skeleton {
20
+ cursor: default;
21
+ pointer-events: none;
22
+ }
23
+
19
24
  .dte-field-type-items-wrapper {
20
25
  &.--grid-2 {
21
26
  display: grid;
@@ -69,6 +69,18 @@
69
69
  gap: 0;
70
70
  }
71
71
 
72
+ /*
73
+ * The panels share one scroller, so the list — always the taller of the two — would otherwise keep
74
+ * driving the scroll height while the fields panel is shown, leaving the fields followed by a
75
+ * screenful of nothing. Both bounds are needed: the min-content floor above keeps a max-height on
76
+ * its own from taking effect.
77
+ */
78
+ .dte-formula-sidebar-slide-track.--detail .dte-formula-sidebar-slide-panel:not(.--form-fields) {
79
+ min-height: 0;
80
+ max-height: 0;
81
+ overflow: hidden;
82
+ }
83
+
72
84
  .dte-formula-sidebar-forms-heading {
73
85
  flex-shrink: 0;
74
86
  }
@@ -94,6 +106,11 @@
94
106
  white-space: nowrap;
95
107
  }
96
108
 
109
+ .dte-formula-field-list-item.--skeleton {
110
+ cursor: default;
111
+ pointer-events: none;
112
+ }
113
+
97
114
  .dte-formula-field-list-item:hover,
98
115
  .dte-formula-field-list-item.--highlight {
99
116
  color: var(--color-neutral-100, #e8e8e8);
@@ -20,7 +20,7 @@
20
20
  background-color: var(--bg-color-light, #ffffff);
21
21
  border: 1px solid var(--border-color, #e0e0e0);
22
22
  border-radius: var(--radius-2, 8px);
23
- position: fixed;
23
+ position: absolute;
24
24
  z-index: 2;
25
25
 
26
26
  &.--vertical {
@@ -28,14 +28,14 @@
28
28
  }
29
29
 
30
30
  &.--right-center {
31
- right: 24px;
31
+ right: 0;
32
32
  top: 50%;
33
- transform: translateY(-50%);
33
+ transform: translate(100%, -50%);
34
34
  }
35
35
 
36
36
  &.--right-bottom {
37
- right: 24px;
38
- bottom: 24px;
37
+ bottom: 12px;
38
+ right: 12px;
39
39
  }
40
40
  }
41
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicetitan/dte-pdf-editor",
3
- "version": "1.92.1",
3
+ "version": "1.92.3",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "typings": "./dist/index.d.ts",
@@ -1,4 +1,4 @@
1
- import { Button, Flex, SearchField, Text } from '@servicetitan/anvil2';
1
+ import { Button, Flex, SearchField, Skeleton, Text } from '@servicetitan/anvil2';
2
2
  import IconArrowBack from '@servicetitan/anvil2/assets/icons/material/round/keyboard_arrow_left.svg';
3
3
  import IconArrowForward from '@servicetitan/anvil2/assets/icons/material/round/keyboard_arrow_right.svg';
4
4
  import { FC, Fragment, useMemo, useState } from 'react';
@@ -152,12 +152,13 @@ export const FieldSidebar: FC<FieldSidebarProps> = ({
152
152
  justifyContent="space-between"
153
153
  alignItems="center"
154
154
  >
155
- <span
155
+ <Text
156
+ size="small"
156
157
  className="dte-text-ellipsis"
157
158
  title={form.name}
158
159
  >
159
160
  {form.name}
160
- </span>
161
+ </Text>
161
162
  <IconArrowForward />
162
163
  </Flex>
163
164
  </li>
@@ -237,6 +238,19 @@ interface FieldOptionListProps {
237
238
  isLoading?: boolean;
238
239
  }
239
240
 
241
+ /** Placeholder rows while a form's fields are in flight, in place of the rows they become. */
242
+ const FieldOptionSkeletonRows: FC = () => {
243
+ return (
244
+ <Fragment>
245
+ {Array.from({ length: 4 }, (_, index) => (
246
+ <div key={index} className="dte-formula-field-list-item --skeleton" aria-hidden>
247
+ <Skeleton.Text />
248
+ </div>
249
+ ))}
250
+ </Fragment>
251
+ );
252
+ };
253
+
240
254
  const FieldOptionList: FC<FieldOptionListProps> = ({
241
255
  highlightElementPath,
242
256
  isLoading,
@@ -258,9 +272,7 @@ const FieldOptionList: FC<FieldOptionListProps> = ({
258
272
  </Text>
259
273
  ) : null}
260
274
  {isLoading ? (
261
- <Text size="small" subdued variant="body">
262
- Loading fields…
263
- </Text>
275
+ <FieldOptionSkeletonRows />
264
276
  ) : options.length === 0 ? (
265
277
  <Text size="small" subdued variant="body">
266
278
  No fields on this form.
@@ -285,9 +297,13 @@ const FieldOptionList: FC<FieldOptionListProps> = ({
285
297
  }
286
298
  }}
287
299
  >
288
- <span className="dte-text-ellipsis" title={opt.label ?? opt.path}>
300
+ <Text
301
+ size="small"
302
+ className="dte-text-ellipsis"
303
+ title={opt.label ?? opt.path}
304
+ >
289
305
  {opt.label ?? opt.path}
290
- </span>
306
+ </Text>
291
307
  <Text
292
308
  size="small"
293
309
  subdued
@@ -1,37 +1,111 @@
1
1
  import { Flex, SearchField, Text } from '@servicetitan/anvil2';
2
- import { FC, Fragment, useMemo, useState } from 'react';
3
- import { type DataModelFieldGroup, type FieldTypeOption } from '../../interface/types';
2
+ import { FC, Fragment, useLayoutEffect, useMemo, useRef, useState } from 'react';
3
+ import {
4
+ type DataModelFieldGroup,
5
+ type FieldTypeOption,
6
+ type FormInfo,
7
+ } from '../../interface/types';
4
8
  import { getFieldSampleData } from '../../utils';
5
9
  import { FieldType } from './field-type';
10
+ import { FormFieldsPanel, FormsList } from './form-fields-type-list';
6
11
 
7
12
  interface DataModelFieldTypeListProps {
8
13
  dataModelGroups?: DataModelFieldGroup[];
14
+ /**
15
+ * Forms offered as a group after the data model groups, already narrowed to the ones that have
16
+ * fields. Omitted (or empty, with nothing loading) hides the group; `onSelectForm` must be
17
+ * provided for the group to render.
18
+ */
19
+ forms?: FormInfo[];
20
+ /** The form whose fields are shown in place of the group list; null shows the group list. */
21
+ selectedForm?: FormInfo | null;
22
+ /** Draggable options for `selectedForm`. */
23
+ formFieldOptions?: FieldTypeOption[];
24
+ /** Placeholder rows to draw while it is still unknown which forms have fields. */
25
+ loadingFormsCount?: number;
26
+ isLoadingFormFields?: boolean;
27
+ onSelectForm?(form: FormInfo | null): void;
9
28
  onDragStart(fieldOption: FieldTypeOption): void;
10
29
  onDragEnd(): void;
11
30
  }
12
31
 
32
+ const matchesSearch = (label: string, searchLower: string) =>
33
+ label.toLowerCase().includes(searchLower);
34
+
13
35
  export const DataModelFieldTypeList: FC<DataModelFieldTypeListProps> = ({
14
36
  dataModelGroups = [],
37
+ formFieldOptions = [],
38
+ forms,
39
+ isLoadingFormFields = false,
40
+ loadingFormsCount = 0,
15
41
  onDragEnd,
16
42
  onDragStart,
43
+ onSelectForm,
44
+ selectedForm = null,
17
45
  }) => {
18
46
  const [searchText, setSearchText] = useState('');
47
+ const searchLower = searchText.trim().toLowerCase();
48
+ const showForms = (!!forms?.length || loadingFormsCount > 0) && !!onSelectForm;
19
49
 
20
50
  const searchedFieldGroups = useMemo(() => {
21
- if (!searchText) {
51
+ if (!searchLower) {
22
52
  return dataModelGroups;
23
53
  }
24
54
 
25
- const searchLower = searchText.toLowerCase();
26
55
  return dataModelGroups
27
56
  .map(group => ({
28
57
  ...group,
29
- fields: group.fields.filter(({ label }) =>
30
- label.toLowerCase().includes(searchLower),
31
- ),
58
+ fields: group.fields.filter(({ label }) => matchesSearch(label, searchLower)),
32
59
  }))
33
60
  .filter(group => group.fields.length > 0);
34
- }, [dataModelGroups, searchText]);
61
+ }, [dataModelGroups, searchLower]);
62
+
63
+ const searchedForms = useMemo(() => {
64
+ if (!forms?.length) {
65
+ return [];
66
+ }
67
+ if (!searchLower) {
68
+ return forms;
69
+ }
70
+ return forms.filter(form => matchesSearch(form.name, searchLower));
71
+ }, [forms, searchLower]);
72
+
73
+ const searchedFormFieldOptions = useMemo(() => {
74
+ if (!searchLower) {
75
+ return formFieldOptions;
76
+ }
77
+ return formFieldOptions.filter(option => matchesSearch(option.label, searchLower));
78
+ }, [formFieldOptions, searchLower]);
79
+
80
+ const isFormDetail = showForms && !!selectedForm;
81
+
82
+ /*
83
+ * Both slide panels share one scroller. The Forms group sits below every data model group, so
84
+ * a form is usually picked with the list scrolled far down — and the fields panel, being much
85
+ * shorter, would then be scrolled clean past its own content. Start the fields panel at the
86
+ * top and hand the list back the offset it was left at.
87
+ *
88
+ * The offset is tracked as the user scrolls rather than read when the panel switches: by then
89
+ * the list has already collapsed (see formula-modal.css) and the browser has clamped the
90
+ * scroller back to 0.
91
+ */
92
+ const scrollerRef = useRef<HTMLDivElement>(null);
93
+ const listScrollTopRef = useRef(0);
94
+
95
+ useLayoutEffect(() => {
96
+ const scroller = scrollerRef.current;
97
+ if (!scroller) {
98
+ return;
99
+ }
100
+ scroller.scrollTop = isFormDetail ? 0 : listScrollTopRef.current;
101
+ }, [isFormDetail]);
102
+
103
+ const handleScroll = () => {
104
+ if (!isFormDetail && scrollerRef.current) {
105
+ listScrollTopRef.current = scrollerRef.current.scrollTop;
106
+ }
107
+ };
108
+
35
109
  return (
36
110
  <Fragment>
37
111
  <SearchField
@@ -40,30 +114,70 @@ export const DataModelFieldTypeList: FC<DataModelFieldTypeListProps> = ({
40
114
  className="dte-field-sidebar-search"
41
115
  onChange={e => setSearchText(e.target.value ?? '')}
42
116
  />
43
- {searchedFieldGroups.map(group => (
44
- <div key={group.groupName} className="dte-field-type-group">
45
- <Flex alignItems="center" gap={1} className="dte-field-type-group-header">
46
- <Text variant="headline" el="h6" size="small">
47
- {group.groupName}
48
- </Text>
49
- </Flex>
50
- {group.fields.map(fieldOption => {
51
- const key = `${fieldOption.type}-${group.groupName}-${fieldOption.path}`;
52
- return (
53
- <FieldType
54
- key={key}
117
+ <div
118
+ className="dte-formula-sidebar-slide-outer"
119
+ ref={scrollerRef}
120
+ onScroll={handleScroll}
121
+ >
122
+ <div
123
+ className={`dte-formula-sidebar-slide-track${isFormDetail ? ' --detail' : ''}`}
124
+ >
125
+ <div className="dte-formula-sidebar-slide-panel">
126
+ {searchedFieldGroups.map(group => (
127
+ <div key={group.groupName} className="dte-field-type-group">
128
+ <Flex
129
+ alignItems="center"
130
+ gap={1}
131
+ className="dte-field-type-group-header"
132
+ >
133
+ <Text variant="headline" el="h6" size="small">
134
+ {group.groupName}
135
+ </Text>
136
+ </Flex>
137
+ {group.fields.map(fieldOption => {
138
+ const key = `${fieldOption.type}-${group.groupName}-${fieldOption.path}`;
139
+ return (
140
+ <FieldType
141
+ key={key}
142
+ onDragEnd={onDragEnd}
143
+ onDragStart={() => onDragStart(fieldOption)}
144
+ label={fieldOption.label}
145
+ sampleData={getFieldSampleData(
146
+ fieldOption.sampleData,
147
+ fieldOption.fieldType,
148
+ )}
149
+ />
150
+ );
151
+ })}
152
+ </div>
153
+ ))}
154
+ {showForms && (searchedForms.length > 0 || !searchLower) && (
155
+ <FormsList
156
+ forms={searchedForms}
157
+ loadingCount={loadingFormsCount}
158
+ onSelectForm={onSelectForm}
159
+ />
160
+ )}
161
+ </div>
162
+
163
+ <div className="dte-formula-sidebar-slide-panel --form-fields">
164
+ {isFormDetail && (
165
+ <FormFieldsPanel
166
+ form={selectedForm}
167
+ fieldOptions={searchedFormFieldOptions}
168
+ hasHiddenFields={
169
+ formFieldOptions.length > 0 &&
170
+ searchedFormFieldOptions.length === 0
171
+ }
172
+ isLoading={isLoadingFormFields}
173
+ onBack={() => onSelectForm(null)}
55
174
  onDragEnd={onDragEnd}
56
- onDragStart={() => onDragStart(fieldOption)}
57
- label={fieldOption.label}
58
- sampleData={getFieldSampleData(
59
- fieldOption.sampleData,
60
- fieldOption.fieldType,
61
- )}
175
+ onDragStart={onDragStart}
62
176
  />
63
- );
64
- })}
177
+ )}
178
+ </div>
65
179
  </div>
66
- ))}
180
+ </div>
67
181
  </Fragment>
68
182
  );
69
183
  };
@@ -14,15 +14,21 @@ import { DataModelFieldTypeList } from './data-model-field-type-list';
14
14
  import { ESignFieldTypeList } from './e-sign-field-type-list';
15
15
  import { FieldMenuGroup } from './field-menu-group';
16
16
  import { FillableFieldTypeList } from './fillable-field-type-list';
17
- import { FormFieldsTypeList } from './form-fields-type-list';
18
17
  import { GenericFieldTypeList } from './generic-field-type-list';
19
18
 
20
19
  interface FieldSidebarProps {
21
20
  dataModelGroups?: DataModelFieldGroup[];
21
+ /**
22
+ * Forms listed as a group inside the Merge Tags tab (alongside the data model groups).
23
+ * Hidden when empty, when `onSelectForm` is missing, or when `hideFields` names
24
+ * `FieldTypeEnum.forms`.
25
+ */
22
26
  forms?: FormInfo[];
23
27
  formFieldOptions?: FieldTypeOption[];
24
28
  hideFields?: FieldTypeEnum[];
25
29
  selectedForm?: FormInfo | null;
30
+ /** Placeholder rows to draw while it is still unknown which forms have fields. */
31
+ loadingFormsCount?: number;
26
32
  isLoadingFormFields?: boolean;
27
33
  onDragEnd(): void;
28
34
  onDragStart(fieldOption: FieldTypeOption): void;
@@ -35,6 +41,7 @@ export const FieldSidebar: FC<FieldSidebarProps> = ({
35
41
  forms,
36
42
  hideFields,
37
43
  isLoadingFormFields = false,
44
+ loadingFormsCount = 0,
38
45
  onDragEnd,
39
46
  onDragStart,
40
47
  onSelectForm,
@@ -49,15 +56,14 @@ export const FieldSidebar: FC<FieldSidebarProps> = ({
49
56
  };
50
57
 
51
58
  const filteredMenuGroups = useMemo(() => {
52
- let groups = menuGroups;
53
- if (hideFields?.length) {
54
- groups = groups.filter(menuGroup => !hideFields.includes(menuGroup.key));
59
+ if (!hideFields?.length) {
60
+ return menuGroups;
55
61
  }
56
- if (!forms?.length || !onSelectForm) {
57
- groups = groups.filter(menuGroup => menuGroup.key !== FieldTypeEnum.forms);
58
- }
59
- return groups;
60
- }, [forms?.length, hideFields, onSelectForm]);
62
+ return menuGroups.filter(menuGroup => !hideFields.includes(menuGroup.key));
63
+ }, [hideFields]);
64
+
65
+ // The list itself hides the group when there are no forms or no selection handler.
66
+ const visibleForms = hideFields?.includes(FieldTypeEnum.forms) ? undefined : forms;
61
67
 
62
68
  return (
63
69
  <Flex className="dte-field-sidebar-container skeleton-item">
@@ -85,17 +91,12 @@ export const FieldSidebar: FC<FieldSidebarProps> = ({
85
91
  {activeFieldType === FieldTypeEnum.dataModel && (
86
92
  <DataModelFieldTypeList
87
93
  dataModelGroups={dataModelGroups}
88
- onDragStart={onDragStart}
89
- onDragEnd={onDragEnd}
90
- />
91
- )}
92
- {activeFieldType === FieldTypeEnum.forms && forms && onSelectForm && (
93
- <FormFieldsTypeList
94
- fieldOptions={formFieldOptions}
95
- forms={forms}
94
+ forms={visibleForms}
95
+ formFieldOptions={formFieldOptions}
96
+ loadingFormsCount={visibleForms ? loadingFormsCount : 0}
96
97
  isLoadingFormFields={isLoadingFormFields}
97
- onDragEnd={onDragEnd}
98
98
  onDragStart={onDragStart}
99
+ onDragEnd={onDragEnd}
99
100
  onSelectForm={onSelectForm}
100
101
  selectedForm={selectedForm}
101
102
  />