@servicetitan/dte-pdf-editor 1.17.0 → 1.18.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 (184) hide show
  1. package/README.md +35 -7
  2. package/dist/components/field-config-panel/advanced-settings.d.ts +9 -0
  3. package/dist/components/field-config-panel/advanced-settings.d.ts.map +1 -0
  4. package/dist/components/field-config-panel/advanced-settings.js +17 -0
  5. package/dist/components/field-config-panel/advanced-settings.js.map +1 -0
  6. package/dist/components/field-config-panel/field-config-panel-overlay.d.ts +4 -1
  7. package/dist/components/field-config-panel/field-config-panel-overlay.d.ts.map +1 -1
  8. package/dist/components/field-config-panel/field-config-panel-overlay.js +2 -2
  9. package/dist/components/field-config-panel/field-config-panel-overlay.js.map +1 -1
  10. package/dist/components/field-config-panel/field-config-panel.d.ts +4 -1
  11. package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
  12. package/dist/components/field-config-panel/field-config-panel.js +11 -5
  13. package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
  14. package/dist/components/field-config-panel/field-sidebar.d.ts +13 -0
  15. package/dist/components/field-config-panel/field-sidebar.d.ts.map +1 -0
  16. package/dist/components/field-config-panel/field-sidebar.js +20 -0
  17. package/dist/components/field-config-panel/field-sidebar.js.map +1 -0
  18. package/dist/components/field-config-panel/formula-generator.d.ts +11 -0
  19. package/dist/components/field-config-panel/formula-generator.d.ts.map +1 -0
  20. package/dist/components/field-config-panel/formula-generator.js +51 -0
  21. package/dist/components/field-config-panel/formula-generator.js.map +1 -0
  22. package/dist/components/field-config-panel/formula-modal.d.ts +12 -0
  23. package/dist/components/field-config-panel/formula-modal.d.ts.map +1 -0
  24. package/dist/components/field-config-panel/formula-modal.js +99 -0
  25. package/dist/components/field-config-panel/formula-modal.js.map +1 -0
  26. package/dist/components/field-config-panel/formula-workspace.d.ts +23 -0
  27. package/dist/components/field-config-panel/formula-workspace.d.ts.map +1 -0
  28. package/dist/components/field-config-panel/formula-workspace.js +28 -0
  29. package/dist/components/field-config-panel/formula-workspace.js.map +1 -0
  30. package/dist/components/field-config-panel/result-type-selector.d.ts +9 -0
  31. package/dist/components/field-config-panel/result-type-selector.d.ts.map +1 -0
  32. package/dist/components/field-config-panel/result-type-selector.js +10 -0
  33. package/dist/components/field-config-panel/result-type-selector.js.map +1 -0
  34. package/dist/components/field-sidebar/calculated-field-type-list.d.ts +9 -0
  35. package/dist/components/field-sidebar/calculated-field-type-list.d.ts.map +1 -0
  36. package/dist/components/field-sidebar/calculated-field-type-list.js +12 -0
  37. package/dist/components/field-sidebar/calculated-field-type-list.js.map +1 -0
  38. package/dist/components/field-sidebar/data-model-field-type-list.d.ts +0 -1
  39. package/dist/components/field-sidebar/data-model-field-type-list.d.ts.map +1 -1
  40. package/dist/components/field-sidebar/data-model-field-type-list.js +8 -7
  41. package/dist/components/field-sidebar/data-model-field-type-list.js.map +1 -1
  42. package/dist/components/field-sidebar/field-menu-group.d.ts +11 -0
  43. package/dist/components/field-sidebar/field-menu-group.d.ts.map +1 -0
  44. package/dist/components/field-sidebar/field-menu-group.js +6 -0
  45. package/dist/components/field-sidebar/field-menu-group.js.map +1 -0
  46. package/dist/components/field-sidebar/field-sidebar.d.ts.map +1 -1
  47. package/dist/components/field-sidebar/field-sidebar.js +6 -15
  48. package/dist/components/field-sidebar/field-sidebar.js.map +1 -1
  49. package/dist/components/field-sidebar/fillable-field-type-list.d.ts +0 -1
  50. package/dist/components/field-sidebar/fillable-field-type-list.d.ts.map +1 -1
  51. package/dist/components/field-sidebar/fillable-field-type-list.js +8 -9
  52. package/dist/components/field-sidebar/fillable-field-type-list.js.map +1 -1
  53. package/dist/components/pdf-editor/pdf-editor.d.ts.map +1 -1
  54. package/dist/components/pdf-editor/pdf-editor.js +1 -1
  55. package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
  56. package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.d.ts +8 -0
  57. package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.d.ts.map +1 -0
  58. package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.js +5 -0
  59. package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.js.map +1 -0
  60. package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts.map +1 -1
  61. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js +2 -1
  62. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
  63. package/dist/components/pdf-view/pdf-view-calculated.d.ts +9 -0
  64. package/dist/components/pdf-view/pdf-view-calculated.d.ts.map +1 -0
  65. package/dist/components/pdf-view/pdf-view-calculated.js +18 -0
  66. package/dist/components/pdf-view/pdf-view-calculated.js.map +1 -0
  67. package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
  68. package/dist/components/pdf-view/pdf-view.js +2 -1
  69. package/dist/components/pdf-view/pdf-view.js.map +1 -1
  70. package/dist/constants/field.constants.d.ts +3 -2
  71. package/dist/constants/field.constants.d.ts.map +1 -1
  72. package/dist/constants/field.constants.js +6 -0
  73. package/dist/constants/field.constants.js.map +1 -1
  74. package/dist/constants/menu-group.d.ts +8 -0
  75. package/dist/constants/menu-group.d.ts.map +1 -0
  76. package/dist/constants/menu-group.js +20 -0
  77. package/dist/constants/menu-group.js.map +1 -0
  78. package/dist/hooks/index.d.ts +1 -0
  79. package/dist/hooks/index.d.ts.map +1 -1
  80. package/dist/hooks/index.js +1 -0
  81. package/dist/hooks/index.js.map +1 -1
  82. package/dist/hooks/useFormulaEditor.d.ts +22 -0
  83. package/dist/hooks/useFormulaEditor.d.ts.map +1 -0
  84. package/dist/hooks/useFormulaEditor.js +290 -0
  85. package/dist/hooks/useFormulaEditor.js.map +1 -0
  86. package/dist/hooks/usePdfFieldDnD.d.ts.map +1 -1
  87. package/dist/hooks/usePdfFieldDnD.js +3 -0
  88. package/dist/hooks/usePdfFieldDnD.js.map +1 -1
  89. package/dist/interface/types.d.ts +45 -3
  90. package/dist/interface/types.d.ts.map +1 -1
  91. package/dist/interface/types.js +3 -0
  92. package/dist/interface/types.js.map +1 -1
  93. package/dist/utils/data-model/extract-fields.utils.d.ts +5 -5
  94. package/dist/utils/data-model/extract-fields.utils.d.ts.map +1 -1
  95. package/dist/utils/data-model/extract-fields.utils.js +42 -8
  96. package/dist/utils/data-model/extract-fields.utils.js.map +1 -1
  97. package/dist/utils/formula/caret.utils.d.ts +3 -0
  98. package/dist/utils/formula/caret.utils.d.ts.map +1 -0
  99. package/dist/utils/formula/caret.utils.js +123 -0
  100. package/dist/utils/formula/caret.utils.js.map +1 -0
  101. package/dist/utils/formula/dom.utils.d.ts +4 -0
  102. package/dist/utils/formula/dom.utils.d.ts.map +1 -0
  103. package/dist/utils/formula/dom.utils.js +34 -0
  104. package/dist/utils/formula/dom.utils.js.map +1 -0
  105. package/dist/utils/formula/evaluate-formula.utils.d.ts +13 -0
  106. package/dist/utils/formula/evaluate-formula.utils.d.ts.map +1 -0
  107. package/dist/utils/formula/evaluate-formula.utils.js +134 -0
  108. package/dist/utils/formula/evaluate-formula.utils.js.map +1 -0
  109. package/dist/utils/formula/expression.utils.d.ts +18 -0
  110. package/dist/utils/formula/expression.utils.d.ts.map +1 -0
  111. package/dist/utils/formula/expression.utils.js +84 -0
  112. package/dist/utils/formula/expression.utils.js.map +1 -0
  113. package/dist/utils/formula/format-calculated-result.utils.d.ts +7 -0
  114. package/dist/utils/formula/format-calculated-result.utils.d.ts.map +1 -0
  115. package/dist/utils/formula/format-calculated-result.utils.js +50 -0
  116. package/dist/utils/formula/format-calculated-result.utils.js.map +1 -0
  117. package/dist/utils/formula/formula-types.d.ts +3 -0
  118. package/dist/utils/formula/formula-types.d.ts.map +1 -0
  119. package/dist/utils/formula/formula-types.js +2 -0
  120. package/dist/utils/formula/formula-types.js.map +1 -0
  121. package/dist/utils/formula/index.d.ts +11 -0
  122. package/dist/utils/formula/index.d.ts.map +1 -0
  123. package/dist/utils/formula/index.js +11 -0
  124. package/dist/utils/formula/index.js.map +1 -0
  125. package/dist/utils/formula/referenced-paths.utils.d.ts +7 -0
  126. package/dist/utils/formula/referenced-paths.utils.d.ts.map +1 -0
  127. package/dist/utils/formula/referenced-paths.utils.js +18 -0
  128. package/dist/utils/formula/referenced-paths.utils.js.map +1 -0
  129. package/dist/utils/formula/render-formula.utils.d.ts +8 -0
  130. package/dist/utils/formula/render-formula.utils.d.ts.map +1 -0
  131. package/dist/utils/formula/render-formula.utils.js +39 -0
  132. package/dist/utils/formula/render-formula.utils.js.map +1 -0
  133. package/dist/utils/formula/serialize-formula.utils.d.ts +14 -0
  134. package/dist/utils/formula/serialize-formula.utils.d.ts.map +1 -0
  135. package/dist/utils/formula/serialize-formula.utils.js +33 -0
  136. package/dist/utils/formula/serialize-formula.utils.js.map +1 -0
  137. package/dist/utils/formula/validate-formula.utils.d.ts +11 -0
  138. package/dist/utils/formula/validate-formula.utils.d.ts.map +1 -0
  139. package/dist/utils/formula/validate-formula.utils.js +79 -0
  140. package/dist/utils/formula/validate-formula.utils.js.map +1 -0
  141. package/dist/utils/index.d.ts +1 -0
  142. package/dist/utils/index.d.ts.map +1 -1
  143. package/dist/utils/index.js +1 -0
  144. package/dist/utils/index.js.map +1 -1
  145. package/package.json +2 -2
  146. package/src/components/field-config-panel/advanced-settings.tsx +113 -0
  147. package/src/components/field-config-panel/field-config-panel-overlay.tsx +8 -1
  148. package/src/components/field-config-panel/field-config-panel.tsx +43 -15
  149. package/src/components/field-config-panel/field-sidebar.tsx +91 -0
  150. package/src/components/field-config-panel/formula-generator.tsx +122 -0
  151. package/src/components/field-config-panel/formula-modal.tsx +229 -0
  152. package/src/components/field-config-panel/formula-workspace.tsx +116 -0
  153. package/src/components/field-config-panel/result-type-selector.tsx +34 -0
  154. package/src/components/field-sidebar/calculated-field-type-list.tsx +29 -0
  155. package/src/components/field-sidebar/data-model-field-type-list.tsx +10 -4
  156. package/src/components/field-sidebar/field-menu-group.tsx +36 -0
  157. package/src/components/field-sidebar/field-sidebar.tsx +14 -55
  158. package/src/components/field-sidebar/fillable-field-type-list.tsx +11 -9
  159. package/src/components/pdf-editor/pdf-editor.tsx +2 -0
  160. package/src/components/pdf-fields-overlay/pdf-overlay-field-calculated.tsx +15 -0
  161. package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +2 -0
  162. package/src/components/pdf-view/pdf-view-calculated.tsx +23 -0
  163. package/src/components/pdf-view/pdf-view.tsx +4 -0
  164. package/src/constants/field.constants.ts +9 -2
  165. package/src/constants/menu-group.ts +26 -0
  166. package/src/hooks/index.ts +1 -0
  167. package/src/hooks/useFormulaEditor.ts +336 -0
  168. package/src/hooks/usePdfFieldDnD.ts +4 -0
  169. package/src/interface/types.ts +38 -2
  170. package/src/styles/formula-modal.css +307 -0
  171. package/src/styles/index.css +1 -0
  172. package/src/utils/data-model/extract-fields.utils.ts +65 -7
  173. package/src/utils/formula/caret.utils.ts +125 -0
  174. package/src/utils/formula/dom.utils.ts +35 -0
  175. package/src/utils/formula/evaluate-formula.utils.ts +159 -0
  176. package/src/utils/formula/expression.utils.ts +99 -0
  177. package/src/utils/formula/format-calculated-result.utils.ts +79 -0
  178. package/src/utils/formula/formula-types.ts +2 -0
  179. package/src/utils/formula/index.ts +10 -0
  180. package/src/utils/formula/referenced-paths.utils.ts +18 -0
  181. package/src/utils/formula/render-formula.utils.ts +40 -0
  182. package/src/utils/formula/serialize-formula.utils.ts +40 -0
  183. package/src/utils/formula/validate-formula.utils.ts +94 -0
  184. package/src/utils/index.ts +1 -0
