@masterteam/formula-builder 0.0.27 → 0.0.28
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
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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.
|
|
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
|
-
|
|
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
|