@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.
- package/README.md +35 -7
- package/dist/components/field-config-panel/advanced-settings.d.ts +9 -0
- package/dist/components/field-config-panel/advanced-settings.d.ts.map +1 -0
- package/dist/components/field-config-panel/advanced-settings.js +17 -0
- package/dist/components/field-config-panel/advanced-settings.js.map +1 -0
- package/dist/components/field-config-panel/field-config-panel-overlay.d.ts +4 -1
- package/dist/components/field-config-panel/field-config-panel-overlay.d.ts.map +1 -1
- package/dist/components/field-config-panel/field-config-panel-overlay.js +2 -2
- package/dist/components/field-config-panel/field-config-panel-overlay.js.map +1 -1
- package/dist/components/field-config-panel/field-config-panel.d.ts +4 -1
- package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
- package/dist/components/field-config-panel/field-config-panel.js +11 -5
- package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
- package/dist/components/field-config-panel/field-sidebar.d.ts +13 -0
- package/dist/components/field-config-panel/field-sidebar.d.ts.map +1 -0
- package/dist/components/field-config-panel/field-sidebar.js +20 -0
- package/dist/components/field-config-panel/field-sidebar.js.map +1 -0
- package/dist/components/field-config-panel/formula-generator.d.ts +11 -0
- package/dist/components/field-config-panel/formula-generator.d.ts.map +1 -0
- package/dist/components/field-config-panel/formula-generator.js +51 -0
- package/dist/components/field-config-panel/formula-generator.js.map +1 -0
- package/dist/components/field-config-panel/formula-modal.d.ts +12 -0
- package/dist/components/field-config-panel/formula-modal.d.ts.map +1 -0
- package/dist/components/field-config-panel/formula-modal.js +99 -0
- package/dist/components/field-config-panel/formula-modal.js.map +1 -0
- package/dist/components/field-config-panel/formula-workspace.d.ts +23 -0
- package/dist/components/field-config-panel/formula-workspace.d.ts.map +1 -0
- package/dist/components/field-config-panel/formula-workspace.js +28 -0
- package/dist/components/field-config-panel/formula-workspace.js.map +1 -0
- package/dist/components/field-config-panel/result-type-selector.d.ts +9 -0
- package/dist/components/field-config-panel/result-type-selector.d.ts.map +1 -0
- package/dist/components/field-config-panel/result-type-selector.js +10 -0
- package/dist/components/field-config-panel/result-type-selector.js.map +1 -0
- package/dist/components/field-sidebar/calculated-field-type-list.d.ts +9 -0
- package/dist/components/field-sidebar/calculated-field-type-list.d.ts.map +1 -0
- package/dist/components/field-sidebar/calculated-field-type-list.js +12 -0
- package/dist/components/field-sidebar/calculated-field-type-list.js.map +1 -0
- package/dist/components/field-sidebar/data-model-field-type-list.d.ts +0 -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 +8 -7
- package/dist/components/field-sidebar/data-model-field-type-list.js.map +1 -1
- package/dist/components/field-sidebar/field-menu-group.d.ts +11 -0
- package/dist/components/field-sidebar/field-menu-group.d.ts.map +1 -0
- package/dist/components/field-sidebar/field-menu-group.js +6 -0
- package/dist/components/field-sidebar/field-menu-group.js.map +1 -0
- package/dist/components/field-sidebar/field-sidebar.d.ts.map +1 -1
- package/dist/components/field-sidebar/field-sidebar.js +6 -15
- package/dist/components/field-sidebar/field-sidebar.js.map +1 -1
- package/dist/components/field-sidebar/fillable-field-type-list.d.ts +0 -1
- package/dist/components/field-sidebar/fillable-field-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/fillable-field-type-list.js +8 -9
- package/dist/components/field-sidebar/fillable-field-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 +1 -1
- package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.d.ts +8 -0
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.d.ts.map +1 -0
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.js +5 -0
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.js.map +1 -0
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.js +2 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-calculated.d.ts +9 -0
- package/dist/components/pdf-view/pdf-view-calculated.d.ts.map +1 -0
- package/dist/components/pdf-view/pdf-view-calculated.js +18 -0
- package/dist/components/pdf-view/pdf-view-calculated.js.map +1 -0
- package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view.js +2 -1
- package/dist/components/pdf-view/pdf-view.js.map +1 -1
- package/dist/constants/field.constants.d.ts +3 -2
- package/dist/constants/field.constants.d.ts.map +1 -1
- package/dist/constants/field.constants.js +6 -0
- package/dist/constants/field.constants.js.map +1 -1
- package/dist/constants/menu-group.d.ts +8 -0
- package/dist/constants/menu-group.d.ts.map +1 -0
- package/dist/constants/menu-group.js +20 -0
- package/dist/constants/menu-group.js.map +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useFormulaEditor.d.ts +22 -0
- package/dist/hooks/useFormulaEditor.d.ts.map +1 -0
- package/dist/hooks/useFormulaEditor.js +290 -0
- package/dist/hooks/useFormulaEditor.js.map +1 -0
- package/dist/hooks/usePdfFieldDnD.d.ts.map +1 -1
- package/dist/hooks/usePdfFieldDnD.js +3 -0
- package/dist/hooks/usePdfFieldDnD.js.map +1 -1
- package/dist/interface/types.d.ts +45 -3
- package/dist/interface/types.d.ts.map +1 -1
- package/dist/interface/types.js +3 -0
- package/dist/interface/types.js.map +1 -1
- package/dist/utils/data-model/extract-fields.utils.d.ts +5 -5
- package/dist/utils/data-model/extract-fields.utils.d.ts.map +1 -1
- package/dist/utils/data-model/extract-fields.utils.js +42 -8
- package/dist/utils/data-model/extract-fields.utils.js.map +1 -1
- package/dist/utils/formula/caret.utils.d.ts +3 -0
- package/dist/utils/formula/caret.utils.d.ts.map +1 -0
- package/dist/utils/formula/caret.utils.js +123 -0
- package/dist/utils/formula/caret.utils.js.map +1 -0
- package/dist/utils/formula/dom.utils.d.ts +4 -0
- package/dist/utils/formula/dom.utils.d.ts.map +1 -0
- package/dist/utils/formula/dom.utils.js +34 -0
- package/dist/utils/formula/dom.utils.js.map +1 -0
- package/dist/utils/formula/evaluate-formula.utils.d.ts +13 -0
- package/dist/utils/formula/evaluate-formula.utils.d.ts.map +1 -0
- package/dist/utils/formula/evaluate-formula.utils.js +134 -0
- package/dist/utils/formula/evaluate-formula.utils.js.map +1 -0
- package/dist/utils/formula/expression.utils.d.ts +18 -0
- package/dist/utils/formula/expression.utils.d.ts.map +1 -0
- package/dist/utils/formula/expression.utils.js +84 -0
- package/dist/utils/formula/expression.utils.js.map +1 -0
- package/dist/utils/formula/format-calculated-result.utils.d.ts +7 -0
- package/dist/utils/formula/format-calculated-result.utils.d.ts.map +1 -0
- package/dist/utils/formula/format-calculated-result.utils.js +50 -0
- package/dist/utils/formula/format-calculated-result.utils.js.map +1 -0
- package/dist/utils/formula/formula-types.d.ts +3 -0
- package/dist/utils/formula/formula-types.d.ts.map +1 -0
- package/dist/utils/formula/formula-types.js +2 -0
- package/dist/utils/formula/formula-types.js.map +1 -0
- package/dist/utils/formula/index.d.ts +11 -0
- package/dist/utils/formula/index.d.ts.map +1 -0
- package/dist/utils/formula/index.js +11 -0
- package/dist/utils/formula/index.js.map +1 -0
- package/dist/utils/formula/referenced-paths.utils.d.ts +7 -0
- package/dist/utils/formula/referenced-paths.utils.d.ts.map +1 -0
- package/dist/utils/formula/referenced-paths.utils.js +18 -0
- package/dist/utils/formula/referenced-paths.utils.js.map +1 -0
- package/dist/utils/formula/render-formula.utils.d.ts +8 -0
- package/dist/utils/formula/render-formula.utils.d.ts.map +1 -0
- package/dist/utils/formula/render-formula.utils.js +39 -0
- package/dist/utils/formula/render-formula.utils.js.map +1 -0
- package/dist/utils/formula/serialize-formula.utils.d.ts +14 -0
- package/dist/utils/formula/serialize-formula.utils.d.ts.map +1 -0
- package/dist/utils/formula/serialize-formula.utils.js +33 -0
- package/dist/utils/formula/serialize-formula.utils.js.map +1 -0
- package/dist/utils/formula/validate-formula.utils.d.ts +11 -0
- package/dist/utils/formula/validate-formula.utils.d.ts.map +1 -0
- package/dist/utils/formula/validate-formula.utils.js +79 -0
- package/dist/utils/formula/validate-formula.utils.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/field-config-panel/advanced-settings.tsx +113 -0
- package/src/components/field-config-panel/field-config-panel-overlay.tsx +8 -1
- package/src/components/field-config-panel/field-config-panel.tsx +43 -15
- package/src/components/field-config-panel/field-sidebar.tsx +91 -0
- package/src/components/field-config-panel/formula-generator.tsx +122 -0
- package/src/components/field-config-panel/formula-modal.tsx +229 -0
- package/src/components/field-config-panel/formula-workspace.tsx +116 -0
- package/src/components/field-config-panel/result-type-selector.tsx +34 -0
- package/src/components/field-sidebar/calculated-field-type-list.tsx +29 -0
- package/src/components/field-sidebar/data-model-field-type-list.tsx +10 -4
- package/src/components/field-sidebar/field-menu-group.tsx +36 -0
- package/src/components/field-sidebar/field-sidebar.tsx +14 -55
- package/src/components/field-sidebar/fillable-field-type-list.tsx +11 -9
- package/src/components/pdf-editor/pdf-editor.tsx +2 -0
- package/src/components/pdf-fields-overlay/pdf-overlay-field-calculated.tsx +15 -0
- package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +2 -0
- package/src/components/pdf-view/pdf-view-calculated.tsx +23 -0
- package/src/components/pdf-view/pdf-view.tsx +4 -0
- package/src/constants/field.constants.ts +9 -2
- package/src/constants/menu-group.ts +26 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useFormulaEditor.ts +336 -0
- package/src/hooks/usePdfFieldDnD.ts +4 -0
- package/src/interface/types.ts +38 -2
- package/src/styles/formula-modal.css +307 -0
- package/src/styles/index.css +1 -0
- package/src/utils/data-model/extract-fields.utils.ts +65 -7
- package/src/utils/formula/caret.utils.ts +125 -0
- package/src/utils/formula/dom.utils.ts +35 -0
- package/src/utils/formula/evaluate-formula.utils.ts +159 -0
- package/src/utils/formula/expression.utils.ts +99 -0
- package/src/utils/formula/format-calculated-result.utils.ts +79 -0
- package/src/utils/formula/formula-types.ts +2 -0
- package/src/utils/formula/index.ts +10 -0
- package/src/utils/formula/referenced-paths.utils.ts +18 -0
- package/src/utils/formula/render-formula.utils.ts +40 -0
- package/src/utils/formula/serialize-formula.utils.ts +40 -0
- package/src/utils/formula/validate-formula.utils.ts +94 -0
- package/src/utils/index.ts +1 -0
package/src/interface/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ export enum FieldTypeEnum {
|
|
|
2
2
|
dataModel = 'dataModel',
|
|
3
3
|
eSign = 'eSign',
|
|
4
4
|
fillable = 'fillable',
|
|
5
|
+
calculated = 'calculated',
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export enum ESignFieldType {
|
|
@@ -13,10 +14,12 @@ export enum ESignFieldType {
|
|
|
13
14
|
|
|
14
15
|
export type FillableFieldType = 'text' | 'date' | 'checkbox' | 'radio' | 'number';
|
|
15
16
|
|
|
17
|
+
export type PdfFieldSubType = FillableFieldType | ESignFieldType;
|
|
18
|
+
|
|
16
19
|
export interface PdfField {
|
|
17
20
|
id: string;
|
|
18
21
|
type: FieldTypeEnum;
|
|
19
|
-
subType?:
|
|
22
|
+
subType?: PdfFieldSubType;
|
|
20
23
|
x: number;
|
|
21
24
|
y: number;
|
|
22
25
|
page: number;
|
|
@@ -27,12 +30,14 @@ export interface PdfField {
|
|
|
27
30
|
path?: string;
|
|
28
31
|
recipient?: string;
|
|
29
32
|
description?: string;
|
|
33
|
+
formula?: StructuredFormula;
|
|
34
|
+
formulaFormat?: CalculatedFieldFormat;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
export interface FieldTypeOption {
|
|
33
38
|
label: string;
|
|
34
39
|
type: FieldTypeEnum;
|
|
35
|
-
subType?:
|
|
40
|
+
subType?: PdfFieldSubType;
|
|
36
41
|
path?: string;
|
|
37
42
|
}
|
|
38
43
|
|
|
@@ -41,11 +46,42 @@ export interface DataModelFieldGroup {
|
|
|
41
46
|
fields: FieldTypeOption[];
|
|
42
47
|
}
|
|
43
48
|
|
|
49
|
+
/** Supported formula operators */
|
|
50
|
+
export const FORMULA_OPERATORS = ['+', '-', '*', '/'];
|
|
51
|
+
export type FormulaOperator = (typeof FORMULA_OPERATORS)[number];
|
|
52
|
+
|
|
53
|
+
/** Single token in a structured formula (number, operator, paren, or field reference) */
|
|
54
|
+
export type FormulaToken =
|
|
55
|
+
| { type: 'number'; value: string }
|
|
56
|
+
| { type: 'operator'; value: FormulaOperator }
|
|
57
|
+
| { type: 'lparen'; value: '(' }
|
|
58
|
+
| { type: 'rparen'; value: ')' }
|
|
59
|
+
| { type: 'field'; path: string; label: string };
|
|
60
|
+
|
|
61
|
+
/** Structured formula representation (AST-like token list) for validation and safe editing */
|
|
62
|
+
export interface StructuredFormula {
|
|
63
|
+
tokens: FormulaToken[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Format options for a calculated field result (display and rounding) */
|
|
67
|
+
export interface CalculatedFieldFormat {
|
|
68
|
+
resultType: 'number' | 'currency' | 'percent';
|
|
69
|
+
thousandsSeparator: boolean;
|
|
70
|
+
decimals: number;
|
|
71
|
+
roundingMode: 'round' | 'floor' | 'ceil';
|
|
72
|
+
decimalSeparatorEnabled: boolean;
|
|
73
|
+
decimalSeparator: '.' | ',';
|
|
74
|
+
prefixText: string;
|
|
75
|
+
postfixText: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
44
78
|
export interface SchemaFieldBaseOptions {
|
|
45
79
|
placeholder?: any;
|
|
46
80
|
description?: any;
|
|
47
81
|
sampleData?: any;
|
|
48
82
|
showInEditor?: boolean;
|
|
83
|
+
useInCalculatedFields?: boolean;
|
|
84
|
+
useInConditionals?: boolean;
|
|
49
85
|
isHighlighted?: boolean;
|
|
50
86
|
}
|
|
51
87
|
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/* Formula modal overlay */
|
|
2
|
+
.dte-formula-modal-overlay {
|
|
3
|
+
position: fixed;
|
|
4
|
+
inset: 0;
|
|
5
|
+
z-index: var(--z-index-drawer);
|
|
6
|
+
background-color: rgba(0, 0, 0, 0.4);
|
|
7
|
+
padding: var(--spacing-2);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.dte-formula-modal {
|
|
11
|
+
background: var(--white);
|
|
12
|
+
border-radius: 8px;
|
|
13
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
14
|
+
width: 800px;
|
|
15
|
+
max-height: 85vh;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.dte-formula-modal-body {
|
|
19
|
+
padding: var(--spacing-2);
|
|
20
|
+
overflow: auto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.dte-formula-modal-columns {
|
|
24
|
+
min-height: 280px;
|
|
25
|
+
max-height: 60vh;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Field sidebar (left) */
|
|
29
|
+
.dte-formula-sidebar {
|
|
30
|
+
flex: 0 0 220px;
|
|
31
|
+
border-right: 1px solid var(--border-color);
|
|
32
|
+
padding-right: var(--spacing-2);
|
|
33
|
+
gap: var(--spacing-2);
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
min-height: 0;
|
|
37
|
+
overflow-y: auto;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.dte-formula-field-list {
|
|
41
|
+
margin: var(--spacing-0);
|
|
42
|
+
padding: var(--spacing-0);
|
|
43
|
+
list-style: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.dte-formula-field-list-item {
|
|
47
|
+
padding: var(--spacing-1) var(--spacing-2);
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
border-left: 3px solid transparent;
|
|
50
|
+
border-bottom: 1px solid var(--border-color);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.dte-formula-field-list-item:hover,
|
|
54
|
+
.dte-formula-field-list-item.--highlight {
|
|
55
|
+
color: var(--color-neutral-100, #e8e8e8);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.dte-formula-field-list-item.--selected {
|
|
59
|
+
border-left-color: var(--border-color-active, #0265dc);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.dte-formula-editor {
|
|
63
|
+
min-height: 100px;
|
|
64
|
+
max-height: 180px;
|
|
65
|
+
overflow-y: auto;
|
|
66
|
+
padding: var(--spacing-2);
|
|
67
|
+
border: 1px solid var(--border-color);
|
|
68
|
+
border-radius: 4px;
|
|
69
|
+
outline: none;
|
|
70
|
+
font-size: var(--typescale-2);
|
|
71
|
+
line-height: var(--base-line-height);
|
|
72
|
+
white-space: pre-wrap;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.dte-formula-editor:focus {
|
|
76
|
+
border-color: var(--border-color-active);
|
|
77
|
+
box-shadow: 0 0 0 1px var(--border-color-active);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.dte-formula-editor-invalid {
|
|
81
|
+
border-color: var(--danger, #dc3545);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.dte-formula-editor:empty::before {
|
|
85
|
+
content: attr(data-placeholder);
|
|
86
|
+
color: var(--color-neutral-200);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.dte-formula-chip {
|
|
90
|
+
display: inline-flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
padding: 2px 6px;
|
|
93
|
+
margin: var(--spacing-0) 2px;
|
|
94
|
+
background: var(--menu-active-color, #0265dc);
|
|
95
|
+
border-radius: var(--spacing-half, 4px);
|
|
96
|
+
color: var(--white);
|
|
97
|
+
gap: var(--spacing-half);
|
|
98
|
+
margin-bottom: var(--spacing-half);
|
|
99
|
+
cursor: grab;
|
|
100
|
+
font-weight: 500;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.dte-formula-chip-label {
|
|
104
|
+
pointer-events: none;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.dte-formula-chip-remove {
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
opacity: 0.9;
|
|
110
|
+
padding: var(--spacing-0) 2px;
|
|
111
|
+
line-height: 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.dte-formula-chip-remove:hover {
|
|
115
|
+
opacity: 1;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.dte-formula-advanced-toggle {
|
|
119
|
+
background: none;
|
|
120
|
+
border: none;
|
|
121
|
+
padding: var(--spacing-0);
|
|
122
|
+
font-size: var(--typescale-2);
|
|
123
|
+
color: var(--menu-active-color, #0265dc);
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
text-decoration: none;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.dte-formula-advanced-toggle:hover {
|
|
129
|
+
text-decoration: underline;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.dte-formula-advanced {
|
|
133
|
+
margin-top: var(--spacing-2);
|
|
134
|
+
padding-top: var(--spacing-2);
|
|
135
|
+
border-top: 1px solid var(--border-color);
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
gap: var(--spacing-2);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.dte-formula-advanced-row {
|
|
142
|
+
margin-bottom: var(--spacing-0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.dte-formula-advanced-value {
|
|
146
|
+
color: var(--color-neutral-300);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.dte-formula-advanced-range {
|
|
150
|
+
width: 100%;
|
|
151
|
+
margin: var(--spacing-0);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.dte-formula-advanced-checkbox {
|
|
155
|
+
display: flex;
|
|
156
|
+
align-items: center;
|
|
157
|
+
gap: var(--spacing-1);
|
|
158
|
+
cursor: pointer;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.dte-formula-advanced-checkbox input {
|
|
162
|
+
margin: var(--spacing-0);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.dte-formula-advanced-input {
|
|
166
|
+
flex: 1;
|
|
167
|
+
min-width: 0;
|
|
168
|
+
max-width: 160px;
|
|
169
|
+
padding: var(--spacing-1) var(--spacing-2);
|
|
170
|
+
border: 1px solid var(--border-color);
|
|
171
|
+
border-radius: 4px;
|
|
172
|
+
font-size: var(--typescale-2);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.dte-formula-modal-header {
|
|
176
|
+
padding: var(--spacing-2);
|
|
177
|
+
border-bottom: 1px solid var(--border-color);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.dte-formula-modal-footer {
|
|
181
|
+
padding: var(--spacing-2);
|
|
182
|
+
border-top: 1px solid var(--border-color);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* Token input */
|
|
186
|
+
.dte-formula-token-input-wrapper {
|
|
187
|
+
display: flex;
|
|
188
|
+
flex-direction: row;
|
|
189
|
+
align-items: stretch;
|
|
190
|
+
gap: var(--spacing-2);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.dte-formula-token-input {
|
|
194
|
+
flex: 1;
|
|
195
|
+
padding: var(--spacing-1) var(--spacing-2);
|
|
196
|
+
border: 1px solid var(--border-color);
|
|
197
|
+
border-radius: 4px;
|
|
198
|
+
outline: none;
|
|
199
|
+
flex-wrap: wrap;
|
|
200
|
+
gap: 2px;
|
|
201
|
+
cursor: text;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.dte-formula-token-input:focus {
|
|
205
|
+
border-color: var(--border-color-active);
|
|
206
|
+
box-shadow: 0 0 0 1px var(--border-color-active);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.dte-formula-placeholder {
|
|
210
|
+
color: var(--color-neutral-200);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.dte-formula-token {
|
|
214
|
+
margin: var(--spacing-half);
|
|
215
|
+
user-select: none;
|
|
216
|
+
font-size: var(--typescale-2);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.dte-formula-token-field {
|
|
220
|
+
padding: 2px var(--spacing-half);
|
|
221
|
+
background: var(--menu-active-color, #0265dc);
|
|
222
|
+
border-radius: var(--spacing-half, 4px);
|
|
223
|
+
color: var(--white);
|
|
224
|
+
flex-shrink: 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.dte-formula-token-delete {
|
|
228
|
+
display: inline-flex;
|
|
229
|
+
align-items: center;
|
|
230
|
+
justify-content: center;
|
|
231
|
+
padding: var(--spacing-0);
|
|
232
|
+
margin: var(--spacing-0);
|
|
233
|
+
margin-left: 2px;
|
|
234
|
+
border: none;
|
|
235
|
+
background: transparent;
|
|
236
|
+
color: var(--color-neutral-200);
|
|
237
|
+
cursor: pointer;
|
|
238
|
+
border-radius: 2px;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.dte-formula-token-delete:hover {
|
|
242
|
+
color: var(--danger, #dc3545);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.dte-formula-token-number {
|
|
246
|
+
font-variant-numeric: tabular-nums;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.dte-formula-caret {
|
|
250
|
+
width: 0;
|
|
251
|
+
height: 1em;
|
|
252
|
+
border-left: 2px solid var(--color-neutral-300);
|
|
253
|
+
margin: var(--spacing-0) 1px;
|
|
254
|
+
animation: dte-formula-caret-blink 1s step-end infinite;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
@keyframes dte-formula-caret-blink {
|
|
258
|
+
50% {
|
|
259
|
+
opacity: 0;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* Autosuggest - left side, always visible */
|
|
264
|
+
.dte-formula-autosuggest {
|
|
265
|
+
margin: var(--spacing-0);
|
|
266
|
+
padding: var(--spacing-0);
|
|
267
|
+
list-style: none;
|
|
268
|
+
width: 200px;
|
|
269
|
+
min-width: 200px;
|
|
270
|
+
max-height: 200px;
|
|
271
|
+
overflow-y: auto;
|
|
272
|
+
background: var(--white);
|
|
273
|
+
border: 1px solid var(--border-color);
|
|
274
|
+
border-radius: 4px;
|
|
275
|
+
flex-shrink: 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.dte-formula-autosuggest-item {
|
|
279
|
+
padding: var(--spacing-1) var(--spacing-2);
|
|
280
|
+
cursor: pointer;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.dte-formula-autosuggest-item:hover,
|
|
284
|
+
.dte-formula-autosuggest-item.--highlight {
|
|
285
|
+
color: var(--menu-active-color);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.dte-formula-autosuggest-item.empty {
|
|
289
|
+
cursor: default;
|
|
290
|
+
color: var(--color-neutral-200);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.dte-formula-box {
|
|
294
|
+
padding: var(--spacing-1) var(--spacing-2);
|
|
295
|
+
border: 1px solid var(--border-color);
|
|
296
|
+
border-radius: 4px;
|
|
297
|
+
background: var(--color-neutral-0);
|
|
298
|
+
cursor: pointer;
|
|
299
|
+
display: flex;
|
|
300
|
+
overflow-x: auto;
|
|
301
|
+
align-items: center;
|
|
302
|
+
gap: 2px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.dte-formula-box:hover {
|
|
306
|
+
border-color: var(--border-color-hover);
|
|
307
|
+
}
|
package/src/styles/index.css
CHANGED
|
@@ -2,17 +2,37 @@ import {
|
|
|
2
2
|
DataModelFieldGroup,
|
|
3
3
|
FieldTypeEnum,
|
|
4
4
|
FieldTypeOption,
|
|
5
|
+
SchemaNode,
|
|
5
6
|
SchemaObject,
|
|
7
|
+
SchemaSimpleNumber,
|
|
6
8
|
} from '../../interface/types';
|
|
7
9
|
|
|
10
|
+
function isSchemaSimpleNumber(node: SchemaNode): node is SchemaSimpleNumber {
|
|
11
|
+
return node.type === 'number';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function useInCalculatedFields(node: SchemaNode): boolean {
|
|
15
|
+
return node.options?.useInCalculatedFields === true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ExtractGroupedFieldsOptions {
|
|
19
|
+
/** When true, only include fields with SchemaFieldBaseOptions.useInCalculatedFields === true */
|
|
20
|
+
onlyUseInCalculatedFields?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
8
23
|
/**
|
|
9
24
|
* Utility function to extract grouped fields from DataModel structure
|
|
10
25
|
* Filters out array types and only processes object properties, generating correct paths for data replacement
|
|
11
26
|
*/
|
|
27
|
+
const DEFAULT_GROUP_NAME = 'Fields';
|
|
28
|
+
|
|
12
29
|
export const extractGroupedFieldsFromDataModel = (
|
|
13
30
|
dataModel: SchemaObject,
|
|
31
|
+
options?: ExtractGroupedFieldsOptions,
|
|
14
32
|
): DataModelFieldGroup[] => {
|
|
33
|
+
const onlyUseInCalculatedFields = options?.onlyUseInCalculatedFields === true;
|
|
15
34
|
const groups: DataModelFieldGroup[] = [];
|
|
35
|
+
const topLevelFields: FieldTypeOption[] = [];
|
|
16
36
|
|
|
17
37
|
if (!dataModel?.properties) {
|
|
18
38
|
return groups;
|
|
@@ -23,7 +43,13 @@ export const extractGroupedFieldsFromDataModel = (
|
|
|
23
43
|
|
|
24
44
|
if (property.type === 'object' && property.properties) {
|
|
25
45
|
const fields: FieldTypeOption[] = [];
|
|
26
|
-
extractFieldsRecursive(
|
|
46
|
+
extractFieldsRecursive(
|
|
47
|
+
property.properties,
|
|
48
|
+
key,
|
|
49
|
+
fields,
|
|
50
|
+
key,
|
|
51
|
+
onlyUseInCalculatedFields,
|
|
52
|
+
);
|
|
27
53
|
|
|
28
54
|
if (fields.length > 0) {
|
|
29
55
|
groups.push({
|
|
@@ -33,18 +59,42 @@ export const extractGroupedFieldsFromDataModel = (
|
|
|
33
59
|
}
|
|
34
60
|
} else if (property.type === 'array' && property.items) {
|
|
35
61
|
// Skip array types - filter them out from the data model
|
|
62
|
+
} else {
|
|
63
|
+
// First-level simple field (string, number, boolean) – add under default group
|
|
64
|
+
const rawType = (property as { type?: string }).type;
|
|
65
|
+
const isLeaf =
|
|
66
|
+
property.type === 'string' ||
|
|
67
|
+
isSchemaSimpleNumber(property as SchemaNode) ||
|
|
68
|
+
rawType === 'boolean';
|
|
69
|
+
const includeField =
|
|
70
|
+
!onlyUseInCalculatedFields || useInCalculatedFields(property as SchemaNode);
|
|
71
|
+
if (isLeaf && includeField) {
|
|
72
|
+
topLevelFields.push({
|
|
73
|
+
label: (property as SchemaNode).title ?? key,
|
|
74
|
+
type: FieldTypeEnum.dataModel,
|
|
75
|
+
path: key,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
36
78
|
}
|
|
37
79
|
});
|
|
38
80
|
|
|
81
|
+
if (topLevelFields.length > 0) {
|
|
82
|
+
groups.unshift({
|
|
83
|
+
groupName: DEFAULT_GROUP_NAME,
|
|
84
|
+
fields: topLevelFields,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
39
88
|
return groups;
|
|
40
89
|
};
|
|
41
90
|
|
|
42
91
|
// Recursive function to extract fields from nested structures
|
|
43
92
|
const extractFieldsRecursive = (
|
|
44
|
-
properties:
|
|
93
|
+
properties: Record<string, SchemaNode>,
|
|
45
94
|
basePath: string,
|
|
46
95
|
fields: FieldTypeOption[],
|
|
47
96
|
groupName: string,
|
|
97
|
+
onlyUseInCalculatedFields: boolean,
|
|
48
98
|
): void => {
|
|
49
99
|
Object.keys(properties).forEach(fieldKey => {
|
|
50
100
|
const fieldProperty = properties[fieldKey];
|
|
@@ -52,11 +102,13 @@ const extractFieldsRecursive = (
|
|
|
52
102
|
? `${basePath}.${fieldKey}`
|
|
53
103
|
: `${basePath}.${fieldKey}`;
|
|
54
104
|
|
|
55
|
-
|
|
105
|
+
const rawType = (fieldProperty as { type?: string }).type;
|
|
106
|
+
const isLeaf =
|
|
56
107
|
fieldProperty.type === 'string' ||
|
|
57
|
-
fieldProperty
|
|
58
|
-
|
|
59
|
-
)
|
|
108
|
+
isSchemaSimpleNumber(fieldProperty) ||
|
|
109
|
+
rawType === 'boolean';
|
|
110
|
+
const includeField = !onlyUseInCalculatedFields || useInCalculatedFields(fieldProperty);
|
|
111
|
+
if (isLeaf && includeField) {
|
|
60
112
|
// Leaf property - add as a field
|
|
61
113
|
const label = fieldProperty.title ?? fieldKey;
|
|
62
114
|
fields.push({
|
|
@@ -65,7 +117,13 @@ const extractFieldsRecursive = (
|
|
|
65
117
|
path: currentPath,
|
|
66
118
|
});
|
|
67
119
|
} else if (fieldProperty.type === 'object' && fieldProperty.properties) {
|
|
68
|
-
extractFieldsRecursive(
|
|
120
|
+
extractFieldsRecursive(
|
|
121
|
+
fieldProperty.properties,
|
|
122
|
+
currentPath,
|
|
123
|
+
fields,
|
|
124
|
+
groupName,
|
|
125
|
+
onlyUseInCalculatedFields,
|
|
126
|
+
);
|
|
69
127
|
} else if (fieldProperty.type === 'array' && fieldProperty.items) {
|
|
70
128
|
// Skip array types - filter them out from the data model
|
|
71
129
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { placeCaretAtEnd, readExpressionFromEditor } from './dom.utils';
|
|
2
|
+
|
|
3
|
+
function getExpressionBeforeCaret(element: HTMLElement): string {
|
|
4
|
+
const selection = window.getSelection();
|
|
5
|
+
if (!selection || selection.rangeCount === 0) {
|
|
6
|
+
return readExpressionFromEditor(element);
|
|
7
|
+
}
|
|
8
|
+
const range = selection.getRangeAt(0);
|
|
9
|
+
const endContainer = range.endContainer;
|
|
10
|
+
const endOffset = range.endOffset;
|
|
11
|
+
const parts: string[] = [];
|
|
12
|
+
|
|
13
|
+
const appendNode = (node: ChildNode) => {
|
|
14
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
15
|
+
parts.push(node.textContent ?? '');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (node instanceof HTMLElement && node.dataset.field) {
|
|
19
|
+
parts.push(node.dataset.field);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
parts.push(node.textContent ?? '');
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const getTopLevelChild = (node: Node): ChildNode | null => {
|
|
26
|
+
let current: Node | null = node;
|
|
27
|
+
while (current && current.parentNode !== element) {
|
|
28
|
+
current = current.parentNode;
|
|
29
|
+
}
|
|
30
|
+
return current && current.parentNode === element ? (current as ChildNode) : null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
if (endContainer === element) {
|
|
34
|
+
element.childNodes.forEach((node, index) => {
|
|
35
|
+
if (index < endOffset) {
|
|
36
|
+
appendNode(node);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return parts.join('').replace(/\u00a0/g, ' ');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const topChild = getTopLevelChild(endContainer);
|
|
43
|
+
if (!topChild) {
|
|
44
|
+
return readExpressionFromEditor(element);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const node of Array.from(element.childNodes)) {
|
|
48
|
+
if (node === topChild) {
|
|
49
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
50
|
+
parts.push((node.textContent ?? '').slice(0, endOffset));
|
|
51
|
+
} else if (node instanceof HTMLElement && node.dataset.field) {
|
|
52
|
+
if (endOffset > 0) {
|
|
53
|
+
parts.push(node.dataset.field);
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
parts.push((node.textContent ?? '').slice(0, endOffset));
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
appendNode(node);
|
|
61
|
+
}
|
|
62
|
+
return parts.join('').replace(/\u00a0/g, ' ');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getCaretExpressionIndex(element: HTMLElement): number {
|
|
66
|
+
return getExpressionBeforeCaret(element).length;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function setCaretByExpressionIndex(element: HTMLElement, index: number): void {
|
|
70
|
+
const selection = window.getSelection();
|
|
71
|
+
if (!selection) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
let remaining = index;
|
|
75
|
+
let targetNode: Node | null = null;
|
|
76
|
+
let targetOffset = 0;
|
|
77
|
+
|
|
78
|
+
const getNodeLength = (node: ChildNode): number => {
|
|
79
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
80
|
+
return node.textContent?.length ?? 0;
|
|
81
|
+
}
|
|
82
|
+
if (node instanceof HTMLElement && node.dataset.field) {
|
|
83
|
+
return node.dataset.field?.length ?? 0;
|
|
84
|
+
}
|
|
85
|
+
return node.textContent?.length ?? 0;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
for (const node of Array.from(element.childNodes)) {
|
|
89
|
+
const len = getNodeLength(node);
|
|
90
|
+
if (remaining <= len) {
|
|
91
|
+
targetNode = node;
|
|
92
|
+
targetOffset = remaining;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
remaining -= len;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!targetNode) {
|
|
99
|
+
placeCaretAtEnd(element);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const range = document.createRange();
|
|
104
|
+
if (targetNode.nodeType === Node.TEXT_NODE) {
|
|
105
|
+
range.setStart(targetNode, targetOffset);
|
|
106
|
+
} else if (targetNode instanceof HTMLElement && targetNode.dataset.field) {
|
|
107
|
+
range.setStartAfter(targetNode);
|
|
108
|
+
} else if (targetNode instanceof HTMLElement) {
|
|
109
|
+
const textNode = Array.from(targetNode.childNodes).find(
|
|
110
|
+
n => n.nodeType === Node.TEXT_NODE,
|
|
111
|
+
) as Text | undefined;
|
|
112
|
+
if (textNode) {
|
|
113
|
+
range.setStart(textNode, Math.min(targetOffset, textNode.textContent?.length ?? 0));
|
|
114
|
+
} else if (targetOffset > 0) {
|
|
115
|
+
range.setStartAfter(targetNode);
|
|
116
|
+
} else {
|
|
117
|
+
range.setStartBefore(targetNode);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
range.setStart(targetNode, targetOffset);
|
|
121
|
+
}
|
|
122
|
+
range.collapse(true);
|
|
123
|
+
selection.removeAllRanges();
|
|
124
|
+
selection.addRange(range);
|
|
125
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const escapeHtml = (value: string): string =>
|
|
2
|
+
value
|
|
3
|
+
.replace(/&/g, '&')
|
|
4
|
+
.replace(/</g, '<')
|
|
5
|
+
.replace(/>/g, '>')
|
|
6
|
+
.replace(/"/g, '"')
|
|
7
|
+
.replace(/'/g, ''');
|
|
8
|
+
|
|
9
|
+
export function readExpressionFromEditor(node: HTMLElement): string {
|
|
10
|
+
const parts: string[] = [];
|
|
11
|
+
node.childNodes.forEach(child => {
|
|
12
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
13
|
+
parts.push(child.textContent ?? '');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (child instanceof HTMLElement && child.dataset.field) {
|
|
17
|
+
parts.push(child.dataset.field);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
parts.push(child.textContent ?? '');
|
|
21
|
+
});
|
|
22
|
+
return parts.join('').replace(/\u00a0/g, ' ');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function placeCaretAtEnd(element: HTMLElement): void {
|
|
26
|
+
const selection = window.getSelection();
|
|
27
|
+
if (!selection) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const range = document.createRange();
|
|
31
|
+
range.selectNodeContents(element);
|
|
32
|
+
range.collapse(false);
|
|
33
|
+
selection.removeAllRanges();
|
|
34
|
+
selection.addRange(range);
|
|
35
|
+
}
|