@masterteam/formula-builder 0.0.27 → 0.0.29

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 CHANGED
@@ -1,76 +1,78 @@
1
- # @masterteam/formula-builder
2
-
3
- A powerful Formula Builder component for Angular 20+ with Tailwind CSS 4.
4
-
5
- ## Features
6
-
7
- - ✅ **Visual Block Editor** - Make.com-inspired formula editing experience
8
- - ✅ **42+ Built-in Functions** - Math, Logic, Aggregation, Hierarchy, Date, String
9
- - ✅ **Real-time Validation** - Syntax checking with error highlighting
10
- - ✅ **Context-aware Autocomplete** - Smart suggestions as you type
11
- - ✅ **Drag & Drop** - Visual block manipulation with Angular CDK
12
- - ✅ **Property References** - @PropertyKey, *.PropertyKey, $.Parent.PropertyKey
13
-
14
- ## Installation
15
-
16
- ```bash
17
- pnpm add @masterteam/formula-builder
18
- ```
19
-
20
- ## Usage
21
-
22
- ```typescript
23
- import { FormulaBuilder, type FormulaBuilderValue } from '@masterteam/formula-builder';
24
- import { FormsModule } from '@angular/forms';
25
-
26
- @Component({
27
- imports: [FormsModule, FormulaBuilder],
28
- template: `
29
- <mt-formula-builder
30
- [(ngModel)]="formulaState"
31
- [levelSchemaId]="levelSchemaId"
32
- (validationChange)="onValidation($event)"
33
- />
34
- `
35
- })
36
- export class MyComponent {
37
- formulaState: FormulaBuilderValue = { expression: '', builder: [] };
38
- levelSchemaId = 6;
39
- }
40
- ```
41
-
42
- ## API
43
-
44
- ### Inputs
45
-
46
- | Input | Type | Description |
47
- |-------|------|-------------|
48
- | `ngModel` / `formControl` | `FormulaBuilderValue` | Form value `{ expression, builder }` (builder is token JSON array) |
49
- | `propertiesByPath` | `Record<string, { key: string; name?: string }[]>` | Scoped properties (optional) |
50
- | `levelSchemaId` | `number` | Level schema ID for validation context |
51
- | `templateId` | `number` | Template ID for validation context |
52
- | `placeholder` | `string` | Placeholder text |
53
- | `hideToolbar` | `boolean` | Hide the top toolbar |
54
- | `hideStatusBar` | `boolean` | Hide the status bar |
55
- | `toolbarTabs` | `ToolbarTab[]` | Visible toolbar tabs, e.g. `['functions', 'operators']` |
56
- | `codeOnly` | `boolean` | Force code mode and hide the mode toggle |
57
- | `builderOnly` | `boolean` | Force block builder mode and hide the code-mode toggle |
58
- | `valueMode` | `'object' \| 'expression'` | Emit `{ expression, builder }` by default, or emit the expression string for legacy string contracts |
59
- | `isProcessBuilder` | `boolean` | Use Process Builder current-only Formula Engine endpoints |
60
-
61
- ### Builder Mode Display Contract
62
-
63
- Lookup/status value chips keep the executable literal as the stable backend key and use the block `display` value only for the friendly label. Builder-only hosts can show readable labels without changing the serialized formula expression.
64
-
65
- ### Outputs
66
-
67
- | Output | Type | Description |
68
- |--------|------|-------------|
69
- | `validationChange` | `ValidationResult` | Emits validation results |
70
- | `tokensChange` | `FormulaToken[]` | Emits when tokens change |
71
-
72
- ## Development
73
-
74
- ```bash
75
- pnpm build:formula-builder
76
- ```
1
+ # @masterteam/formula-builder
2
+
3
+ A powerful Formula Builder component for Angular 20+ with Tailwind CSS 4.
4
+
5
+ ## Features
6
+
7
+ - ✅ **Visual Block Editor** - Make.com-inspired formula editing experience
8
+ - ✅ **42+ Built-in Functions** - Math, Logic, Aggregation, Hierarchy, Date, String
9
+ - ✅ **Real-time Validation** - Syntax checking with error highlighting
10
+ - ✅ **Context-aware Autocomplete** - Smart suggestions as you type
11
+ - ✅ **Drag & Drop** - Visual block manipulation with Angular CDK
12
+ - ✅ **Property References** - @PropertyKey, *.PropertyKey, $.Parent.PropertyKey
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pnpm add @masterteam/formula-builder
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```typescript
23
+ import { FormulaBuilder, type FormulaBuilderValue } from '@masterteam/formula-builder';
24
+ import { FormsModule } from '@angular/forms';
25
+
26
+ @Component({
27
+ imports: [FormsModule, FormulaBuilder],
28
+ template: `
29
+ <mt-formula-builder
30
+ [(ngModel)]="formulaState"
31
+ [levelSchemaId]="levelSchemaId"
32
+ (validationChange)="onValidation($event)"
33
+ />
34
+ `
35
+ })
36
+ export class MyComponent {
37
+ formulaState: FormulaBuilderValue = { expression: '', builder: [] };
38
+ levelSchemaId = 6;
39
+ }
40
+ ```
41
+
42
+ ## API
43
+
44
+ ### Inputs
45
+
46
+ | Input | Type | Description |
47
+ |-------|------|-------------|
48
+ | `ngModel` / `formControl` | `FormulaBuilderValue` | Form value `{ expression, builder }` (builder is token JSON array) |
49
+ | `propertiesByPath` | `Record<string, { key: string; name?: string }[]>` | Scoped properties (optional) |
50
+ | `levelSchemaId` | `number` | Level schema ID for validation context |
51
+ | `templateId` | `number` | Template ID for validation context |
52
+ | `placeholder` | `string` | Placeholder text |
53
+ | `hideToolbar` | `boolean` | Hide the top toolbar |
54
+ | `hideStatusBar` | `boolean` | Hide the status bar |
55
+ | `toolbarTabs` | `ToolbarTab[]` | Visible toolbar tabs, e.g. `['functions', 'operators']` |
56
+ | `functionCategories` | `FunctionCategoryGroup[]` | Optional host-owned catalog; overrides API context functions when provided |
57
+ | `operators` | `OperatorDefinition[]` | Operators shown in the toolbar; defaults to the shared formula operator catalog |
58
+ | `codeOnly` | `boolean` | Force code mode and hide the mode toggle |
59
+ | `builderOnly` | `boolean` | Force block builder mode and hide the code-mode toggle |
60
+ | `valueMode` | `'object' \| 'expression'` | Emit `{ expression, builder }` by default, or emit the expression string for legacy string contracts |
61
+ | `isProcessBuilder` | `boolean` | Use Process Builder current-only Formula Engine endpoints |
62
+
63
+ ### Builder Mode Display Contract
64
+
65
+ Lookup/status value chips keep the executable literal as the stable backend key and use the block `display` value only for the friendly label. Builder-only hosts can show readable labels without changing the serialized formula expression.
66
+
67
+ ### Outputs
68
+
69
+ | Output | Type | Description |
70
+ |--------|------|-------------|
71
+ | `validationChange` | `ValidationResult` | Emits validation results |
72
+ | `tokensChange` | `FormulaToken[]` | Emits when tokens change |
73
+
74
+ ## Development
75
+
76
+ ```bash
77
+ pnpm build:formula-builder
78
+ ```
@@ -1,2 +1,2 @@
1
- /*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
1
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
2
  @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-900:oklch(41.4% .112 45.904);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-900:oklch(37.8% .077 168.94);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wider:.05em;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.static{position:static}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mt-2{margin-top:calc(var(--spacing) * 2)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.block{display:block}.flex{display:flex}.hidden{display:none}.inline{display:inline}.table{display:table}.size-5{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.h-4{height:calc(var(--spacing) * 4)}.max-h-\[min\(24rem\,calc\(100vh-10rem\)\)\]{max-height:min(24rem,100vh - 10rem)}.min-h-11{min-height:calc(var(--spacing) * 11)}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-4{gap:calc(var(--spacing) * 4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-slate-200{border-color:var(--color-slate-200)}.border-surface-200{border-color:var(--p-surface-200)}@supports (color:color-mix(in lab, red, red)){.border-surface-200{border-color:color-mix(in srgb, var(--p-surface-200) calc(100% * 1), transparent)}}.bg-amber-100{background-color:var(--color-amber-100)}.bg-emerald-100{background-color:var(--color-emerald-100)}.bg-primary{background-color:var(--p-primary-color)}@supports (color:color-mix(in lab, red, red)){.bg-primary{background-color:color-mix(in srgb, var(--p-primary-color) calc(100% * 1), transparent)}}.bg-red-500{background-color:var(--color-red-500)}.bg-slate-50{background-color:var(--color-slate-50)}.bg-slate-200{background-color:var(--color-slate-200)}.bg-white{background-color:var(--color-white)}.p-0{padding:0}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pr-1{padding-right:var(--spacing)}.text-left{text-align:left}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.text-amber-700{color:var(--color-amber-700)}.text-emerald-600{color:var(--color-emerald-600)}.text-slate-300{color:var(--color-slate-300)}.text-slate-400{color:var(--color-slate-400)}.text-slate-600{color:var(--color-slate-600)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-primary{--tw-ring-color:var(--p-primary-color)}@supports (color:color-mix(in lab, red, red)){.ring-primary{--tw-ring-color:color-mix(in srgb, var(--p-primary-color) calc(100% * 1), transparent)}}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:bg-amber-200:hover{background-color:var(--color-amber-200)}.hover\:bg-red-600:hover{background-color:var(--color-red-600)}.hover\:bg-slate-50:hover{background-color:var(--color-slate-50)}.hover\:bg-slate-100:hover{background-color:var(--color-slate-100)}.hover\:opacity-90:hover{opacity:.9}}.disabled\:opacity-50:disabled{opacity:.5}.dark\:border-slate-700:where(.dark,.dark *){border-color:var(--color-slate-700)}.dark\:border-surface-700:where(.dark,.dark *){border-color:var(--p-surface-700)}@supports (color:color-mix(in lab, red, red)){.dark\:border-surface-700:where(.dark,.dark *){border-color:color-mix(in srgb, var(--p-surface-700) calc(100% * 1), transparent)}}.dark\:bg-amber-900\/30:where(.dark,.dark *){background-color:#7b33064d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-amber-900) 30%, transparent)}}.dark\:bg-emerald-900\/30:where(.dark,.dark *){background-color:#004e3b4d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-emerald-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-emerald-900) 30%, transparent)}}.dark\:bg-slate-700:where(.dark,.dark *){background-color:var(--color-slate-700)}.dark\:bg-slate-800\/50:where(.dark,.dark *){background-color:#1d293d80}@supports (color:color-mix(in lab, red, red)){.dark\:bg-slate-800\/50:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-slate-800) 50%, transparent)}}.dark\:bg-surface-800:where(.dark,.dark *){background-color:var(--p-surface-800)}@supports (color:color-mix(in lab, red, red)){.dark\:bg-surface-800:where(.dark,.dark *){background-color:color-mix(in srgb, var(--p-surface-800) calc(100% * 1), transparent)}}.dark\:text-amber-300:where(.dark,.dark *){color:var(--color-amber-300)}.dark\:text-emerald-400:where(.dark,.dark *){color:var(--color-emerald-400)}.dark\:text-slate-300:where(.dark,.dark *){color:var(--color-slate-300)}.dark\:text-slate-600:where(.dark,.dark *){color:var(--color-slate-600)}@media (hover:hover){.dark\:hover\:bg-amber-900\/50:where(.dark,.dark *):hover{background-color:#7b330680}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-amber-900\/50:where(.dark,.dark *):hover{background-color:color-mix(in oklab, var(--color-amber-900) 50%, transparent)}}.dark\:hover\:bg-slate-700:where(.dark,.dark *):hover{background-color:var(--color-slate-700)}}}@keyframes enter{0%{opacity:var(--p-enter-opacity,1);transform:translate3d(var(--p-enter-translate-x,0), var(--p-enter-translate-y,0), 0) scale3d(var(--p-enter-scale,1), var(--p-enter-scale,1), var(--p-enter-scale,1)) rotate(var(--p-enter-rotate,0))}}@keyframes leave{to{opacity:var(--p-leave-opacity,1);transform:translate3d(var(--p-leave-translate-x,0), var(--p-leave-translate-y,0), 0) scale3d(var(--p-leave-scale,1), var(--p-leave-scale,1), var(--p-leave-scale,1)) rotate(var(--p-leave-rotate,0))}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
@@ -14,7 +14,7 @@ import * as i2 from 'primeng/popover';
14
14
  import { PopoverModule } from 'primeng/popover';
15
15
  import { Skeleton } from 'primeng/skeleton';
16
16
  import { Subject, debounceTime, distinctUntilChanged, tap, switchMap, of, map, catchError } from 'rxjs';
17
- import { VALIDATION_FUNCTION_CATEGORIES, serializeTokens, tokenizeFormulaTemplate, createPropertyBlock, createLiteralBlock, FormulaToolbar, FormulaStatusBar, FormulaEditor, FormulaEditorCode } from '@masterteam/components/formula';
17
+ import { DEFAULT_OPERATORS, VALIDATION_FUNCTION_CATEGORIES, serializeTokens, tokenizeFormulaTemplate, createPropertyBlock, createLiteralBlock, FormulaToolbar, FormulaStatusBar, FormulaEditor, FormulaEditorCode } from '@masterteam/components/formula';
18
18
  export { createFunctionBlock, createLiteralBlock, createOperatorBlock, createPropertyBlock } from '@masterteam/components/formula';
19
19
  import { HttpClient } from '@angular/common/http';
20
20
 
@@ -484,6 +484,15 @@ function appendToPath(basePath, operatorToken, value, isBase) {
484
484
  return value ? `${withOperator}.${value}` : withOperator;
485
485
  }
486
486
 
487
+ /** Resolve a host override before API context and the package fallback catalog. */
488
+ function resolveFormulaFunctionCategories(configured, context, fallback) {
489
+ if (configured.length > 0)
490
+ return configured;
491
+ if (context.length > 0)
492
+ return context;
493
+ return fallback;
494
+ }
495
+
487
496
  const DEFAULT_SCOPE_ICON = 'general.placeholder';
