ngx-t-forms 2.0.30 → 2.0.32
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/fesm2022/ngx-t-forms-auto-complete-input-element.component-CaXs4561.mjs +104 -0
- package/fesm2022/ngx-t-forms-auto-complete-input-element.component-CaXs4561.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-basic-input-input-element.component-Dotyd-Qs.mjs +85 -0
- package/fesm2022/ngx-t-forms-basic-input-input-element.component-Dotyd-Qs.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-calculated-field-rules.component-BhxT6tRq.mjs +643 -0
- package/fesm2022/ngx-t-forms-calculated-field-rules.component-BhxT6tRq.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-chip-options-creator-editor.component-d4QeVhsp.mjs +97 -0
- package/fesm2022/ngx-t-forms-chip-options-creator-editor.component-d4QeVhsp.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-config-mscoa-additional-inputs.component-Gn8exJ9a.mjs +195 -0
- package/fesm2022/ngx-t-forms-config-mscoa-additional-inputs.component-Gn8exJ9a.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-data-source-picker.component-Ebf_if9j.mjs +261 -0
- package/fesm2022/ngx-t-forms-data-source-picker.component-Ebf_if9j.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-date-picker-input-element.component-kdinBGRA.mjs +85 -0
- package/fesm2022/ngx-t-forms-date-picker-input-element.component-kdinBGRA.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-date-range-picker-input-element.component-4W6uvrDU.mjs +156 -0
- package/fesm2022/ngx-t-forms-date-range-picker-input-element.component-4W6uvrDU.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-document-list-label-config-editor.component-CR6EvgJO.mjs +368 -0
- package/fesm2022/ngx-t-forms-document-list-label-config-editor.component-CR6EvgJO.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-document-picker.component-BThdRFec.mjs +704 -0
- package/fesm2022/ngx-t-forms-document-picker.component-BThdRFec.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-editor-input-element.component-1X6uAPeZ.mjs +294 -0
- package/fesm2022/ngx-t-forms-editor-input-element.component-1X6uAPeZ.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-editor-js-input.component-5MD8wRj0.mjs +240 -0
- package/fesm2022/ngx-t-forms-editor-js-input.component-5MD8wRj0.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-file-upload-input-element.component-BAtuymMY.mjs +205 -0
- package/fesm2022/ngx-t-forms-file-upload-input-element.component-BAtuymMY.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-form-input-selector.component-B42xP3jh.mjs +86 -0
- package/fesm2022/ngx-t-forms-form-input-selector.component-B42xP3jh.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-form-json-view.component-DnnLXqR0.mjs +22 -0
- package/fesm2022/ngx-t-forms-form-json-view.component-DnnLXqR0.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-form-payload-projection.component-Ip9ewB18.mjs +179 -0
- package/fesm2022/ngx-t-forms-form-payload-projection.component-Ip9ewB18.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-form-section-stepper.component-BPgPfZSy.mjs +319 -0
- package/fesm2022/ngx-t-forms-form-section-stepper.component-BPgPfZSy.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-forms-builder-menu.component-Dv0Dfw79.mjs +379 -0
- package/fesm2022/ngx-t-forms-forms-builder-menu.component-Dv0Dfw79.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-geo-location.component-Bmd84Gcb.mjs +124 -0
- package/fesm2022/ngx-t-forms-geo-location.component-Bmd84Gcb.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-getInputIcon-B4ADgevZ.mjs +31 -0
- package/fesm2022/ngx-t-forms-getInputIcon-B4ADgevZ.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-image-capture-input-element.component-CUd04Ghl.mjs +180 -0
- package/fesm2022/ngx-t-forms-image-capture-input-element.component-CUd04Ghl.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-index-BcrQ01DQ.mjs +2 -0
- package/fesm2022/ngx-t-forms-index-BcrQ01DQ.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-input-custom.component-Cn-KH0Lb.mjs +105 -0
- package/fesm2022/ngx-t-forms-input-custom.component-Cn-KH0Lb.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-input-editor.component-DLru1Ezu.mjs +193 -0
- package/fesm2022/ngx-t-forms-input-editor.component-DLru1Ezu.mjs.map +1 -0
- package/fesm2022/{ngx-t-forms-map-mat-options-keys-SM5XM9uy.mjs → ngx-t-forms-map-mat-options-keys-CVlPdrCO.mjs} +12 -14
- package/fesm2022/ngx-t-forms-map-mat-options-keys-CVlPdrCO.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-mat-chip-list-editor.component-BWisS3Em.mjs +66 -0
- package/fesm2022/ngx-t-forms-mat-chip-list-editor.component-BWisS3Em.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-mat-slider-editor.component-CTSBrM-j.mjs +211 -0
- package/fesm2022/ngx-t-forms-mat-slider-editor.component-CTSBrM-j.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-mat-slider-toggle-editor.component-CcYiwx-8.mjs +165 -0
- package/fesm2022/ngx-t-forms-mat-slider-toggle-editor.component-CcYiwx-8.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-missing-form-configs.component-DxdynZY6.mjs +38 -0
- package/fesm2022/ngx-t-forms-missing-form-configs.component-DxdynZY6.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-mscoa-chart-toolbar.component-D4Xa_Yi0.mjs +38 -0
- package/fesm2022/ngx-t-forms-mscoa-chart-toolbar.component-D4Xa_Yi0.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-mscoa-error-display.component-99DpVSy7.mjs +126 -0
- package/fesm2022/ngx-t-forms-mscoa-error-display.component-99DpVSy7.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-mscoa-segment-config.component-Bo0aDEMy.mjs +447 -0
- package/fesm2022/ngx-t-forms-mscoa-segment-config.component-Bo0aDEMy.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-mscoa-temporary-hint.component-B1Z-IXSL.mjs +74 -0
- package/fesm2022/ngx-t-forms-mscoa-temporary-hint.component-B1Z-IXSL.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-multiple-input-input-element.component-C8JP3D6r.mjs +905 -0
- package/fesm2022/ngx-t-forms-multiple-input-input-element.component-C8JP3D6r.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-ngx-t-forms-C2G8_WQk.mjs +20310 -0
- package/fesm2022/ngx-t-forms-ngx-t-forms-C2G8_WQk.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-paginated-selection-table-0OI1ikWW.mjs +555 -0
- package/fesm2022/ngx-t-forms-paginated-selection-table-0OI1ikWW.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-pipeline-generator.component-CZ21sd77.mjs +748 -0
- package/fesm2022/ngx-t-forms-pipeline-generator.component-CZ21sd77.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-record-list-manager.component-CykBq_nW.mjs +358 -0
- package/fesm2022/ngx-t-forms-record-list-manager.component-CykBq_nW.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-required-inputs.component-ONbhxVSH.mjs +272 -0
- package/fesm2022/ngx-t-forms-required-inputs.component-ONbhxVSH.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-rest-api-call-setup.component-WPUxtY7Q.mjs +398 -0
- package/fesm2022/ngx-t-forms-rest-api-call-setup.component-WPUxtY7Q.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-search-field.component-B2ZO7lqO.mjs +38 -0
- package/fesm2022/ngx-t-forms-search-field.component-B2ZO7lqO.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-section-report.component-C1w16LYm.mjs +98 -0
- package/fesm2022/ngx-t-forms-section-report.component-C1w16LYm.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-select-input-element.component-CWcywuS6.mjs +150 -0
- package/fesm2022/ngx-t-forms-select-input-element.component-CWcywuS6.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-selection-options-editor.component-KjbZhc2u.mjs +169 -0
- package/fesm2022/ngx-t-forms-selection-options-editor.component-KjbZhc2u.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-t-workflow-picker.component-CtavFAUq.mjs +204 -0
- package/fesm2022/ngx-t-forms-t-workflow-picker.component-CtavFAUq.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-textarea-input-element.component-DkJkBQif.mjs +95 -0
- package/fesm2022/ngx-t-forms-textarea-input-element.component-DkJkBQif.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-toggle-input-element.component-Dr7MNli8.mjs +82 -0
- package/fesm2022/ngx-t-forms-toggle-input-element.component-Dr7MNli8.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-validators-config.component-BknyAmV_.mjs +574 -0
- package/fesm2022/ngx-t-forms-validators-config.component-BknyAmV_.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-workflow-adjudication.component-CPvwm7f4.mjs +1303 -0
- package/fesm2022/ngx-t-forms-workflow-adjudication.component-CPvwm7f4.mjs.map +1 -0
- package/fesm2022/ngx-t-forms.mjs +2 -1
- package/fesm2022/ngx-t-forms.mjs.map +1 -1
- package/package.json +20 -18
- package/styles/_editor-mixins.scss +62 -0
- package/styles/_json-editor-syntax.scss +26 -0
- package/styles/_signature-pad.scss +26 -0
- package/styles/_tokens.scss +148 -0
- package/types/ngx-t-forms.d.ts +1921 -733
- package/fesm2022/ngx-t-forms-calculated-field-rules.component-Ct6_c_Lj.mjs +0 -313
- package/fesm2022/ngx-t-forms-calculated-field-rules.component-Ct6_c_Lj.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-chip-options-creator-editor.component-yuM1KHho.mjs +0 -191
- package/fesm2022/ngx-t-forms-chip-options-creator-editor.component-yuM1KHho.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-config-mscoa-additional-inputs.component-BptpYSe-.mjs +0 -207
- package/fesm2022/ngx-t-forms-config-mscoa-additional-inputs.component-BptpYSe-.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-data-source-picker.component-Badna1Rl.mjs +0 -204
- package/fesm2022/ngx-t-forms-data-source-picker.component-Badna1Rl.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-document-list-label-config-editor.component-2_8XzUgD.mjs +0 -289
- package/fesm2022/ngx-t-forms-document-list-label-config-editor.component-2_8XzUgD.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-form-input-selector.component-DV4Sts9F.mjs +0 -134
- package/fesm2022/ngx-t-forms-form-input-selector.component-DV4Sts9F.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-form-json-view.component-B8seYzMQ.mjs +0 -22
- package/fesm2022/ngx-t-forms-form-json-view.component-B8seYzMQ.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-form-section-stepper.component-x_83iAWA.mjs +0 -281
- package/fesm2022/ngx-t-forms-form-section-stepper.component-x_83iAWA.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-forms-builder-menu.component-UWo_dyVt.mjs +0 -345
- package/fesm2022/ngx-t-forms-forms-builder-menu.component-UWo_dyVt.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-input-editor.component-B_kkOoEO.mjs +0 -147
- package/fesm2022/ngx-t-forms-input-editor.component-B_kkOoEO.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-map-mat-options-keys-SM5XM9uy.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-mat-chip-list-editor.component-C41AL9Et.mjs +0 -105
- package/fesm2022/ngx-t-forms-mat-chip-list-editor.component-C41AL9Et.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-mat-slider-editor.component-BWe8U-sI.mjs +0 -109
- package/fesm2022/ngx-t-forms-mat-slider-editor.component-BWe8U-sI.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-mat-slider-toggle-editor.component-B_XlkHuK.mjs +0 -155
- package/fesm2022/ngx-t-forms-mat-slider-toggle-editor.component-B_XlkHuK.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-missing-form-configs.component-DPNNyKkt.mjs +0 -28
- package/fesm2022/ngx-t-forms-missing-form-configs.component-DPNNyKkt.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-mscoa-chart-toolbar.component-DY1QnG08.mjs +0 -43
- package/fesm2022/ngx-t-forms-mscoa-chart-toolbar.component-DY1QnG08.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-mscoa-error-display.component-CRc_4l3l.mjs +0 -116
- package/fesm2022/ngx-t-forms-mscoa-error-display.component-CRc_4l3l.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-mscoa-segment-config.component-Ckr_nuZF.mjs +0 -296
- package/fesm2022/ngx-t-forms-mscoa-segment-config.component-Ckr_nuZF.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-mscoa-temporary-hint.component-GYxT-56Y.mjs +0 -83
- package/fesm2022/ngx-t-forms-mscoa-temporary-hint.component-GYxT-56Y.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-ngx-t-forms-DP2koSL5.mjs +0 -17401
- package/fesm2022/ngx-t-forms-ngx-t-forms-DP2koSL5.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-pipeline-generator.component-BxHetD_Q.mjs +0 -613
- package/fesm2022/ngx-t-forms-pipeline-generator.component-BxHetD_Q.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-record-list-manager.component-BQuMkoXo.mjs +0 -269
- package/fesm2022/ngx-t-forms-record-list-manager.component-BQuMkoXo.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-required-inputs.component-CLyq9dIR.mjs +0 -190
- package/fesm2022/ngx-t-forms-required-inputs.component-CLyq9dIR.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-rest-api-call-setup.component-CWeIUKLz.mjs +0 -291
- package/fesm2022/ngx-t-forms-rest-api-call-setup.component-CWeIUKLz.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-section-report.component-BtaF39WD.mjs +0 -156
- package/fesm2022/ngx-t-forms-section-report.component-BtaF39WD.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-selection-options-editor.component-B4cEGWrK.mjs +0 -186
- package/fesm2022/ngx-t-forms-selection-options-editor.component-B4cEGWrK.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-t-workflow-picker.component-BkVN4Wdk.mjs +0 -187
- package/fesm2022/ngx-t-forms-t-workflow-picker.component-BkVN4Wdk.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-validators-config.component-Cq07Er-G.mjs +0 -215
- package/fesm2022/ngx-t-forms-validators-config.component-Cq07Er-G.mjs.map +0 -1
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, computed, inject, ElementRef, PLATFORM_ID, effect, ViewEncapsulation, ChangeDetectionStrategy, Component, output, signal, viewChild, Input } from '@angular/core';
|
|
3
|
+
import { isPlatformBrowser, CommonModule } from '@angular/common';
|
|
4
|
+
import * as i1 from '@angular/forms';
|
|
5
|
+
import { NgControl, FormsModule } from '@angular/forms';
|
|
6
|
+
import * as i8 from '@angular/material/select';
|
|
7
|
+
import { MatSelectModule } from '@angular/material/select';
|
|
8
|
+
import * as i2$1 from '@angular/material/form-field';
|
|
9
|
+
import { MatFormFieldModule, MatFormFieldControl } from '@angular/material/form-field';
|
|
10
|
+
import * as i2$2 from '@angular/material/input';
|
|
11
|
+
import { MatInputModule } from '@angular/material/input';
|
|
12
|
+
import * as i3 from '@angular/material/icon';
|
|
13
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
14
|
+
import * as i3$1 from '@angular/material/card';
|
|
15
|
+
import { MatCardModule } from '@angular/material/card';
|
|
16
|
+
import * as i1$1 from '@angular/material/button';
|
|
17
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
18
|
+
import * as i4 from '@angular/material/chips';
|
|
19
|
+
import { MatChipsModule } from '@angular/material/chips';
|
|
20
|
+
import * as i2 from '@angular/cdk/overlay';
|
|
21
|
+
import { OverlayModule } from '@angular/cdk/overlay';
|
|
22
|
+
import { Subject } from 'rxjs';
|
|
23
|
+
import { v4 } from 'uuid';
|
|
24
|
+
import { CalculationFunctions } from 'ngx-t-forms-types';
|
|
25
|
+
import { _ as _isEqual } from './ngx-t-forms-ngx-t-forms-C2G8_WQk.mjs';
|
|
26
|
+
import katex from 'katex';
|
|
27
|
+
import { parse } from 'mathjs';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Parses a strictly-infix expression (the format `math.evaluate` consumes) and,
|
|
31
|
+
* when it is syntactically valid, returns its LaTeX form for KaTeX to render.
|
|
32
|
+
*
|
|
33
|
+
* The expression is never rewritten — what the user types is what gets stored
|
|
34
|
+
* and later evaluated by mathjs. This only *reads* it to derive a preview and a
|
|
35
|
+
* friendly validity message; an empty expression is treated as "not yet valid"
|
|
36
|
+
* rather than an error.
|
|
37
|
+
*/
|
|
38
|
+
function parseInfixFormula(expression) {
|
|
39
|
+
const trimmed = expression.trim();
|
|
40
|
+
if (!trimmed) {
|
|
41
|
+
return { tex: null, error: null };
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
return { tex: parse(trimmed).toTex(), error: null };
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return { tex: null, error: friendlyMessage(error) };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Maps mathjs' technical parser errors onto guidance a non-technical user can act on. */
|
|
51
|
+
function friendlyMessage(error) {
|
|
52
|
+
const raw = error instanceof Error ? error.message : '';
|
|
53
|
+
if (/end of expression/i.test(raw)) {
|
|
54
|
+
return 'The equation looks unfinished — add the missing number or variable.';
|
|
55
|
+
}
|
|
56
|
+
if (/parenthesis|bracket/i.test(raw)) {
|
|
57
|
+
return 'Check that every opening bracket "(" has a matching closing bracket ")".';
|
|
58
|
+
}
|
|
59
|
+
if (/value expected/i.test(raw)) {
|
|
60
|
+
return 'An operator (like + or ×) is missing a number or variable next to it.';
|
|
61
|
+
}
|
|
62
|
+
if (/unexpected/i.test(raw)) {
|
|
63
|
+
return 'There is an unexpected symbol in the equation.';
|
|
64
|
+
}
|
|
65
|
+
return 'This equation is not valid yet.';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Read-only "natural maths" preview of a strictly-infix expression.
|
|
70
|
+
*
|
|
71
|
+
* It parses the expression with mathjs and renders the result as MathML via
|
|
72
|
+
* KaTeX (no stylesheet required — the browser draws the glyphs natively). The
|
|
73
|
+
* expression itself is never modified, so what is previewed is exactly what
|
|
74
|
+
* `math.evaluate` will later consume.
|
|
75
|
+
*
|
|
76
|
+
* Internal to `t-dynamic-data-edit` — not part of the library's public surface.
|
|
77
|
+
*/
|
|
78
|
+
class MathPreviewComponent {
|
|
79
|
+
#host;
|
|
80
|
+
#isBrowser;
|
|
81
|
+
constructor() {
|
|
82
|
+
/** Strictly-infix expression to render (mathjs syntax, e.g. `((a+2)/(b-1))*2`). */
|
|
83
|
+
this.expression = input('', ...(ngDevMode ? [{ debugName: "expression" }] : /* istanbul ignore next */ []));
|
|
84
|
+
/** Text shown when there is nothing to preview. */
|
|
85
|
+
this.placeholder = input('Nothing to preview yet', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
86
|
+
/** `'empty'` when no expression is set, otherwise `'rendered'`. */
|
|
87
|
+
this.state = computed(() => this.expression().trim() ? 'rendered' : 'empty', ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
88
|
+
this.#host = inject(ElementRef);
|
|
89
|
+
this.#isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
90
|
+
effect(() => {
|
|
91
|
+
const expression = this.expression().trim();
|
|
92
|
+
const placeholder = this.placeholder();
|
|
93
|
+
const element = this.#host.nativeElement;
|
|
94
|
+
// On the server KaTeX has no DOM to render into — fall back to plain text.
|
|
95
|
+
if (!this.#isBrowser) {
|
|
96
|
+
element.textContent = expression || placeholder;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (!expression) {
|
|
100
|
+
element.textContent = placeholder;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const { tex } = parseInfixFormula(expression);
|
|
104
|
+
if (tex == null) {
|
|
105
|
+
// Mid-edit / not yet valid: show the raw infix until it parses cleanly.
|
|
106
|
+
element.textContent = expression;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
katex.render(tex, element, {
|
|
110
|
+
output: 'mathml',
|
|
111
|
+
throwOnError: false,
|
|
112
|
+
displayMode: true,
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: MathPreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
117
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: MathPreviewComponent, isStandalone: true, selector: "lib-math-preview", inputs: { expression: { classPropertyName: "expression", publicName: "expression", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-state": "state()" }, classAttribute: "lib-math-preview" }, ngImport: i0, template: '', isInline: true, styles: [":host{display:flex;align-items:center;justify-content:center;min-height:3rem;padding:.5rem .75rem;overflow-x:auto;color:var(--lib-forms-on-surface);font-size:1.375rem;line-height:1.4;text-align:center}:host([data-state=empty]){color:var(--lib-forms-on-surface-variant);font-size:.9375rem;font-style:italic}:host ::ng-deep math{max-width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
118
|
+
}
|
|
119
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: MathPreviewComponent, decorators: [{
|
|
120
|
+
type: Component,
|
|
121
|
+
args: [{ selector: 'lib-math-preview', template: '', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, host: {
|
|
122
|
+
'class': 'lib-math-preview',
|
|
123
|
+
'[attr.data-state]': 'state()',
|
|
124
|
+
}, styles: [":host{display:flex;align-items:center;justify-content:center;min-height:3rem;padding:.5rem .75rem;overflow-x:auto;color:var(--lib-forms-on-surface);font-size:1.375rem;line-height:1.4;text-align:center}:host([data-state=empty]){color:var(--lib-forms-on-surface-variant);font-size:.9375rem;font-style:italic}:host ::ng-deep math{max-width:100%}\n"] }]
|
|
125
|
+
}], ctorParameters: () => [], propDecorators: { expression: [{ type: i0.Input, args: [{ isSignal: true, alias: "expression", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }] } });
|
|
126
|
+
|
|
127
|
+
/** Escapes a string for safe inclusion as a literal inside a `RegExp`. */
|
|
128
|
+
function escapeRegExp(value) {
|
|
129
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Internal calculated-field-rules editor implementing
|
|
133
|
+
* `MatFormFieldControl<CalculatedFieldRules | undefined>` and
|
|
134
|
+
* `ControlValueAccessor`.
|
|
135
|
+
*
|
|
136
|
+
* Rendered by `t-dynamic-data-edit`, which supplies the surrounding field label,
|
|
137
|
+
* hint, and validation errors — so this component owns only its two modes: a
|
|
138
|
+
* read-only formula summary (view) and a sectioned editor (edit) for the
|
|
139
|
+
* formula, its variables, the optional formula source link, and the rounding
|
|
140
|
+
* settings.
|
|
141
|
+
*
|
|
142
|
+
* NOTE: `MatFormFieldControl<T>` and `ControlValueAccessor` mandate plain
|
|
143
|
+
* mutable `value` / `placeholder` / `required` / `disabled` / `id` members,
|
|
144
|
+
* `@Input()` decorators on them, the `focused` / `touched` / `stateChanges` /
|
|
145
|
+
* `onChange` / `onTouched` members, and the `useExisting` provider. These are
|
|
146
|
+
* framework wiring, not defects — the CLAUDE.md bans on `@Input()` /
|
|
147
|
+
* `useExisting` do not apply to MatFormFieldControl integration. Genuinely
|
|
148
|
+
* internal state is signal-based.
|
|
149
|
+
*/
|
|
150
|
+
class CalculatedFieldRulesComponent {
|
|
151
|
+
static { this.nextId = 0; }
|
|
152
|
+
/** Index of the `$` that opened the picker, and the caret at that moment. */
|
|
153
|
+
#dollarIndex;
|
|
154
|
+
#cursorPosition;
|
|
155
|
+
#elementRef;
|
|
156
|
+
constructor() {
|
|
157
|
+
// #region MatFormFieldControl framework members (plain mutable — not signals)
|
|
158
|
+
this.placeholder = '';
|
|
159
|
+
this.required = false;
|
|
160
|
+
this.disabled = false;
|
|
161
|
+
this.stateChanges = new Subject();
|
|
162
|
+
this.controlType = 'app-calculated-field-rules';
|
|
163
|
+
this.id = `app-calculated-field-rules-${CalculatedFieldRulesComponent.nextId++}`;
|
|
164
|
+
this.focused = false;
|
|
165
|
+
this.touched = false;
|
|
166
|
+
this.describedBy = '';
|
|
167
|
+
// #endregion
|
|
168
|
+
// #region Non-MFC inputs / outputs (contract preserved exactly)
|
|
169
|
+
/** Owning input definition; supplies table mode and the formula-source map. */
|
|
170
|
+
this.mapToData = input(undefined, ...(ngDevMode ? [{ debugName: "mapToData" }] : /* istanbul ignore next */ []));
|
|
171
|
+
/** All form inputs available to bind as calculation variables. */
|
|
172
|
+
this.formInputs = input([], ...(ngDevMode ? [{ debugName: "formInputs" }] : /* istanbul ignore next */ []));
|
|
173
|
+
/** Validation errors surfaced by the parent for error-state derivation. */
|
|
174
|
+
this.errors = input([], ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
|
|
175
|
+
/** Retained on the contract; reserved for path-scoped value emission. */
|
|
176
|
+
this.valueChange2dWithPath = output();
|
|
177
|
+
/** Emits the persisted rules whenever the editor commits a new value. */
|
|
178
|
+
this.valueChanged = output();
|
|
179
|
+
// #endregion
|
|
180
|
+
// #region Internal signal state
|
|
181
|
+
/** Whether the editor card is open (edit mode) versus the summary (view). */
|
|
182
|
+
this.isEditing = signal(false, ...(ngDevMode ? [{ debugName: "isEditing" }] : /* istanbul ignore next */ []));
|
|
183
|
+
/** Working copy edited in the card; committed to `value` on save. */
|
|
184
|
+
this.tempValue = signal({}, ...(ngDevMode ? [{ debugName: "tempValue" }] : /* istanbul ignore next */ []));
|
|
185
|
+
/** Whether the inline `$` field-picker is open. */
|
|
186
|
+
this.pickerOpen = signal(false, ...(ngDevMode ? [{ debugName: "pickerOpen" }] : /* istanbul ignore next */ []));
|
|
187
|
+
/** Text typed after `$`, used to filter the field-picker. */
|
|
188
|
+
this.pickerQuery = signal('', ...(ngDevMode ? [{ debugName: "pickerQuery" }] : /* istanbul ignore next */ []));
|
|
189
|
+
/** Keyboard-highlighted result in the field-picker. */
|
|
190
|
+
this.pickerActiveIndex = signal(0, ...(ngDevMode ? [{ debugName: "pickerActiveIndex" }] : /* istanbul ignore next */ []));
|
|
191
|
+
/** Index of the `$` that opened the picker, and the caret at that moment. */
|
|
192
|
+
this.#dollarIndex = -1;
|
|
193
|
+
this.#cursorPosition = 0;
|
|
194
|
+
// #endregion
|
|
195
|
+
this.roundingType = [
|
|
196
|
+
{ value: 'ROUND', label: 'Nearest' },
|
|
197
|
+
{ value: 'FLOOR', label: 'Round down' },
|
|
198
|
+
{ value: 'CEIL', label: 'Round up' },
|
|
199
|
+
];
|
|
200
|
+
/** Operator palette inserted into the equation; glyphs are friendly, tokens are infix. */
|
|
201
|
+
this.operatorKeys = [
|
|
202
|
+
{ label: '+', token: ' + ', caretBack: 0, aria: 'Add' },
|
|
203
|
+
{ label: '−', token: ' - ', caretBack: 0, aria: 'Subtract' },
|
|
204
|
+
{ label: '×', token: ' * ', caretBack: 0, aria: 'Multiply' },
|
|
205
|
+
{ label: '÷', token: ' / ', caretBack: 0, aria: 'Divide' },
|
|
206
|
+
{ label: '( )', token: '()', caretBack: 1, aria: 'Brackets' },
|
|
207
|
+
{ label: 'xⁿ', token: '^', caretBack: 0, aria: 'Power' },
|
|
208
|
+
];
|
|
209
|
+
/** The equation textarea, used for caret-aware token insertion. */
|
|
210
|
+
this.formulaInputRef = viewChild('formulaInput', ...(ngDevMode ? [{ debugName: "formulaInputRef" }] : /* istanbul ignore next */ []));
|
|
211
|
+
/** Live, non-technical validity of the working equation (drives the preview banner). */
|
|
212
|
+
this.formulaStatus = computed(() => {
|
|
213
|
+
const formula = (this.tempValue().formula ?? '').trim();
|
|
214
|
+
if (!formula) {
|
|
215
|
+
return { kind: 'empty', message: 'Start building your equation below.' };
|
|
216
|
+
}
|
|
217
|
+
const { error } = parseInfixFormula(formula);
|
|
218
|
+
return error
|
|
219
|
+
? { kind: 'invalid', message: error }
|
|
220
|
+
: { kind: 'valid', message: 'This equation is valid.' };
|
|
221
|
+
}, ...(ngDevMode ? [{ debugName: "formulaStatus" }] : /* istanbul ignore next */ []));
|
|
222
|
+
/** Number-typed inputs that may back a calculation variable. */
|
|
223
|
+
this.cachedNumberInputs = computed(() => this.formInputs().filter((input) => input.dataType === 'number' || input.type === 'number'), ...(ngDevMode ? [{ debugName: "cachedNumberInputs" }] : /* istanbul ignore next */ []));
|
|
224
|
+
/** Field-picker results: number inputs filtered by the text typed after `$`. */
|
|
225
|
+
this.pickerResults = computed(() => {
|
|
226
|
+
const query = this.pickerQuery().trim().toLowerCase();
|
|
227
|
+
const inputs = this.cachedNumberInputs();
|
|
228
|
+
if (!query) {
|
|
229
|
+
return inputs;
|
|
230
|
+
}
|
|
231
|
+
return inputs.filter((input) => input.label?.toLowerCase().includes(query) ||
|
|
232
|
+
input.formControlName?.toLowerCase().includes(query));
|
|
233
|
+
}, ...(ngDevMode ? [{ debugName: "pickerResults" }] : /* istanbul ignore next */ []));
|
|
234
|
+
/** Every calculation function offered as a list-aggregation chip. */
|
|
235
|
+
this.allCalculationFunction = computed(() => Object.values(CalculationFunctions), ...(ngDevMode ? [{ debugName: "allCalculationFunction" }] : /* istanbul ignore next */ []));
|
|
236
|
+
/** True when this editor targets a table column rather than a scalar input. */
|
|
237
|
+
this.tableMode = computed(() => !!this.mapToData()?.tableConfig, ...(ngDevMode ? [{ debugName: "tableMode" }] : /* istanbul ignore next */ []));
|
|
238
|
+
/** Table configuration when in table mode. */
|
|
239
|
+
this.tableConfig = computed(() => this.mapToData()?.tableConfig, ...(ngDevMode ? [{ debugName: "tableConfig" }] : /* istanbul ignore next */ []));
|
|
240
|
+
this.ngControl = inject(NgControl, { self: true, optional: true });
|
|
241
|
+
this.#elementRef = inject(ElementRef);
|
|
242
|
+
// ControlValueAccessor — Angular registers an untyped change callback.
|
|
243
|
+
this.onChange = () => { };
|
|
244
|
+
this.onTouched = () => { };
|
|
245
|
+
if (this.ngControl != null) {
|
|
246
|
+
this.ngControl.valueAccessor = this;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
ngOnDestroy() {
|
|
250
|
+
this.stateChanges.next();
|
|
251
|
+
this.stateChanges.complete();
|
|
252
|
+
}
|
|
253
|
+
// #region Value accessor & MatFormFieldControl logic
|
|
254
|
+
#value;
|
|
255
|
+
get value() {
|
|
256
|
+
return this.#value;
|
|
257
|
+
}
|
|
258
|
+
set value(val) {
|
|
259
|
+
if (_isEqual(this.#value, val)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
this.#value = val ? { ...val } : undefined;
|
|
263
|
+
this.stateChanges.next();
|
|
264
|
+
this.valueChanged.emit(this.#value ?? null);
|
|
265
|
+
this.onChange(this.#value);
|
|
266
|
+
}
|
|
267
|
+
// MatFormFieldControl reads these as plain getters during its own change
|
|
268
|
+
// detection, so they stay getters rather than `computed()` signals.
|
|
269
|
+
get empty() {
|
|
270
|
+
return !this.#value;
|
|
271
|
+
}
|
|
272
|
+
get shouldLabelFloat() {
|
|
273
|
+
return this.focused || !this.empty;
|
|
274
|
+
}
|
|
275
|
+
get errorState() {
|
|
276
|
+
const hasCustomError = (this.errors() ?? []).length > 0;
|
|
277
|
+
const hasControlError = this.ngControl?.control?.errors != null;
|
|
278
|
+
return ((this.touched && (hasControlError || hasCustomError)) ||
|
|
279
|
+
(this.required && !this.#value));
|
|
280
|
+
}
|
|
281
|
+
writeValue(value) {
|
|
282
|
+
this.value = value;
|
|
283
|
+
}
|
|
284
|
+
registerOnChange(fn) {
|
|
285
|
+
this.onChange = fn;
|
|
286
|
+
}
|
|
287
|
+
registerOnTouched(fn) {
|
|
288
|
+
this.onTouched = fn;
|
|
289
|
+
}
|
|
290
|
+
setDisabledState(isDisabled) {
|
|
291
|
+
this.disabled = isDisabled;
|
|
292
|
+
this.stateChanges.next();
|
|
293
|
+
}
|
|
294
|
+
setDescribedByIds(ids) {
|
|
295
|
+
const controlElement = this.#elementRef.nativeElement.querySelector('.app-calculated-field-rules');
|
|
296
|
+
controlElement?.setAttribute('aria-describedby', ids.join(' '));
|
|
297
|
+
this.describedBy = ids.join(' ');
|
|
298
|
+
this.stateChanges.next();
|
|
299
|
+
}
|
|
300
|
+
onContainerClick() {
|
|
301
|
+
if (!this.focused) {
|
|
302
|
+
this.focused = true;
|
|
303
|
+
this.stateChanges.next();
|
|
304
|
+
}
|
|
305
|
+
this.markAsTouched();
|
|
306
|
+
}
|
|
307
|
+
markAsTouched() {
|
|
308
|
+
if (!this.touched) {
|
|
309
|
+
this.onTouched();
|
|
310
|
+
this.touched = true;
|
|
311
|
+
this.stateChanges.next();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// #endregion
|
|
315
|
+
// #region Edit-mode lifecycle
|
|
316
|
+
startEdit() {
|
|
317
|
+
this.tempValue.set(this.#value ? structuredClone(this.#value) : {});
|
|
318
|
+
this.isEditing.set(true);
|
|
319
|
+
this.markAsTouched();
|
|
320
|
+
}
|
|
321
|
+
saveEdit() {
|
|
322
|
+
this.value = this.tempValue();
|
|
323
|
+
this.isEditing.set(false);
|
|
324
|
+
}
|
|
325
|
+
cancelEdit() {
|
|
326
|
+
this.isEditing.set(false);
|
|
327
|
+
this.tempValue.set({});
|
|
328
|
+
}
|
|
329
|
+
// #endregion
|
|
330
|
+
// #region Formula / variable editing (operates on tempValue)
|
|
331
|
+
checkIfInputCanBeLinkedToCalVariable(currentInput, inputToDisable) {
|
|
332
|
+
if (!currentInput) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
if (currentInput.formControlName === inputToDisable.formControlName) {
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
if (!currentInput.multipleInputInEditId) {
|
|
339
|
+
return !!inputToDisable.parentInput || !!inputToDisable.multipleInputInEditId;
|
|
340
|
+
}
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
linkFormulaSource(event) {
|
|
344
|
+
// Resetting formula to undefined intentionally during edit; persisted value
|
|
345
|
+
// re-validates on save.
|
|
346
|
+
this.tempValue.update((current) => ({
|
|
347
|
+
...current,
|
|
348
|
+
formControlWithFormula: event.value,
|
|
349
|
+
formula: '',
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
removeVariable(variableId) {
|
|
353
|
+
const current = this.tempValue();
|
|
354
|
+
const variables = current.variables ?? [];
|
|
355
|
+
const target = variables.find((v) => v.id === variableId);
|
|
356
|
+
let formula = current.formula ?? '';
|
|
357
|
+
if (target?.variable) {
|
|
358
|
+
formula = formula
|
|
359
|
+
.replace(new RegExp(`\\b${escapeRegExp(target.variable)}\\b`, 'g'), '')
|
|
360
|
+
.replace(/\s{2,}/g, ' ')
|
|
361
|
+
.trim();
|
|
362
|
+
}
|
|
363
|
+
this.tempValue.set({
|
|
364
|
+
...current,
|
|
365
|
+
variables: variables.filter((v) => v.id !== variableId),
|
|
366
|
+
formula,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
appendVariableToFormula(variableName) {
|
|
370
|
+
// A variable carries no spacing of its own, so pad it only where it would
|
|
371
|
+
// otherwise fuse with an adjacent token (e.g. `ab` instead of `a b`).
|
|
372
|
+
const { before, after } = this.#caretSlices();
|
|
373
|
+
const lead = before.length > 0 && !/[\s(]$/.test(before) ? ' ' : '';
|
|
374
|
+
const trail = after.length > 0 && !/^[\s)]/.test(after) ? ' ' : '';
|
|
375
|
+
this.insertToken(`${lead}${variableName}${trail}`, trail ? 1 : 0);
|
|
376
|
+
}
|
|
377
|
+
setFormula(formula) {
|
|
378
|
+
this.tempValue.update((current) => ({ ...current, formula }));
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Inserts an infix token at the caret (or appends it when the textarea is not
|
|
382
|
+
* focused), then restores the caret just after the insertion. Used by both the
|
|
383
|
+
* operator palette and the variable chips so the equation reads naturally while
|
|
384
|
+
* staying strictly infix.
|
|
385
|
+
*/
|
|
386
|
+
insertToken(token, caretBack = 0) {
|
|
387
|
+
const element = this.formulaInputRef()?.nativeElement;
|
|
388
|
+
const { before, after } = this.#caretSlices();
|
|
389
|
+
const next = before + token + after;
|
|
390
|
+
this.setFormula(next);
|
|
391
|
+
if (!element) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const caret = before.length + token.length - caretBack;
|
|
395
|
+
// Restore focus + caret after Angular has written the new value back to the DOM.
|
|
396
|
+
setTimeout(() => {
|
|
397
|
+
element.focus();
|
|
398
|
+
element.setSelectionRange(caret, caret);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
/** The formula split around the current caret/selection (end-of-string when unfocused). */
|
|
402
|
+
#caretSlices() {
|
|
403
|
+
const current = this.tempValue().formula ?? '';
|
|
404
|
+
const element = this.formulaInputRef()?.nativeElement;
|
|
405
|
+
const start = element?.selectionStart ?? current.length;
|
|
406
|
+
const end = element?.selectionEnd ?? current.length;
|
|
407
|
+
return { before: current.slice(0, start), after: current.slice(end) };
|
|
408
|
+
}
|
|
409
|
+
setDecimalPlaces(value) {
|
|
410
|
+
const parsed = value === null || value === '' ? undefined : Number(value);
|
|
411
|
+
this.tempValue.update((current) => ({
|
|
412
|
+
...current,
|
|
413
|
+
decimalPlaces: parsed !== undefined && Number.isFinite(parsed) ? parsed : undefined,
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
setRoundingMode(mode) {
|
|
417
|
+
this.tempValue.update((current) => ({ ...current, roundingMode: mode }));
|
|
418
|
+
}
|
|
419
|
+
/** Friendly label for a stored rounding mode, used in the read-only summary. */
|
|
420
|
+
roundingLabel(mode) {
|
|
421
|
+
return this.roundingType.find((option) => option.value === mode)?.label ?? 'nearest';
|
|
422
|
+
}
|
|
423
|
+
// #endregion
|
|
424
|
+
// #region Inline `$` field-picker & variable binding
|
|
425
|
+
/** Formula text changed: persist it and re-evaluate whether the `$` picker should show. */
|
|
426
|
+
onFormulaChange(value) {
|
|
427
|
+
this.setFormula(value);
|
|
428
|
+
this.#syncPicker();
|
|
429
|
+
}
|
|
430
|
+
/** Caret moved (click / arrow keys / focus): re-evaluate the `$` picker against the new caret. */
|
|
431
|
+
onFormulaCaret() {
|
|
432
|
+
this.#syncPicker();
|
|
433
|
+
}
|
|
434
|
+
/** Closes the picker shortly after blur, leaving time for a result click to register. */
|
|
435
|
+
onFormulaBlur() {
|
|
436
|
+
setTimeout(() => this.closePicker(), 150);
|
|
437
|
+
}
|
|
438
|
+
/** Keyboard control for the open picker: navigate, select, dismiss. */
|
|
439
|
+
onFormulaKeydown(event) {
|
|
440
|
+
if (!this.pickerOpen()) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const results = this.pickerResults();
|
|
444
|
+
switch (event.key) {
|
|
445
|
+
case 'ArrowDown':
|
|
446
|
+
event.preventDefault();
|
|
447
|
+
this.pickerActiveIndex.update((i) => Math.min(i + 1, results.length - 1));
|
|
448
|
+
break;
|
|
449
|
+
case 'ArrowUp':
|
|
450
|
+
event.preventDefault();
|
|
451
|
+
this.pickerActiveIndex.update((i) => Math.max(i - 1, 0));
|
|
452
|
+
break;
|
|
453
|
+
case 'Enter': {
|
|
454
|
+
const item = results[this.pickerActiveIndex()];
|
|
455
|
+
if (item) {
|
|
456
|
+
event.preventDefault();
|
|
457
|
+
this.selectField(item);
|
|
458
|
+
}
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
case 'Escape':
|
|
462
|
+
event.preventDefault();
|
|
463
|
+
this.closePicker();
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
closePicker() {
|
|
468
|
+
this.pickerOpen.set(false);
|
|
469
|
+
this.pickerActiveIndex.set(0);
|
|
470
|
+
this.#dollarIndex = -1;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Binds a form field to a calculation variable and drops its short token into
|
|
474
|
+
* the equation. Reuses an existing scalar binding for the same field; otherwise
|
|
475
|
+
* mints the next free short name (a, b, c, …). When opened from `$`, the typed
|
|
476
|
+
* `$query` is replaced in place; otherwise the token is appended.
|
|
477
|
+
*/
|
|
478
|
+
selectField(input) {
|
|
479
|
+
const current = this.tempValue();
|
|
480
|
+
const variables = current.variables ?? [];
|
|
481
|
+
const existing = variables.find((v) => v.formControlName === input.formControlName && !v.function && !v.parentInputId);
|
|
482
|
+
const token = existing ? existing.variable : this.#nextShortName();
|
|
483
|
+
const nextVars = existing ? variables : [...variables, this.#makeVariable(input, token)];
|
|
484
|
+
const formula = current.formula ?? '';
|
|
485
|
+
let nextFormula;
|
|
486
|
+
let caret;
|
|
487
|
+
if (this.pickerOpen() && this.#dollarIndex >= 0) {
|
|
488
|
+
const before = formula.slice(0, this.#dollarIndex);
|
|
489
|
+
const after = formula.slice(this.#cursorPosition);
|
|
490
|
+
const lead = before.length > 0 && !/[\s(]$/.test(before) ? ' ' : '';
|
|
491
|
+
const insert = `${lead}${token} `;
|
|
492
|
+
nextFormula = before + insert + after;
|
|
493
|
+
caret = (before + insert).length;
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
const lead = formula.length > 0 && !/[\s(]$/.test(formula) ? ' ' : '';
|
|
497
|
+
nextFormula = `${formula}${lead}${token} `;
|
|
498
|
+
caret = nextFormula.length;
|
|
499
|
+
}
|
|
500
|
+
this.tempValue.set({ ...current, formula: nextFormula, variables: nextVars });
|
|
501
|
+
this.closePicker();
|
|
502
|
+
const element = this.formulaInputRef()?.nativeElement;
|
|
503
|
+
if (element) {
|
|
504
|
+
setTimeout(() => {
|
|
505
|
+
element.focus();
|
|
506
|
+
element.setSelectionRange(caret, caret);
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Renames a variable's short token, updating every whole-word occurrence in the
|
|
512
|
+
* formula. An empty name is ignored so the token never desyncs from the formula
|
|
513
|
+
* (the field repopulates with the current name on the next render).
|
|
514
|
+
*/
|
|
515
|
+
setVariableShortName(id, rawName) {
|
|
516
|
+
const name = rawName.trim();
|
|
517
|
+
const current = this.tempValue();
|
|
518
|
+
const variables = current.variables ?? [];
|
|
519
|
+
const target = variables.find((v) => v.id === id);
|
|
520
|
+
if (!target || !name || name === target.variable) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const formula = target.variable
|
|
524
|
+
? (current.formula ?? '').replace(new RegExp(`\\b${escapeRegExp(target.variable)}\\b`, 'g'), name)
|
|
525
|
+
: (current.formula ?? '');
|
|
526
|
+
this.tempValue.set({
|
|
527
|
+
...current,
|
|
528
|
+
formula,
|
|
529
|
+
variables: variables.map((v) => (v.id === id ? { ...v, variable: name } : v)),
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
/** Toggles a list-aggregation function (sum/avg/…) on a sub-item variable. */
|
|
533
|
+
setVariableFunction(id, func) {
|
|
534
|
+
this.tempValue.update((current) => ({
|
|
535
|
+
...current,
|
|
536
|
+
variables: (current.variables ?? []).map((v) => {
|
|
537
|
+
if (v.id !== id) {
|
|
538
|
+
return v;
|
|
539
|
+
}
|
|
540
|
+
if (v.function === func) {
|
|
541
|
+
return {
|
|
542
|
+
...v,
|
|
543
|
+
function: undefined,
|
|
544
|
+
applyFunctionToCol: undefined,
|
|
545
|
+
applyFunctionToLabel: undefined,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
...v,
|
|
550
|
+
function: func,
|
|
551
|
+
applyFunctionToCol: v.formControlName,
|
|
552
|
+
applyFunctionToLabel: v.label,
|
|
553
|
+
};
|
|
554
|
+
}),
|
|
555
|
+
}));
|
|
556
|
+
}
|
|
557
|
+
/** Recomputes the picker visibility/query from the current formula and caret. */
|
|
558
|
+
#syncPicker() {
|
|
559
|
+
const element = this.formulaInputRef()?.nativeElement;
|
|
560
|
+
if (!element) {
|
|
561
|
+
this.closePicker();
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
const position = element.selectionStart ?? 0;
|
|
565
|
+
this.#cursorPosition = position;
|
|
566
|
+
const formula = this.tempValue().formula ?? '';
|
|
567
|
+
const lastDollar = formula.slice(0, position).lastIndexOf('$');
|
|
568
|
+
if (lastDollar === -1) {
|
|
569
|
+
this.closePicker();
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const query = formula.slice(lastDollar + 1, position);
|
|
573
|
+
// Only a run of letters/digits/spaces keeps the picker open; anything else
|
|
574
|
+
// (an operator, a newline) means the `$` was not a field trigger.
|
|
575
|
+
if (!/^[a-zA-Z0-9 ]*$/.test(query)) {
|
|
576
|
+
this.closePicker();
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
this.#dollarIndex = lastDollar;
|
|
580
|
+
this.pickerQuery.set(query);
|
|
581
|
+
this.pickerActiveIndex.set(0);
|
|
582
|
+
this.pickerOpen.set(true);
|
|
583
|
+
}
|
|
584
|
+
/** Builds a calculation variable bound to a form field, carrying the list parent when present. */
|
|
585
|
+
#makeVariable(input, token) {
|
|
586
|
+
return {
|
|
587
|
+
id: v4(),
|
|
588
|
+
variable: token,
|
|
589
|
+
formControlName: input.formControlName,
|
|
590
|
+
inputId: input.id,
|
|
591
|
+
label: input.label,
|
|
592
|
+
...(input.multipleInputInEditId ? { parentInputId: input.multipleInputInEditId } : {}),
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
/** Next free single-letter token (a, b, c, …), skipping mathjs constants and falling back to v1, v2…. */
|
|
596
|
+
#nextShortName() {
|
|
597
|
+
const used = new Set((this.tempValue().variables ?? []).map((v) => v.variable));
|
|
598
|
+
const reserved = new Set(['e', 'i']); // mathjs constants
|
|
599
|
+
for (let code = 97; code <= 122; code++) {
|
|
600
|
+
const char = String.fromCharCode(code);
|
|
601
|
+
if (!used.has(char) && !reserved.has(char)) {
|
|
602
|
+
return char;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
let n = 1;
|
|
606
|
+
while (used.has(`v${n}`)) {
|
|
607
|
+
n++;
|
|
608
|
+
}
|
|
609
|
+
return `v${n}`;
|
|
610
|
+
}
|
|
611
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: CalculatedFieldRulesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
612
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: CalculatedFieldRulesComponent, isStandalone: true, selector: "lib-calculated-field-rules", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: false, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: false, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, mapToData: { classPropertyName: "mapToData", publicName: "mapToData", isSignal: true, isRequired: false, transformFunction: null }, formInputs: { classPropertyName: "formInputs", publicName: "formInputs", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { valueChange2dWithPath: "valueChange2dWithPath", valueChanged: "valueChanged" }, host: { properties: { "attr.aria-describedby": "describedBy" }, classAttribute: "lib-calculated-field-rules" }, providers: [{ provide: MatFormFieldControl, useExisting: CalculatedFieldRulesComponent }], viewQueries: [{ propertyName: "formulaInputRef", first: true, predicate: ["formulaInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- View mode: read-only calculation summary -->\n@if (!isEditing()) {\n<div class=\"cfr-summary\">\n @if (!!value && !mapToData()?.id && !tableMode()) {\n <div class=\"cfr-hint\">\n <mat-icon class=\"cfr-hint__icon\" color=\"primary\">info</mat-icon>\n <span>Save the input first to enable calculated field configurations</span>\n </div>\n }\n\n <div class=\"cfr-formula-card\">\n <span class=\"cfr-section__label\">Calculation</span>\n <lib-math-preview class=\"cfr-formula-card__preview\" [expression]=\"value?.formula || ''\"\n placeholder=\"No calculation set up yet\" />\n @if (value?.decimalPlaces) {\n <div class=\"cfr-formula-card__meta\">\n <mat-icon class=\"cfr-formula-card__meta-icon\">tune</mat-icon>\n <span>Shown to {{ value?.decimalPlaces }} decimal place(s) \u00B7 {{ value?.roundingMode ? roundingLabel(value?.roundingMode) : 'nearest' }}</span>\n </div>\n }\n </div>\n\n <button class=\"cfr-edit-trigger\" mat-stroked-button color=\"primary\" [disabled]=\"disabled\"\n (click)=\"startEdit()\">\n <mat-icon>edit</mat-icon>\n {{ !!value?.formula ? 'Edit calculation' : 'Set up calculation' }}\n </button>\n</div>\n}\n\n<!-- Edit mode: guided calculation builder -->\n@else {\n<mat-card appearance=\"outlined\" class=\"cfr-editor\">\n <header class=\"cfr-editor__head\">\n <h3 class=\"cfr-editor__title\">\n <mat-icon>functions</mat-icon>\n Build the calculation\n </h3>\n <p class=\"cfr-editor__subtitle\">\n Combine your form fields with operators to work out this value automatically.\n </p>\n </header>\n\n <!-- Live preview + validity -->\n <div class=\"cfr-preview\" [attr.data-state]=\"formulaStatus().kind\">\n <span class=\"cfr-section__label\">Live preview</span>\n <lib-math-preview class=\"cfr-preview__render\" [expression]=\"tempValue().formula || ''\"\n placeholder=\"Your equation will appear here\" />\n <p class=\"cfr-preview__status\">\n <mat-icon class=\"cfr-preview__status-icon\">\n {{ formulaStatus().kind === 'valid' ? 'check_circle' : formulaStatus().kind === 'invalid' ? 'error' : 'lightbulb' }}\n </mat-icon>\n <span>{{ formulaStatus().message }}</span>\n </p>\n </div>\n\n <section class=\"cfr-section\">\n <!-- Equation input + inline field-picker + operator keypad -->\n <div class=\"cfr-equation\">\n <div class=\"cfr-equation__field\">\n <mat-form-field class=\"cfr-field\" [subscriptSizing]=\"'dynamic'\" [floatLabel]=\"'always'\"\n appearance=\"outline\">\n <mat-label>Equation</mat-label>\n <textarea #formulaInput matInput [ngModel]=\"tempValue().formula\"\n (ngModelChange)=\"onFormulaChange($event)\" (keyup)=\"onFormulaCaret()\"\n (click)=\"onFormulaCaret()\" (focus)=\"onFormulaCaret()\"\n (keydown)=\"onFormulaKeydown($event)\" (blur)=\"onFormulaBlur()\"\n placeholder=\"e.g. (a + 2) / b\" rows=\"2\" spellcheck=\"false\" autocapitalize=\"off\"\n autocomplete=\"off\"></textarea>\n <mat-hint>Type <strong>$</strong> to add a form field, then join fields with the operator keys.</mat-hint>\n </mat-form-field>\n\n @if (pickerOpen()) {\n <div class=\"cfr-picker\" role=\"listbox\" aria-label=\"Form fields\">\n <div class=\"cfr-picker__head\">Insert a field</div>\n @if (pickerResults().length === 0) {\n <div class=\"cfr-picker__empty\">No matching number fields</div>\n } @else {\n @for (item of pickerResults(); track item.id; let idx = $index) {\n <button type=\"button\" role=\"option\" class=\"cfr-picker__item\"\n [class.cfr-picker__item--active]=\"idx === pickerActiveIndex()\"\n [attr.aria-selected]=\"idx === pickerActiveIndex()\"\n (mousedown)=\"$event.preventDefault()\" (click)=\"selectField(item)\">\n <mat-icon class=\"cfr-picker__icon\">{{ item.multipleInputInEditId ? 'format_list_numbered' : 'tag' }}</mat-icon>\n <span class=\"cfr-picker__label\">{{ item.label }}</span>\n <span class=\"cfr-picker__meta\">{{ item.formControlName }}</span>\n </button>\n }\n }\n </div>\n }\n </div>\n\n <div class=\"cfr-keypad\" role=\"group\" aria-label=\"Operators\">\n @for (key of operatorKeys; track key.token) {\n <button type=\"button\" class=\"cfr-key\" [attr.aria-label]=\"key.aria\"\n (click)=\"insertToken(key.token, key.caretBack)\">\n {{ key.label }}\n </button>\n }\n </div>\n </div>\n\n <!-- Optional equation source link -->\n @if (!!tempValue().getFormulaFromAFormInput) {\n <mat-form-field class=\"cfr-field\" [subscriptSizing]=\"'dynamic'\" [floatLabel]=\"'always'\"\n appearance=\"outline\">\n <mat-label>Equation source</mat-label>\n <mat-select [value]=\"tempValue().formControlWithFormula\"\n (selectionChange)=\"linkFormulaSource($event)\" placeholder=\"Select an input that holds a formula\">\n @for (input of formInputs(); track input.id) {\n <mat-option\n [disabled]=\"checkIfInputCanBeLinkedToCalVariable(mapToData(), input) || input.element !== 'select'\"\n [value]=\"input.formControlName\">\n {{ input.label }}\n </mat-option>\n }\n </mat-select>\n <mat-hint>Pull the equation from another input's value</mat-hint>\n </mat-form-field>\n }\n\n <!-- Bound fields legend -->\n <div class=\"cfr-variables\">\n <div class=\"cfr-block-head\">\n <span class=\"cfr-section__label\">Fields in this calculation</span>\n <span class=\"cfr-block-head__hint\">Rename a letter, or type $ above to add another</span>\n </div>\n\n @if ((tempValue().variables || []).length === 0) {\n <p class=\"cfr-variables__empty\">\n Type <strong>$</strong> in the equation above to add a form field \u2014 each becomes a short\n letter you can use in the equation.\n </p>\n } @else {\n <ul class=\"cfr-legend\">\n @for (variable of (tempValue().variables || []); track variable.id) {\n <li class=\"cfr-legend__row\">\n <div class=\"cfr-legend__main\">\n <input class=\"cfr-legend__token\" [ngModel]=\"variable.variable\"\n (ngModelChange)=\"setVariableShortName(variable.id, $event)\" aria-label=\"Short name\"\n maxlength=\"12\" spellcheck=\"false\" autocomplete=\"off\" autocapitalize=\"off\">\n <mat-icon class=\"cfr-legend__arrow\">arrow_right_alt</mat-icon>\n <button type=\"button\" class=\"cfr-legend__field\"\n [attr.aria-label]=\"'Insert ' + variable.label + ' into the equation'\"\n (click)=\"appendVariableToFormula(variable.variable)\">\n {{ variable.label }}\n </button>\n <button type=\"button\" class=\"cfr-legend__remove\"\n [attr.aria-label]=\"'Remove ' + variable.variable\"\n (click)=\"removeVariable(variable.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n @if (variable.parentInputId) {\n <div class=\"cfr-legend__fns\" role=\"group\" aria-label=\"Combine list values with\">\n <span class=\"cfr-legend__fns-label\" aria-hidden=\"true\">\u0192</span>\n @for (func of allCalculationFunction(); track func) {\n <button type=\"button\" class=\"cfr-legend__fn\"\n [class.cfr-legend__fn--active]=\"variable.function === func\"\n (click)=\"setVariableFunction(variable.id, func)\">\n {{ func }}\n </button>\n }\n </div>\n }\n </li>\n }\n </ul>\n }\n </div>\n\n <!-- Result formatting -->\n <div class=\"cfr-rounding\">\n <div class=\"cfr-block-head\">\n <span class=\"cfr-section__label\">Result formatting</span>\n <span class=\"cfr-block-head__hint\">Optional \u2014 how the answer is rounded</span>\n </div>\n\n <div class=\"cfr-rounding__row\">\n <mat-form-field class=\"cfr-field cfr-rounding__decimals\" [subscriptSizing]=\"'dynamic'\"\n [floatLabel]=\"'always'\" appearance=\"outline\">\n <mat-label>Decimal places</mat-label>\n <input matInput [ngModel]=\"tempValue().decimalPlaces\"\n (ngModelChange)=\"setDecimalPlaces($event)\" placeholder=\"0\" type=\"number\" min=\"0\">\n </mat-form-field>\n\n @if (tempValue().decimalPlaces) {\n <div class=\"cfr-rounding__mode\">\n <label class=\"cfr-section__label\">Rounding</label>\n <mat-chip-listbox aria-label=\"Rounding method\">\n @for (type of roundingType; track type.value) {\n <mat-chip-option (click)=\"setRoundingMode(type.value)\"\n [selected]=\"tempValue().roundingMode === type.value\">\n {{ type.label }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n </div>\n }\n </div>\n </div>\n </section>\n\n <div class=\"cfr-editor__actions\">\n <button mat-button (click)=\"cancelEdit()\">Cancel</button>\n <button mat-flat-button color=\"primary\" [disabled]=\"formulaStatus().kind === 'invalid'\"\n (click)=\"saveEdit()\">\n Save calculation\n </button>\n </div>\n</mat-card>\n}\n\n<!-- Disabled overlay -->\n<ng-template cdkConnectedOverlay [cdkConnectedOverlayOpen]=\"disabled\">\n <div class=\"cfr-disabled-overlay\"></div>\n</ng-template>\n", styles: [":host{display:block;position:relative}.cfr-hint{display:flex;align-items:center;gap:.5rem;padding:.875rem 1rem;background:var(--sem-info-surface);border-radius:var(--lib-forms-radius-sm, 8px);color:var(--lib-forms-on-surface);font-size:.875rem}.cfr-hint__icon{flex:0 0 auto}.cfr-section__label{display:block;font-size:.625rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--lib-forms-on-surface-variant)}.cfr-field{width:100%}.cfr-block-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem .75rem;margin-bottom:.75rem}.cfr-block-head__hint{font-size:.8125rem;color:var(--lib-forms-on-surface-variant)}.cfr-summary{display:flex;flex-direction:column;gap:1.5rem}.cfr-formula-card{background:var(--lib-forms-surface);border:1px solid color-mix(in srgb,var(--lib-forms-outline) 15%,transparent);border-radius:var(--lib-forms-radius-lg, 12px);box-shadow:var(--lib-forms-shadow-resting);display:flex;flex-direction:column;gap:1rem;padding:2rem}.cfr-formula-card__preview{display:block;border-radius:var(--lib-forms-radius-md, 10px);background:var(--lib-forms-surface-container-low)}.cfr-formula-card__meta{display:flex;align-items:center;gap:.5rem;font-size:.8125rem;color:var(--lib-forms-on-surface-variant)}.cfr-formula-card__meta-icon{font-size:1.125rem;width:1.125rem;height:1.125rem}.cfr-edit-trigger{width:100%}.cfr-editor{display:flex;flex-direction:column;gap:1.5rem;padding:1.5rem;border-color:color-mix(in srgb,var(--lib-forms-primary) 30%,transparent)}.cfr-editor__head{display:flex;flex-direction:column;gap:.5rem}.cfr-editor__title{display:flex;align-items:center;gap:.5rem;margin:0;font-size:1.25rem;font-weight:500;letter-spacing:-.01em;color:var(--lib-forms-on-surface)}.cfr-editor__subtitle{margin:0;font-size:.9375rem;color:var(--lib-forms-on-surface-variant)}.cfr-preview{display:flex;flex-direction:column;gap:.875rem;padding:1.25rem 1.5rem;border-radius:var(--lib-forms-radius-lg, 12px);background:var(--lib-forms-surface-container-low);border:1px solid color-mix(in srgb,var(--lib-forms-outline) 15%,transparent);transition:border-color var(--lib-forms-duration, .4s) var(--lib-forms-easing)}.cfr-preview[data-state=valid]{border-color:color-mix(in srgb,var(--sem-success) 45%,transparent)}.cfr-preview[data-state=invalid]{border-color:color-mix(in srgb,var(--sem-error) 45%,transparent)}.cfr-preview__render{display:block}.cfr-preview__status{display:flex;align-items:center;gap:.5rem;margin:0;font-size:.875rem;color:var(--lib-forms-on-surface-variant)}.cfr-preview__status-icon{flex:0 0 auto;font-size:1.125rem;width:1.125rem;height:1.125rem}.cfr-preview[data-state=valid] .cfr-preview__status{color:var(--sem-success)}.cfr-preview[data-state=invalid] .cfr-preview__status{color:var(--sem-error)}.cfr-section{display:flex;flex-direction:column;gap:1.5rem}.cfr-equation{display:flex;flex-direction:column;gap:.625rem}.cfr-equation__field{position:relative}.cfr-keypad{display:flex;flex-wrap:wrap;gap:.375rem}.cfr-key{display:inline-flex;align-items:center;justify-content:center;min-width:2.5rem;height:2.25rem;padding:0 .625rem;border:1px solid var(--lib-forms-outline-variant);border-radius:var(--lib-forms-radius-sm, 8px);background:var(--lib-forms-surface);color:var(--lib-forms-on-surface);font-size:1rem;font-weight:500;cursor:pointer;transition:border-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),background-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),transform var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.cfr-key:hover{border-color:var(--lib-forms-primary);background:color-mix(in srgb,var(--lib-forms-primary) 8%,transparent)}.cfr-key:active{transform:translateY(1px)}.cfr-picker{position:absolute;z-index:20;left:0;right:0;top:calc(100% - 1.25rem);max-height:16rem;overflow-y:auto;padding:.5rem;border-radius:var(--lib-forms-radius-md, 10px);background:var(--lib-forms-surface);border:1px solid color-mix(in srgb,var(--lib-forms-outline) 20%,transparent);box-shadow:var(--lib-forms-shadow-elevated, var(--lib-forms-shadow-resting))}.cfr-picker__head{padding:.25rem .5rem .5rem;font-size:.625rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--lib-forms-on-surface-variant)}.cfr-picker__empty{padding:.75rem .5rem;font-size:.875rem;color:var(--lib-forms-on-surface-variant)}.cfr-picker__item{display:flex;align-items:center;gap:.625rem;width:100%;padding:.5rem .625rem;border:none;border-radius:var(--lib-forms-radius-sm, 8px);background:transparent;color:var(--lib-forms-on-surface);font-size:.9375rem;text-align:left;cursor:pointer;transition:background-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.cfr-picker__item:hover,.cfr-picker__item--active{background:color-mix(in srgb,var(--lib-forms-primary) 12%,transparent)}.cfr-picker__icon{flex:0 0 auto;font-size:1.125rem;width:1.125rem;height:1.125rem;color:var(--lib-forms-on-surface-variant)}.cfr-picker__label{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cfr-picker__meta{flex:0 0 auto;font-size:.75rem;color:var(--lib-forms-on-surface-variant)}.cfr-variables{display:flex;flex-direction:column;gap:.75rem}.cfr-variables__empty{margin:0;font-size:.875rem;color:var(--lib-forms-on-surface-variant)}.cfr-legend{display:flex;flex-direction:column;gap:.5rem;margin:0;padding:0;list-style:none}.cfr-legend__row{display:flex;flex-direction:column;gap:.5rem;padding:.5rem .625rem;border-radius:var(--lib-forms-radius-md, 10px);background:var(--lib-forms-surface-container-low);border:1px solid color-mix(in srgb,var(--lib-forms-outline) 12%,transparent)}.cfr-legend__main{display:flex;align-items:center;gap:.5rem}.cfr-legend__token{flex:0 0 auto;width:2.5rem;height:1.875rem;padding:0 .25rem;text-align:center;font-family:JetBrains Mono,SF Mono,ui-monospace,monospace;font-size:.9375rem;font-weight:600;color:var(--lib-forms-primary);background:color-mix(in srgb,var(--lib-forms-primary) 8%,var(--lib-forms-surface));border:1px solid color-mix(in srgb,var(--lib-forms-primary) 30%,transparent);border-radius:var(--lib-forms-radius-sm, 8px);transition:border-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),box-shadow var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.cfr-legend__token:focus{outline:none;border-color:var(--lib-forms-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--lib-forms-primary) 25%,transparent)}.cfr-legend__arrow{flex:0 0 auto;font-size:1.125rem;width:1.125rem;height:1.125rem;color:var(--lib-forms-on-surface-variant)}.cfr-legend__field{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:.3125rem .625rem;border:1px solid transparent;border-radius:var(--lib-forms-radius-sm, 8px);background:transparent;color:var(--lib-forms-on-surface);font-size:.875rem;text-align:left;cursor:pointer;transition:border-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),background-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.cfr-legend__field:hover{border-color:var(--lib-forms-outline-variant);background:color-mix(in srgb,var(--lib-forms-primary) 6%,transparent)}.cfr-legend__remove{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border:none;border-radius:50%;background:transparent;color:var(--lib-forms-on-surface-variant);cursor:pointer;transition:color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),background-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.cfr-legend__remove mat-icon{font-size:1.125rem;width:1.125rem;height:1.125rem}.cfr-legend__remove:hover{color:var(--sem-error);background:var(--sem-error-surface)}.cfr-legend__fns{display:flex;flex-wrap:wrap;align-items:center;gap:.25rem;padding-left:3rem}.cfr-legend__fns-label{margin-right:.125rem;font-style:italic;font-size:.8125rem;color:var(--lib-forms-on-surface-variant)}.cfr-legend__fn{padding:.1875rem .5rem;border:1px solid var(--lib-forms-outline-variant);border-radius:var(--lib-forms-radius-pill, 999px);background:transparent;color:var(--lib-forms-on-surface-variant);font-size:.6875rem;font-weight:500;letter-spacing:.02em;cursor:pointer;transition:border-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),background-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.cfr-legend__fn:hover{border-color:var(--lib-forms-primary);color:var(--lib-forms-on-surface)}.cfr-legend__fn--active{border-color:transparent;background:var(--lib-forms-primary);color:var(--lib-forms-on-primary)}.cfr-rounding{display:flex;flex-direction:column}.cfr-rounding__row{display:flex;gap:1.5rem;flex-wrap:wrap}.cfr-rounding__decimals{flex:1 1 150px;min-width:150px}.cfr-rounding__mode{flex:2 1 220px;display:flex;flex-direction:column;gap:.5rem}.cfr-editor__actions{display:flex;justify-content:flex-end;gap:.75rem}.cfr-disabled-overlay{position:absolute;inset:0;background:color-mix(in srgb,var(--lib-forms-shadow-color) 50%,transparent);z-index:10}mat-chip{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MathPreviewComponent, selector: "lib-math-preview", inputs: ["expression", "placeholder"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i2.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i2$2.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i4.MatChipListbox, selector: "mat-chip-listbox", inputs: ["multiple", "aria-orientation", "selectable", "compareWith", "required", "hideSingleSelectionIndicator", "value"], outputs: ["change"] }, { kind: "component", type: i4.MatChipOption, selector: "mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]", inputs: ["selectable", "selected"], outputs: ["selectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
613
|
+
}
|
|
614
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: CalculatedFieldRulesComponent, decorators: [{
|
|
615
|
+
type: Component,
|
|
616
|
+
args: [{ selector: 'lib-calculated-field-rules', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, imports: [
|
|
617
|
+
CommonModule,
|
|
618
|
+
FormsModule,
|
|
619
|
+
MathPreviewComponent,
|
|
620
|
+
OverlayModule,
|
|
621
|
+
MatFormFieldModule,
|
|
622
|
+
MatInputModule,
|
|
623
|
+
MatSelectModule,
|
|
624
|
+
MatIconModule,
|
|
625
|
+
MatCardModule,
|
|
626
|
+
MatButtonModule,
|
|
627
|
+
MatChipsModule,
|
|
628
|
+
], providers: [{ provide: MatFormFieldControl, useExisting: CalculatedFieldRulesComponent }], host: {
|
|
629
|
+
'class': 'lib-calculated-field-rules',
|
|
630
|
+
'[attr.aria-describedby]': 'describedBy',
|
|
631
|
+
}, template: "<!-- View mode: read-only calculation summary -->\n@if (!isEditing()) {\n<div class=\"cfr-summary\">\n @if (!!value && !mapToData()?.id && !tableMode()) {\n <div class=\"cfr-hint\">\n <mat-icon class=\"cfr-hint__icon\" color=\"primary\">info</mat-icon>\n <span>Save the input first to enable calculated field configurations</span>\n </div>\n }\n\n <div class=\"cfr-formula-card\">\n <span class=\"cfr-section__label\">Calculation</span>\n <lib-math-preview class=\"cfr-formula-card__preview\" [expression]=\"value?.formula || ''\"\n placeholder=\"No calculation set up yet\" />\n @if (value?.decimalPlaces) {\n <div class=\"cfr-formula-card__meta\">\n <mat-icon class=\"cfr-formula-card__meta-icon\">tune</mat-icon>\n <span>Shown to {{ value?.decimalPlaces }} decimal place(s) \u00B7 {{ value?.roundingMode ? roundingLabel(value?.roundingMode) : 'nearest' }}</span>\n </div>\n }\n </div>\n\n <button class=\"cfr-edit-trigger\" mat-stroked-button color=\"primary\" [disabled]=\"disabled\"\n (click)=\"startEdit()\">\n <mat-icon>edit</mat-icon>\n {{ !!value?.formula ? 'Edit calculation' : 'Set up calculation' }}\n </button>\n</div>\n}\n\n<!-- Edit mode: guided calculation builder -->\n@else {\n<mat-card appearance=\"outlined\" class=\"cfr-editor\">\n <header class=\"cfr-editor__head\">\n <h3 class=\"cfr-editor__title\">\n <mat-icon>functions</mat-icon>\n Build the calculation\n </h3>\n <p class=\"cfr-editor__subtitle\">\n Combine your form fields with operators to work out this value automatically.\n </p>\n </header>\n\n <!-- Live preview + validity -->\n <div class=\"cfr-preview\" [attr.data-state]=\"formulaStatus().kind\">\n <span class=\"cfr-section__label\">Live preview</span>\n <lib-math-preview class=\"cfr-preview__render\" [expression]=\"tempValue().formula || ''\"\n placeholder=\"Your equation will appear here\" />\n <p class=\"cfr-preview__status\">\n <mat-icon class=\"cfr-preview__status-icon\">\n {{ formulaStatus().kind === 'valid' ? 'check_circle' : formulaStatus().kind === 'invalid' ? 'error' : 'lightbulb' }}\n </mat-icon>\n <span>{{ formulaStatus().message }}</span>\n </p>\n </div>\n\n <section class=\"cfr-section\">\n <!-- Equation input + inline field-picker + operator keypad -->\n <div class=\"cfr-equation\">\n <div class=\"cfr-equation__field\">\n <mat-form-field class=\"cfr-field\" [subscriptSizing]=\"'dynamic'\" [floatLabel]=\"'always'\"\n appearance=\"outline\">\n <mat-label>Equation</mat-label>\n <textarea #formulaInput matInput [ngModel]=\"tempValue().formula\"\n (ngModelChange)=\"onFormulaChange($event)\" (keyup)=\"onFormulaCaret()\"\n (click)=\"onFormulaCaret()\" (focus)=\"onFormulaCaret()\"\n (keydown)=\"onFormulaKeydown($event)\" (blur)=\"onFormulaBlur()\"\n placeholder=\"e.g. (a + 2) / b\" rows=\"2\" spellcheck=\"false\" autocapitalize=\"off\"\n autocomplete=\"off\"></textarea>\n <mat-hint>Type <strong>$</strong> to add a form field, then join fields with the operator keys.</mat-hint>\n </mat-form-field>\n\n @if (pickerOpen()) {\n <div class=\"cfr-picker\" role=\"listbox\" aria-label=\"Form fields\">\n <div class=\"cfr-picker__head\">Insert a field</div>\n @if (pickerResults().length === 0) {\n <div class=\"cfr-picker__empty\">No matching number fields</div>\n } @else {\n @for (item of pickerResults(); track item.id; let idx = $index) {\n <button type=\"button\" role=\"option\" class=\"cfr-picker__item\"\n [class.cfr-picker__item--active]=\"idx === pickerActiveIndex()\"\n [attr.aria-selected]=\"idx === pickerActiveIndex()\"\n (mousedown)=\"$event.preventDefault()\" (click)=\"selectField(item)\">\n <mat-icon class=\"cfr-picker__icon\">{{ item.multipleInputInEditId ? 'format_list_numbered' : 'tag' }}</mat-icon>\n <span class=\"cfr-picker__label\">{{ item.label }}</span>\n <span class=\"cfr-picker__meta\">{{ item.formControlName }}</span>\n </button>\n }\n }\n </div>\n }\n </div>\n\n <div class=\"cfr-keypad\" role=\"group\" aria-label=\"Operators\">\n @for (key of operatorKeys; track key.token) {\n <button type=\"button\" class=\"cfr-key\" [attr.aria-label]=\"key.aria\"\n (click)=\"insertToken(key.token, key.caretBack)\">\n {{ key.label }}\n </button>\n }\n </div>\n </div>\n\n <!-- Optional equation source link -->\n @if (!!tempValue().getFormulaFromAFormInput) {\n <mat-form-field class=\"cfr-field\" [subscriptSizing]=\"'dynamic'\" [floatLabel]=\"'always'\"\n appearance=\"outline\">\n <mat-label>Equation source</mat-label>\n <mat-select [value]=\"tempValue().formControlWithFormula\"\n (selectionChange)=\"linkFormulaSource($event)\" placeholder=\"Select an input that holds a formula\">\n @for (input of formInputs(); track input.id) {\n <mat-option\n [disabled]=\"checkIfInputCanBeLinkedToCalVariable(mapToData(), input) || input.element !== 'select'\"\n [value]=\"input.formControlName\">\n {{ input.label }}\n </mat-option>\n }\n </mat-select>\n <mat-hint>Pull the equation from another input's value</mat-hint>\n </mat-form-field>\n }\n\n <!-- Bound fields legend -->\n <div class=\"cfr-variables\">\n <div class=\"cfr-block-head\">\n <span class=\"cfr-section__label\">Fields in this calculation</span>\n <span class=\"cfr-block-head__hint\">Rename a letter, or type $ above to add another</span>\n </div>\n\n @if ((tempValue().variables || []).length === 0) {\n <p class=\"cfr-variables__empty\">\n Type <strong>$</strong> in the equation above to add a form field \u2014 each becomes a short\n letter you can use in the equation.\n </p>\n } @else {\n <ul class=\"cfr-legend\">\n @for (variable of (tempValue().variables || []); track variable.id) {\n <li class=\"cfr-legend__row\">\n <div class=\"cfr-legend__main\">\n <input class=\"cfr-legend__token\" [ngModel]=\"variable.variable\"\n (ngModelChange)=\"setVariableShortName(variable.id, $event)\" aria-label=\"Short name\"\n maxlength=\"12\" spellcheck=\"false\" autocomplete=\"off\" autocapitalize=\"off\">\n <mat-icon class=\"cfr-legend__arrow\">arrow_right_alt</mat-icon>\n <button type=\"button\" class=\"cfr-legend__field\"\n [attr.aria-label]=\"'Insert ' + variable.label + ' into the equation'\"\n (click)=\"appendVariableToFormula(variable.variable)\">\n {{ variable.label }}\n </button>\n <button type=\"button\" class=\"cfr-legend__remove\"\n [attr.aria-label]=\"'Remove ' + variable.variable\"\n (click)=\"removeVariable(variable.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n @if (variable.parentInputId) {\n <div class=\"cfr-legend__fns\" role=\"group\" aria-label=\"Combine list values with\">\n <span class=\"cfr-legend__fns-label\" aria-hidden=\"true\">\u0192</span>\n @for (func of allCalculationFunction(); track func) {\n <button type=\"button\" class=\"cfr-legend__fn\"\n [class.cfr-legend__fn--active]=\"variable.function === func\"\n (click)=\"setVariableFunction(variable.id, func)\">\n {{ func }}\n </button>\n }\n </div>\n }\n </li>\n }\n </ul>\n }\n </div>\n\n <!-- Result formatting -->\n <div class=\"cfr-rounding\">\n <div class=\"cfr-block-head\">\n <span class=\"cfr-section__label\">Result formatting</span>\n <span class=\"cfr-block-head__hint\">Optional \u2014 how the answer is rounded</span>\n </div>\n\n <div class=\"cfr-rounding__row\">\n <mat-form-field class=\"cfr-field cfr-rounding__decimals\" [subscriptSizing]=\"'dynamic'\"\n [floatLabel]=\"'always'\" appearance=\"outline\">\n <mat-label>Decimal places</mat-label>\n <input matInput [ngModel]=\"tempValue().decimalPlaces\"\n (ngModelChange)=\"setDecimalPlaces($event)\" placeholder=\"0\" type=\"number\" min=\"0\">\n </mat-form-field>\n\n @if (tempValue().decimalPlaces) {\n <div class=\"cfr-rounding__mode\">\n <label class=\"cfr-section__label\">Rounding</label>\n <mat-chip-listbox aria-label=\"Rounding method\">\n @for (type of roundingType; track type.value) {\n <mat-chip-option (click)=\"setRoundingMode(type.value)\"\n [selected]=\"tempValue().roundingMode === type.value\">\n {{ type.label }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n </div>\n }\n </div>\n </div>\n </section>\n\n <div class=\"cfr-editor__actions\">\n <button mat-button (click)=\"cancelEdit()\">Cancel</button>\n <button mat-flat-button color=\"primary\" [disabled]=\"formulaStatus().kind === 'invalid'\"\n (click)=\"saveEdit()\">\n Save calculation\n </button>\n </div>\n</mat-card>\n}\n\n<!-- Disabled overlay -->\n<ng-template cdkConnectedOverlay [cdkConnectedOverlayOpen]=\"disabled\">\n <div class=\"cfr-disabled-overlay\"></div>\n</ng-template>\n", styles: [":host{display:block;position:relative}.cfr-hint{display:flex;align-items:center;gap:.5rem;padding:.875rem 1rem;background:var(--sem-info-surface);border-radius:var(--lib-forms-radius-sm, 8px);color:var(--lib-forms-on-surface);font-size:.875rem}.cfr-hint__icon{flex:0 0 auto}.cfr-section__label{display:block;font-size:.625rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--lib-forms-on-surface-variant)}.cfr-field{width:100%}.cfr-block-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem .75rem;margin-bottom:.75rem}.cfr-block-head__hint{font-size:.8125rem;color:var(--lib-forms-on-surface-variant)}.cfr-summary{display:flex;flex-direction:column;gap:1.5rem}.cfr-formula-card{background:var(--lib-forms-surface);border:1px solid color-mix(in srgb,var(--lib-forms-outline) 15%,transparent);border-radius:var(--lib-forms-radius-lg, 12px);box-shadow:var(--lib-forms-shadow-resting);display:flex;flex-direction:column;gap:1rem;padding:2rem}.cfr-formula-card__preview{display:block;border-radius:var(--lib-forms-radius-md, 10px);background:var(--lib-forms-surface-container-low)}.cfr-formula-card__meta{display:flex;align-items:center;gap:.5rem;font-size:.8125rem;color:var(--lib-forms-on-surface-variant)}.cfr-formula-card__meta-icon{font-size:1.125rem;width:1.125rem;height:1.125rem}.cfr-edit-trigger{width:100%}.cfr-editor{display:flex;flex-direction:column;gap:1.5rem;padding:1.5rem;border-color:color-mix(in srgb,var(--lib-forms-primary) 30%,transparent)}.cfr-editor__head{display:flex;flex-direction:column;gap:.5rem}.cfr-editor__title{display:flex;align-items:center;gap:.5rem;margin:0;font-size:1.25rem;font-weight:500;letter-spacing:-.01em;color:var(--lib-forms-on-surface)}.cfr-editor__subtitle{margin:0;font-size:.9375rem;color:var(--lib-forms-on-surface-variant)}.cfr-preview{display:flex;flex-direction:column;gap:.875rem;padding:1.25rem 1.5rem;border-radius:var(--lib-forms-radius-lg, 12px);background:var(--lib-forms-surface-container-low);border:1px solid color-mix(in srgb,var(--lib-forms-outline) 15%,transparent);transition:border-color var(--lib-forms-duration, .4s) var(--lib-forms-easing)}.cfr-preview[data-state=valid]{border-color:color-mix(in srgb,var(--sem-success) 45%,transparent)}.cfr-preview[data-state=invalid]{border-color:color-mix(in srgb,var(--sem-error) 45%,transparent)}.cfr-preview__render{display:block}.cfr-preview__status{display:flex;align-items:center;gap:.5rem;margin:0;font-size:.875rem;color:var(--lib-forms-on-surface-variant)}.cfr-preview__status-icon{flex:0 0 auto;font-size:1.125rem;width:1.125rem;height:1.125rem}.cfr-preview[data-state=valid] .cfr-preview__status{color:var(--sem-success)}.cfr-preview[data-state=invalid] .cfr-preview__status{color:var(--sem-error)}.cfr-section{display:flex;flex-direction:column;gap:1.5rem}.cfr-equation{display:flex;flex-direction:column;gap:.625rem}.cfr-equation__field{position:relative}.cfr-keypad{display:flex;flex-wrap:wrap;gap:.375rem}.cfr-key{display:inline-flex;align-items:center;justify-content:center;min-width:2.5rem;height:2.25rem;padding:0 .625rem;border:1px solid var(--lib-forms-outline-variant);border-radius:var(--lib-forms-radius-sm, 8px);background:var(--lib-forms-surface);color:var(--lib-forms-on-surface);font-size:1rem;font-weight:500;cursor:pointer;transition:border-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),background-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),transform var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.cfr-key:hover{border-color:var(--lib-forms-primary);background:color-mix(in srgb,var(--lib-forms-primary) 8%,transparent)}.cfr-key:active{transform:translateY(1px)}.cfr-picker{position:absolute;z-index:20;left:0;right:0;top:calc(100% - 1.25rem);max-height:16rem;overflow-y:auto;padding:.5rem;border-radius:var(--lib-forms-radius-md, 10px);background:var(--lib-forms-surface);border:1px solid color-mix(in srgb,var(--lib-forms-outline) 20%,transparent);box-shadow:var(--lib-forms-shadow-elevated, var(--lib-forms-shadow-resting))}.cfr-picker__head{padding:.25rem .5rem .5rem;font-size:.625rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--lib-forms-on-surface-variant)}.cfr-picker__empty{padding:.75rem .5rem;font-size:.875rem;color:var(--lib-forms-on-surface-variant)}.cfr-picker__item{display:flex;align-items:center;gap:.625rem;width:100%;padding:.5rem .625rem;border:none;border-radius:var(--lib-forms-radius-sm, 8px);background:transparent;color:var(--lib-forms-on-surface);font-size:.9375rem;text-align:left;cursor:pointer;transition:background-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.cfr-picker__item:hover,.cfr-picker__item--active{background:color-mix(in srgb,var(--lib-forms-primary) 12%,transparent)}.cfr-picker__icon{flex:0 0 auto;font-size:1.125rem;width:1.125rem;height:1.125rem;color:var(--lib-forms-on-surface-variant)}.cfr-picker__label{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cfr-picker__meta{flex:0 0 auto;font-size:.75rem;color:var(--lib-forms-on-surface-variant)}.cfr-variables{display:flex;flex-direction:column;gap:.75rem}.cfr-variables__empty{margin:0;font-size:.875rem;color:var(--lib-forms-on-surface-variant)}.cfr-legend{display:flex;flex-direction:column;gap:.5rem;margin:0;padding:0;list-style:none}.cfr-legend__row{display:flex;flex-direction:column;gap:.5rem;padding:.5rem .625rem;border-radius:var(--lib-forms-radius-md, 10px);background:var(--lib-forms-surface-container-low);border:1px solid color-mix(in srgb,var(--lib-forms-outline) 12%,transparent)}.cfr-legend__main{display:flex;align-items:center;gap:.5rem}.cfr-legend__token{flex:0 0 auto;width:2.5rem;height:1.875rem;padding:0 .25rem;text-align:center;font-family:JetBrains Mono,SF Mono,ui-monospace,monospace;font-size:.9375rem;font-weight:600;color:var(--lib-forms-primary);background:color-mix(in srgb,var(--lib-forms-primary) 8%,var(--lib-forms-surface));border:1px solid color-mix(in srgb,var(--lib-forms-primary) 30%,transparent);border-radius:var(--lib-forms-radius-sm, 8px);transition:border-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),box-shadow var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.cfr-legend__token:focus{outline:none;border-color:var(--lib-forms-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--lib-forms-primary) 25%,transparent)}.cfr-legend__arrow{flex:0 0 auto;font-size:1.125rem;width:1.125rem;height:1.125rem;color:var(--lib-forms-on-surface-variant)}.cfr-legend__field{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:.3125rem .625rem;border:1px solid transparent;border-radius:var(--lib-forms-radius-sm, 8px);background:transparent;color:var(--lib-forms-on-surface);font-size:.875rem;text-align:left;cursor:pointer;transition:border-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),background-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.cfr-legend__field:hover{border-color:var(--lib-forms-outline-variant);background:color-mix(in srgb,var(--lib-forms-primary) 6%,transparent)}.cfr-legend__remove{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border:none;border-radius:50%;background:transparent;color:var(--lib-forms-on-surface-variant);cursor:pointer;transition:color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),background-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.cfr-legend__remove mat-icon{font-size:1.125rem;width:1.125rem;height:1.125rem}.cfr-legend__remove:hover{color:var(--sem-error);background:var(--sem-error-surface)}.cfr-legend__fns{display:flex;flex-wrap:wrap;align-items:center;gap:.25rem;padding-left:3rem}.cfr-legend__fns-label{margin-right:.125rem;font-style:italic;font-size:.8125rem;color:var(--lib-forms-on-surface-variant)}.cfr-legend__fn{padding:.1875rem .5rem;border:1px solid var(--lib-forms-outline-variant);border-radius:var(--lib-forms-radius-pill, 999px);background:transparent;color:var(--lib-forms-on-surface-variant);font-size:.6875rem;font-weight:500;letter-spacing:.02em;cursor:pointer;transition:border-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),background-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.cfr-legend__fn:hover{border-color:var(--lib-forms-primary);color:var(--lib-forms-on-surface)}.cfr-legend__fn--active{border-color:transparent;background:var(--lib-forms-primary);color:var(--lib-forms-on-primary)}.cfr-rounding{display:flex;flex-direction:column}.cfr-rounding__row{display:flex;gap:1.5rem;flex-wrap:wrap}.cfr-rounding__decimals{flex:1 1 150px;min-width:150px}.cfr-rounding__mode{flex:2 1 220px;display:flex;flex-direction:column;gap:.5rem}.cfr-editor__actions{display:flex;justify-content:flex-end;gap:.75rem}.cfr-disabled-overlay{position:absolute;inset:0;background:color-mix(in srgb,var(--lib-forms-shadow-color) 50%,transparent);z-index:10}mat-chip{cursor:pointer}\n"] }]
|
|
632
|
+
}], ctorParameters: () => [], propDecorators: { placeholder: [{
|
|
633
|
+
type: Input
|
|
634
|
+
}], required: [{
|
|
635
|
+
type: Input
|
|
636
|
+
}], disabled: [{
|
|
637
|
+
type: Input
|
|
638
|
+
}], mapToData: [{ type: i0.Input, args: [{ isSignal: true, alias: "mapToData", required: false }] }], formInputs: [{ type: i0.Input, args: [{ isSignal: true, alias: "formInputs", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], valueChange2dWithPath: [{ type: i0.Output, args: ["valueChange2dWithPath"] }], valueChanged: [{ type: i0.Output, args: ["valueChanged"] }], formulaInputRef: [{ type: i0.ViewChild, args: ['formulaInput', { isSignal: true }] }], value: [{
|
|
639
|
+
type: Input
|
|
640
|
+
}] } });
|
|
641
|
+
|
|
642
|
+
export { CalculatedFieldRulesComponent };
|
|
643
|
+
//# sourceMappingURL=ngx-t-forms-calculated-field-rules.component-BhxT6tRq.mjs.map
|