@@ -0,0 +1,39 @@
1
+ import { escapeHtml } from './dom.utils';
2
+ import { tokenizeExpression } from './expression.utils';
3
+ /**
4
+ * Renders formula tokens to HTML for the read-only preview (e.g. formula box).
5
+ * Uses the same token classes as the modal (dte-formula-token, dte-formula-token-field, etc.).
6
+ */
7
+ export function renderFormulaPreviewHtml(tokens) {
8
+ if (!(tokens === null || tokens === void 0 ? void 0 : tokens.length)) {
9
+ return '';
10
+ }
11
+ return tokens
12
+ .map(token => {
13
+ const type = token.type;
14
+ const text = type === 'field' ? token.label : token.value;
15
+ const escaped = escapeHtml(text).replace(/ /g, ' ');
16
+ return `<span class="dte-formula-token dte-formula-token-${type}">${escaped}</span>`;
17
+ })
18
+ .join('');
19
+ }
20
+ export function renderFormulaHtml(expression, labelMap) {
21
+ if (!expression.trim()) {
22
+ return '';
23
+ }
24
+ const parts = tokenizeExpression(expression);
25
+ let fieldIndex = 0;
26
+ return parts
27
+ .map(part => {
28
+ var _a;
29
+ if (part.type === 'field') {
30
+ const displayValue = (_a = labelMap.get(part.value)) !== null && _a !== void 0 ? _a : part.value;
31
+ const currentIndex = fieldIndex;
32
+ fieldIndex += 1;
33
+ return `<span class="dte-formula-chip" contenteditable="false" data-field="${escapeHtml(part.value)}" data-field-index="${currentIndex}" draggable="true"><span class="dte-formula-chip-label">${escapeHtml(displayValue)}</span><span class="dte-formula-chip-remove" data-field-remove="true" role="button" aria-label="Remove field">&times;</span></span>`;
34
+ }
35
+ return `<span>${escapeHtml(part.value).replace(/ /g, '&nbsp;')}</span>`;
36
+ })
37
+ .join('');
38
+ }
39
+ //# sourceMappingURL=render-formula.utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-formula.utils.js","sourceRoot":"","sources":["../../../src/utils/formula/render-formula.utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAsB;IAC3D,IAAI,CAAC,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,MAAM,CAAA,EAAE,CAAC;QAClB,OAAO,EAAE,CAAC;IACd,CAAC;IACD,OAAO,MAAM;SACR,GAAG,CAAC,KAAK,CAAC,EAAE;QACT,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAE,KAA2B,CAAC,KAAK,CAAC;QACjF,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACzD,OAAO,oDAAoD,IAAI,KAAK,OAAO,SAAS,CAAC;IACzF,CAAC,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,UAAkB,EAAE,QAA6B;IAC/E,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;QACrB,OAAO,EAAE,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC7C,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,OAAO,KAAK;SACP,GAAG,CAAC,IAAI,CAAC,EAAE;;QACR,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACxB,MAAM,YAAY,GAAG,MAAA,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,mCAAI,IAAI,CAAC,KAAK,CAAC;YAC5D,MAAM,YAAY,GAAG,UAAU,CAAC;YAChC,UAAU,IAAI,CAAC,CAAC;YAChB,OAAO,sEAAsE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,uBAAuB,YAAY,2DAA2D,UAAU,CAAC,YAAY,CAAC,qIAAqI,CAAC;QACnW,CAAC;QACD,OAAO,SAAS,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC;IAC5E,CAAC,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { StructuredFormula } from '../../interface/types';
2
+ export interface SerializedFormula {
3
+ v: number;
4
+ tokens: StructuredFormula['tokens'];
5
+ }
6
+ /**
7
+ * Serialize structured formula for persistence (e.g. to JSON in store).
8
+ */
9
+ export declare function serializeFormula(formula: StructuredFormula): string;
10
+ /**
11
+ * Deserialize formula from persisted string. Returns null if invalid.
12
+ */
13
+ export declare function deserializeFormula(json: string): StructuredFormula | null;
14
+ //# sourceMappingURL=serialize-formula.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize-formula.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/formula/serialize-formula.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAI1D,MAAM,WAAW,iBAAiB;IAC9B,CAAC,EAAE,MAAM,CAAC;IACV,MAAM,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAC;CACvC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAMnE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAgBzE"}
@@ -0,0 +1,33 @@
1
+ const SERIALIZATION_VERSION = 1;
2
+ /**
3
+ * Serialize structured formula for persistence (e.g. to JSON in store).
4
+ */
5
+ export function serializeFormula(formula) {
6
+ const payload = {
7
+ v: SERIALIZATION_VERSION,
8
+ tokens: formula.tokens,
9
+ };
10
+ return JSON.stringify(payload);
11
+ }
12
+ /**
13
+ * Deserialize formula from persisted string. Returns null if invalid.
14
+ */
15
+ export function deserializeFormula(json) {
16
+ if (!json || typeof json !== 'string') {
17
+ return null;
18
+ }
19
+ try {
20
+ const payload = JSON.parse(json);
21
+ if ((payload === null || payload === void 0 ? void 0 : payload.v) !== SERIALIZATION_VERSION || !Array.isArray(payload.tokens)) {
22
+ return null;
23
+ }
24
+ if (payload.tokens.length === 0) {
25
+ return { tokens: [] };
26
+ }
27
+ return { tokens: payload.tokens };
28
+ }
29
+ catch (_a) {
30
+ return null;
31
+ }
32
+ }
33
+ //# sourceMappingURL=serialize-formula.utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize-formula.utils.js","sourceRoot":"","sources":["../../../src/utils/formula/serialize-formula.utils.ts"],"names":[],"mappings":"AAEA,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAOhC;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAA0B;IACvD,MAAM,OAAO,GAAsB;QAC/B,CAAC,EAAE,qBAAqB;QACxB,MAAM,EAAE,OAAO,CAAC,MAAM;KACzB,CAAC;IACF,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC3C,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAsB,CAAC;QACtD,IAAI,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,CAAC,MAAK,qBAAqB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACzE,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;QAC1B,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;IACtC,CAAC;IAAC,WAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { StructuredFormula } from '../../interface/types';
2
+ export interface FormulaValidationResult {
3
+ valid: boolean;
4
+ errors: string[];
5
+ }
6
+ /**
7
+ * Validates a structured formula.
8
+ * Rules: not empty, valid operator placement, balanced parentheses, supported operators only, all field paths exist.
9
+ */
10
+ export declare function validateFormula(formula: StructuredFormula | undefined | null, validPaths: Set<string>): FormulaValidationResult;
11
+ //# sourceMappingURL=validate-formula.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-formula.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/formula/validate-formula.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,iBAAiB,EACpB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CACpB;AAYD;;;GAGG;AACH,wBAAgB,eAAe,CAC3B,OAAO,EAAE,iBAAiB,GAAG,SAAS,GAAG,IAAI,EAC7C,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACxB,uBAAuB,CAiEzB"}
@@ -0,0 +1,79 @@
1
+ import { FORMULA_OPERATORS, } from '../../interface/types';
2
+ const OPERATOR_SET = new Set(FORMULA_OPERATORS);
3
+ function isOperatorToken(t) {
4
+ return t.type === 'operator';
5
+ }
6
+ function isOperandToken(t) {
7
+ return t.type === 'number' || t.type === 'field';
8
+ }
9
+ /**
10
+ * Validates a structured formula.
11
+ * Rules: not empty, valid operator placement, balanced parentheses, supported operators only, all field paths exist.
12
+ */
13
+ export function validateFormula(formula, validPaths) {
14
+ var _a;
15
+ const errors = [];
16
+ if (!((_a = formula === null || formula === void 0 ? void 0 : formula.tokens) === null || _a === void 0 ? void 0 : _a.length)) {
17
+ return { valid: false, errors: ['Formula cannot be empty'] };
18
+ }
19
+ const tokens = formula.tokens;
20
+ let parenBalance = 0;
21
+ for (let i = 0; i < tokens.length; i++) {
22
+ const t = tokens[i];
23
+ const prev = tokens[i - 1];
24
+ const next = tokens[i + 1];
25
+ if (t.type === 'lparen') {
26
+ parenBalance++;
27
+ if (prev != null && isOperandToken(prev)) {
28
+ errors.push('Operator required before opening parenthesis');
29
+ }
30
+ }
31
+ else if (t.type === 'rparen') {
32
+ parenBalance--;
33
+ if (parenBalance < 0) {
34
+ errors.push('Unbalanced parentheses');
35
+ }
36
+ if (prev != null && isOperatorToken(prev)) {
37
+ errors.push('Operand required before closing parenthesis');
38
+ }
39
+ }
40
+ else if (t.type === 'operator') {
41
+ if (prev == null || next == null) {
42
+ errors.push('Operator must be between two operands or expressions');
43
+ }
44
+ else if (prev.type === 'operator' || prev.type === 'lparen') {
45
+ errors.push('Operand required before operator');
46
+ }
47
+ else if (next.type === 'operator' || next.type === 'rparen') {
48
+ errors.push('Operand required after operator');
49
+ }
50
+ }
51
+ else if (t.type === 'field') {
52
+ if (!validPaths.has(t.path)) {
53
+ errors.push(`Unknown field: "${t.label}" (path: ${t.path})`);
54
+ }
55
+ }
56
+ else if (t.type === 'number') {
57
+ const num = parseFloat(t.value);
58
+ if (Number.isNaN(num)) {
59
+ errors.push(`Invalid number: "${t.value}"`);
60
+ }
61
+ }
62
+ }
63
+ if (parenBalance !== 0) {
64
+ errors.push('Unbalanced parentheses');
65
+ }
66
+ const first = tokens[0];
67
+ const last = tokens[tokens.length - 1];
68
+ if (first && (first.type === 'operator' || first.type === 'rparen')) {
69
+ errors.push('Formula must start with an operand or opening parenthesis');
70
+ }
71
+ if (last && (last.type === 'operator' || last.type === 'lparen')) {
72
+ errors.push('Formula must end with an operand or closing parenthesis');
73
+ }
74
+ return {
75
+ valid: errors.length === 0,
76
+ errors,
77
+ };
78
+ }
79
+ //# sourceMappingURL=validate-formula.utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-formula.utils.js","sourceRoot":"","sources":["../../../src/utils/formula/validate-formula.utils.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  export * from './field';
2
2
  export * from './pdf';
