ngx-dynamic-entity 0.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +129 -0
  2. package/esm2022/lib/field-types/array-field.component.mjs +160 -0
  3. package/esm2022/lib/field-types/boolean-field.component.mjs +100 -0
  4. package/esm2022/lib/field-types/checkbox-field.component.mjs +25 -17
  5. package/esm2022/lib/field-types/currency-field.component.mjs +101 -0
  6. package/esm2022/lib/field-types/date-field.component.mjs +21 -13
  7. package/esm2022/lib/field-types/dropdown-field.component.mjs +120 -29
  8. package/esm2022/lib/field-types/email-field.component.mjs +101 -0
  9. package/esm2022/lib/field-types/entity-ref-field.component.mjs +96 -32
  10. package/esm2022/lib/field-types/file-field.component.mjs +156 -0
  11. package/esm2022/lib/field-types/group-field.component.mjs +89 -0
  12. package/esm2022/lib/field-types/image-field.component.mjs +168 -0
  13. package/esm2022/lib/field-types/month-year-field.component.mjs +151 -0
  14. package/esm2022/lib/field-types/multi-select-field.component.mjs +79 -25
  15. package/esm2022/lib/field-types/number-field.component.mjs +60 -17
  16. package/esm2022/lib/field-types/password-field.component.mjs +109 -0
  17. package/esm2022/lib/field-types/radio-field.component.mjs +152 -0
  18. package/esm2022/lib/field-types/text-field.component.mjs +58 -18
  19. package/esm2022/lib/field-types/textarea-field.component.mjs +26 -15
  20. package/esm2022/lib/form/dynamic-field/dynamic-field.component.mjs +44 -26
  21. package/esm2022/lib/form/dynamic-form.component.mjs +676 -69
  22. package/esm2022/lib/form/dynamic-record-form.component.mjs +494 -0
  23. package/esm2022/lib/providers/provide-field-types.mjs +72 -0
  24. package/esm2022/lib/providers/provide-ngx-dynamic-entity.mjs +4 -5
  25. package/esm2022/lib/services/cascade-data.service.mjs +129 -0
  26. package/esm2022/lib/services/config-source.service.mjs +67 -0
  27. package/esm2022/lib/services/entity-ref-cache.mjs +20 -0
  28. package/esm2022/lib/services/entity-ref-registry.service.mjs +3 -3
  29. package/esm2022/lib/services/entity-ref-selection.service.mjs +27 -0
  30. package/esm2022/lib/services/entity-reference.service.mjs +168 -0
  31. package/esm2022/lib/services/field-registry.service.mjs +35 -30
  32. package/esm2022/lib/services/file-upload.service.mjs +89 -0
  33. package/esm2022/lib/services/lookup-registry.service.mjs +246 -0
  34. package/esm2022/lib/services/rbac.service.mjs +5 -11
  35. package/esm2022/lib/services/rules-evaluation.service.mjs +27 -0
  36. package/esm2022/lib/services/validator-registry.service.mjs +35 -7
  37. package/esm2022/lib/tokens/injection-tokens.mjs +104 -7
  38. package/esm2022/public-api.mjs +31 -6
  39. package/fesm2022/ngx-dynamic-entity.mjs +4094 -759
  40. package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
  41. package/lib/field-types/array-field.component.d.ts +22 -0
  42. package/lib/field-types/boolean-field.component.d.ts +18 -0
  43. package/lib/field-types/checkbox-field.component.d.ts +3 -2
  44. package/lib/field-types/currency-field.component.d.ts +17 -0
  45. package/lib/field-types/date-field.component.d.ts +3 -2
  46. package/lib/field-types/dropdown-field.component.d.ts +24 -4
  47. package/lib/field-types/email-field.component.d.ts +15 -0
  48. package/lib/field-types/entity-ref-field.component.d.ts +24 -11
  49. package/lib/field-types/file-field.component.d.ts +25 -0
  50. package/lib/field-types/group-field.component.d.ts +17 -0
  51. package/lib/field-types/image-field.component.d.ts +30 -0
  52. package/lib/field-types/month-year-field.component.d.ts +28 -0
  53. package/lib/field-types/multi-select-field.component.d.ts +18 -4
  54. package/lib/field-types/number-field.component.d.ts +5 -2
  55. package/lib/field-types/password-field.component.d.ts +17 -0
  56. package/lib/field-types/radio-field.component.d.ts +33 -0
  57. package/lib/field-types/text-field.component.d.ts +5 -3
  58. package/lib/field-types/textarea-field.component.d.ts +4 -2
  59. package/lib/form/dynamic-field/dynamic-field.component.d.ts +20 -7
  60. package/lib/form/dynamic-form.component.d.ts +203 -24
  61. package/lib/form/dynamic-record-form.component.d.ts +180 -0
  62. package/lib/providers/provide-field-types.d.ts +24 -0
  63. package/lib/providers/provide-ngx-dynamic-entity.d.ts +19 -8
  64. package/lib/services/cascade-data.service.d.ts +45 -0
  65. package/lib/services/config-source.service.d.ts +14 -0
  66. package/lib/services/entity-ref-cache.d.ts +27 -0
  67. package/lib/services/entity-ref-registry.service.d.ts +4 -3
  68. package/lib/services/entity-ref-selection.service.d.ts +24 -0
  69. package/lib/services/entity-reference.service.d.ts +49 -0
  70. package/lib/services/field-registry.service.d.ts +17 -7
  71. package/lib/services/file-upload.service.d.ts +27 -0
  72. package/lib/services/lookup-registry.service.d.ts +94 -0
  73. package/lib/services/rbac.service.d.ts +4 -9
  74. package/lib/services/rules-evaluation.service.d.ts +17 -0
  75. package/lib/services/validator-registry.service.d.ts +5 -4
  76. package/lib/tokens/injection-tokens.d.ts +113 -8
  77. package/package.json +18 -8
  78. package/public-api.d.ts +21 -4
  79. package/esm2022/lib/services/config.service.mjs +0 -58
  80. package/esm2022/lib/services/version.service.mjs +0 -40
  81. package/esm2022/lib/table/dynamic-table.component.mjs +0 -160
  82. package/lib/services/config.service.d.ts +0 -25
  83. package/lib/services/version.service.d.ts +0 -26
  84. package/lib/table/dynamic-table.component.d.ts +0 -56
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # ngx-dynamic-entity
2
+
3
+ [![npm version](https://img.shields.io/npm/v/ngx-dynamic-entity.svg?color=red)](https://www.npmjs.com/package/ngx-dynamic-entity)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](../../LICENSE)
5
+
6
+ Angular dynamic form renderer and tabbed record editor, powered by `@dynamic-entity/core`. Supports Angular 17 through 22.
7
+
8
+ > This package renders **forms**. It ships no data table — rendering a table over your records is the consumer's choice.
9
+
10
+ ---
11
+
12
+ ## 📦 Installation
13
+
14
+ ```bash
15
+ npm install ngx-dynamic-entity @dynamic-entity/core
16
+ ```
17
+
18
+ No Angular Material required. This package has no dependency on Material or the CDK.
19
+
20
+ ---
21
+
22
+ ## ✨ Features
23
+
24
+ - **`DynamicFormComponent`** — tabbed dynamic forms generated from an `EntityFormConfig`, built on Angular Reactive Forms.
25
+ - **`DynamicRecordFormComponent`** — record editor with summary drawer (`showOnMinimize`), profile header, and per-section saving.
26
+ - **Reactive rules** — real-time condition evaluation driving field/tab visibility, validation errors and warnings, and info banners.
27
+ - **Entity references & cascades** — consumer-registered loaders, parent→child dropdown filtering, and `autoPatch` record copying.
28
+ - **19 field types** — each a standalone component, registered explicitly so unused types are never bundled.
29
+
30
+ ---
31
+
32
+ ## 🚀 Setup & Registration
33
+
34
+ Field types are **not** registered automatically. Without this step the form renders no fields.
35
+
36
+ ```typescript
37
+ // app.config.ts
38
+ import { ApplicationConfig } from '@angular/core';
39
+ import { provideNgxDynamicEntity, provideBuiltInFieldTypes } from 'ngx-dynamic-entity';
40
+
41
+ export const appConfig: ApplicationConfig = {
42
+ providers: [
43
+ provideNgxDynamicEntity({
44
+ maskedRoles: ['IT_SUPPORT'],
45
+ entityRefs: {
46
+ countries: () => fetch('/api/countries').then(res => res.json()),
47
+ },
48
+ }),
49
+ provideBuiltInFieldTypes(),
50
+ ],
51
+ };
52
+ ```
53
+
54
+ To bundle only the types you use, register them individually instead:
55
+
56
+ ```typescript
57
+ import { provideFieldTypes, TextFieldComponent, DropdownFieldComponent } from 'ngx-dynamic-entity';
58
+
59
+ provideFieldTypes({ text: TextFieldComponent, dropdown: DropdownFieldComponent });
60
+ ```
61
+
62
+ Multiple `provideFieldTypes` calls merge; on a key collision the later registration wins. Pass your own component for a key to override a built-in, or add a key of your own for a custom type.
63
+
64
+ ---
65
+
66
+ ## 💡 Usage
67
+
68
+ ```html
69
+ <ngx-dynamic-form
70
+ [config]="formConfig"
71
+ [initialData]="record"
72
+ [userRoles]="['editor']"
73
+ [language]="'en'"
74
+ (formSubmit)="onSave($event)"
75
+ />
76
+ ```
77
+
78
+ ### Inputs
79
+
80
+ | Input | Type | Notes |
81
+ |---|---|---|
82
+ | `config` | `EntityFormConfig` | Required. |
83
+ | `initialData` | `Record<string, any>` | See **Record shape** below. |
84
+ | `userRoles` | `string[]` | Drives permission and masking checks. Defaults to `[]`. |
85
+ | `language` | `string` | Defaults to `'en'`. |
86
+ | `rules` | `FormRule[]` | Optional reactive rules. |
87
+ | `originalData` | `Record<string, any>` | Baseline for the `VALUE_CHANGED` operator. |
88
+ | `readonly` | `boolean` | Renders the whole form read-only. |
89
+ | `readOnlyFields` | `string[]` | Read-only by field id. |
90
+ | `loading` / `error` | `boolean` / `string \| null` | Render loading and error states. |
91
+
92
+ ### Outputs
93
+
94
+ | Output | Payload |
95
+ |---|---|
96
+ | `formSubmit` | `Record<string, any>` |
97
+ | `formChange` | `Record<string, any>` |
98
+ | `formReset` | `void` |
99
+ | `activeTabChange` | `string` (tab id) |
100
+
101
+ ---
102
+
103
+ ## 🗂 Record shape
104
+
105
+ A record is **nested by tab id** by default:
106
+
107
+ ```typescript
108
+ const record = { general: { firstName: 'Alice' }, billing: { vatNumber: 'GB123' } };
109
+ ```
110
+
111
+ Set `flatData: true` on a tab to store that tab's fields at the record root:
112
+
113
+ ```typescript
114
+ const record = { firstName: 'Alice', lastName: 'Smith' };
115
+ ```
116
+
117
+ This applies in both directions — `initialData` is read with it and `formSubmit` emits with it. **Passing a flat record to a tab that is not marked `flatData` leaves those fields empty**, with no error.
118
+
119
+ ---
120
+
121
+ ## 🔐 Security
122
+
123
+ `userRoles`, `EntityPermissions`, and `maskData` affect **rendering only**. A masked field displays `XXXXXXXXX` while the real value stays in the form control and is included in the `formSubmit` payload. Enforce authorization on the server and never send a user data they may not see.
124
+
125
+ ---
126
+
127
+ ## 🎨 Styling
128
+
129
+ This package ships no CSS. Field components emit `ngx-field`, `ngx-field__label`, `ngx-field__input`, and `ngx-field__error` for you to style. See `packages/demo-angular/src/styles.css` in the repository for a working reference.
@@ -0,0 +1,160 @@
1
+ import { Component, Input, inject, forwardRef } from '@angular/core';
2
+ import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
3
+ import { resolveLabel } from '@dynamic-entity/core';
4
+ import { DynamicFieldComponent } from '../form/dynamic-field/dynamic-field.component';
5
+ import { ValidatorRegistryService } from '../services/validator-registry.service';
6
+ import * as i0 from "@angular/core";
7
+ /** ADR-008: ArrayFieldComponent — renders repeating rows for FormArray with + Add Item and Remove */
8
+ export class ArrayFieldComponent {
9
+ field;
10
+ control;
11
+ config;
12
+ language = 'en';
13
+ readonly = false;
14
+ masked = false;
15
+ userRoles = [];
16
+ fb = inject(FormBuilder);
17
+ validatorRegistry = inject(ValidatorRegistryService);
18
+ get label() {
19
+ return resolveLabel(this.field?.label, this.language);
20
+ }
21
+ get formArray() {
22
+ return this.control;
23
+ }
24
+ getItemChildControl(itemGroup, childId) {
25
+ return itemGroup.get(childId);
26
+ }
27
+ addItem() {
28
+ const controls = {};
29
+ for (const child of this.field.children ?? []) {
30
+ const validators = this.validatorRegistry.resolveFromConfig(child.validators);
31
+ controls[child.id] = [child.defaultValue ?? null, validators];
32
+ }
33
+ const group = this.fb.group(controls);
34
+ this.formArray.push(group);
35
+ }
36
+ removeItem(index) {
37
+ this.formArray.removeAt(index);
38
+ }
39
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
40
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: ArrayFieldComponent, isStandalone: true, selector: "ngx-array-field", inputs: { field: "field", control: "control", config: "config", language: "language", readonly: "readonly", masked: "masked", userRoles: "userRoles" }, ngImport: i0, template: `
41
+ <div class="ngx-field ngx-field--array"
42
+ [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
43
+ <div class="ngx-field__array-header">
44
+ <label class="ngx-field__label">{{ label }}</label>
45
+ @if (!readonly && !masked) {
46
+ <button type="button" class="ngx-field__array-add-btn"
47
+ [attr.data-testid]="'field-' + field.id + '-add'" (click)="addItem()">
48
+ + Add Item
49
+ </button>
50
+ }
51
+ </div>
52
+
53
+ @if (masked) {
54
+ <span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
55
+ } @else {
56
+ <div class="ngx-field__array-list">
57
+ @for (itemGroup of formArray.controls; track idx; let idx = $index) {
58
+ <div class="ngx-field__array-item" [attr.data-testid]="'field-' + field.id + '-row'">
59
+ <div class="ngx-field__array-item-header">
60
+ <span class="ngx-field__array-item-title">Item #{{ idx + 1 }}</span>
61
+ @if (!readonly) {
62
+ <button type="button" class="ngx-field__array-remove-btn"
63
+ [attr.data-testid]="'field-' + field.id + '-remove-' + idx" (click)="removeItem(idx)">
64
+ Remove
65
+ </button>
66
+ }
67
+ </div>
68
+ <div class="ngx-field__array-item-fields">
69
+ @for (child of field.children ?? []; track child.id) {
70
+ @if (child.visibility !== false) {
71
+ <ngx-dynamic-field
72
+ [field]="child"
73
+ [control]="getItemChildControl(itemGroup, child.id)"
74
+ [config]="config"
75
+ [language]="language"
76
+ [readonly]="readonly || !!child.readonly"
77
+ [userRoles]="userRoles"
78
+ />
79
+ }
80
+ }
81
+ </div>
82
+ </div>
83
+ }
84
+ @if (formArray.controls.length === 0) {
85
+ <div class="ngx-field__array-empty" [attr.data-testid]="'field-' + field.id + '-empty'">No items added yet.</div>
86
+ }
87
+ </div>
88
+ }
89
+ </div>
90
+ `, isInline: true, styles: [".ngx-field--array{border:1px solid #e2e8f0;border-radius:8px;padding:16px;margin-bottom:16px;background:#f8fafc}.ngx-field__array-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.ngx-field__array-add-btn{background:#2563eb;color:#fff;border:none;padding:6px 12px;border-radius:4px;font-weight:500;cursor:pointer}.ngx-field__array-add-btn:hover{background:#1d4ed8}.ngx-field__array-list{display:flex;flex-direction:column;gap:12px}.ngx-field__array-item{background:#fff;border:1px solid #cbd5e1;border-radius:6px;padding:12px}.ngx-field__array-item-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;border-bottom:1px solid #f1f5f9;padding-bottom:6px}.ngx-field__array-item-title{font-weight:600;font-size:13px;color:#475569}.ngx-field__array-remove-btn{background:#ef4444;color:#fff;border:none;padding:4px 8px;border-radius:4px;font-size:12px;cursor:pointer}.ngx-field__array-empty{color:#94a3b8;font-style:italic;font-size:13px}\n"], dependencies: [{ kind: "ngmodule", type: i0.forwardRef(() => ReactiveFormsModule) }, { kind: "component", type: i0.forwardRef(() => DynamicFieldComponent), selector: "ngx-dynamic-field", inputs: ["field", "control", "config", "currentTabId", "language", "readonly", "userRoles"] }] });
91
+ }
92
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ArrayFieldComponent, decorators: [{
93
+ type: Component,
94
+ args: [{ selector: 'ngx-array-field', standalone: true, imports: [ReactiveFormsModule, forwardRef(() => DynamicFieldComponent)], template: `
95
+ <div class="ngx-field ngx-field--array"
96
+ [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
97
+ <div class="ngx-field__array-header">
98
+ <label class="ngx-field__label">{{ label }}</label>
99
+ @if (!readonly && !masked) {
100
+ <button type="button" class="ngx-field__array-add-btn"
101
+ [attr.data-testid]="'field-' + field.id + '-add'" (click)="addItem()">
102
+ + Add Item
103
+ </button>
104
+ }
105
+ </div>
106
+
107
+ @if (masked) {
108
+ <span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
109
+ } @else {
110
+ <div class="ngx-field__array-list">
111
+ @for (itemGroup of formArray.controls; track idx; let idx = $index) {
112
+ <div class="ngx-field__array-item" [attr.data-testid]="'field-' + field.id + '-row'">
113
+ <div class="ngx-field__array-item-header">
114
+ <span class="ngx-field__array-item-title">Item #{{ idx + 1 }}</span>
115
+ @if (!readonly) {
116
+ <button type="button" class="ngx-field__array-remove-btn"
117
+ [attr.data-testid]="'field-' + field.id + '-remove-' + idx" (click)="removeItem(idx)">
118
+ Remove
119
+ </button>
120
+ }
121
+ </div>
122
+ <div class="ngx-field__array-item-fields">
123
+ @for (child of field.children ?? []; track child.id) {
124
+ @if (child.visibility !== false) {
125
+ <ngx-dynamic-field
126
+ [field]="child"
127
+ [control]="getItemChildControl(itemGroup, child.id)"
128
+ [config]="config"
129
+ [language]="language"
130
+ [readonly]="readonly || !!child.readonly"
131
+ [userRoles]="userRoles"
132
+ />
133
+ }
134
+ }
135
+ </div>
136
+ </div>
137
+ }
138
+ @if (formArray.controls.length === 0) {
139
+ <div class="ngx-field__array-empty" [attr.data-testid]="'field-' + field.id + '-empty'">No items added yet.</div>
140
+ }
141
+ </div>
142
+ }
143
+ </div>
144
+ `, styles: [".ngx-field--array{border:1px solid #e2e8f0;border-radius:8px;padding:16px;margin-bottom:16px;background:#f8fafc}.ngx-field__array-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.ngx-field__array-add-btn{background:#2563eb;color:#fff;border:none;padding:6px 12px;border-radius:4px;font-weight:500;cursor:pointer}.ngx-field__array-add-btn:hover{background:#1d4ed8}.ngx-field__array-list{display:flex;flex-direction:column;gap:12px}.ngx-field__array-item{background:#fff;border:1px solid #cbd5e1;border-radius:6px;padding:12px}.ngx-field__array-item-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;border-bottom:1px solid #f1f5f9;padding-bottom:6px}.ngx-field__array-item-title{font-weight:600;font-size:13px;color:#475569}.ngx-field__array-remove-btn{background:#ef4444;color:#fff;border:none;padding:4px 8px;border-radius:4px;font-size:12px;cursor:pointer}.ngx-field__array-empty{color:#94a3b8;font-style:italic;font-size:13px}\n"] }]
145
+ }], propDecorators: { field: [{
146
+ type: Input
147
+ }], control: [{
148
+ type: Input
149
+ }], config: [{
150
+ type: Input
151
+ }], language: [{
152
+ type: Input
153
+ }], readonly: [{
154
+ type: Input
155
+ }], masked: [{
156
+ type: Input
157
+ }], userRoles: [{
158
+ type: Input
159
+ }] } });
160
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXJyYXktZmllbGQuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9maWVsZC10eXBlcy9hcnJheS1maWVsZC5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUNyRSxPQUFPLEVBQThCLFdBQVcsRUFBYSxtQkFBbUIsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBRXpHLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNwRCxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSwrQ0FBK0MsQ0FBQztBQUN0RixPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSx3Q0FBd0MsQ0FBQzs7QUFFbEYscUdBQXFHO0FBNEhyRyxNQUFNLE9BQU8sbUJBQW1CO0lBQ3JCLEtBQUssQ0FBcUI7SUFDMUIsT0FBTyxDQUFtQjtJQUMxQixNQUFNLENBQW9CO0lBQzFCLFFBQVEsR0FBVyxJQUFJLENBQUM7SUFDeEIsUUFBUSxHQUFZLEtBQUssQ0FBQztJQUMxQixNQUFNLEdBQVksS0FBSyxDQUFDO0lBQ3hCLFNBQVMsR0FBYSxFQUFFLENBQUM7SUFFakIsRUFBRSxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUN6QixpQkFBaUIsR0FBRyxNQUFNLENBQUMsd0JBQXdCLENBQUMsQ0FBQztJQUV0RSxJQUFJLEtBQUs7UUFDUCxPQUFPLFlBQVksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDeEQsQ0FBQztJQUVELElBQUksU0FBUztRQUNYLE9BQU8sSUFBSSxDQUFDLE9BQW9CLENBQUM7SUFDbkMsQ0FBQztJQUVELG1CQUFtQixDQUFDLFNBQTBCLEVBQUUsT0FBZTtRQUM3RCxPQUFRLFNBQXVCLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBb0IsQ0FBQztJQUNsRSxDQUFDO0lBRUQsT0FBTztRQUNMLE1BQU0sUUFBUSxHQUF3QixFQUFFLENBQUM7UUFDekMsS0FBSyxNQUFNLEtBQUssSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLFFBQVEsSUFBSSxFQUFFLEVBQUUsQ0FBQztZQUM5QyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsaUJBQWlCLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1lBQzlFLFFBQVEsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsWUFBWSxJQUFJLElBQUksRUFBRSxVQUFVLENBQUMsQ0FBQztRQUNoRSxDQUFDO1FBQ0QsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDdEMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDN0IsQ0FBQztJQUVELFVBQVUsQ0FBQyxLQUFhO1FBQ3RCLElBQUksQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ2pDLENBQUM7d0dBcENVLG1CQUFtQjs0RkFBbkIsbUJBQW1CLG1PQXZIcEI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0RULDZrQ0FuRFMsbUJBQW1CLG9EQUFtQixxQkFBcUI7OzRGQXdIMUQsbUJBQW1CO2tCQTNIL0IsU0FBUzsrQkFDRSxpQkFBaUIsY0FDZixJQUFJLFdBQ1AsQ0FBQyxtQkFBbUIsRUFBRSxVQUFVLENBQUMsR0FBRyxFQUFFLENBQUMscUJBQXFCLENBQUMsQ0FBQyxZQUM3RDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FrRFQ7OEJBc0VRLEtBQUs7c0JBQWIsS0FBSztnQkFDRyxPQUFPO3NCQUFmLEtBQUs7Z0JBQ0csTUFBTTtzQkFBZCxLQUFLO2dCQUNHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBQ0csUUFBUTtzQkFBaEIsS0FBSztnQkFDRyxNQUFNO3NCQUFkLEtBQUs7Z0JBQ0csU0FBUztzQkFBakIsS0FBSyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbXBvbmVudCwgSW5wdXQsIGluamVjdCwgZm9yd2FyZFJlZiB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQWJzdHJhY3RDb250cm9sLCBGb3JtQXJyYXksIEZvcm1CdWlsZGVyLCBGb3JtR3JvdXAsIFJlYWN0aXZlRm9ybXNNb2R1bGUgfSBmcm9tICdAYW5ndWxhci9mb3Jtcyc7XG5pbXBvcnQgdHlwZSB7IE5lc3RlZEZpZWxkQ29uZmlnLCBFbnRpdHlGb3JtQ29uZmlnIH0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuaW1wb3J0IHsgcmVzb2x2ZUxhYmVsIH0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuaW1wb3J0IHsgRHluYW1pY0ZpZWxkQ29tcG9uZW50IH0gZnJvbSAnLi4vZm9ybS9keW5hbWljLWZpZWxkL2R5bmFtaWMtZmllbGQuY29tcG9uZW50JztcbmltcG9ydCB7IFZhbGlkYXRvclJlZ2lzdHJ5U2VydmljZSB9IGZyb20gJy4uL3NlcnZpY2VzL3ZhbGlkYXRvci1yZWdpc3RyeS5zZXJ2aWNlJztcblxuLyoqIEFEUi0wMDg6IEFycmF5RmllbGRDb21wb25lbnQg4oCUIHJlbmRlcnMgcmVwZWF0aW5nIHJvd3MgZm9yIEZvcm1BcnJheSB3aXRoICsgQWRkIEl0ZW0gYW5kIFJlbW92ZSAqL1xuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiAnbmd4LWFycmF5LWZpZWxkJyxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW1JlYWN0aXZlRm9ybXNNb2R1bGUsIGZvcndhcmRSZWYoKCkgPT4gRHluYW1pY0ZpZWxkQ29tcG9uZW50KV0sXG4gIHRlbXBsYXRlOiBgXG4gICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZCBuZ3gtZmllbGQtLWFycmF5XCJcbiAgICAgIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWRcIiBbYXR0ci5kYXRhLWZpZWxkLXR5cGVdPVwiZmllbGQudHlwZVwiIFtjbGFzcy5uZ3gtZmllbGQtLXJlYWRvbmx5XT1cInJlYWRvbmx5XCIgW2NsYXNzLm5neC1maWVsZC0tbWFza2VkXT1cIm1hc2tlZFwiPlxuICAgICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktaGVhZGVyXCI+XG4gICAgICAgIDxsYWJlbCBjbGFzcz1cIm5neC1maWVsZF9fbGFiZWxcIj57eyBsYWJlbCB9fTwvbGFiZWw+XG4gICAgICAgIEBpZiAoIXJlYWRvbmx5ICYmICFtYXNrZWQpIHtcbiAgICAgICAgICA8YnV0dG9uIHR5cGU9XCJidXR0b25cIiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktYWRkLWJ0blwiXG4gICAgICAgICAgICBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkICsgJy1hZGQnXCIgKGNsaWNrKT1cImFkZEl0ZW0oKVwiPlxuICAgICAgICAgICAgKyBBZGQgSXRlbVxuICAgICAgICAgIDwvYnV0dG9uPlxuICAgICAgICB9XG4gICAgICA8L2Rpdj5cblxuICAgICAgQGlmIChtYXNrZWQpIHtcbiAgICAgICAgPHNwYW4gY2xhc3M9XCJuZ3gtZmllbGRfX3ZhbHVlIG5neC1maWVsZF9fdmFsdWUtLW1hc2tlZFwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLW1hc2tlZCdcIj5YWFhYWFhYWFg8L3NwYW4+XG4gICAgICB9IEBlbHNlIHtcbiAgICAgICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktbGlzdFwiPlxuICAgICAgICAgIEBmb3IgKGl0ZW1Hcm91cCBvZiBmb3JtQXJyYXkuY29udHJvbHM7IHRyYWNrIGlkeDsgbGV0IGlkeCA9ICRpbmRleCkge1xuICAgICAgICAgICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktaXRlbVwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLXJvdydcIj5cbiAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktaXRlbS1oZWFkZXJcIj5cbiAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktaXRlbS10aXRsZVwiPkl0ZW0gI3t7IGlkeCArIDEgfX08L3NwYW4+XG4gICAgICAgICAgICAgICAgQGlmICghcmVhZG9ubHkpIHtcbiAgICAgICAgICAgICAgICAgIDxidXR0b24gdHlwZT1cImJ1dHRvblwiIGNsYXNzPVwibmd4LWZpZWxkX19hcnJheS1yZW1vdmUtYnRuXCJcbiAgICAgICAgICAgICAgICAgICAgW2F0dHIuZGF0YS10ZXN0aWRdPVwiJ2ZpZWxkLScgKyBmaWVsZC5pZCArICctcmVtb3ZlLScgKyBpZHhcIiAoY2xpY2spPVwicmVtb3ZlSXRlbShpZHgpXCI+XG4gICAgICAgICAgICAgICAgICAgIFJlbW92ZVxuICAgICAgICAgICAgICAgICAgPC9idXR0b24+XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktaXRlbS1maWVsZHNcIj5cbiAgICAgICAgICAgICAgICBAZm9yIChjaGlsZCBvZiBmaWVsZC5jaGlsZHJlbiA/PyBbXTsgdHJhY2sgY2hpbGQuaWQpIHtcbiAgICAgICAgICAgICAgICAgIEBpZiAoY2hpbGQudmlzaWJpbGl0eSAhPT0gZmFsc2UpIHtcbiAgICAgICAgICAgICAgICAgICAgPG5neC1keW5hbWljLWZpZWxkXG4gICAgICAgICAgICAgICAgICAgICAgW2ZpZWxkXT1cImNoaWxkXCJcbiAgICAgICAgICAgICAgICAgICAgICBbY29udHJvbF09XCJnZXRJdGVtQ2hpbGRDb250cm9sKGl0ZW1Hcm91cCwgY2hpbGQuaWQpXCJcbiAgICAgICAgICAgICAgICAgICAgICBbY29uZmlnXT1cImNvbmZpZ1wiXG4gICAgICAgICAgICAgICAgICAgICAgW2xhbmd1YWdlXT1cImxhbmd1YWdlXCJcbiAgICAgICAgICAgICAgICAgICAgICBbcmVhZG9ubHldPVwicmVhZG9ubHkgfHwgISFjaGlsZC5yZWFkb25seVwiXG4gICAgICAgICAgICAgICAgICAgICAgW3VzZXJSb2xlc109XCJ1c2VyUm9sZXNcIlxuICAgICAgICAgICAgICAgICAgICAvPlxuICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgPC9kaXY+XG4gICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICB9XG4gICAgICAgICAgQGlmIChmb3JtQXJyYXkuY29udHJvbHMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICA8ZGl2IGNsYXNzPVwibmd4LWZpZWxkX19hcnJheS1lbXB0eVwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLWVtcHR5J1wiPk5vIGl0ZW1zIGFkZGVkIHlldC48L2Rpdj5cbiAgICAgICAgICB9XG4gICAgICAgIDwvZGl2PlxuICAgICAgfVxuICAgIDwvZGl2PlxuICBgLFxuICBzdHlsZXM6IFtcbiAgICBgXG4gICAgICAubmd4LWZpZWxkLS1hcnJheSB7XG4gICAgICAgIGJvcmRlcjogMXB4IHNvbGlkICNlMmU4ZjA7XG4gICAgICAgIGJvcmRlci1yYWRpdXM6IDhweDtcbiAgICAgICAgcGFkZGluZzogMTZweDtcbiAgICAgICAgbWFyZ2luLWJvdHRvbTogMTZweDtcbiAgICAgICAgYmFja2dyb3VuZDogI2Y4ZmFmYztcbiAgICAgIH1cbiAgICAgIC5uZ3gtZmllbGRfX2FycmF5LWhlYWRlciB7XG4gICAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICAgIGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjtcbiAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgICAgICAgbWFyZ2luLWJvdHRvbTogMTJweDtcbiAgICAgIH1cbiAgICAgIC5uZ3gtZmllbGRfX2FycmF5LWFkZC1idG4ge1xuICAgICAgICBiYWNrZ3JvdW5kOiAjMjU2M2ViO1xuICAgICAgICBjb2xvcjogI2ZmZjtcbiAgICAgICAgYm9yZGVyOiBub25lO1xuICAgICAgICBwYWRkaW5nOiA2cHggMTJweDtcbiAgICAgICAgYm9yZGVyLXJhZGl1czogNHB4O1xuICAgICAgICBmb250LXdlaWdodDogNTAwO1xuICAgICAgICBjdXJzb3I6IHBvaW50ZXI7XG4gICAgICB9XG4gICAgICAubmd4LWZpZWxkX19hcnJheS1hZGQtYnRuOmhvdmVyIHtcbiAgICAgICAgYmFja2dyb3VuZDogIzFkNGVkODtcbiAgICAgIH1cbiAgICAgIC5uZ3gtZmllbGRfX2FycmF5LWxpc3Qge1xuICAgICAgICBkaXNwbGF5OiBmbGV4O1xuICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuICAgICAgICBnYXA6IDEycHg7XG4gICAgICB9XG4gICAgICAubmd4LWZpZWxkX19hcnJheS1pdGVtIHtcbiAgICAgICAgYmFja2dyb3VuZDogI2ZmZjtcbiAgICAgICAgYm9yZGVyOiAxcHggc29saWQgI2NiZDVlMTtcbiAgICAgICAgYm9yZGVyLXJhZGl1czogNnB4O1xuICAgICAgICBwYWRkaW5nOiAxMnB4O1xuICAgICAgfVxuICAgICAgLm5neC1maWVsZF9fYXJyYXktaXRlbS1oZWFkZXIge1xuICAgICAgICBkaXNwbGF5OiBmbGV4O1xuICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47XG4gICAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG4gICAgICAgIG1hcmdpbi1ib3R0b206IDhweDtcbiAgICAgICAgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkICNmMWY1Zjk7XG4gICAgICAgIHBhZGRpbmctYm90dG9tOiA2cHg7XG4gICAgICB9XG4gICAgICAubmd4LWZpZWxkX19hcnJheS1pdGVtLXRpdGxlIHtcbiAgICAgICAgZm9udC13ZWlnaHQ6IDYwMDtcbiAgICAgICAgZm9udC1zaXplOiAxM3B4O1xuICAgICAgICBjb2xvcjogIzQ3NTU2OTtcbiAgICAgIH1cbiAgICAgIC5uZ3gtZmllbGRfX2FycmF5LXJlbW92ZS1idG4ge1xuICAgICAgICBiYWNrZ3JvdW5kOiAjZWY0NDQ0O1xuICAgICAgICBjb2xvcjogI2ZmZjtcbiAgICAgICAgYm9yZGVyOiBub25lO1xuICAgICAgICBwYWRkaW5nOiA0cHggOHB4O1xuICAgICAgICBib3JkZXItcmFkaXVzOiA0cHg7XG4gICAgICAgIGZvbnQtc2l6ZTogMTJweDtcbiAgICAgICAgY3Vyc29yOiBwb2ludGVyO1xuICAgICAgfVxuICAgICAgLm5neC1maWVsZF9fYXJyYXktZW1wdHkge1xuICAgICAgICBjb2xvcjogIzk0YTNiODtcbiAgICAgICAgZm9udC1zdHlsZTogaXRhbGljO1xuICAgICAgICBmb250LXNpemU6IDEzcHg7XG4gICAgICB9XG4gICAgYCxcbiAgXSxcbn0pXG5leHBvcnQgY2xhc3MgQXJyYXlGaWVsZENvbXBvbmVudCB7XG4gIEBJbnB1dCgpIGZpZWxkITogTmVzdGVkRmllbGRDb25maWc7XG4gIEBJbnB1dCgpIGNvbnRyb2whOiBBYnN0cmFjdENvbnRyb2w7XG4gIEBJbnB1dCgpIGNvbmZpZyE6IEVudGl0eUZvcm1Db25maWc7XG4gIEBJbnB1dCgpIGxhbmd1YWdlOiBzdHJpbmcgPSAnZW4nO1xuICBASW5wdXQoKSByZWFkb25seTogYm9vbGVhbiA9IGZhbHNlO1xuICBASW5wdXQoKSBtYXNrZWQ6IGJvb2xlYW4gPSBmYWxzZTtcbiAgQElucHV0KCkgdXNlclJvbGVzOiBzdHJpbmdbXSA9IFtdO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgZmIgPSBpbmplY3QoRm9ybUJ1aWxkZXIpO1xuICBwcml2YXRlIHJlYWRvbmx5IHZhbGlkYXRvclJlZ2lzdHJ5ID0gaW5qZWN0KFZhbGlkYXRvclJlZ2lzdHJ5U2VydmljZSk7XG5cbiAgZ2V0IGxhYmVsKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIHJlc29sdmVMYWJlbCh0aGlzLmZpZWxkPy5sYWJlbCwgdGhpcy5sYW5ndWFnZSk7XG4gIH1cblxuICBnZXQgZm9ybUFycmF5KCk6IEZvcm1BcnJheSB7XG4gICAgcmV0dXJuIHRoaXMuY29udHJvbCBhcyBGb3JtQXJyYXk7XG4gIH1cblxuICBnZXRJdGVtQ2hpbGRDb250cm9sKGl0ZW1Hcm91cDogQWJzdHJhY3RDb250cm9sLCBjaGlsZElkOiBzdHJpbmcpOiBBYnN0cmFjdENvbnRyb2wge1xuICAgIHJldHVybiAoaXRlbUdyb3VwIGFzIEZvcm1Hcm91cCkuZ2V0KGNoaWxkSWQpIGFzIEFic3RyYWN0Q29udHJvbDtcbiAgfVxuXG4gIGFkZEl0ZW0oKTogdm9pZCB7XG4gICAgY29uc3QgY29udHJvbHM6IFJlY29yZDxzdHJpbmcsIGFueT4gPSB7fTtcbiAgICBmb3IgKGNvbnN0IGNoaWxkIG9mIHRoaXMuZmllbGQuY2hpbGRyZW4gPz8gW10pIHtcbiAgICAgIGNvbnN0IHZhbGlkYXRvcnMgPSB0aGlzLnZhbGlkYXRvclJlZ2lzdHJ5LnJlc29sdmVGcm9tQ29uZmlnKGNoaWxkLnZhbGlkYXRvcnMpO1xuICAgICAgY29udHJvbHNbY2hpbGQuaWRdID0gW2NoaWxkLmRlZmF1bHRWYWx1ZSA/PyBudWxsLCB2YWxpZGF0b3JzXTtcbiAgICB9XG4gICAgY29uc3QgZ3JvdXAgPSB0aGlzLmZiLmdyb3VwKGNvbnRyb2xzKTtcbiAgICB0aGlzLmZvcm1BcnJheS5wdXNoKGdyb3VwKTtcbiAgfVxuXG4gIHJlbW92ZUl0ZW0oaW5kZXg6IG51bWJlcik6IHZvaWQge1xuICAgIHRoaXMuZm9ybUFycmF5LnJlbW92ZUF0KGluZGV4KTtcbiAgfVxufVxuIl19
@@ -0,0 +1,100 @@
1
+ import { Component, Input } from '@angular/core';
2
+ import { ReactiveFormsModule } from '@angular/forms';
3
+ import { resolveLabel } from '@dynamic-entity/core';
4
+ import * as i0 from "@angular/core";
5
+ import * as i1 from "@angular/forms";
6
+ /**
7
+ * Boolean field: a slide-toggle style switch (true/false).
8
+ * Distinct from `checkbox` (which is a standalone bool) in that it renders as a
9
+ * toggle switch with Yes/No labels for clearer binary state communication.
10
+ */
11
+ export class BooleanFieldComponent {
12
+ field;
13
+ control;
14
+ language = 'en';
15
+ readonly = false;
16
+ masked = false;
17
+ get label() {
18
+ return resolveLabel(this.field?.label, this.language);
19
+ }
20
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BooleanFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
21
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: BooleanFieldComponent, isStandalone: true, selector: "ngx-boolean-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
22
+ <div class="ngx-field ngx-field--boolean"
23
+ [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
24
+ @if (masked) {
25
+ <label class="ngx-field__label">{{ label }}</label>
26
+ <span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
27
+ } @else if (readonly) {
28
+ <label class="ngx-field__label">{{ label }}</label>
29
+ <span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ? 'Yes' : 'No' }}</span>
30
+ } @else {
31
+ <div class="ngx-field__toggle-wrap">
32
+ <label class="ngx-field__toggle-label">{{ label }}</label>
33
+ <label class="ngx-field__toggle-switch" [class.ngx-field__toggle-switch--checked]="control.value">
34
+ <input
35
+ class="ngx-field__toggle-input"
36
+ type="checkbox"
37
+ role="switch"
38
+ [formControl]="$any(control)"
39
+ [attr.disabled]="field.disabled ? true : null"
40
+ [attr.aria-checked]="control.value"
41
+ />
42
+ <span class="ngx-field__toggle-track">
43
+ <span class="ngx-field__toggle-thumb"></span>
44
+ </span>
45
+ <span class="ngx-field__toggle-text">{{ control.value ? 'Yes' : 'No' }}</span>
46
+ </label>
47
+ </div>
48
+ }
49
+ </div>
50
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
51
+ }
52
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BooleanFieldComponent, decorators: [{
53
+ type: Component,
54
+ args: [{
55
+ selector: 'ngx-boolean-field',
56
+ standalone: true,
57
+ imports: [ReactiveFormsModule],
58
+ template: `
59
+ <div class="ngx-field ngx-field--boolean"
60
+ [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
61
+ @if (masked) {
62
+ <label class="ngx-field__label">{{ label }}</label>
63
+ <span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
64
+ } @else if (readonly) {
65
+ <label class="ngx-field__label">{{ label }}</label>
66
+ <span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ? 'Yes' : 'No' }}</span>
67
+ } @else {
68
+ <div class="ngx-field__toggle-wrap">
69
+ <label class="ngx-field__toggle-label">{{ label }}</label>
70
+ <label class="ngx-field__toggle-switch" [class.ngx-field__toggle-switch--checked]="control.value">
71
+ <input
72
+ class="ngx-field__toggle-input"
73
+ type="checkbox"
74
+ role="switch"
75
+ [formControl]="$any(control)"
76
+ [attr.disabled]="field.disabled ? true : null"
77
+ [attr.aria-checked]="control.value"
78
+ />
79
+ <span class="ngx-field__toggle-track">
80
+ <span class="ngx-field__toggle-thumb"></span>
81
+ </span>
82
+ <span class="ngx-field__toggle-text">{{ control.value ? 'Yes' : 'No' }}</span>
83
+ </label>
84
+ </div>
85
+ }
86
+ </div>
87
+ `,
88
+ }]
89
+ }], propDecorators: { field: [{
90
+ type: Input
91
+ }], control: [{
92
+ type: Input
93
+ }], language: [{
94
+ type: Input
95
+ }], readonly: [{
96
+ type: Input
97
+ }], masked: [{
98
+ type: Input
99
+ }] } });
100
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYm9vbGVhbi1maWVsZC5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL2ZpZWxkLXR5cGVzL2Jvb2xlYW4tZmllbGQuY29tcG9uZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ2pELE9BQU8sRUFBbUIsbUJBQW1CLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUV0RSxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7OztBQUVwRDs7OztHQUlHO0FBb0NILE1BQU0sT0FBTyxxQkFBcUI7SUFDdkIsS0FBSyxDQUFxQjtJQUMxQixPQUFPLENBQW1CO0lBQzFCLFFBQVEsR0FBVyxJQUFJLENBQUM7SUFDeEIsUUFBUSxHQUFZLEtBQUssQ0FBQztJQUMxQixNQUFNLEdBQVksS0FBSyxDQUFDO0lBRWpDLElBQUksS0FBSztRQUNQLE9BQU8sWUFBWSxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsS0FBSyxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUN4RCxDQUFDO3dHQVRVLHFCQUFxQjs0RkFBckIscUJBQXFCLDJMQS9CdEI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBNkJULDJEQTlCUyxtQkFBbUI7OzRGQWdDbEIscUJBQXFCO2tCQW5DakMsU0FBUzttQkFBQztvQkFDVCxRQUFRLEVBQUUsbUJBQW1CO29CQUM3QixVQUFVLEVBQUUsSUFBSTtvQkFDaEIsT0FBTyxFQUFFLENBQUMsbUJBQW1CLENBQUM7b0JBQzlCLFFBQVEsRUFBRTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E2QlQ7aUJBQ0Y7OEJBRVUsS0FBSztzQkFBYixLQUFLO2dCQUNHLE9BQU87c0JBQWYsS0FBSztnQkFDRyxRQUFRO3NCQUFoQixLQUFLO2dCQUNHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBQ0csTUFBTTtzQkFBZCxLQUFLIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcG9uZW50LCBJbnB1dCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQWJzdHJhY3RDb250cm9sLCBSZWFjdGl2ZUZvcm1zTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvZm9ybXMnO1xuaW1wb3J0IHR5cGUgeyBOZXN0ZWRGaWVsZENvbmZpZyB9IGZyb20gJ0BkeW5hbWljLWVudGl0eS9jb3JlJztcbmltcG9ydCB7IHJlc29sdmVMYWJlbCB9IGZyb20gJ0BkeW5hbWljLWVudGl0eS9jb3JlJztcblxuLyoqXG4gKiBCb29sZWFuIGZpZWxkOiBhIHNsaWRlLXRvZ2dsZSBzdHlsZSBzd2l0Y2ggKHRydWUvZmFsc2UpLlxuICogRGlzdGluY3QgZnJvbSBgY2hlY2tib3hgICh3aGljaCBpcyBhIHN0YW5kYWxvbmUgYm9vbCkgaW4gdGhhdCBpdCByZW5kZXJzIGFzIGFcbiAqIHRvZ2dsZSBzd2l0Y2ggd2l0aCBZZXMvTm8gbGFiZWxzIGZvciBjbGVhcmVyIGJpbmFyeSBzdGF0ZSBjb21tdW5pY2F0aW9uLlxuICovXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICduZ3gtYm9vbGVhbi1maWVsZCcsXG4gIHN0YW5kYWxvbmU6IHRydWUsXG4gIGltcG9ydHM6IFtSZWFjdGl2ZUZvcm1zTW9kdWxlXSxcbiAgdGVtcGxhdGU6IGBcbiAgICA8ZGl2IGNsYXNzPVwibmd4LWZpZWxkIG5neC1maWVsZC0tYm9vbGVhblwiXG4gICAgICBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkXCIgW2F0dHIuZGF0YS1maWVsZC10eXBlXT1cImZpZWxkLnR5cGVcIiBbY2xhc3Mubmd4LWZpZWxkLS1yZWFkb25seV09XCJyZWFkb25seVwiIFtjbGFzcy5uZ3gtZmllbGQtLW1hc2tlZF09XCJtYXNrZWRcIj5cbiAgICAgIEBpZiAobWFza2VkKSB7XG4gICAgICAgIDxsYWJlbCBjbGFzcz1cIm5neC1maWVsZF9fbGFiZWxcIj57eyBsYWJlbCB9fTwvbGFiZWw+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LWZpZWxkX192YWx1ZSBuZ3gtZmllbGRfX3ZhbHVlLS1tYXNrZWRcIiBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkICsgJy1tYXNrZWQnXCI+WFhYWFhYWFhYPC9zcGFuPlxuICAgICAgfSBAZWxzZSBpZiAocmVhZG9ubHkpIHtcbiAgICAgICAgPGxhYmVsIGNsYXNzPVwibmd4LWZpZWxkX19sYWJlbFwiPnt7IGxhYmVsIH19PC9sYWJlbD5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJuZ3gtZmllbGRfX3ZhbHVlXCIgW2F0dHIuZGF0YS10ZXN0aWRdPVwiJ2ZpZWxkLScgKyBmaWVsZC5pZCArICctdmFsdWUnXCI+e3sgY29udHJvbC52YWx1ZSA/ICdZZXMnIDogJ05vJyB9fTwvc3Bhbj5cbiAgICAgIH0gQGVsc2Uge1xuICAgICAgICA8ZGl2IGNsYXNzPVwibmd4LWZpZWxkX190b2dnbGUtd3JhcFwiPlxuICAgICAgICAgIDxsYWJlbCBjbGFzcz1cIm5neC1maWVsZF9fdG9nZ2xlLWxhYmVsXCI+e3sgbGFiZWwgfX08L2xhYmVsPlxuICAgICAgICAgIDxsYWJlbCBjbGFzcz1cIm5neC1maWVsZF9fdG9nZ2xlLXN3aXRjaFwiIFtjbGFzcy5uZ3gtZmllbGRfX3RvZ2dsZS1zd2l0Y2gtLWNoZWNrZWRdPVwiY29udHJvbC52YWx1ZVwiPlxuICAgICAgICAgICAgPGlucHV0XG4gICAgICAgICAgICAgIGNsYXNzPVwibmd4LWZpZWxkX190b2dnbGUtaW5wdXRcIlxuICAgICAgICAgICAgICB0eXBlPVwiY2hlY2tib3hcIlxuICAgICAgICAgICAgICByb2xlPVwic3dpdGNoXCJcbiAgICAgICAgICAgICAgW2Zvcm1Db250cm9sXT1cIiRhbnkoY29udHJvbClcIlxuICAgICAgICAgICAgICBbYXR0ci5kaXNhYmxlZF09XCJmaWVsZC5kaXNhYmxlZCA/IHRydWUgOiBudWxsXCJcbiAgICAgICAgICAgICAgW2F0dHIuYXJpYS1jaGVja2VkXT1cImNvbnRyb2wudmFsdWVcIlxuICAgICAgICAgICAgLz5cbiAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LWZpZWxkX190b2dnbGUtdHJhY2tcIj5cbiAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9XCJuZ3gtZmllbGRfX3RvZ2dsZS10aHVtYlwiPjwvc3Bhbj5cbiAgICAgICAgICAgIDwvc3Bhbj5cbiAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LWZpZWxkX190b2dnbGUtdGV4dFwiPnt7IGNvbnRyb2wudmFsdWUgPyAnWWVzJyA6ICdObycgfX08L3NwYW4+XG4gICAgICAgICAgPC9sYWJlbD5cbiAgICAgICAgPC9kaXY+XG4gICAgICB9XG4gICAgPC9kaXY+XG4gIGAsXG59KVxuZXhwb3J0IGNsYXNzIEJvb2xlYW5GaWVsZENvbXBvbmVudCB7XG4gIEBJbnB1dCgpIGZpZWxkITogTmVzdGVkRmllbGRDb25maWc7XG4gIEBJbnB1dCgpIGNvbnRyb2whOiBBYnN0cmFjdENvbnRyb2w7XG4gIEBJbnB1dCgpIGxhbmd1YWdlOiBzdHJpbmcgPSAnZW4nO1xuICBASW5wdXQoKSByZWFkb25seTogYm9vbGVhbiA9IGZhbHNlO1xuICBASW5wdXQoKSBtYXNrZWQ6IGJvb2xlYW4gPSBmYWxzZTtcblxuICBnZXQgbGFiZWwoKTogc3RyaW5nIHtcbiAgICByZXR1cm4gcmVzb2x2ZUxhYmVsKHRoaXMuZmllbGQ/LmxhYmVsLCB0aGlzLmxhbmd1YWdlKTtcbiAgfVxufVxuIl19
@@ -1,5 +1,6 @@
1
1
  import { Component, Input } from '@angular/core';