488
497
  const DEFAULT_TOOLBAR_TABS = [
489
498
  'functions',
@@ -554,6 +563,10 @@ class FormulaBuilder {
554
563
  hideStatusBar = input(false, ...(ngDevMode ? [{ debugName: "hideStatusBar" }] : /* istanbul ignore next */ []));
555
564
  /** Visible toolbar tabs */
556
565
  toolbarTabs = input(DEFAULT_TOOLBAR_TABS, ...(ngDevMode ? [{ debugName: "toolbarTabs" }] : /* istanbul ignore next */ []));
566
+ /** Optional host-owned function catalog. Takes precedence over API context. */
567
+ functionCategories = input([], ...(ngDevMode ? [{ debugName: "functionCategories" }] : /* istanbul ignore next */ []));
568
+ /** Operators shown in the formula toolbar. */
569
+ operators = input(DEFAULT_OPERATORS, ...(ngDevMode ? [{ debugName: "operators" }] : /* istanbul ignore next */ []));
557
570
  /** Force code editor mode and hide the mode toggle */
558
571
  codeOnly = input(false, ...(ngDevMode ? [{ debugName: "codeOnly" }] : /* istanbul ignore next */ []));
559
572
  /** Keep the block builder active and hide the code-mode toggle. */
@@ -688,11 +701,8 @@ class FormulaBuilder {
688
701
  isValidating = this.validatorService.isValidating;
689
702
  /** Builder context from API */
690
703
  builderContext = signal(null, ...(ngDevMode ? [{ debugName: "builderContext" }] : /* istanbul ignore next */ []));
691
- /** Function categories from context */
692
- functionCategories = computed(() => {
693
- const categories = this.builderContext()?.functions?.categories ?? [];
694
- return categories.length ? categories : VALIDATION_FUNCTION_CATEGORIES;
695
- }, ...(ngDevMode ? [{ debugName: "functionCategories" }] : /* istanbul ignore next */ []));
704
+ /** Function categories from the host, context API, or validation fallback. */
705
+ resolvedFunctionCategories = computed(() => resolveFormulaFunctionCategories(this.functionCategories(), this.builderContext()?.functions?.categories ?? [], VALIDATION_FUNCTION_CATEGORIES), ...(ngDevMode ? [{ debugName: "resolvedFunctionCategories" }] : /* istanbul ignore next */ []));
696
706
  /** Is builder context loading */
697
707
  isContextLoading = signal(false, ...(ngDevMode ? [{ debugName: "isContextLoading" }] : /* istanbul ignore next */ []));
698
708
  contextRequestId = 0;
@@ -1482,13 +1492,13 @@ class FormulaBuilder {
1482
1492
  return translated && translated !== key ? translated : token;
1483
1493
  }
1484
1494
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FormulaBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
1485
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: FormulaBuilder, isStandalone: true, selector: "mt-formula-builder", inputs: { propertiesByPath: { classPropertyName: "propertiesByPath", publicName: "propertiesByPath", isSignal: true, isRequired: false, transformFunction: null }, levelSchemaId: { classPropertyName: "levelSchemaId", publicName: "levelSchemaId", isSignal: true, isRequired: false, transformFunction: null }, moduleId: { classPropertyName: "moduleId", publicName: "moduleId", isSignal: true, isRequired: false, transformFunction: null }, contextKey: { classPropertyName: "contextKey", publicName: "contextKey", isSignal: true, isRequired: false, transformFunction: null }, contextEntityTypeKey: { classPropertyName: "contextEntityTypeKey", publicName: "contextEntityTypeKey", isSignal: true, isRequired: false, transformFunction: null }, templateId: { classPropertyName: "templateId", publicName: "templateId", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, hideToolbar: { classPropertyName: "hideToolbar", publicName: "hideToolbar", isSignal: true, isRequired: false, transformFunction: null }, hideStatusBar: { classPropertyName: "hideStatusBar", publicName: "hideStatusBar", isSignal: true, isRequired: false, transformFunction: null }, toolbarTabs: { classPropertyName: "toolbarTabs", publicName: "toolbarTabs", isSignal: true, isRequired: false, transformFunction: null }, codeOnly: { classPropertyName: "codeOnly", publicName: "codeOnly", isSignal: true, isRequired: false, transformFunction: null }, builderOnly: { classPropertyName: "builderOnly", publicName: "builderOnly", isSignal: true, isRequired: false, transformFunction: null }, valueMode: { classPropertyName: "valueMode", publicName: "valueMode", isSignal: true, isRequired: false, transformFunction: null }, isProcessBuilder: { classPropertyName: "isProcessBuilder", publicName: "isProcessBuilder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { validationChange: "validationChange", tokensChange: "tokensChange" }, host: { classAttribute: "block" }, providers: [
1495
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: FormulaBuilder, isStandalone: true, selector: "mt-formula-builder", inputs: { propertiesByPath: { classPropertyName: "propertiesByPath", publicName: "propertiesByPath", isSignal: true, isRequired: false, transformFunction: null }, levelSchemaId: { classPropertyName: "levelSchemaId", publicName: "levelSchemaId", isSignal: true, isRequired: false, transformFunction: null }, moduleId: { classPropertyName: "moduleId", publicName: "moduleId", isSignal: true, isRequired: false, transformFunction: null }, contextKey: { classPropertyName: "contextKey", publicName: "contextKey", isSignal: true, isRequired: false, transformFunction: null }, contextEntityTypeKey: { classPropertyName: "contextEntityTypeKey", publicName: "contextEntityTypeKey", isSignal: true, isRequired: false, transformFunction: null }, templateId: { classPropertyName: "templateId", publicName: "templateId", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, hideToolbar: { classPropertyName: "hideToolbar", publicName: "hideToolbar", isSignal: true, isRequired: false, transformFunction: null }, hideStatusBar: { classPropertyName: "hideStatusBar", publicName: "hideStatusBar", isSignal: true, isRequired: false, transformFunction: null }, toolbarTabs: { classPropertyName: "toolbarTabs", publicName: "toolbarTabs", isSignal: true, isRequired: false, transformFunction: null }, functionCategories: { classPropertyName: "functionCategories", publicName: "functionCategories", isSignal: true, isRequired: false, transformFunction: null }, operators: { classPropertyName: "operators", publicName: "operators", isSignal: true, isRequired: false, transformFunction: null }, codeOnly: { classPropertyName: "codeOnly", publicName: "codeOnly", isSignal: true, isRequired: false, transformFunction: null }, builderOnly: { classPropertyName: "builderOnly", publicName: "builderOnly", isSignal: true, isRequired: false, transformFunction: null }, valueMode: { classPropertyName: "valueMode", publicName: "valueMode", isSignal: true, isRequired: false, transformFunction: null }, isProcessBuilder: { classPropertyName: "isProcessBuilder", publicName: "isProcessBuilder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { validationChange: "validationChange", tokensChange: "tokensChange" }, host: { classAttribute: "block" }, providers: [
1486
1496
  {
1487
1497
  provide: NG_VALUE_ACCESSOR,
1488
1498
  useExisting: forwardRef(() => FormulaBuilder),
1489
1499
  multi: true,
1490
1500
  },
1491
- ], viewQueries: [{ propertyName: "editor", first: true, predicate: ["formulaEditor"], descendants: true, isSignal: true }, { propertyName: "codeEditor", first: true, predicate: ["formulaEditorCode"], descendants: true, isSignal: true }], ngImport: i0, template: "<mt-card\r\n headless\r\n [class.ring-2]=\"hasFocus()\"\r\n [class.ring-primary]=\"hasFocus()\"\r\n [paddingless]=\"true\"\r\n>\r\n <div\r\n *transloco=\"let t; prefix: 'formulaBuilder'\"\r\n class=\"flex flex-col overflow-hidden\"\r\n >\r\n <!-- Toolbar - Pass data via inputs (pure component) -->\r\n @if (!hideToolbar()) {\r\n <mt-formula-toolbar\r\n [functionCategories]=\"functionCategories()\"\r\n [visibleTabs]=\"resolvedToolbarTabs()\"\r\n (onBlockInsert)=\"onBlockInsert($event)\"\r\n >\r\n <ng-template #properties let-insertBlock=\"insertBlock\">\r\n @if (isContextLoading()) {\r\n <div class=\"flex flex-col gap-4 p-4\">\r\n <div class=\"flex items-center justify-between gap-4\">\r\n <p-skeleton\r\n width=\"18rem\"\r\n height=\"2.5rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n <p-skeleton\r\n width=\"7rem\"\r\n height=\"2.5rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n </div>\r\n <div class=\"flex items-center gap-4\">\r\n <div\r\n class=\"flex min-h-11 min-w-0 flex-1 flex-col gap-2 rounded-lg bg-slate-50 px-3 py-2 dark:bg-slate-800/50\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <p-skeleton\r\n width=\"3rem\"\r\n height=\"1rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n <div\r\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\r\n ></div>\r\n <p-skeleton\r\n class=\"flex-1\"\r\n height=\"2rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n </div>\r\n <div\r\n class=\"flex items-center gap-2 border-t border-slate-200 pt-2 dark:border-slate-700\"\r\n >\r\n <p-skeleton\r\n width=\"3rem\"\r\n height=\"1rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n <div\r\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\r\n ></div>\r\n <p-skeleton\r\n class=\"flex-1\"\r\n height=\"2.5rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n </div>\r\n <p-skeleton height=\"2rem\" styleClass=\"rounded-md\" />\r\n </div>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"flex flex-col gap-4 p-4\">\r\n <!-- Scope Selection - Full width, centered -->\r\n <div class=\"flex items-center justify-between gap-4\">\r\n <mt-radio-cards\r\n [options]=\"scopeOptions()\"\r\n [activeId]=\"propertyScope()\"\r\n (selectionChange)=\"onScopeChange($event)\"\r\n size=\"small\"\r\n />\r\n </div>\r\n\r\n <!-- Path + Field + Preview Row -->\r\n <div class=\"flex items-center gap-4\">\r\n <!-- Path Container - Flexible -->\r\n <div\r\n class=\"flex min-h-11 min-w-0 flex-1 flex-col gap-2 rounded-lg bg-slate-50 px-3 py-2 dark:bg-slate-800/50\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <span\r\n class=\"shrink-0 text-xs font-semibold uppercase tracking-wider text-slate-400\"\r\n >{{ t(\"properties.path\") }}</span\r\n >\r\n <div\r\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\r\n ></div>\r\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\r\n @if (isDirectAccess()) {\r\n <span\r\n class=\"rounded-md bg-emerald-100 px-2.5 py-1 text-xs font-medium text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400\"\r\n >\r\n \u2713 {{ t(\"properties.direct-access\") }}\r\n </span>\r\n } @else if (pathSegments().length === 0) {\r\n <span class=\"text-xs italic text-slate-400\">{{\r\n t(\"properties.no-path-configured\")\r\n }}</span>\r\n } @else {\r\n <div\r\n class=\"flex min-w-0 flex-1 items-center gap-1.5 overflow-x-auto\"\r\n >\r\n @for (\r\n segment of pathSegments();\r\n track $index;\r\n let segmentIndex = $index\r\n ) {\r\n @if (segmentIndex > 0) {\r\n <span\r\n class=\"shrink-0 text-slate-300 dark:text-slate-600\"\r\n >\u203A</span\r\n >\r\n }\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"\r\n segment.value ||\r\n t('properties.select') +\r\n ' ' +\r\n getOperatorDisplayName(segment.operatorToken)\r\n \"\r\n icon=\"arrow.chevron-down\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n [severity]=\"\r\n segment.value ? 'primary' : 'secondary'\r\n \"\r\n [iconPos]=\"'end'\"\r\n (onClick)=\"pathPopover.toggle($event)\"\r\n ></mt-button>\r\n\r\n <p-popover\r\n #pathPopover\r\n [style]=\"{ width: 'max-content' }\"\r\n appendTo=\"body\"\r\n >\r\n <div class=\"p-2\">\r\n <div\r\n class=\"mb-2 text-xs font-semibold uppercase text-slate-400\"\r\n >\r\n {{ t(\"properties.select\") }}\r\n {{\r\n getOperatorDisplayName(\r\n segment.operatorToken\r\n )\r\n }}\r\n </div>\r\n <div\r\n class=\"flex max-h-[min(24rem,calc(100vh-10rem))] flex-col gap-1 overflow-y-auto pr-1\"\r\n >\r\n @for (\r\n option of segmentOptions(segmentIndex);\r\n track option.key\r\n ) {\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"option.name\"\r\n [icon]=\"\r\n segment.value === option.key\r\n ? 'general.check'\r\n : 'general.minus'\r\n \"\r\n [iconPos]=\"'end'\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n [styleClass]=\"\r\n 'w-full justify-start rounded-md px-2.5 py-1.5 text-left text-xs font-medium transition-colors ' +\r\n (segment.value === option.key\r\n ? 'bg-primary text-white'\r\n : 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700')\r\n \"\r\n (onClick)=\"\r\n setPathSegmentValue(\r\n segmentIndex,\r\n option.key\r\n );\r\n pathPopover.hide()\r\n \"\r\n ></mt-button>\r\n }\r\n </div>\r\n @if (segment.optional) {\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"t('actions.clear')\"\r\n [outlined]=\"true\"\r\n icon=\"general.x-close\"\r\n [iconPos]=\"'end'\"\r\n size=\"small\"\r\n severity=\"danger\"\r\n styleClass=\"mt-2 w-full rounded-md border border-slate-200 py-1 text-xs text-slate-400 hover:bg-slate-50 dark:border-slate-700\"\r\n (onClick)=\"\r\n setPathSegmentValue(segmentIndex, null);\r\n pathPopover.hide()\r\n \"\r\n ></mt-button>\r\n }\r\n </div>\r\n </p-popover>\r\n\r\n @if (\r\n segment.canRemove &&\r\n segmentIndex === pathSegments().length - 1\r\n ) {\r\n <mt-button\r\n type=\"button\"\r\n icon=\"general.x-close\"\r\n size=\"small\"\r\n severity=\"danger\"\r\n styleClass=\"flex size-5 shrink-0 items-center justify-center rounded-full bg-red-500 text-white hover:bg-red-600\"\r\n (onClick)=\"removePathSegment(segmentIndex)\"\r\n ></mt-button>\r\n }\r\n }\r\n <mt-button\r\n type=\"button\"\r\n icon=\"general.plus\"\r\n size=\"small\"\r\n severity=\"primary\"\r\n [disabled]=\"!canAddNextSegment()\"\r\n [class.hidden]=\"!canAddNextSegment()\"\r\n styleClass=\"flex size-6 shrink-0 items-center justify-center rounded-md bg-primary text-xs font-bold text-white hover:opacity-90 disabled:opacity-50\"\r\n (onClick)=\"nextOperatorPopover.toggle($event)\"\r\n ></mt-button>\r\n </div>\r\n }\r\n <p-popover\r\n #nextOperatorPopover\r\n [style]=\"{ width: 'max-content' }\"\r\n appendTo=\"body\"\r\n >\r\n <div class=\"p-2\">\r\n <div\r\n class=\"mb-2 text-xs font-semibold uppercase text-slate-400\"\r\n >\r\n {{ t(\"properties.add-segment\") }}\r\n </div>\r\n <div\r\n class=\"flex max-h-[min(24rem,calc(100vh-10rem))] flex-col gap-1 overflow-y-auto pr-1\"\r\n >\r\n @for (\r\n option of nextOperatorOptions();\r\n track option.token\r\n ) {\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"getOperatorDisplayName(option.token)\"\r\n icon=\"general.plus\"\r\n [iconPos]=\"'end'\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n styleClass=\"w-full justify-start rounded-md px-2.5 py-1.5 text-left text-xs font-medium transition-colors text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700\"\r\n (onClick)=\"\r\n onAddSegmentSelection(\r\n option.token,\r\n nextOperatorPopover\r\n )\r\n \"\r\n ></mt-button>\r\n }\r\n </div>\r\n </div>\r\n </p-popover>\r\n </div>\r\n </div>\r\n\r\n <!-- Field Selection - Under Path -->\r\n <div\r\n class=\"flex items-center gap-2 border-t border-slate-200 pt-2 dark:border-slate-700\"\r\n >\r\n <span\r\n class=\"shrink-0 text-xs font-semibold uppercase tracking-wider text-slate-400\"\r\n >{{ t(\"properties.field\") }}</span\r\n >\r\n <div\r\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\r\n ></div>\r\n @if (isPropertyLoading()) {\r\n <p-skeleton\r\n class=\"flex-1\"\r\n height=\"2.5rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n } @else {\r\n <mt-select-field\r\n class=\"flex-1\"\r\n [label]=\"''\"\r\n [filter]=\"true\"\r\n [hasPlaceholderPrefix]=\"false\"\r\n [placeholder]=\"t('properties.select')\"\r\n [(ngModel)]=\"propertyFieldKey\"\r\n [options]=\"propertyOptions()\"\r\n optionLabel=\"name\"\r\n optionValue=\"key\"\r\n [size]=\"'small'\"\r\n />\r\n }\r\n </div>\r\n\r\n <!-- Value Selection - Only for lookup / status / lookup-matrix fields -->\r\n @if (isLookupProperty()) {\r\n <div\r\n class=\"flex items-center gap-2 border-t border-slate-200 pt-2 dark:border-slate-700\"\r\n >\r\n <span\r\n class=\"shrink-0 text-xs font-semibold uppercase tracking-wider text-slate-400\"\r\n >{{ t(\"properties.value\") }}</span\r\n >\r\n <div\r\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\r\n ></div>\r\n @if (isLookupItemsLoading()) {\r\n <p-skeleton\r\n class=\"flex-1\"\r\n height=\"2.5rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n } @else {\r\n <mt-select-field\r\n class=\"flex-1\"\r\n [label]=\"''\"\r\n [filter]=\"true\"\r\n [hasPlaceholderPrefix]=\"false\"\r\n [placeholder]=\"t('properties.select-value')\"\r\n [(ngModel)]=\"lookupValueKey\"\r\n [options]=\"lookupItems()\"\r\n optionLabel=\"name\"\r\n optionValue=\"key\"\r\n [size]=\"'small'\"\r\n />\r\n }\r\n </div>\r\n }\r\n <div\r\n class=\"w-full truncate rounded-lg bg-amber-100 px-3 py-2 text-sm font-semibold text-amber-700 shadow-sm hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-300 dark:hover:bg-amber-900/50\"\r\n [mtTooltip]=\"\r\n '@' +\r\n propertyTablePath() +\r\n '::' +\r\n (selectedProperty()?.key ?? '...')\r\n \"\r\n tooltipPosition=\"top\"\r\n >\r\n @{{ propertyTablePath() }}::{{\r\n selectedProperty()?.key ?? \"...\"\r\n }}\r\n </div>\r\n <div class=\"flex justify-end gap-2\">\r\n @if (isLookupProperty()) {\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"t('actions.insert-value')\"\r\n icon=\"general.plus\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n [disabled]=\"!canInsertLookupValue()\"\r\n (onClick)=\"insertSelectedLookupValue(insertBlock)\"\r\n ></mt-button>\r\n }\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"t('actions.insert')\"\r\n icon=\"general.plus\"\r\n severity=\"primary\"\r\n [disabled]=\"!canInsertProperty()\"\r\n (onClick)=\"insertSelectedProperty(insertBlock)\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n </ng-template>\r\n </mt-formula-toolbar>\r\n }\r\n\r\n <!-- Editor Area -->\r\n <div class=\"p-3\">\r\n <div\r\n class=\"overflow-hidden rounded-lg border border-surface-200 bg-white dark:border-surface-700 dark:bg-surface-800\"\r\n >\r\n @if (showModeToggle()) {\r\n <div\r\n class=\"flex items-center justify-end border-b border-surface-200 px-3 py-2 dark:border-surface-700\"\r\n >\r\n <mt-toggle-field\r\n [label]=\"t('codeMode')\"\r\n labelPosition=\"end\"\r\n size=\"small\"\r\n [ngModel]=\"isCodeMode()\"\r\n (ngModelChange)=\"onModeToggle($event)\"\r\n />\r\n </div>\r\n }\r\n <div class=\"p-0\">\r\n @if (isCodeMode()) {\r\n <mt-formula-editor-code\r\n #formulaEditorCode\r\n [placeholder]=\"placeholder() || t('placeholder')\"\r\n [initialFormula]=\"expression()\"\r\n [borderless]=\"true\"\r\n [autocompleteProvider]=\"autocompleteProvider\"\r\n (formulaChange)=\"onCodeFormulaChange($event)\"\r\n (onFocus)=\"onEditorFocus()\"\r\n (onBlur)=\"onEditorBlur()\"\r\n />\r\n } @else {\r\n <mt-formula-editor\r\n #formulaEditor\r\n [placeholder]=\"placeholder() || t('placeholder')\"\r\n [initialTokens]=\"tokens()\"\r\n [borderless]=\"true\"\r\n (formulaChange)=\"onFormulaChange($event)\"\r\n (tokensChange)=\"onTokensChange($event)\"\r\n (onFocus)=\"onEditorFocus()\"\r\n (onBlur)=\"onEditorBlur()\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Status Bar - Pass data via inputs (pure component) -->\r\n @if (!hideStatusBar()) {\r\n <mt-formula-status-bar [validation]=\"validation()\" />\r\n }\r\n </div>\r\n</mt-card>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "hint", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required", "group", "size", "optionGroupLabel", "optionGroupChildren", "loading", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape", "markCurrentUser"], outputs: ["onChange"] }, { kind: "directive", type: Tooltip, selector: "[mtTooltip]" }, { kind: "component", type: RadioCards, selector: "mt-radio-cards", inputs: ["circle", "color", "size", "columns", "options", "activeId", "itemTemplate"], outputs: ["optionsChange", "activeIdChange", "selectionChange"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i2.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "component", type: Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: FormulaToolbar, selector: "mt-formula-toolbar", inputs: ["knownProperties", "propertiesTemplate", "functionCategories", "operators", "initialTab", "visibleTabs", "searchPlaceholder", "labels"], outputs: ["onBlockInsert", "onTabChange"] }, { kind: "component", type: FormulaStatusBar, selector: "mt-formula-status-bar", inputs: ["validation", "labels"] }, { kind: "component", type: FormulaEditor, selector: "mt-formula-editor", inputs: ["placeholder", "initialTokens", "disabled", "borderless"], outputs: ["formulaChange", "tokensChange", "onBlur", "onFocus"] }, { kind: "component", type: FormulaEditorCode, selector: "mt-formula-editor-code", inputs: ["placeholder", "initialFormula", "disabled", "language", "theme", "borderless", "autocompleteProvider", "autocompleteDebounceMs"], outputs: ["formulaChange", "onBlur", "onFocus"] }] });
1501
+ ], viewQueries: [{ propertyName: "editor", first: true, predicate: ["formulaEditor"], descendants: true, isSignal: true }, { propertyName: "codeEditor", first: true, predicate: ["formulaEditorCode"], descendants: true, isSignal: true }], ngImport: i0, template: "<mt-card\n headless\n [class.ring-2]=\"hasFocus()\"\n [class.ring-primary]=\"hasFocus()\"\n [paddingless]=\"true\"\n>\n <div\n *transloco=\"let t; prefix: 'formulaBuilder'\"\n class=\"flex flex-col overflow-hidden\"\n >\n <!-- Toolbar - Pass data via inputs (pure component) -->\n @if (!hideToolbar()) {\n <mt-formula-toolbar\n [functionCategories]=\"resolvedFunctionCategories()\"\n [operators]=\"operators()\"\n [visibleTabs]=\"resolvedToolbarTabs()\"\n (onBlockInsert)=\"onBlockInsert($event)\"\n >\n <ng-template #properties let-insertBlock=\"insertBlock\">\n @if (isContextLoading()) {\n <div class=\"flex flex-col gap-4 p-4\">\n <div class=\"flex items-center justify-between gap-4\">\n <p-skeleton\n width=\"18rem\"\n height=\"2.5rem\"\n styleClass=\"rounded-md\"\n />\n <p-skeleton\n width=\"7rem\"\n height=\"2.5rem\"\n styleClass=\"rounded-md\"\n />\n </div>\n <div class=\"flex items-center gap-4\">\n <div\n class=\"flex min-h-11 min-w-0 flex-1 flex-col gap-2 rounded-lg bg-slate-50 px-3 py-2 dark:bg-slate-800/50\"\n >\n <div class=\"flex items-center gap-2\">\n <p-skeleton\n width=\"3rem\"\n height=\"1rem\"\n styleClass=\"rounded-md\"\n />\n <div\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\n ></div>\n <p-skeleton\n class=\"flex-1\"\n height=\"2rem\"\n styleClass=\"rounded-md\"\n />\n </div>\n <div\n class=\"flex items-center gap-2 border-t border-slate-200 pt-2 dark:border-slate-700\"\n >\n <p-skeleton\n width=\"3rem\"\n height=\"1rem\"\n styleClass=\"rounded-md\"\n />\n <div\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\n ></div>\n <p-skeleton\n class=\"flex-1\"\n height=\"2.5rem\"\n styleClass=\"rounded-md\"\n />\n </div>\n <p-skeleton height=\"2rem\" styleClass=\"rounded-md\" />\n </div>\n </div>\n </div>\n } @else {\n <div class=\"flex flex-col gap-4 p-4\">\n <!-- Scope Selection - Full width, centered -->\n <div class=\"flex items-center justify-between gap-4\">\n <mt-radio-cards\n [options]=\"scopeOptions()\"\n [activeId]=\"propertyScope()\"\n (selectionChange)=\"onScopeChange($event)\"\n size=\"small\"\n />\n </div>\n\n <!-- Path + Field + Preview Row -->\n <div class=\"flex items-center gap-4\">\n <!-- Path Container - Flexible -->\n <div\n class=\"flex min-h-11 min-w-0 flex-1 flex-col gap-2 rounded-lg bg-slate-50 px-3 py-2 dark:bg-slate-800/50\"\n >\n <div class=\"flex items-center gap-2\">\n <span\n class=\"shrink-0 text-xs font-semibold uppercase tracking-wider text-slate-400\"\n >{{ t(\"properties.path\") }}</span\n >\n <div\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\n ></div>\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\n @if (isDirectAccess()) {\n <span\n class=\"rounded-md bg-emerald-100 px-2.5 py-1 text-xs font-medium text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400\"\n >\n \u2713 {{ t(\"properties.direct-access\") }}\n </span>\n } @else if (pathSegments().length === 0) {\n <span class=\"text-xs italic text-slate-400\">{{\n t(\"properties.no-path-configured\")\n }}</span>\n } @else {\n <div\n class=\"flex min-w-0 flex-1 items-center gap-1.5 overflow-x-auto\"\n >\n @for (\n segment of pathSegments();\n track $index;\n let segmentIndex = $index\n ) {\n @if (segmentIndex > 0) {\n <span\n class=\"shrink-0 text-slate-300 dark:text-slate-600\"\n >\u203A</span\n >\n }\n <mt-button\n type=\"button\"\n [label]=\"\n segment.value ||\n t('properties.select') +\n ' ' +\n getOperatorDisplayName(segment.operatorToken)\n \"\n icon=\"arrow.chevron-down\"\n [outlined]=\"true\"\n size=\"small\"\n [severity]=\"\n segment.value ? 'primary' : 'secondary'\n \"\n [iconPos]=\"'end'\"\n (onClick)=\"pathPopover.toggle($event)\"\n ></mt-button>\n\n <p-popover\n #pathPopover\n [style]=\"{ width: 'max-content' }\"\n appendTo=\"body\"\n >\n <div class=\"p-2\">\n <div\n class=\"mb-2 text-xs font-semibold uppercase text-slate-400\"\n >\n {{ t(\"properties.select\") }}\n {{\n getOperatorDisplayName(\n segment.operatorToken\n )\n }}\n </div>\n <div\n class=\"flex max-h-[min(24rem,calc(100vh-10rem))] flex-col gap-1 overflow-y-auto pr-1\"\n >\n @for (\n option of segmentOptions(segmentIndex);\n track option.key\n ) {\n <mt-button\n type=\"button\"\n [label]=\"option.name\"\n [icon]=\"\n segment.value === option.key\n ? 'general.check'\n : 'general.minus'\n \"\n [iconPos]=\"'end'\"\n size=\"small\"\n severity=\"secondary\"\n [styleClass]=\"\n 'w-full justify-start rounded-md px-2.5 py-1.5 text-left text-xs font-medium transition-colors ' +\n (segment.value === option.key\n ? 'bg-primary text-white'\n : 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700')\n \"\n (onClick)=\"\n setPathSegmentValue(\n segmentIndex,\n option.key\n );\n pathPopover.hide()\n \"\n ></mt-button>\n }\n </div>\n @if (segment.optional) {\n <mt-button\n type=\"button\"\n [label]=\"t('actions.clear')\"\n [outlined]=\"true\"\n icon=\"general.x-close\"\n [iconPos]=\"'end'\"\n size=\"small\"\n severity=\"danger\"\n styleClass=\"mt-2 w-full rounded-md border border-slate-200 py-1 text-xs text-slate-400 hover:bg-slate-50 dark:border-slate-700\"\n (onClick)=\"\n setPathSegmentValue(segmentIndex, null);\n pathPopover.hide()\n \"\n ></mt-button>\n }\n </div>\n </p-popover>\n\n @if (\n segment.canRemove &&\n segmentIndex === pathSegments().length - 1\n ) {\n <mt-button\n type=\"button\"\n icon=\"general.x-close\"\n size=\"small\"\n severity=\"danger\"\n styleClass=\"flex size-5 shrink-0 items-center justify-center rounded-full bg-red-500 text-white hover:bg-red-600\"\n (onClick)=\"removePathSegment(segmentIndex)\"\n ></mt-button>\n }\n }\n <mt-button\n type=\"button\"\n icon=\"general.plus\"\n size=\"small\"\n severity=\"primary\"\n [disabled]=\"!canAddNextSegment()\"\n [class.hidden]=\"!canAddNextSegment()\"\n styleClass=\"flex size-6 shrink-0 items-center justify-center rounded-md bg-primary text-xs font-bold text-white hover:opacity-90 disabled:opacity-50\"\n (onClick)=\"nextOperatorPopover.toggle($event)\"\n ></mt-button>\n </div>\n }\n <p-popover\n #nextOperatorPopover\n [style]=\"{ width: 'max-content' }\"\n appendTo=\"body\"\n >\n <div class=\"p-2\">\n <div\n class=\"mb-2 text-xs font-semibold uppercase text-slate-400\"\n >\n {{ t(\"properties.add-segment\") }}\n </div>\n <div\n class=\"flex max-h-[min(24rem,calc(100vh-10rem))] flex-col gap-1 overflow-y-auto pr-1\"\n >\n @for (\n option of nextOperatorOptions();\n track option.token\n ) {\n <mt-button\n type=\"button\"\n [label]=\"getOperatorDisplayName(option.token)\"\n icon=\"general.plus\"\n [iconPos]=\"'end'\"\n size=\"small\"\n severity=\"secondary\"\n styleClass=\"w-full justify-start rounded-md px-2.5 py-1.5 text-left text-xs font-medium transition-colors text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700\"\n (onClick)=\"\n onAddSegmentSelection(\n option.token,\n nextOperatorPopover\n )\n \"\n ></mt-button>\n }\n </div>\n </div>\n </p-popover>\n </div>\n </div>\n\n <!-- Field Selection - Under Path -->\n <div\n class=\"flex items-center gap-2 border-t border-slate-200 pt-2 dark:border-slate-700\"\n >\n <span\n class=\"shrink-0 text-xs font-semibold uppercase tracking-wider text-slate-400\"\n >{{ t(\"properties.field\") }}</span\n >\n <div\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\n ></div>\n @if (isPropertyLoading()) {\n <p-skeleton\n class=\"flex-1\"\n height=\"2.5rem\"\n styleClass=\"rounded-md\"\n />\n } @else {\n <mt-select-field\n class=\"flex-1\"\n [label]=\"''\"\n [filter]=\"true\"\n [hasPlaceholderPrefix]=\"false\"\n [placeholder]=\"t('properties.select')\"\n [(ngModel)]=\"propertyFieldKey\"\n [options]=\"propertyOptions()\"\n optionLabel=\"name\"\n optionValue=\"key\"\n [size]=\"'small'\"\n />\n }\n </div>\n\n <!-- Value Selection - Only for lookup / status / lookup-matrix fields -->\n @if (isLookupProperty()) {\n <div\n class=\"flex items-center gap-2 border-t border-slate-200 pt-2 dark:border-slate-700\"\n >\n <span\n class=\"shrink-0 text-xs font-semibold uppercase tracking-wider text-slate-400\"\n >{{ t(\"properties.value\") }}</span\n >\n <div\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\n ></div>\n @if (isLookupItemsLoading()) {\n <p-skeleton\n class=\"flex-1\"\n height=\"2.5rem\"\n styleClass=\"rounded-md\"\n />\n } @else {\n <mt-select-field\n class=\"flex-1\"\n [label]=\"''\"\n [filter]=\"true\"\n [hasPlaceholderPrefix]=\"false\"\n [placeholder]=\"t('properties.select-value')\"\n [(ngModel)]=\"lookupValueKey\"\n [options]=\"lookupItems()\"\n optionLabel=\"name\"\n optionValue=\"key\"\n [size]=\"'small'\"\n />\n }\n </div>\n }\n <div\n class=\"w-full truncate rounded-lg bg-amber-100 px-3 py-2 text-sm font-semibold text-amber-700 shadow-sm hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-300 dark:hover:bg-amber-900/50\"\n [mtTooltip]=\"\n '@' +\n propertyTablePath() +\n '::' +\n (selectedProperty()?.key ?? '...')\n \"\n tooltipPosition=\"top\"\n >\n @{{ propertyTablePath() }}::{{\n selectedProperty()?.key ?? \"...\"\n }}\n </div>\n <div class=\"flex justify-end gap-2\">\n @if (isLookupProperty()) {\n <mt-button\n type=\"button\"\n [label]=\"t('actions.insert-value')\"\n icon=\"general.plus\"\n severity=\"secondary\"\n [outlined]=\"true\"\n [disabled]=\"!canInsertLookupValue()\"\n (onClick)=\"insertSelectedLookupValue(insertBlock)\"\n ></mt-button>\n }\n <mt-button\n type=\"button\"\n [label]=\"t('actions.insert')\"\n icon=\"general.plus\"\n severity=\"primary\"\n [disabled]=\"!canInsertProperty()\"\n (onClick)=\"insertSelectedProperty(insertBlock)\"\n ></mt-button>\n </div>\n </div>\n </div>\n </div>\n }\n </ng-template>\n </mt-formula-toolbar>\n }\n\n <!-- Editor Area -->\n <div class=\"p-3\">\n <div\n class=\"overflow-hidden rounded-lg border border-surface-200 bg-white dark:border-surface-700 dark:bg-surface-800\"\n >\n @if (showModeToggle()) {\n <div\n class=\"flex items-center justify-end border-b border-surface-200 px-3 py-2 dark:border-surface-700\"\n >\n <mt-toggle-field\n [label]=\"t('codeMode')\"\n labelPosition=\"end\"\n size=\"small\"\n [ngModel]=\"isCodeMode()\"\n (ngModelChange)=\"onModeToggle($event)\"\n />\n </div>\n }\n <div class=\"p-0\">\n @if (isCodeMode()) {\n <mt-formula-editor-code\n #formulaEditorCode\n [placeholder]=\"placeholder() || t('placeholder')\"\n [initialFormula]=\"expression()\"\n [borderless]=\"true\"\n [autocompleteProvider]=\"autocompleteProvider\"\n (formulaChange)=\"onCodeFormulaChange($event)\"\n (onFocus)=\"onEditorFocus()\"\n (onBlur)=\"onEditorBlur()\"\n />\n } @else {\n <mt-formula-editor\n #formulaEditor\n [placeholder]=\"placeholder() || t('placeholder')\"\n [initialTokens]=\"tokens()\"\n [borderless]=\"true\"\n (formulaChange)=\"onFormulaChange($event)\"\n (tokensChange)=\"onTokensChange($event)\"\n (onFocus)=\"onEditorFocus()\"\n (onBlur)=\"onEditorBlur()\"\n />\n }\n </div>\n </div>\n </div>\n\n <!-- Status Bar - Pass data via inputs (pure component) -->\n @if (!hideStatusBar()) {\n <mt-formula-status-bar [validation]=\"validation()\" />\n }\n </div>\n</mt-card>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "hint", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required", "group", "size", "optionGroupLabel", "optionGroupChildren", "loading", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape", "markCurrentUser"], outputs: ["onChange"] }, { kind: "directive", type: Tooltip, selector: "[mtTooltip]" }, { kind: "component", type: RadioCards, selector: "mt-radio-cards", inputs: ["circle", "color", "size", "columns", "options", "activeId", "itemTemplate"], outputs: ["optionsChange", "activeIdChange", "selectionChange"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i2.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "component", type: Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: FormulaToolbar, selector: "mt-formula-toolbar", inputs: ["knownProperties", "propertiesTemplate", "functionCategories", "operators", "initialTab", "visibleTabs", "searchPlaceholder", "labels"], outputs: ["onBlockInsert", "onTabChange"] }, { kind: "component", type: FormulaStatusBar, selector: "mt-formula-status-bar", inputs: ["validation", "labels"] }, { kind: "component", type: FormulaEditor, selector: "mt-formula-editor", inputs: ["placeholder", "initialTokens", "disabled", "borderless"], outputs: ["formulaChange", "tokensChange", "onBlur", "onFocus"] }, { kind: "component", type: FormulaEditorCode, selector: "mt-formula-editor-code", inputs: ["placeholder", "initialFormula", "disabled", "language", "theme", "borderless", "autocompleteProvider", "autocompleteDebounceMs"], outputs: ["formulaChange", "onBlur", "onFocus"] }] });
1492
1502
  }
1493
1503
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FormulaBuilder, decorators: [{
1494
1504
  type: Component,
@@ -1516,8 +1526,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1516
1526
  useExisting: forwardRef(() => FormulaBuilder),
1517
1527
  multi: true,
1518
1528
  },
1519
- ], template: "<mt-card\r\n headless\r\n [class.ring-2]=\"hasFocus()\"\r\n [class.ring-primary]=\"hasFocus()\"\r\n [paddingless]=\"true\"\r\n>\r\n <div\r\n *transloco=\"let t; prefix: 'formulaBuilder'\"\r\n class=\"flex flex-col overflow-hidden\"\r\n >\r\n <!-- Toolbar - Pass data via inputs (pure component) -->\r\n @if (!hideToolbar()) {\r\n <mt-formula-toolbar\r\n [functionCategories]=\"functionCategories()\"\r\n [visibleTabs]=\"resolvedToolbarTabs()\"\r\n (onBlockInsert)=\"onBlockInsert($event)\"\r\n >\r\n <ng-template #properties let-insertBlock=\"insertBlock\">\r\n @if (isContextLoading()) {\r\n <div class=\"flex flex-col gap-4 p-4\">\r\n <div class=\"flex items-center justify-between gap-4\">\r\n <p-skeleton\r\n width=\"18rem\"\r\n height=\"2.5rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n <p-skeleton\r\n width=\"7rem\"\r\n height=\"2.5rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n </div>\r\n <div class=\"flex items-center gap-4\">\r\n <div\r\n class=\"flex min-h-11 min-w-0 flex-1 flex-col gap-2 rounded-lg bg-slate-50 px-3 py-2 dark:bg-slate-800/50\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <p-skeleton\r\n width=\"3rem\"\r\n height=\"1rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n <div\r\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\r\n ></div>\r\n <p-skeleton\r\n class=\"flex-1\"\r\n height=\"2rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n </div>\r\n <div\r\n class=\"flex items-center gap-2 border-t border-slate-200 pt-2 dark:border-slate-700\"\r\n >\r\n <p-skeleton\r\n width=\"3rem\"\r\n height=\"1rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n <div\r\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\r\n ></div>\r\n <p-skeleton\r\n class=\"flex-1\"\r\n height=\"2.5rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n </div>\r\n <p-skeleton height=\"2rem\" styleClass=\"rounded-md\" />\r\n </div>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"flex flex-col gap-4 p-4\">\r\n <!-- Scope Selection - Full width, centered -->\r\n <div class=\"flex items-center justify-between gap-4\">\r\n <mt-radio-cards\r\n [options]=\"scopeOptions()\"\r\n [activeId]=\"propertyScope()\"\r\n (selectionChange)=\"onScopeChange($event)\"\r\n size=\"small\"\r\n />\r\n </div>\r\n\r\n <!-- Path + Field + Preview Row -->\r\n <div class=\"flex items-center gap-4\">\r\n <!-- Path Container - Flexible -->\r\n <div\r\n class=\"flex min-h-11 min-w-0 flex-1 flex-col gap-2 rounded-lg bg-slate-50 px-3 py-2 dark:bg-slate-800/50\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <span\r\n class=\"shrink-0 text-xs font-semibold uppercase tracking-wider text-slate-400\"\r\n >{{ t(\"properties.path\") }}</span\r\n >\r\n <div\r\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\r\n ></div>\r\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\r\n @if (isDirectAccess()) {\r\n <span\r\n class=\"rounded-md bg-emerald-100 px-2.5 py-1 text-xs font-medium text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400\"\r\n >\r\n \u2713 {{ t(\"properties.direct-access\") }}\r\n </span>\r\n } @else if (pathSegments().length === 0) {\r\n <span class=\"text-xs italic text-slate-400\">{{\r\n t(\"properties.no-path-configured\")\r\n }}</span>\r\n } @else {\r\n <div\r\n class=\"flex min-w-0 flex-1 items-center gap-1.5 overflow-x-auto\"\r\n >\r\n @for (\r\n segment of pathSegments();\r\n track $index;\r\n let segmentIndex = $index\r\n ) {\r\n @if (segmentIndex > 0) {\r\n <span\r\n class=\"shrink-0 text-slate-300 dark:text-slate-600\"\r\n >\u203A</span\r\n >\r\n }\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"\r\n segment.value ||\r\n t('properties.select') +\r\n ' ' +\r\n getOperatorDisplayName(segment.operatorToken)\r\n \"\r\n icon=\"arrow.chevron-down\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n [severity]=\"\r\n segment.value ? 'primary' : 'secondary'\r\n \"\r\n [iconPos]=\"'end'\"\r\n (onClick)=\"pathPopover.toggle($event)\"\r\n ></mt-button>\r\n\r\n <p-popover\r\n #pathPopover\r\n [style]=\"{ width: 'max-content' }\"\r\n appendTo=\"body\"\r\n >\r\n <div class=\"p-2\">\r\n <div\r\n class=\"mb-2 text-xs font-semibold uppercase text-slate-400\"\r\n >\r\n {{ t(\"properties.select\") }}\r\n {{\r\n getOperatorDisplayName(\r\n segment.operatorToken\r\n )\r\n }}\r\n </div>\r\n <div\r\n class=\"flex max-h-[min(24rem,calc(100vh-10rem))] flex-col gap-1 overflow-y-auto pr-1\"\r\n >\r\n @for (\r\n option of segmentOptions(segmentIndex);\r\n track option.key\r\n ) {\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"option.name\"\r\n [icon]=\"\r\n segment.value === option.key\r\n ? 'general.check'\r\n : 'general.minus'\r\n \"\r\n [iconPos]=\"'end'\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n [styleClass]=\"\r\n 'w-full justify-start rounded-md px-2.5 py-1.5 text-left text-xs font-medium transition-colors ' +\r\n (segment.value === option.key\r\n ? 'bg-primary text-white'\r\n : 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700')\r\n \"\r\n (onClick)=\"\r\n setPathSegmentValue(\r\n segmentIndex,\r\n option.key\r\n );\r\n pathPopover.hide()\r\n \"\r\n ></mt-button>\r\n }\r\n </div>\r\n @if (segment.optional) {\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"t('actions.clear')\"\r\n [outlined]=\"true\"\r\n icon=\"general.x-close\"\r\n [iconPos]=\"'end'\"\r\n size=\"small\"\r\n severity=\"danger\"\r\n styleClass=\"mt-2 w-full rounded-md border border-slate-200 py-1 text-xs text-slate-400 hover:bg-slate-50 dark:border-slate-700\"\r\n (onClick)=\"\r\n setPathSegmentValue(segmentIndex, null);\r\n pathPopover.hide()\r\n \"\r\n ></mt-button>\r\n }\r\n </div>\r\n </p-popover>\r\n\r\n @if (\r\n segment.canRemove &&\r\n segmentIndex === pathSegments().length - 1\r\n ) {\r\n <mt-button\r\n type=\"button\"\r\n icon=\"general.x-close\"\r\n size=\"small\"\r\n severity=\"danger\"\r\n styleClass=\"flex size-5 shrink-0 items-center justify-center rounded-full bg-red-500 text-white hover:bg-red-600\"\r\n (onClick)=\"removePathSegment(segmentIndex)\"\r\n ></mt-button>\r\n }\r\n }\r\n <mt-button\r\n type=\"button\"\r\n icon=\"general.plus\"\r\n size=\"small\"\r\n severity=\"primary\"\r\n [disabled]=\"!canAddNextSegment()\"\r\n [class.hidden]=\"!canAddNextSegment()\"\r\n styleClass=\"flex size-6 shrink-0 items-center justify-center rounded-md bg-primary text-xs font-bold text-white hover:opacity-90 disabled:opacity-50\"\r\n (onClick)=\"nextOperatorPopover.toggle($event)\"\r\n ></mt-button>\r\n </div>\r\n }\r\n <p-popover\r\n #nextOperatorPopover\r\n [style]=\"{ width: 'max-content' }\"\r\n appendTo=\"body\"\r\n >\r\n <div class=\"p-2\">\r\n <div\r\n class=\"mb-2 text-xs font-semibold uppercase text-slate-400\"\r\n >\r\n {{ t(\"properties.add-segment\") }}\r\n </div>\r\n <div\r\n class=\"flex max-h-[min(24rem,calc(100vh-10rem))] flex-col gap-1 overflow-y-auto pr-1\"\r\n >\r\n @for (\r\n option of nextOperatorOptions();\r\n track option.token\r\n ) {\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"getOperatorDisplayName(option.token)\"\r\n icon=\"general.plus\"\r\n [iconPos]=\"'end'\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n styleClass=\"w-full justify-start rounded-md px-2.5 py-1.5 text-left text-xs font-medium transition-colors text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700\"\r\n (onClick)=\"\r\n onAddSegmentSelection(\r\n option.token,\r\n nextOperatorPopover\r\n )\r\n \"\r\n ></mt-button>\r\n }\r\n </div>\r\n </div>\r\n </p-popover>\r\n </div>\r\n </div>\r\n\r\n <!-- Field Selection - Under Path -->\r\n <div\r\n class=\"flex items-center gap-2 border-t border-slate-200 pt-2 dark:border-slate-700\"\r\n >\r\n <span\r\n class=\"shrink-0 text-xs font-semibold uppercase tracking-wider text-slate-400\"\r\n >{{ t(\"properties.field\") }}</span\r\n >\r\n <div\r\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\r\n ></div>\r\n @if (isPropertyLoading()) {\r\n <p-skeleton\r\n class=\"flex-1\"\r\n height=\"2.5rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n } @else {\r\n <mt-select-field\r\n class=\"flex-1\"\r\n [label]=\"''\"\r\n [filter]=\"true\"\r\n [hasPlaceholderPrefix]=\"false\"\r\n [placeholder]=\"t('properties.select')\"\r\n [(ngModel)]=\"propertyFieldKey\"\r\n [options]=\"propertyOptions()\"\r\n optionLabel=\"name\"\r\n optionValue=\"key\"\r\n [size]=\"'small'\"\r\n />\r\n }\r\n </div>\r\n\r\n <!-- Value Selection - Only for lookup / status / lookup-matrix fields -->\r\n @if (isLookupProperty()) {\r\n <div\r\n class=\"flex items-center gap-2 border-t border-slate-200 pt-2 dark:border-slate-700\"\r\n >\r\n <span\r\n class=\"shrink-0 text-xs font-semibold uppercase tracking-wider text-slate-400\"\r\n >{{ t(\"properties.value\") }}</span\r\n >\r\n <div\r\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\r\n ></div>\r\n @if (isLookupItemsLoading()) {\r\n <p-skeleton\r\n class=\"flex-1\"\r\n height=\"2.5rem\"\r\n styleClass=\"rounded-md\"\r\n />\r\n } @else {\r\n <mt-select-field\r\n class=\"flex-1\"\r\n [label]=\"''\"\r\n [filter]=\"true\"\r\n [hasPlaceholderPrefix]=\"false\"\r\n [placeholder]=\"t('properties.select-value')\"\r\n [(ngModel)]=\"lookupValueKey\"\r\n [options]=\"lookupItems()\"\r\n optionLabel=\"name\"\r\n optionValue=\"key\"\r\n [size]=\"'small'\"\r\n />\r\n }\r\n </div>\r\n }\r\n <div\r\n class=\"w-full truncate rounded-lg bg-amber-100 px-3 py-2 text-sm font-semibold text-amber-700 shadow-sm hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-300 dark:hover:bg-amber-900/50\"\r\n [mtTooltip]=\"\r\n '@' +\r\n propertyTablePath() +\r\n '::' +\r\n (selectedProperty()?.key ?? '...')\r\n \"\r\n tooltipPosition=\"top\"\r\n >\r\n @{{ propertyTablePath() }}::{{\r\n selectedProperty()?.key ?? \"...\"\r\n }}\r\n </div>\r\n <div class=\"flex justify-end gap-2\">\r\n @if (isLookupProperty()) {\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"t('actions.insert-value')\"\r\n icon=\"general.plus\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n [disabled]=\"!canInsertLookupValue()\"\r\n (onClick)=\"insertSelectedLookupValue(insertBlock)\"\r\n ></mt-button>\r\n }\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"t('actions.insert')\"\r\n icon=\"general.plus\"\r\n severity=\"primary\"\r\n [disabled]=\"!canInsertProperty()\"\r\n (onClick)=\"insertSelectedProperty(insertBlock)\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n </ng-template>\r\n </mt-formula-toolbar>\r\n }\r\n\r\n <!-- Editor Area -->\r\n <div class=\"p-3\">\r\n <div\r\n class=\"overflow-hidden rounded-lg border border-surface-200 bg-white dark:border-surface-700 dark:bg-surface-800\"\r\n >\r\n @if (showModeToggle()) {\r\n <div\r\n class=\"flex items-center justify-end border-b border-surface-200 px-3 py-2 dark:border-surface-700\"\r\n >\r\n <mt-toggle-field\r\n [label]=\"t('codeMode')\"\r\n labelPosition=\"end\"\r\n size=\"small\"\r\n [ngModel]=\"isCodeMode()\"\r\n (ngModelChange)=\"onModeToggle($event)\"\r\n />\r\n </div>\r\n }\r\n <div class=\"p-0\">\r\n @if (isCodeMode()) {\r\n <mt-formula-editor-code\r\n #formulaEditorCode\r\n [placeholder]=\"placeholder() || t('placeholder')\"\r\n [initialFormula]=\"expression()\"\r\n [borderless]=\"true\"\r\n [autocompleteProvider]=\"autocompleteProvider\"\r\n (formulaChange)=\"onCodeFormulaChange($event)\"\r\n (onFocus)=\"onEditorFocus()\"\r\n (onBlur)=\"onEditorBlur()\"\r\n />\r\n } @else {\r\n <mt-formula-editor\r\n #formulaEditor\r\n [placeholder]=\"placeholder() || t('placeholder')\"\r\n [initialTokens]=\"tokens()\"\r\n [borderless]=\"true\"\r\n (formulaChange)=\"onFormulaChange($event)\"\r\n (tokensChange)=\"onTokensChange($event)\"\r\n (onFocus)=\"onEditorFocus()\"\r\n (onBlur)=\"onEditorBlur()\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Status Bar - Pass data via inputs (pure component) -->\r\n @if (!hideStatusBar()) {\r\n <mt-formula-status-bar [validation]=\"validation()\" />\r\n }\r\n </div>\r\n</mt-card>\r\n" }]
1520
- }], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.ViewChild, args: ['formulaEditor', { isSignal: true }] }], codeEditor: [{ type: i0.ViewChild, args: ['formulaEditorCode', { isSignal: true }] }], propertiesByPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "propertiesByPath", required: false }] }], levelSchemaId: [{ type: i0.Input, args: [{ isSignal: true, alias: "levelSchemaId", required: false }] }], moduleId: [{ type: i0.Input, args: [{ isSignal: true, alias: "moduleId", required: false }] }], contextKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "contextKey", required: false }] }], contextEntityTypeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "contextEntityTypeKey", required: false }] }], templateId: [{ type: i0.Input, args: [{ isSignal: true, alias: "templateId", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hideToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideToolbar", required: false }] }], hideStatusBar: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideStatusBar", required: false }] }], toolbarTabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolbarTabs", required: false }] }], codeOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "codeOnly", required: false }] }], builderOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "builderOnly", required: false }] }], valueMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueMode", required: false }] }], isProcessBuilder: [{ type: i0.Input, args: [{ isSignal: true, alias: "isProcessBuilder", required: false }] }], validationChange: [{ type: i0.Output, args: ["validationChange"] }], tokensChange: [{ type: i0.Output, args: ["tokensChange"] }] } });
1529
+ ], template: "<mt-card\n headless\n [class.ring-2]=\"hasFocus()\"\n [class.ring-primary]=\"hasFocus()\"\n [paddingless]=\"true\"\n>\n <div\n *transloco=\"let t; prefix: 'formulaBuilder'\"\n class=\"flex flex-col overflow-hidden\"\n >\n <!-- Toolbar - Pass data via inputs (pure component) -->\n @if (!hideToolbar()) {\n <mt-formula-toolbar\n [functionCategories]=\"resolvedFunctionCategories()\"\n [operators]=\"operators()\"\n [visibleTabs]=\"resolvedToolbarTabs()\"\n (onBlockInsert)=\"onBlockInsert($event)\"\n >\n <ng-template #properties let-insertBlock=\"insertBlock\">\n @if (isContextLoading()) {\n <div class=\"flex flex-col gap-4 p-4\">\n <div class=\"flex items-center justify-between gap-4\">\n <p-skeleton\n width=\"18rem\"\n height=\"2.5rem\"\n styleClass=\"rounded-md\"\n />\n <p-skeleton\n width=\"7rem\"\n height=\"2.5rem\"\n styleClass=\"rounded-md\"\n />\n </div>\n <div class=\"flex items-center gap-4\">\n <div\n class=\"flex min-h-11 min-w-0 flex-1 flex-col gap-2 rounded-lg bg-slate-50 px-3 py-2 dark:bg-slate-800/50\"\n >\n <div class=\"flex items-center gap-2\">\n <p-skeleton\n width=\"3rem\"\n height=\"1rem\"\n styleClass=\"rounded-md\"\n />\n <div\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\n ></div>\n <p-skeleton\n class=\"flex-1\"\n height=\"2rem\"\n styleClass=\"rounded-md\"\n />\n </div>\n <div\n class=\"flex items-center gap-2 border-t border-slate-200 pt-2 dark:border-slate-700\"\n >\n <p-skeleton\n width=\"3rem\"\n height=\"1rem\"\n styleClass=\"rounded-md\"\n />\n <div\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\n ></div>\n <p-skeleton\n class=\"flex-1\"\n height=\"2.5rem\"\n styleClass=\"rounded-md\"\n />\n </div>\n <p-skeleton height=\"2rem\" styleClass=\"rounded-md\" />\n </div>\n </div>\n </div>\n } @else {\n <div class=\"flex flex-col gap-4 p-4\">\n <!-- Scope Selection - Full width, centered -->\n <div class=\"flex items-center justify-between gap-4\">\n <mt-radio-cards\n [options]=\"scopeOptions()\"\n [activeId]=\"propertyScope()\"\n (selectionChange)=\"onScopeChange($event)\"\n size=\"small\"\n />\n </div>\n\n <!-- Path + Field + Preview Row -->\n <div class=\"flex items-center gap-4\">\n <!-- Path Container - Flexible -->\n <div\n class=\"flex min-h-11 min-w-0 flex-1 flex-col gap-2 rounded-lg bg-slate-50 px-3 py-2 dark:bg-slate-800/50\"\n >\n <div class=\"flex items-center gap-2\">\n <span\n class=\"shrink-0 text-xs font-semibold uppercase tracking-wider text-slate-400\"\n >{{ t(\"properties.path\") }}</span\n >\n <div\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\n ></div>\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\n @if (isDirectAccess()) {\n <span\n class=\"rounded-md bg-emerald-100 px-2.5 py-1 text-xs font-medium text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400\"\n >\n \u2713 {{ t(\"properties.direct-access\") }}\n </span>\n } @else if (pathSegments().length === 0) {\n <span class=\"text-xs italic text-slate-400\">{{\n t(\"properties.no-path-configured\")\n }}</span>\n } @else {\n <div\n class=\"flex min-w-0 flex-1 items-center gap-1.5 overflow-x-auto\"\n >\n @for (\n segment of pathSegments();\n track $index;\n let segmentIndex = $index\n ) {\n @if (segmentIndex > 0) {\n <span\n class=\"shrink-0 text-slate-300 dark:text-slate-600\"\n >\u203A</span\n >\n }\n <mt-button\n type=\"button\"\n [label]=\"\n segment.value ||\n t('properties.select') +\n ' ' +\n getOperatorDisplayName(segment.operatorToken)\n \"\n icon=\"arrow.chevron-down\"\n [outlined]=\"true\"\n size=\"small\"\n [severity]=\"\n segment.value ? 'primary' : 'secondary'\n \"\n [iconPos]=\"'end'\"\n (onClick)=\"pathPopover.toggle($event)\"\n ></mt-button>\n\n <p-popover\n #pathPopover\n [style]=\"{ width: 'max-content' }\"\n appendTo=\"body\"\n >\n <div class=\"p-2\">\n <div\n class=\"mb-2 text-xs font-semibold uppercase text-slate-400\"\n >\n {{ t(\"properties.select\") }}\n {{\n getOperatorDisplayName(\n segment.operatorToken\n )\n }}\n </div>\n <div\n class=\"flex max-h-[min(24rem,calc(100vh-10rem))] flex-col gap-1 overflow-y-auto pr-1\"\n >\n @for (\n option of segmentOptions(segmentIndex);\n track option.key\n ) {\n <mt-button\n type=\"button\"\n [label]=\"option.name\"\n [icon]=\"\n segment.value === option.key\n ? 'general.check'\n : 'general.minus'\n \"\n [iconPos]=\"'end'\"\n size=\"small\"\n severity=\"secondary\"\n [styleClass]=\"\n 'w-full justify-start rounded-md px-2.5 py-1.5 text-left text-xs font-medium transition-colors ' +\n (segment.value === option.key\n ? 'bg-primary text-white'\n : 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700')\n \"\n (onClick)=\"\n setPathSegmentValue(\n segmentIndex,\n option.key\n );\n pathPopover.hide()\n \"\n ></mt-button>\n }\n </div>\n @if (segment.optional) {\n <mt-button\n type=\"button\"\n [label]=\"t('actions.clear')\"\n [outlined]=\"true\"\n icon=\"general.x-close\"\n [iconPos]=\"'end'\"\n size=\"small\"\n severity=\"danger\"\n styleClass=\"mt-2 w-full rounded-md border border-slate-200 py-1 text-xs text-slate-400 hover:bg-slate-50 dark:border-slate-700\"\n (onClick)=\"\n setPathSegmentValue(segmentIndex, null);\n pathPopover.hide()\n \"\n ></mt-button>\n }\n </div>\n </p-popover>\n\n @if (\n segment.canRemove &&\n segmentIndex === pathSegments().length - 1\n ) {\n <mt-button\n type=\"button\"\n icon=\"general.x-close\"\n size=\"small\"\n severity=\"danger\"\n styleClass=\"flex size-5 shrink-0 items-center justify-center rounded-full bg-red-500 text-white hover:bg-red-600\"\n (onClick)=\"removePathSegment(segmentIndex)\"\n ></mt-button>\n }\n }\n <mt-button\n type=\"button\"\n icon=\"general.plus\"\n size=\"small\"\n severity=\"primary\"\n [disabled]=\"!canAddNextSegment()\"\n [class.hidden]=\"!canAddNextSegment()\"\n styleClass=\"flex size-6 shrink-0 items-center justify-center rounded-md bg-primary text-xs font-bold text-white hover:opacity-90 disabled:opacity-50\"\n (onClick)=\"nextOperatorPopover.toggle($event)\"\n ></mt-button>\n </div>\n }\n <p-popover\n #nextOperatorPopover\n [style]=\"{ width: 'max-content' }\"\n appendTo=\"body\"\n >\n <div class=\"p-2\">\n <div\n class=\"mb-2 text-xs font-semibold uppercase text-slate-400\"\n >\n {{ t(\"properties.add-segment\") }}\n </div>\n <div\n class=\"flex max-h-[min(24rem,calc(100vh-10rem))] flex-col gap-1 overflow-y-auto pr-1\"\n >\n @for (\n option of nextOperatorOptions();\n track option.token\n ) {\n <mt-button\n type=\"button\"\n [label]=\"getOperatorDisplayName(option.token)\"\n icon=\"general.plus\"\n [iconPos]=\"'end'\"\n size=\"small\"\n severity=\"secondary\"\n styleClass=\"w-full justify-start rounded-md px-2.5 py-1.5 text-left text-xs font-medium transition-colors text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700\"\n (onClick)=\"\n onAddSegmentSelection(\n option.token,\n nextOperatorPopover\n )\n \"\n ></mt-button>\n }\n </div>\n </div>\n </p-popover>\n </div>\n </div>\n\n <!-- Field Selection - Under Path -->\n <div\n class=\"flex items-center gap-2 border-t border-slate-200 pt-2 dark:border-slate-700\"\n >\n <span\n class=\"shrink-0 text-xs font-semibold uppercase tracking-wider text-slate-400\"\n >{{ t(\"properties.field\") }}</span\n >\n <div\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\n ></div>\n @if (isPropertyLoading()) {\n <p-skeleton\n class=\"flex-1\"\n height=\"2.5rem\"\n styleClass=\"rounded-md\"\n />\n } @else {\n <mt-select-field\n class=\"flex-1\"\n [label]=\"''\"\n [filter]=\"true\"\n [hasPlaceholderPrefix]=\"false\"\n [placeholder]=\"t('properties.select')\"\n [(ngModel)]=\"propertyFieldKey\"\n [options]=\"propertyOptions()\"\n optionLabel=\"name\"\n optionValue=\"key\"\n [size]=\"'small'\"\n />\n }\n </div>\n\n <!-- Value Selection - Only for lookup / status / lookup-matrix fields -->\n @if (isLookupProperty()) {\n <div\n class=\"flex items-center gap-2 border-t border-slate-200 pt-2 dark:border-slate-700\"\n >\n <span\n class=\"shrink-0 text-xs font-semibold uppercase tracking-wider text-slate-400\"\n >{{ t(\"properties.value\") }}</span\n >\n <div\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\n ></div>\n @if (isLookupItemsLoading()) {\n <p-skeleton\n class=\"flex-1\"\n height=\"2.5rem\"\n styleClass=\"rounded-md\"\n />\n } @else {\n <mt-select-field\n class=\"flex-1\"\n [label]=\"''\"\n [filter]=\"true\"\n [hasPlaceholderPrefix]=\"false\"\n [placeholder]=\"t('properties.select-value')\"\n [(ngModel)]=\"lookupValueKey\"\n [options]=\"lookupItems()\"\n optionLabel=\"name\"\n optionValue=\"key\"\n [size]=\"'small'\"\n />\n }\n </div>\n }\n <div\n class=\"w-full truncate rounded-lg bg-amber-100 px-3 py-2 text-sm font-semibold text-amber-700 shadow-sm hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-300 dark:hover:bg-amber-900/50\"\n [mtTooltip]=\"\n '@' +\n propertyTablePath() +\n '::' +\n (selectedProperty()?.key ?? '...')\n \"\n tooltipPosition=\"top\"\n >\n @{{ propertyTablePath() }}::{{\n selectedProperty()?.key ?? \"...\"\n }}\n </div>\n <div class=\"flex justify-end gap-2\">\n @if (isLookupProperty()) {\n <mt-button\n type=\"button\"\n [label]=\"t('actions.insert-value')\"\n icon=\"general.plus\"\n severity=\"secondary\"\n [outlined]=\"true\"\n [disabled]=\"!canInsertLookupValue()\"\n (onClick)=\"insertSelectedLookupValue(insertBlock)\"\n ></mt-button>\n }\n <mt-button\n type=\"button\"\n [label]=\"t('actions.insert')\"\n icon=\"general.plus\"\n severity=\"primary\"\n [disabled]=\"!canInsertProperty()\"\n (onClick)=\"insertSelectedProperty(insertBlock)\"\n ></mt-button>\n </div>\n </div>\n </div>\n </div>\n }\n </ng-template>\n </mt-formula-toolbar>\n }\n\n <!-- Editor Area -->\n <div class=\"p-3\">\n <div\n class=\"overflow-hidden rounded-lg border border-surface-200 bg-white dark:border-surface-700 dark:bg-surface-800\"\n >\n @if (showModeToggle()) {\n <div\n class=\"flex items-center justify-end border-b border-surface-200 px-3 py-2 dark:border-surface-700\"\n >\n <mt-toggle-field\n [label]=\"t('codeMode')\"\n labelPosition=\"end\"\n size=\"small\"\n [ngModel]=\"isCodeMode()\"\n (ngModelChange)=\"onModeToggle($event)\"\n />\n </div>\n }\n <div class=\"p-0\">\n @if (isCodeMode()) {\n <mt-formula-editor-code\n #formulaEditorCode\n [placeholder]=\"placeholder() || t('placeholder')\"\n [initialFormula]=\"expression()\"\n [borderless]=\"true\"\n [autocompleteProvider]=\"autocompleteProvider\"\n (formulaChange)=\"onCodeFormulaChange($event)\"\n (onFocus)=\"onEditorFocus()\"\n (onBlur)=\"onEditorBlur()\"\n />\n } @else {\n <mt-formula-editor\n #formulaEditor\n [placeholder]=\"placeholder() || t('placeholder')\"\n [initialTokens]=\"tokens()\"\n [borderless]=\"true\"\n (formulaChange)=\"onFormulaChange($event)\"\n (tokensChange)=\"onTokensChange($event)\"\n (onFocus)=\"onEditorFocus()\"\n (onBlur)=\"onEditorBlur()\"\n />\n }\n </div>\n </div>\n </div>\n\n <!-- Status Bar - Pass data via inputs (pure component) -->\n @if (!hideStatusBar()) {\n <mt-formula-status-bar [validation]=\"validation()\" />\n }\n </div>\n</mt-card>\n" }]
1530
+ }], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.ViewChild, args: ['formulaEditor', { isSignal: true }] }], codeEditor: [{ type: i0.ViewChild, args: ['formulaEditorCode', { isSignal: true }] }], propertiesByPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "propertiesByPath", required: false }] }], levelSchemaId: [{ type: i0.Input, args: [{ isSignal: true, alias: "levelSchemaId", required: false }] }], moduleId: [{ type: i0.Input, args: [{ isSignal: true, alias: "moduleId", required: false }] }], contextKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "contextKey", required: false }] }], contextEntityTypeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "contextEntityTypeKey", required: false }] }], templateId: [{ type: i0.Input, args: [{ isSignal: true, alias: "templateId", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hideToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideToolbar", required: false }] }], hideStatusBar: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideStatusBar", required: false }] }], toolbarTabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolbarTabs", required: false }] }], functionCategories: [{ type: i0.Input, args: [{ isSignal: true, alias: "functionCategories", required: false }] }], operators: [{ type: i0.Input, args: [{ isSignal: true, alias: "operators", required: false }] }], codeOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "codeOnly", required: false }] }], builderOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "builderOnly", required: false }] }], valueMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueMode", required: false }] }], isProcessBuilder: [{ type: i0.Input, args: [{ isSignal: true, alias: "isProcessBuilder", required: false }] }], validationChange: [{ type: i0.Output, args: ["validationChange"] }], tokensChange: [{ type: i0.Output, args: ["tokensChange"] }] } });
1521
1531
 
1522
1532
  /**
1523
1533
  * Services barrel export