3
3
  export * from './data-model';
4
+ export * from './formula';
4
5
  export * from './path';
5
6
  export * from './recipients';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,cAAc,CAAC;AAC7B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC"}
@@ -1,6 +1,7 @@
1
1
  export * from './field';
2
2
  export * from './pdf';
3
3
  export * from './data-model';
4
+ export * from './formula';
4
5
  export * from './path';
5
6
  export * from './recipients';
6
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,cAAc,CAAC;AAC7B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicetitan/dte-pdf-editor",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "typings": "./dist/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  "uuid": "^9.0.0"
22
22
  },
23
23
  "peerDependencies": {
24
- "@servicetitan/anvil2": "^1.46.9",
24
+ "@servicetitan/anvil2": "^2.0.2",
25
25
  "react": "~18.3.1",
26
26
  "react-dom": "~18.3.1"
27
27
  }
@@ -0,0 +1,113 @@
1
+ import { Button, Flex, Text } from '@servicetitan/anvil2';
2
+ import { Dispatch, FC, SetStateAction } from 'react';
3
+ import { CalculatedFieldFormat } from '../../interface/types';
4
+
5
+ interface AdvancedSettingsProps {
6
+ format: CalculatedFieldFormat;
7
+ setFormat: Dispatch<SetStateAction<CalculatedFieldFormat>>;
8
+ }
9
+
10
+ const ROUNDING_OPTIONS: { label: string; value: CalculatedFieldFormat['roundingMode'] }[] = [
11
+ { label: 'Round', value: 'round' },
12
+ { label: 'Floor', value: 'floor' },
13
+ { label: 'Ceil', value: 'ceil' },
14
+ ];
15
+
16
+ export const AdvancedSettings: FC<AdvancedSettingsProps> = ({ format, setFormat }) => (
17
+ <div className="dte-formula-advanced">
18
+ <Text variant="body" size="small">
19
+ Rounding mode
20
+ </Text>
21
+ <Flex gap={1}>
22
+ {ROUNDING_OPTIONS.map(({ label, value }) => (
23
+ <Button
24
+ key={value}
25
+ appearance={format.roundingMode === value ? 'primary' : 'secondary'}
26
+ size="small"
27
+ onClick={() => setFormat(prev => ({ ...prev, roundingMode: value }))}
28
+ >
29
+ {label}
30
+ </Button>
31
+ ))}
32
+ </Flex>
33
+ <Flex
34
+ alignItems="center"
35
+ justifyContent="space-between"
36
+ className="dte-formula-advanced-row"
37
+ >
38
+ <Text variant="body" size="small">
39
+ Decimal places
40
+ </Text>
41
+ <Text variant="body" size="small" className="dte-formula-advanced-value">
42
+ {format.decimals}
43
+ </Text>
44
+ </Flex>
45
+ <input
46
+ type="range"
47
+ className="dte-formula-advanced-range"
48
+ min={0}
49
+ max={10}
50
+ value={format.decimals}
51
+ onChange={e =>
52
+ setFormat(prev => ({
53
+ ...prev,
54
+ decimals: Number(e.target.value),
55
+ }))
56
+ }
57
+ />
58
+ <label className="dte-formula-advanced-checkbox">
59
+ <input
60
+ type="checkbox"
61
+ checked={format.thousandsSeparator}
62
+ onChange={e =>
63
+ setFormat(prev => ({
64
+ ...prev,
65
+ thousandsSeparator: e.target.checked,
66
+ decimalSeparatorEnabled: e.target.checked,
67
+ decimalSeparator: e.target.checked ? prev.decimalSeparator : '.',
68
+ }))
69
+ }
70
+ />
71
+ <Text variant="body" size="small">
72
+ Thousands separator
73
+ </Text>
74
+ </label>
75
+ {format.thousandsSeparator && (
76
+ <Flex gap={2}>
77
+ {(['.', ','] as const).map(sep => (
78
+ <Button
79
+ key={sep}
80
+ appearance={format.decimalSeparator === sep ? 'primary' : 'secondary'}
81
+ size="small"
82
+ onClick={() => setFormat(prev => ({ ...prev, decimalSeparator: sep }))}
83
+ >
84
+ {sep}
85
+ </Button>
86
+ ))}
87
+ </Flex>
88
+ )}
89
+ <Text variant="body" size="small">
90
+ Result affixes
91
+ </Text>
92
+ <Flex gap="2" alignItems="center">
93
+ <Text variant="body" size="small">
94
+ Prefix
95
+ </Text>
96
+ <input
97
+ type="text"
98
+ className="dte-formula-advanced-input"
99
+ value={format.prefixText}
100
+ onChange={e => setFormat(prev => ({ ...prev, prefixText: e.target.value }))}
101
+ />
102
+ <Text variant="body" size="small">
103
+ Postfix
104
+ </Text>
105
+ <input
106
+ type="text"
107
+ className="dte-formula-advanced-input"
108
+ value={format.postfixText}
109
+ onChange={e => setFormat(prev => ({ ...prev, postfixText: e.target.value }))}
110
+ />
111
+ </Flex>
112
+ </div>
113
+ );
@@ -1,10 +1,13 @@
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 } from '../../interface/types';
4
+ import { PdfField, RecipientInfo, SchemaObject } from '../../interface/types';
5
5
  import { FieldConfigPanel } from './field-config-panel';