2
2
  import { ReactiveFormsModule } from '@angular/forms';
3
+ import { resolveLabel } from '@dynamic-entity/core';
3
4
  import * as i0 from "@angular/core";
4
5
  import * as i1 from "@angular/forms";
5
6
  export class CheckboxFieldComponent {
@@ -8,24 +9,29 @@ export class CheckboxFieldComponent {
8
9
  language = 'en';
9
10
  readonly = false;
10
11
  masked = false;
12
+ get label() {
13
+ return resolveLabel(this.field?.label, this.language);
14
+ }
11
15
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12
16
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: CheckboxFieldComponent, isStandalone: true, selector: "ngx-checkbox-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
13
- <div class="ngx-field ngx-field--checkbox" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
17
+ <div class="ngx-field ngx-field--checkbox"
18
+ [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
14
19
  @if (masked) {
15
- <label class="ngx-field__label">{{ field.label[language] || field.label['en'] }}</label>
16
- <span class="ngx-field__value ngx-field__value--masked">XXXXXXXXX</span>
20
+ <label class="ngx-field__label">{{ label }}</label>
21
+ <span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
17
22
  } @else if (readonly) {
18
- <label class="ngx-field__label">{{ field.label[language] || field.label['en'] }}</label>
19
- <span class="ngx-field__value">{{ control.value ? 'Yes' : 'No' }}</span>
23
+ <label class="ngx-field__label">{{ label }}</label>
24
+ <span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ? 'Yes' : 'No' }}</span>
20
25
  } @else {
21
- <label class="ngx-field__label">
26
+ <label class="ngx-field__label" [attr.for]="field.id">
22
27
  <input
23
- class="ngx-field__input"
28
+ [id]="field.id"
29
+ class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
24
30
  type="checkbox"
25
31
  [formControl]="$any(control)"
26
32
  [attr.disabled]="field.disabled ? true : null"
27
33
  />
28
- {{ field.label[language] || field.label['en'] }}
34
+ {{ label }}
29
35
  </label>
30
36
  }
31
37
  </div>
@@ -38,22 +44,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
38
44
  standalone: true,
39
45
  imports: [ReactiveFormsModule],
40
46
  template: `
41
- <div class="ngx-field ngx-field--checkbox" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
47
+ <div class="ngx-field ngx-field--checkbox"
48
+ [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
42
49
  @if (masked) {
43
- <label class="ngx-field__label">{{ field.label[language] || field.label['en'] }}</label>
44
- <span class="ngx-field__value ngx-field__value--masked">XXXXXXXXX</span>
50
+ <label class="ngx-field__label">{{ label }}</label>
51
+ <span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
45
52
  } @else if (readonly) {
46
- <label class="ngx-field__label">{{ field.label[language] || field.label['en'] }}</label>
47
- <span class="ngx-field__value">{{ control.value ? 'Yes' : 'No' }}</span>
53
+ <label class="ngx-field__label">{{ label }}</label>
54
+ <span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ? 'Yes' : 'No' }}</span>
48
55
  } @else {
49
- <label class="ngx-field__label">
56
+ <label class="ngx-field__label" [attr.for]="field.id">
50
57
  <input
51
- class="ngx-field__input"
58
+ [id]="field.id"
59
+ class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
52
60
  type="checkbox"
53
61
  [formControl]="$any(control)"
54
62
  [attr.disabled]="field.disabled ? true : null"
55
63
  />
56
- {{ field.label[language] || field.label['en'] }}
64
+ {{ label }}
57
65
  </label>
58
66
  }
59
67
  </div>
@@ -70,4 +78,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
70
78
  }], masked: [{
71
79
  type: Input
72
80
  }] } });
73
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2tib3gtZmllbGQuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9maWVsZC10eXBlcy9jaGVja2JveC1maWVsZC5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDakQsT0FBTyxFQUFtQixtQkFBbUIsRUFBRSxNQUFNLGdCQUFnQixDQUFDOzs7QUE2QnRFLE1BQU0sT0FBTyxzQkFBc0I7SUFDeEIsS0FBSyxDQUFlO0lBQ3BCLE9BQU8sQ0FBbUI7SUFDMUIsUUFBUSxHQUFXLElBQUksQ0FBQztJQUN4QixRQUFRLEdBQVksS0FBSyxDQUFDO0lBQzFCLE1BQU0sR0FBWSxLQUFLLENBQUM7d0dBTHRCLHNCQUFzQjs0RkFBdEIsc0JBQXNCLDRMQXRCdkI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBb0JULDJEQXJCUyxtQkFBbUI7OzRGQXVCbEIsc0JBQXNCO2tCQTFCbEMsU0FBUzttQkFBQztvQkFDVCxRQUFRLEVBQUUsb0JBQW9CO29CQUM5QixVQUFVLEVBQUUsSUFBSTtvQkFDaEIsT0FBTyxFQUFFLENBQUMsbUJBQW1CLENBQUM7b0JBQzlCLFFBQVEsRUFBRTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FvQlQ7aUJBQ0Y7OEJBRVUsS0FBSztzQkFBYixLQUFLO2dCQUNHLE9BQU87c0JBQWYsS0FBSztnQkFDRyxRQUFRO3NCQUFoQixLQUFLO2dCQUNHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBQ0csTUFBTTtzQkFBZCxLQUFLIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcG9uZW50LCBJbnB1dCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQWJzdHJhY3RDb250cm9sLCBSZWFjdGl2ZUZvcm1zTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvZm9ybXMnO1xuaW1wb3J0IHR5cGUgeyBGaWVsZENvbmZpZyB9IGZyb20gJ0BkeW5hbWljLWVudGl0eS9jb3JlJztcblxuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiAnbmd4LWNoZWNrYm94LWZpZWxkJyxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW1JlYWN0aXZlRm9ybXNNb2R1bGVdLFxuICB0ZW1wbGF0ZTogYFxuICAgIDxkaXYgY2xhc3M9XCJuZ3gtZmllbGQgbmd4LWZpZWxkLS1jaGVja2JveFwiIFtjbGFzcy5uZ3gtZmllbGQtLXJlYWRvbmx5XT1cInJlYWRvbmx5XCIgW2NsYXNzLm5neC1maWVsZC0tbWFza2VkXT1cIm1hc2tlZFwiPlxuICAgICAgQGlmIChtYXNrZWQpIHtcbiAgICAgICAgPGxhYmVsIGNsYXNzPVwibmd4LWZpZWxkX19sYWJlbFwiPnt7IGZpZWxkLmxhYmVsW2xhbmd1YWdlXSB8fCBmaWVsZC5sYWJlbFsnZW4nXSB9fTwvbGFiZWw+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LWZpZWxkX192YWx1ZSBuZ3gtZmllbGRfX3ZhbHVlLS1tYXNrZWRcIj5YWFhYWFhYWFg8L3NwYW4+XG4gICAgICB9IEBlbHNlIGlmIChyZWFkb25seSkge1xuICAgICAgICA8bGFiZWwgY2xhc3M9XCJuZ3gtZmllbGRfX2xhYmVsXCI+e3sgZmllbGQubGFiZWxbbGFuZ3VhZ2VdIHx8IGZpZWxkLmxhYmVsWydlbiddIH19PC9sYWJlbD5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJuZ3gtZmllbGRfX3ZhbHVlXCI+e3sgY29udHJvbC52YWx1ZSA/ICdZZXMnIDogJ05vJyB9fTwvc3Bhbj5cbiAgICAgIH0gQGVsc2Uge1xuICAgICAgICA8bGFiZWwgY2xhc3M9XCJuZ3gtZmllbGRfX2xhYmVsXCI+XG4gICAgICAgICAgPGlucHV0XG4gICAgICAgICAgICBjbGFzcz1cIm5neC1maWVsZF9faW5wdXRcIlxuICAgICAgICAgICAgdHlwZT1cImNoZWNrYm94XCJcbiAgICAgICAgICAgIFtmb3JtQ29udHJvbF09XCIkYW55KGNvbnRyb2wpXCJcbiAgICAgICAgICAgIFthdHRyLmRpc2FibGVkXT1cImZpZWxkLmRpc2FibGVkID8gdHJ1ZSA6IG51bGxcIlxuICAgICAgICAgIC8+XG4gICAgICAgICAge3sgZmllbGQubGFiZWxbbGFuZ3VhZ2VdIHx8IGZpZWxkLmxhYmVsWydlbiddIH19XG4gICAgICAgIDwvbGFiZWw+XG4gICAgICB9XG4gICAgPC9kaXY+XG4gIGAsXG59KVxuZXhwb3J0IGNsYXNzIENoZWNrYm94RmllbGRDb21wb25lbnQge1xuICBASW5wdXQoKSBmaWVsZCE6IEZpZWxkQ29uZmlnO1xuICBASW5wdXQoKSBjb250cm9sITogQWJzdHJhY3RDb250cm9sO1xuICBASW5wdXQoKSBsYW5ndWFnZTogc3RyaW5nID0gJ2VuJztcbiAgQElucHV0KCkgcmVhZG9ubHk6IGJvb2xlYW4gPSBmYWxzZTtcbiAgQElucHV0KCkgbWFza2VkOiBib29sZWFuID0gZmFsc2U7XG59XG4iXX0=
81
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2tib3gtZmllbGQuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9maWVsZC10eXBlcy9jaGVja2JveC1maWVsZC5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDakQsT0FBTyxFQUFtQixtQkFBbUIsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBRXRFLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQzs7O0FBOEJwRCxNQUFNLE9BQU8sc0JBQXNCO0lBQ3hCLEtBQUssQ0FBcUI7SUFDMUIsT0FBTyxDQUFtQjtJQUMxQixRQUFRLEdBQVcsSUFBSSxDQUFDO0lBQ3hCLFFBQVEsR0FBWSxLQUFLLENBQUM7SUFDMUIsTUFBTSxHQUFZLEtBQUssQ0FBQztJQUVqQyxJQUFJLEtBQUs7UUFDUCxPQUFPLFlBQVksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDeEQsQ0FBQzt3R0FUVSxzQkFBc0I7NEZBQXRCLHNCQUFzQiw0TEF4QnZCOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBc0JULDJEQXZCUyxtQkFBbUI7OzRGQXlCbEIsc0JBQXNCO2tCQTVCbEMsU0FBUzttQkFBQztvQkFDVCxRQUFRLEVBQUUsb0JBQW9CO29CQUM5QixVQUFVLEVBQUUsSUFBSTtvQkFDaEIsT0FBTyxFQUFFLENBQUMsbUJBQW1CLENBQUM7b0JBQzlCLFFBQVEsRUFBRTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXNCVDtpQkFDRjs4QkFFVSxLQUFLO3NCQUFiLEtBQUs7Z0JBQ0csT0FBTztzQkFBZixLQUFLO2dCQUNHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBQ0csUUFBUTtzQkFBaEIsS0FBSztnQkFDRyxNQUFNO3NCQUFkLEtBQUsiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb25lbnQsIElucHV0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBBYnN0cmFjdENvbnRyb2wsIFJlYWN0aXZlRm9ybXNNb2R1bGUgfSBmcm9tICdAYW5ndWxhci9mb3Jtcyc7XG5pbXBvcnQgdHlwZSB7IE5lc3RlZEZpZWxkQ29uZmlnIH0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuaW1wb3J0IHsgcmVzb2x2ZUxhYmVsIH0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICduZ3gtY2hlY2tib3gtZmllbGQnLFxuICBzdGFuZGFsb25lOiB0cnVlLFxuICBpbXBvcnRzOiBbUmVhY3RpdmVGb3Jtc01vZHVsZV0sXG4gIHRlbXBsYXRlOiBgXG4gICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZCBuZ3gtZmllbGQtLWNoZWNrYm94XCJcbiAgICAgIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWRcIiBbYXR0ci5kYXRhLWZpZWxkLXR5cGVdPVwiZmllbGQudHlwZVwiIFtjbGFzcy5uZ3gtZmllbGQtLXJlYWRvbmx5XT1cInJlYWRvbmx5XCIgW2NsYXNzLm5neC1maWVsZC0tbWFza2VkXT1cIm1hc2tlZFwiPlxuICAgICAgQGlmIChtYXNrZWQpIHtcbiAgICAgICAgPGxhYmVsIGNsYXNzPVwibmd4LWZpZWxkX19sYWJlbFwiPnt7IGxhYmVsIH19PC9sYWJlbD5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJuZ3gtZmllbGRfX3ZhbHVlIG5neC1maWVsZF9fdmFsdWUtLW1hc2tlZFwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLW1hc2tlZCdcIj5YWFhYWFhYWFg8L3NwYW4+XG4gICAgICB9IEBlbHNlIGlmIChyZWFkb25seSkge1xuICAgICAgICA8bGFiZWwgY2xhc3M9XCJuZ3gtZmllbGRfX2xhYmVsXCI+e3sgbGFiZWwgfX08L2xhYmVsPlxuICAgICAgICA8c3BhbiBjbGFzcz1cIm5neC1maWVsZF9fdmFsdWVcIiBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkICsgJy12YWx1ZSdcIj57eyBjb250cm9sLnZhbHVlID8gJ1llcycgOiAnTm8nIH19PC9zcGFuPlxuICAgICAgfSBAZWxzZSB7XG4gICAgICAgIDxsYWJlbCBjbGFzcz1cIm5neC1maWVsZF9fbGFiZWxcIiBbYXR0ci5mb3JdPVwiZmllbGQuaWRcIj5cbiAgICAgICAgICA8aW5wdXRcbiAgICAgICAgICAgIFtpZF09XCJmaWVsZC5pZFwiXG4gICAgICAgICAgICBjbGFzcz1cIm5neC1maWVsZF9faW5wdXRcIiBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkICsgJy1pbnB1dCdcIlxuICAgICAgICAgICAgdHlwZT1cImNoZWNrYm94XCJcbiAgICAgICAgICAgIFtmb3JtQ29udHJvbF09XCIkYW55KGNvbnRyb2wpXCJcbiAgICAgICAgICAgIFthdHRyLmRpc2FibGVkXT1cImZpZWxkLmRpc2FibGVkID8gdHJ1ZSA6IG51bGxcIlxuICAgICAgICAgIC8+XG4gICAgICAgICAge3sgbGFiZWwgfX1cbiAgICAgICAgPC9sYWJlbD5cbiAgICAgIH1cbiAgICA8L2Rpdj5cbiAgYCxcbn0pXG5leHBvcnQgY2xhc3MgQ2hlY2tib3hGaWVsZENvbXBvbmVudCB7XG4gIEBJbnB1dCgpIGZpZWxkITogTmVzdGVkRmllbGRDb25maWc7XG4gIEBJbnB1dCgpIGNvbnRyb2whOiBBYnN0cmFjdENvbnRyb2w7XG4gIEBJbnB1dCgpIGxhbmd1YWdlOiBzdHJpbmcgPSAnZW4nO1xuICBASW5wdXQoKSByZWFkb25seTogYm9vbGVhbiA9IGZhbHNlO1xuICBASW5wdXQoKSBtYXNrZWQ6IGJvb2xlYW4gPSBmYWxzZTtcblxuICBnZXQgbGFiZWwoKTogc3RyaW5nIHtcbiAgICByZXR1cm4gcmVzb2x2ZUxhYmVsKHRoaXMuZmllbGQ/LmxhYmVsLCB0aGlzLmxhbmd1YWdlKTtcbiAgfVxufVxuIl19
@@ -0,0 +1,101 @@
1
+ import { Component, Input } from '@angular/core';
2
+ import { ReactiveFormsModule } from '@angular/forms';
3
+ import { resolveLabel } from '@dynamic-entity/core';
4
+ import * as i0 from "@angular/core";
5
+ import * as i1 from "@angular/forms";
6
+ /** Currency field: number input with locale-aware currency symbol prefix. */
7
+ export class CurrencyFieldComponent {
8
+ field;
9
+ control;
10
+ language = 'en';
11
+ readonly = false;
12
+ masked = false;
13
+ get label() {
14
+ return resolveLabel(this.field?.label, this.language);
15
+ }
16
+ get placeholder() {
17
+ return resolveLabel(this.field?.placeholder, this.language);
18
+ }
19
+ /** Picks the currency symbol from the locale — defaults to '$'. */
20
+ get symbol() {
21
+ try {
22
+ return (0).toLocaleString(this.language, { style: 'currency', currency: 'USD', minimumFractionDigits: 0 })
23
+ .replace(/[\d,.\s]/g, '').trim() || '$';
24
+ }
25
+ catch {
26
+ return '$';
27
+ }
28
+ }
29
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CurrencyFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
30
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: CurrencyFieldComponent, isStandalone: true, selector: "ngx-currency-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
31
+ <div class="ngx-field ngx-field--currency"
32
+ [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
33
+ <label class="ngx-field__label">{{ label }}</label>
34
+ @if (masked) {
35
+ <span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
36
+ } @else if (readonly) {
37
+ <span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ symbol }}{{ control.value }}</span>
38
+ } @else {
39
+ <div class="ngx-field__currency-wrap">
40
+ <span class="ngx-field__currency-symbol">{{ symbol }}</span>
41
+ <input
42
+ class="ngx-field__input ngx-field__input--currency" [attr.data-testid]="'field-' + field.id + '-input'"
43
+ type="number"
44
+ step="0.01"
45
+ [formControl]="$any(control)"
46
+ [placeholder]="placeholder || '0.00'"
47
+ [attr.disabled]="field.disabled ? true : null"
48
+ />
49
+ </div>
50
+ @if (control.invalid && control.touched) {
51
+ <span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
52
+ }
53
+ }
54
+ </div>
55
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
56
+ }
57
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CurrencyFieldComponent, decorators: [{
58
+ type: Component,
59
+ args: [{
60
+ selector: 'ngx-currency-field',
61
+ standalone: true,
62
+ imports: [ReactiveFormsModule],
63
+ template: `
64
+ <div class="ngx-field ngx-field--currency"
65
+ [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
66
+ <label class="ngx-field__label">{{ label }}</label>
67
+ @if (masked) {
68
+ <span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
69
+ } @else if (readonly) {
70
+ <span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ symbol }}{{ control.value }}</span>
71
+ } @else {
72
+ <div class="ngx-field__currency-wrap">
73
+ <span class="ngx-field__currency-symbol">{{ symbol }}</span>
74
+ <input
75
+ class="ngx-field__input ngx-field__input--currency" [attr.data-testid]="'field-' + field.id + '-input'"
76
+ type="number"
77
+ step="0.01"
78
+ [formControl]="$any(control)"
79
+ [placeholder]="placeholder || '0.00'"
80
+ [attr.disabled]="field.disabled ? true : null"
81
+ />
82
+ </div>
83
+ @if (control.invalid && control.touched) {
84
+ <span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
85
+ }
86
+ }
87
+ </div>
88
+ `,
89
+ }]
90
+ }], propDecorators: { field: [{
91
+ type: Input
92
+ }], control: [{
93
+ type: Input
94
+ }], language: [{
95
+ type: Input
96
+ }], readonly: [{
97
+ type: Input
98
+ }], masked: [{
99
+ type: Input
100
+ }] } });
101
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3VycmVuY3ktZmllbGQuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9maWVsZC10eXBlcy9jdXJyZW5jeS1maWVsZC5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDakQsT0FBTyxFQUFtQixtQkFBbUIsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBRXRFLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQzs7O0FBRXBELDZFQUE2RTtBQWdDN0UsTUFBTSxPQUFPLHNCQUFzQjtJQUN4QixLQUFLLENBQXFCO0lBQzFCLE9BQU8sQ0FBbUI7SUFDMUIsUUFBUSxHQUFXLElBQUksQ0FBQztJQUN4QixRQUFRLEdBQVksS0FBSyxDQUFDO0lBQzFCLE1BQU0sR0FBWSxLQUFLLENBQUM7SUFFakMsSUFBSSxLQUFLO1FBQ1AsT0FBTyxZQUFZLENBQUMsSUFBSSxDQUFDLEtBQUssRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3hELENBQUM7SUFFRCxJQUFJLFdBQVc7UUFDYixPQUFPLFlBQVksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLFdBQVcsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDOUQsQ0FBQztJQUVELG1FQUFtRTtJQUNuRSxJQUFJLE1BQU07UUFDUixJQUFJLENBQUM7WUFDSCxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUscUJBQXFCLEVBQUUsQ0FBQyxFQUFFLENBQUM7aUJBQ3ZHLE9BQU8sQ0FBQyxXQUFXLEVBQUUsRUFBRSxDQUFDLENBQUMsSUFBSSxFQUFFLElBQUksR0FBRyxDQUFDO1FBQzVDLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxPQUFPLEdBQUcsQ0FBQztRQUNiLENBQUM7SUFDSCxDQUFDO3dHQXZCVSxzQkFBc0I7NEZBQXRCLHNCQUFzQiw0TEEzQnZCOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBeUJULDJEQTFCUyxtQkFBbUI7OzRGQTRCbEIsc0JBQXNCO2tCQS9CbEMsU0FBUzttQkFBQztvQkFDVCxRQUFRLEVBQUUsb0JBQW9CO29CQUM5QixVQUFVLEVBQUUsSUFBSTtvQkFDaEIsT0FBTyxFQUFFLENBQUMsbUJBQW1CLENBQUM7b0JBQzlCLFFBQVEsRUFBRTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXlCVDtpQkFDRjs4QkFFVSxLQUFLO3NCQUFiLEtBQUs7Z0JBQ0csT0FBTztzQkFBZixLQUFLO2dCQUNHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBQ0csUUFBUTtzQkFBaEIsS0FBSztnQkFDRyxNQUFNO3NCQUFkLEtBQUsiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb25lbnQsIElucHV0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBBYnN0cmFjdENvbnRyb2wsIFJlYWN0aXZlRm9ybXNNb2R1bGUgfSBmcm9tICdAYW5ndWxhci9mb3Jtcyc7XG5pbXBvcnQgdHlwZSB7IE5lc3RlZEZpZWxkQ29uZmlnIH0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuaW1wb3J0IHsgcmVzb2x2ZUxhYmVsIH0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuXG4vKiogQ3VycmVuY3kgZmllbGQ6IG51bWJlciBpbnB1dCB3aXRoIGxvY2FsZS1hd2FyZSBjdXJyZW5jeSBzeW1ib2wgcHJlZml4LiAqL1xuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiAnbmd4LWN1cnJlbmN5LWZpZWxkJyxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW1JlYWN0aXZlRm9ybXNNb2R1bGVdLFxuICB0ZW1wbGF0ZTogYFxuICAgIDxkaXYgY2xhc3M9XCJuZ3gtZmllbGQgbmd4LWZpZWxkLS1jdXJyZW5jeVwiXG4gICAgICBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkXCIgW2F0dHIuZGF0YS1maWVsZC10eXBlXT1cImZpZWxkLnR5cGVcIiBbY2xhc3Mubmd4LWZpZWxkLS1yZWFkb25seV09XCJyZWFkb25seVwiIFtjbGFzcy5uZ3gtZmllbGQtLW1hc2tlZF09XCJtYXNrZWRcIj5cbiAgICAgIDxsYWJlbCBjbGFzcz1cIm5neC1maWVsZF9fbGFiZWxcIj57eyBsYWJlbCB9fTwvbGFiZWw+XG4gICAgICBAaWYgKG1hc2tlZCkge1xuICAgICAgICA8c3BhbiBjbGFzcz1cIm5neC1maWVsZF9fdmFsdWUgbmd4LWZpZWxkX192YWx1ZS0tbWFza2VkXCIgW2F0dHIuZGF0YS10ZXN0aWRdPVwiJ2ZpZWxkLScgKyBmaWVsZC5pZCArICctbWFza2VkJ1wiPlhYWFhYWFhYWDwvc3Bhbj5cbiAgICAgIH0gQGVsc2UgaWYgKHJlYWRvbmx5KSB7XG4gICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LWZpZWxkX192YWx1ZVwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLXZhbHVlJ1wiPnt7IHN5bWJvbCB9fXt7IGNvbnRyb2wudmFsdWUgfX08L3NwYW4+XG4gICAgICB9IEBlbHNlIHtcbiAgICAgICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZF9fY3VycmVuY3ktd3JhcFwiPlxuICAgICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LWZpZWxkX19jdXJyZW5jeS1zeW1ib2xcIj57eyBzeW1ib2wgfX08L3NwYW4+XG4gICAgICAgICAgPGlucHV0XG4gICAgICAgICAgICBjbGFzcz1cIm5neC1maWVsZF9faW5wdXQgbmd4LWZpZWxkX19pbnB1dC0tY3VycmVuY3lcIiBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkICsgJy1pbnB1dCdcIlxuICAgICAgICAgICAgdHlwZT1cIm51bWJlclwiXG4gICAgICAgICAgICBzdGVwPVwiMC4wMVwiXG4gICAgICAgICAgICBbZm9ybUNvbnRyb2xdPVwiJGFueShjb250cm9sKVwiXG4gICAgICAgICAgICBbcGxhY2Vob2xkZXJdPVwicGxhY2Vob2xkZXIgfHwgJzAuMDAnXCJcbiAgICAgICAgICAgIFthdHRyLmRpc2FibGVkXT1cImZpZWxkLmRpc2FibGVkID8gdHJ1ZSA6IG51bGxcIlxuICAgICAgICAgIC8+XG4gICAgICAgIDwvZGl2PlxuICAgICAgICBAaWYgKGNvbnRyb2wuaW52YWxpZCAmJiBjb250cm9sLnRvdWNoZWQpIHtcbiAgICAgICAgICA8c3BhbiBjbGFzcz1cIm5neC1maWVsZF9fZXJyb3JcIiBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkICsgJy1lcnJvcidcIj5UaGlzIGZpZWxkIGhhcyBhbiBlcnJvcjwvc3Bhbj5cbiAgICAgICAgfVxuICAgICAgfVxuICAgIDwvZGl2PlxuICBgLFxufSlcbmV4cG9ydCBjbGFzcyBDdXJyZW5jeUZpZWxkQ29tcG9uZW50IHtcbiAgQElucHV0KCkgZmllbGQhOiBOZXN0ZWRGaWVsZENvbmZpZztcbiAgQElucHV0KCkgY29udHJvbCE6IEFic3RyYWN0Q29udHJvbDtcbiAgQElucHV0KCkgbGFuZ3VhZ2U6IHN0cmluZyA9ICdlbic7XG4gIEBJbnB1dCgpIHJlYWRvbmx5OiBib29sZWFuID0gZmFsc2U7XG4gIEBJbnB1dCgpIG1hc2tlZDogYm9vbGVhbiA9IGZhbHNlO1xuXG4gIGdldCBsYWJlbCgpOiBzdHJpbmcge1xuICAgIHJldHVybiByZXNvbHZlTGFiZWwodGhpcy5maWVsZD8ubGFiZWwsIHRoaXMubGFuZ3VhZ2UpO1xuICB9XG5cbiAgZ2V0IHBsYWNlaG9sZGVyKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIHJlc29sdmVMYWJlbCh0aGlzLmZpZWxkPy5wbGFjZWhvbGRlciwgdGhpcy5sYW5ndWFnZSk7XG4gIH1cblxuICAvKiogUGlja3MgdGhlIGN1cnJlbmN5IHN5bWJvbCBmcm9tIHRoZSBsb2NhbGUg4oCUIGRlZmF1bHRzIHRvICckJy4gKi9cbiAgZ2V0IHN5bWJvbCgpOiBzdHJpbmcge1xuICAgIHRyeSB7XG4gICAgICByZXR1cm4gKDApLnRvTG9jYWxlU3RyaW5nKHRoaXMubGFuZ3VhZ2UsIHsgc3R5bGU6ICdjdXJyZW5jeScsIGN1cnJlbmN5OiAnVVNEJywgbWluaW11bUZyYWN0aW9uRGlnaXRzOiAwIH0pXG4gICAgICAgIC5yZXBsYWNlKC9bXFxkLC5cXHNdL2csICcnKS50cmltKCkgfHwgJyQnO1xuICAgIH0gY2F0Y2gge1xuICAgICAgcmV0dXJuICckJztcbiAgICB9XG4gIH1cbn1cbiJdfQ==