@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.
- package/dist/components/field-config-panel/field-sidebar.d.ts.map +1 -1
- package/dist/components/field-config-panel/field-sidebar.js +8 -4
- package/dist/components/field-config-panel/field-sidebar.js.map +1 -1
- package/dist/components/field-sidebar/data-model-field-type-list.d.ts +15 -1
- package/dist/components/field-sidebar/data-model-field-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/data-model-field-type-list.js +54 -10
- package/dist/components/field-sidebar/data-model-field-type-list.js.map +1 -1
- package/dist/components/field-sidebar/field-sidebar.d.ts +7 -0
- package/dist/components/field-sidebar/field-sidebar.d.ts.map +1 -1
- package/dist/components/field-sidebar/field-sidebar.js +8 -11
- package/dist/components/field-sidebar/field-sidebar.js.map +1 -1
- package/dist/components/field-sidebar/form-fields-type-list.d.ts +28 -5
- package/dist/components/field-sidebar/form-fields-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/form-fields-type-list.js +27 -6
- package/dist/components/field-sidebar/form-fields-type-list.js.map +1 -1
- package/dist/components/pdf-editor/pdf-editor.d.ts.map +1 -1
- package/dist/components/pdf-editor/pdf-editor.js +3 -2
- package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
- package/dist/components/pdf-view/pdf-view.js +1 -1
- package/dist/components/pdf-view/pdf-view.js.map +1 -1
- package/dist/constants/menu-group.d.ts +5 -0
- package/dist/constants/menu-group.d.ts.map +1 -1
- package/dist/constants/menu-group.js +5 -2
- package/dist/constants/menu-group.js.map +1 -1
- package/dist/hooks/useFormFields.d.ts +3 -1
- package/dist/hooks/useFormFields.d.ts.map +1 -1
- package/dist/hooks/useFormFields.js +20 -3
- package/dist/hooks/useFormFields.js.map +1 -1
- package/dist/styles/field-type.css +5 -0
- package/dist/styles/formula-modal.css +17 -0
- package/dist/styles/pdf-canvas.css +5 -5
- package/package.json +1 -1
- package/src/components/field-config-panel/field-sidebar.tsx +24 -8
- package/src/components/field-sidebar/data-model-field-type-list.tsx +143 -29
- package/src/components/field-sidebar/field-sidebar.tsx +19 -18
- package/src/components/field-sidebar/form-fields-type-list.tsx +137 -95
- package/src/components/pdf-editor/pdf-editor.tsx +4 -3
- package/src/components/pdf-view/pdf-view.tsx +1 -1
- package/src/constants/menu-group.ts +5 -2
- package/src/hooks/useFormFields.ts +24 -2
- package/src/styles/field-type.css +5 -0
- package/src/styles/formula-modal.css +17 -0
- 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,
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC1E,OAAO,EACH,aAAa,GAKhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,8BAA8B,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"useFormFields.js","sourceRoot":"","sources":["../../src/hooks/useFormFields.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC1E,OAAO,EACH,aAAa,GAKhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,8BAA8B,EAAE,MAAM,UAAU,CAAC;AAc1D,MAAM,WAAW,GAAe,EAAE,CAAC;AAEnC,MAAM,CAAC,MAAM,aAAa,GAAG,CACzB,MAAkB,EAClB,YAGS,EACT,QAAoB,WAAW,EACZ,EAAE;IACrB,MAAM,CAAC,kBAAkB,EAAE,qBAAqB,CAAC,GAAG,QAAQ,CAAsB,EAAE,CAAC,CAAC;IACtF,MAAM,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IACtF,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAkB,IAAI,CAAC,CAAC;IACxE,MAAM,CAAC,wBAAwB,EAAE,2BAA2B,CAAC,GAAG,QAAQ,CACpE,EAAE,CACL,CAAC;IAEF,MAAM,qBAAqB,GAAG,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACzD,qBAAqB,CAAC,OAAO,GAAG,kBAAkB,CAAC;IAEnD,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,YAAY,EAAE,CAAC;YAChB,OAAO;QACX,CAAC;QAED;;;;;WAKG;QACH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAS,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACnE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;;YACnB,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,CAAC,UAAU,EAAE,CAAC;gBAC1C,IAAI,MAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,OAAO,0CAAE,MAAM,CAAC,MAAM,EAAE,CAAC;oBAChC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;wBACjC,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;4BAC/C,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;wBAClD,CAAC;oBACL,CAAC,CAAC,CAAC;gBACP,CAAC;YACL,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;YACvB,OAAO;QACX,CAAC;QAED,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,MAAM,KAAK,GAAG,qBAAqB,CAAC,GAAG,EAAE;YACrC,IAAI,SAAS,EAAE,CAAC;gBACZ,OAAO;YACX,CAAC;YACD,MAAM,KAAK,GAAG,qBAAqB,CAAC,OAAO,CAAC;YAC5C,MAAM,cAAc,GAAG,CAAC,GAAG,cAAc,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;YAC5E,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC;gBACzB,OAAO;YACX,CAAC;YACD,2BAA2B,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACjC,GAAG,IAAI;gBACP,GAAG,MAAM,CAAC,WAAW,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;aAC9D,CAAC,CAAC,CAAC;YACJ,YAAY,CAAC,cAAc,EAAE,gBAAgB,CAAC,EAAE;gBAC5C,IAAI,SAAS,EAAE,CAAC;oBACZ,OAAO;gBACX,CAAC;gBACD,qBAAqB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,gBAAgB,EAAE,CAAC,CAAC,CAAC;gBAClE,2BAA2B,CAAC,IAAI,CAAC,EAAE;oBAC/B,MAAM,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;oBACzB,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;wBAC7C,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;oBAC5B,CAAC;oBACD,OAAO,IAAI,CAAC;gBAChB,CAAC,CAAC,CAAC;YACP,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;QAEH,OAAO,GAAG,EAAE;YACR,SAAS,GAAG,IAAI,CAAC;YACjB,oBAAoB,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC,CAAC;IACN,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;IAElC,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,EAAE;QAClC,IAAI,CAAC,YAAY,EAAE,CAAC;YAChB,OAAO,EAAE,CAAC;QACd,CAAC;QACD,MAAM,IAAI,GAAG,kBAAkB,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,CAAA,EAAE,CAAC;YAChB,OAAO,EAAE,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,8BAA8B,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IAChG,CAAC,EAAE,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAC,CAAC;IAEvC,MAAM,gBAAgB,GAAG,WAAW,CAChC,CAAC,IAAqB,EAAE,EAAE;;QACtB,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,IAAI,EAAE,CAAC;YACP,IAAI,CAAC,CAAA,MAAA,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,0CAAE,MAAM,CAAA,IAAI,YAAY,EAAE,CAAC;gBACvD,2BAA2B,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;gBACjD,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,cAAc,CAAC,EAAE;oBACrC,qBAAqB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,cAAc,EAAE,CAAC,CAAC,CAAC;oBAChE,2BAA2B,CAAC,IAAI,CAAC,EAAE;wBAC/B,MAAM,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;wBACzB,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;4BAC3C,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;wBAC5B,CAAC;wBACD,OAAO,IAAI,CAAC;oBAChB,CAAC,CAAC,CAAC;gBACP,CAAC,CAAC,CAAC;YACP,CAAC;iBAAM,CAAC;gBACJ,2BAA2B,CAAC,EAAE,CAAC,CAAC;YACpC,CAAC;QACL,CAAC;aAAM,CAAC;YACJ,2BAA2B,CAAC,EAAE,CAAC,CAAC;QACpC,CAAC;IACL,CAAC,EACD,CAAC,YAAY,EAAE,kBAAkB,CAAC,CACrC,CAAC;IAEF,MAAM,qBAAqB,GAAG,WAAW,CACrC,CAAC,MAAc,EAAE,EAAE;;QACf,IAAI,CAAC,YAAY,KAAI,MAAA,kBAAkB,CAAC,MAAM,CAAC,0CAAE,MAAM,CAAA,EAAE,CAAC;YACtD,OAAO;QACX,CAAC;QACD,uBAAuB,CAAC,MAAM,CAAC,CAAC;QAChC,YAAY,CAAC,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC,EAAE;YACpC,qBAAqB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,cAAc,EAAE,CAAC,CAAC,CAAC;YAChE,uBAAuB,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;IACP,CAAC,EACD,CAAC,kBAAkB,EAAE,YAAY,CAAC,CACrC,CAAC;IAEF;;;OAGG;IACH,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,EAAE;QACjC,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,WAAC,OAAA,CAAC,CAAC,CAAA,MAAA,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,0CAAE,MAAM,CAAA,CAAA,EAAA,CAAC,CAAC;IACvE,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAEhC,MAAM,SAAS,GAAG,YAAY,IAAI,IAAI,IAAI,wBAAwB,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACpF,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,wBAAwB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAE/E,OAAO;QACH,SAAS;QACT,cAAc;QACd,eAAe;QACf,kBAAkB;QAClB,oBAAoB;QACpB,gBAAgB;QAChB,YAAY;QACZ,gBAAgB;QAChB,qBAAqB;KACxB,CAAC;AACN,CAAC,CAAC"}
|
|
@@ -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:
|
|
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:
|
|
31
|
+
right: 0;
|
|
32
32
|
top: 50%;
|
|
33
|
-
transform:
|
|
33
|
+
transform: translate(100%, -50%);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
&.--right-bottom {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
bottom: 12px;
|
|
38
|
+
right: 12px;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
package/package.json
CHANGED
|
@@ -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
|
-
<
|
|
155
|
+
<Text
|
|
156
|
+
size="small"
|
|
156
157
|
className="dte-text-ellipsis"
|
|
157
158
|
title={form.name}
|
|
158
159
|
>
|
|
159
160
|
{form.name}
|
|
160
|
-
</
|
|
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
|
-
<
|
|
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
|
-
<
|
|
300
|
+
<Text
|
|
301
|
+
size="small"
|
|
302
|
+
className="dte-text-ellipsis"
|
|
303
|
+
title={opt.label ?? opt.path}
|
|
304
|
+
>
|
|
289
305
|
{opt.label ?? opt.path}
|
|
290
|
-
</
|
|
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 {
|
|
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 (!
|
|
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,
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
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={
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
groups = groups.filter(menuGroup => !hideFields.includes(menuGroup.key));
|
|
59
|
+
if (!hideFields?.length) {
|
|
60
|
+
return menuGroups;
|
|
55
61
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
/>
|