6
6
 
7
7
  interface FieldConfigPanelOverlayProps {
8
+ dataModel?: SchemaObject;
9
+ /** All fields on the document (e.g. for formula builder fillable section). */
10
+ documentFields?: PdfField[];
8
11
  selectedField: PdfField;
9
12
  recipients?: RecipientInfo[];
10
13
  onFieldConfigChange(updates: Partial<PdfField>): void;
@@ -13,6 +16,8 @@ interface FieldConfigPanelOverlayProps {
13
16
  }
14
17
 
15
18
  export const FieldConfigPanelOverlay: FC<FieldConfigPanelOverlayProps> = ({
19
+ dataModel,
20
+ documentFields = [],
16
21
  onDeleteField,
17
22
  onDeselectField,
18
23
  onFieldConfigChange,
@@ -39,6 +44,8 @@ export const FieldConfigPanelOverlay: FC<FieldConfigPanelOverlayProps> = ({
39
44
  </Flex>
40
45
  <div className="dte-field-config-panel-content">
41
46
  <FieldConfigPanel
47
+ dataModel={dataModel}
48
+ documentFields={documentFields}
42
49
  field={selectedField}
43
50
  recipients={recipients}
44
51
  onFieldConfigChange={onFieldConfigChange}
@@ -1,17 +1,29 @@
1
- import { Button, Combobox, Flex, Textarea, TextField } from '@servicetitan/anvil2';
1
+ import { Button, Checkbox, Combobox, Flex, Textarea, TextField } from '@servicetitan/anvil2';
2
2
  import { FC, useMemo } from 'react';
3
3
  import { E_SIGN_FIELD_TYPE_OPTIONS } from '../../constants';
4
- import { ESignFieldType, FieldTypeEnum, PdfField, RecipientInfo } from '../../interface/types';
4
+ import {
5
+ ESignFieldType,
6
+ FieldTypeEnum,
7
+ PdfField,
8
+ RecipientInfo,
9
+ SchemaObject,
10
+ } from '../../interface/types';
5
11
  import { generateESignPath, generateFillablePath } from '../../utils';
12
+ import { FormulaGenerator } from './formula-generator';
6
13
 
7
14
  interface FieldConfigPanelProps {
8
15
  field: PdfField;
16
+ dataModel?: SchemaObject;
17
+ /** All fields on the document (e.g. for formula builder fillable section). */
18
+ documentFields?: PdfField[];
9
19
  recipients?: RecipientInfo[];
10
20
  onDeleteField(): void;
11
21
  onFieldConfigChange(updates: Partial<PdfField>): void;
12
22
  }
13
23
 
14
24
  export const FieldConfigPanel: FC<FieldConfigPanelProps> = ({
25
+ dataModel,
26
+ documentFields = [],
15
27
  field,
16
28
  onDeleteField,
17
29
  onFieldConfigChange,
@@ -63,8 +75,11 @@ export const FieldConfigPanel: FC<FieldConfigPanelProps> = ({
63
75
 
64
76
  return (
65
77
  <Flex direction="column" gap="4">
66
- {(field.type === FieldTypeEnum.fillable || field.type === FieldTypeEnum.eSign) && (
78
+ {[FieldTypeEnum.fillable, FieldTypeEnum.eSign, FieldTypeEnum.calculated].includes(
79
+ field.type,
80
+ ) && (
67
81
  <TextField
82
+ required
68
83
  label="Label"
69
84
  value={field.label}
70
85
  onChange={e =>
@@ -118,18 +133,31 @@ export const FieldConfigPanel: FC<FieldConfigPanelProps> = ({
118
133
  </Combobox>
119
134
  )}
120
135
  <TextField label="Data Path" value={field.path} disabled />
121
- {/* Todo need to uncomment when in MFE starts handle validations */}
122
- {/* {field.type === FieldTypeEnum.fillable && (*/}
123
- {/* <Checkbox*/}
124
- {/* label="Required"*/}
125
- {/* checked={field.required}*/}
126
- {/* onChange={() =>*/}
127
- {/* onFieldConfigChange({*/}
128
- {/* required: !field.required,*/}
129
- {/* })*/}
130
- {/* }*/}
131
- {/* />*/}
132
- {/* )}*/}
136
+ {field.type === FieldTypeEnum.fillable && field.subType !== 'checkbox' && (
137
+ <Checkbox
138
+ label="Required"
139
+ checked={field.required}
140
+ onChange={() =>
141
+ onFieldConfigChange({
142
+ required: !field.required,
143
+ })
144
+ }
145
+ />
146
+ )}
147
+ {field.type === FieldTypeEnum.calculated && (
148
+ <FormulaGenerator
149
+ dataModel={dataModel}
150
+ documentFields={documentFields}
151
+ formula={field.formula}
152
+ formulaFormat={field.formulaFormat}
153
+ onFormulaChange={(formula, formulaFormat) =>
154
+ onFieldConfigChange({
155
+ formula,
156
+ formulaFormat,
157
+ })
158
+ }
159
+ />
160
+ )}
133
161
  <Textarea
134
162
  label="Description"
135
163
  value={field.description}
@@ -0,0 +1,91 @@
1
+ import { Text } from '@servicetitan/anvil2';
2
+ import { FC } from 'react';
3
+ import { FieldTypeOption } from '../../interface/types';
4
+
5
+ interface FieldSidebarProps {
6
+ fillableOptions: FieldTypeOption[];
7
+ mergeTagOptions: FieldTypeOption[];
8
+ highlightElementPath: string;
9
+ onHover: (path: string) => void;
10
+ onSelect: (path: string) => void;
11
+ selectedPaths: Set<string>;
12
+ }
13
+
14
+ export const FieldSidebar: FC<FieldSidebarProps> = ({
15
+ fillableOptions,
16
+ highlightElementPath,
17
+ mergeTagOptions,
18
+ onHover,
19
+ onSelect,
20
+ selectedPaths,
21
+ }) => {
22
+ return (
23
+ <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
+ />
40
+ </div>
41
+ );
42
+ };
43
+
44
+ interface FieldOptionListProps {
45
+ highlightElementPath: string;
46
+ onHover: (path: string) => void;
47
+ onSelect: (path: string) => void;
48
+ options: FieldTypeOption[];
49
+ selectedPaths: Set<string>;
50
+ title: string;
51
+ }
52
+
53
+ const FieldOptionList: FC<FieldOptionListProps> = ({
54
+ highlightElementPath,
55
+ onHover,
56
+ onSelect,
57
+ options,
58
+ selectedPaths,
59
+ title,
60
+ }) => {
61
+ if (options.length === 0) {
62
+ return null;
63
+ }
64
+
65
+ return (
66
+ <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>
89
+ </div>
90
+ );
91
+ };