ngx-dynamic-entity 1.0.0 → 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 (47) hide show
  1. package/README.md +129 -139
  2. package/esm2022/lib/field-types/array-field.component.mjs +101 -101
  3. package/esm2022/lib/field-types/boolean-field.component.mjs +59 -59
  4. package/esm2022/lib/field-types/checkbox-field.component.mjs +45 -45
  5. package/esm2022/lib/field-types/currency-field.component.mjs +51 -51
  6. package/esm2022/lib/field-types/date-field.component.mjs +43 -43
  7. package/esm2022/lib/field-types/dropdown-field.component.mjs +81 -81
  8. package/esm2022/lib/field-types/email-field.component.mjs +61 -61
  9. package/esm2022/lib/field-types/entity-ref-field.component.mjs +67 -67
  10. package/esm2022/lib/field-types/file-field.component.mjs +85 -85
  11. package/esm2022/lib/field-types/group-field.component.mjs +47 -47
  12. package/esm2022/lib/field-types/image-field.component.mjs +89 -89
  13. package/esm2022/lib/field-types/month-year-field.component.mjs +77 -77
  14. package/esm2022/lib/field-types/multi-select-field.component.mjs +63 -63
  15. package/esm2022/lib/field-types/number-field.component.mjs +65 -65
  16. package/esm2022/lib/field-types/password-field.component.mjs +65 -65
  17. package/esm2022/lib/field-types/radio-field.component.mjs +63 -63
  18. package/esm2022/lib/field-types/text-field.component.mjs +59 -59
  19. package/esm2022/lib/field-types/textarea-field.component.mjs +45 -45
  20. package/esm2022/lib/form/dynamic-field/dynamic-field.component.mjs +1 -1
  21. package/esm2022/lib/form/dynamic-form.component.mjs +183 -10
  22. package/esm2022/lib/form/dynamic-record-form.component.mjs +36 -11
  23. package/esm2022/lib/providers/provide-field-types.mjs +1 -1
  24. package/esm2022/lib/providers/provide-ngx-dynamic-entity.mjs +3 -2
  25. package/esm2022/lib/services/cascade-data.service.mjs +1 -1
  26. package/esm2022/lib/services/entity-ref-cache.mjs +1 -1
  27. package/esm2022/lib/services/entity-ref-registry.service.mjs +1 -1
  28. package/esm2022/lib/services/entity-ref-selection.service.mjs +1 -1
  29. package/esm2022/lib/services/entity-reference.service.mjs +1 -1
  30. package/esm2022/lib/services/field-registry.service.mjs +1 -1
  31. package/esm2022/lib/services/file-upload.service.mjs +1 -1
  32. package/esm2022/lib/services/rbac.service.mjs +1 -1
  33. package/esm2022/lib/services/validator-registry.service.mjs +12 -1
  34. package/esm2022/lib/tokens/injection-tokens.mjs +17 -1
  35. package/esm2022/public-api.mjs +18 -2
  36. package/fesm2022/ngx-dynamic-entity.mjs +1392 -1166
  37. package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
  38. package/lib/field-types/dropdown-field.component.d.ts +1 -1
  39. package/lib/field-types/entity-ref-field.component.d.ts +1 -1
  40. package/lib/field-types/multi-select-field.component.d.ts +1 -1
  41. package/lib/field-types/radio-field.component.d.ts +1 -1
  42. package/lib/form/dynamic-form.component.d.ts +93 -6
  43. package/lib/form/dynamic-record-form.component.d.ts +13 -2
  44. package/lib/providers/provide-ngx-dynamic-entity.d.ts +8 -2
  45. package/lib/tokens/injection-tokens.d.ts +24 -2
  46. package/package.json +17 -18
  47. package/public-api.d.ts +10 -0
package/README.md CHANGED
@@ -1,139 +1,129 @@
1
- # ngx-dynamic-entity
2
-
3
- Angular dynamic form renderer and tabbed record editor, powered by `@dynamic-entity/core`.
4
-
5
- ## Features
6
- - **DynamicFormComponent** — rich tabbed forms from `EntityFormConfig`, built on signals and reactive forms.
7
- - **DynamicRecordFormComponent** — record editor with summary drawer (`showOnMinimize`), profile header, and session-baseline tracking.
8
- - **Rules engine** — condition evaluation, info/warning/error banners, field and tab hiding.
9
- - **Entity references with cascades** — consumer-registered loaders, parent→child filtering, `autoPatch`.
10
- - **19 field type keys** across 18 components — including date, month-year, entity-ref, file, and image.
11
-
12
- ## Installation
13
- ```bash
14
- npm install ngx-dynamic-entity @dynamic-entity/core
15
- ```
16
-
17
- ## Setup
18
-
19
- Two providers: one for services and registries, one for field components.
20
-
21
- ```ts
22
- import {
23
- provideNgxDynamicEntity,
24
- provideBuiltInFieldTypes,
25
- } from 'ngx-dynamic-entity';
26
-
27
- export const appConfig: ApplicationConfig = {
28
- providers: [
29
- provideNgxDynamicEntity({
30
- maskedRoles: ['IT_SUPPORT'],
31
- entityRefs: { countries: ctx => api.listCountries(ctx) },
32
- }),
33
- provideBuiltInFieldTypes(),
34
- ],
35
- };
36
- ```
37
-
38
- ### Field components are opt-in (tree-shaking)
39
-
40
- `FieldRegistryService` imports no components. Only what you register is bundled — so an app
41
- that renders three field types does not ship nineteen.
42
-
43
- ```ts
44
- // Everything (simplest):
45
- provideBuiltInFieldTypes()
46
-
47
- // Or only what this app renders:
48
- provideFieldTypes({
49
- text: TextFieldComponent,
50
- number: NumberFieldComponent,
51
- dropdown: DropdownFieldComponent,
52
- })
53
- ```
54
-
55
- `provideFieldTypes` is a multi-provider: several calls compose, and a later set wins on a key
56
- collision. `provideNgxDynamicEntity({ fieldTypes })` still overrides everything — use it to
57
- swap a built-in for your own component. An unregistered type renders nothing and logs a
58
- console warning in dev builds.
59
-
60
- ## Rendering
61
-
62
- ```html
63
- <ngx-dynamic-form
64
- [config]="formConfig"
65
- [rules]="formRules"
66
- [initialData]="initialRecord"
67
- [originalData]="sessionBaseline"
68
- (formSubmit)="onSave($event)"
69
- />
70
- ```
71
-
72
- ## Entity references and cascades
73
-
74
- Register a loader per entity key. It receives `{ parentValue, filters, lang }` and may return
75
- an array, a Promise, or an Observable of `ReferenceOption[]`:
76
-
77
- ```ts
78
- provideNgxDynamicEntity({
79
- entityRefs: {
80
- countries: () => http.get<ReferenceOption[]>('/api/countries'),
81
- cities: ctx => http.get<ReferenceOption[]>('/api/cities', { params: { country: ctx?.parentValue } }),
82
- },
83
- });
84
- ```
85
-
86
- A field cascades when its `entityReference.parentField` names a sibling. The child reloads on
87
- every parent change, drops a now-invalid selection, and shows nothing until the parent has a
88
- value. Filtering happens server-side (via `parentValue`) or client-side:
89
-
90
- - `lookupFilter` — keep options whose record matches the parent value at that dot-path.
91
- - `lookupPath` — take options from a nested array on the selected parent's record.
92
-
93
- Raw records are labelled through `entityReference.displayFields`; the full record rides along on
94
- `ReferenceOption.record`, which is what `autoPatch` copies from.
95
-
96
- ## autoPatch and patchOnTrue
97
-
98
- ```jsonc
99
- // Selecting a company fills in its address on the "address" tab
100
- { "id": "company", "type": "entity-ref",
101
- "autoPatch": { "targetTab": "address", "mappings": [{ "source": "city", "target": "city" }] } }
102
-
103
- // Ticking "same as home" copies one field into another
104
- { "id": "billingSame", "type": "boolean", "patchOnTrue": [{ "from": "home", "to": "billing" }] }
105
- ```
106
-
107
- `patchOnTrue` fires on the false→true transition only, so a later manual edit is not clobbered.
108
-
109
- ## criticalField
110
-
111
- A field marked `criticalField` renders read-only behind a lock toggle. Once unlocked and
112
- changed, the form shows a deferred notice naming the field — compared against the session
113
- baseline (`originalData`, or the values captured at first build). The same baseline feeds
114
- `VALUE_CHANGED` rules.
115
-
116
- ## File and image fields
117
-
118
- Both store a `FileRef`. Register an upload handler to persist on select:
119
-
120
- ```ts
121
- { provide: UPLOAD_HANDLER, useValue: (file: File) => http.post<{url: string}>('/upload', file) }
122
- ```
123
-
124
- With a handler the value is `{ url, name, size, mimeType }`; without one it is
125
- `{ file, name, size, mimeType }` for you to upload at submit time. Image previews use object
126
- URLs for unpersisted files and revoke them automatically.
127
-
128
- ## Injection tokens
129
-
130
- | Token | Purpose |
131
- |---|---|
132
- | `MASKED_ROLES` | Roles that see `XXXXXXXXX` for masked fields |
133
- | `FIELD_TYPE_REGISTRY` | Consumer overrides: field type → component (highest priority) |
134
- | `FIELD_TYPE_SETS` | Multi: field-type sets contributed by `provideFieldTypes()` |
135
- | `ENTITY_REF_REGISTRY` | Entity key → `EntityReferenceLoader` |
136
- | `VALIDATOR_REGISTRY` | Validator name → `ValidatorFn` |
137
- | `HOOK_REGISTRY` | Hook key → handler (e.g. `entity:beforeSave`) |
138
- | `COMMON_MODULES_REGISTRY` | Consumer-provided shared tab modules |
139
- | `UPLOAD_HANDLER` | `(file) => UploadResult \| Promise \| Observable` |
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.
@@ -37,110 +37,110 @@ export class ArrayFieldComponent {
37
37
  this.formArray.removeAt(index);
38
38
  }
39
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>
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
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
91
  }
92
92
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ArrayFieldComponent, decorators: [{
93
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>
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
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
145
  }], propDecorators: { field: [{
146
146
  type: Input
@@ -157,4 +157,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
157
157
  }], userRoles: [{
158
158
  type: Input
159
159
  }] } });
160
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXJyYXktZmllbGQuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9maWVsZC10eXBlcy9hcnJheS1maWVsZC5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUNyRSxPQUFPLEVBQThCLFdBQVcsRUFBYSxtQkFBbUIsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBRXpHLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNwRCxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSwrQ0FBK0MsQ0FBQztBQUN0RixPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSx3Q0FBd0MsQ0FBQzs7QUFFbEYscUdBQXFHO0FBNEhyRyxNQUFNLE9BQU8sbUJBQW1CO0lBQ3JCLEtBQUssQ0FBcUI7SUFDMUIsT0FBTyxDQUFtQjtJQUMxQixNQUFNLENBQW9CO0lBQzFCLFFBQVEsR0FBVyxJQUFJLENBQUM7SUFDeEIsUUFBUSxHQUFZLEtBQUssQ0FBQztJQUMxQixNQUFNLEdBQVksS0FBSyxDQUFDO0lBQ3hCLFNBQVMsR0FBYSxFQUFFLENBQUM7SUFFakIsRUFBRSxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUN6QixpQkFBaUIsR0FBRyxNQUFNLENBQUMsd0JBQXdCLENBQUMsQ0FBQztJQUV0RSxJQUFJLEtBQUs7UUFDUCxPQUFPLFlBQVksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDeEQsQ0FBQztJQUVELElBQUksU0FBUztRQUNYLE9BQU8sSUFBSSxDQUFDLE9BQW9CLENBQUM7SUFDbkMsQ0FBQztJQUVELG1CQUFtQixDQUFDLFNBQTBCLEVBQUUsT0FBZTtRQUM3RCxPQUFRLFNBQXVCLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBb0IsQ0FBQztJQUNsRSxDQUFDO0lBRUQsT0FBTztRQUNMLE1BQU0sUUFBUSxHQUF3QixFQUFFLENBQUM7UUFDekMsS0FBSyxNQUFNLEtBQUssSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLFFBQVEsSUFBSSxFQUFFLEVBQUUsQ0FBQztZQUM5QyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsaUJBQWlCLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1lBQzlFLFFBQVEsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsWUFBWSxJQUFJLElBQUksRUFBRSxVQUFVLENBQUMsQ0FBQztRQUNoRSxDQUFDO1FBQ0QsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDdEMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDN0IsQ0FBQztJQUVELFVBQVUsQ0FBQyxLQUFhO1FBQ3RCLElBQUksQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ2pDLENBQUM7d0dBcENVLG1CQUFtQjs0RkFBbkIsbUJBQW1CLG1PQXZIcEI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0RULDZrQ0FuRFMsbUJBQW1CLG9EQUFtQixxQkFBcUI7OzRGQXdIMUQsbUJBQW1CO2tCQTNIL0IsU0FBUzsrQkFDRSxpQkFBaUIsY0FDZixJQUFJLFdBQ1AsQ0FBQyxtQkFBbUIsRUFBRSxVQUFVLENBQUMsR0FBRyxFQUFFLENBQUMscUJBQXFCLENBQUMsQ0FBQyxZQUM3RDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FrRFQ7OEJBc0VRLEtBQUs7c0JBQWIsS0FBSztnQkFDRyxPQUFPO3NCQUFmLEtBQUs7Z0JBQ0csTUFBTTtzQkFBZCxLQUFLO2dCQUNHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBQ0csUUFBUTtzQkFBaEIsS0FBSztnQkFDRyxNQUFNO3NCQUFkLEtBQUs7Z0JBQ0csU0FBUztzQkFBakIsS0FBSyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbXBvbmVudCwgSW5wdXQsIGluamVjdCwgZm9yd2FyZFJlZiB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xyXG5pbXBvcnQgeyBBYnN0cmFjdENvbnRyb2wsIEZvcm1BcnJheSwgRm9ybUJ1aWxkZXIsIEZvcm1Hcm91cCwgUmVhY3RpdmVGb3Jtc01vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL2Zvcm1zJztcclxuaW1wb3J0IHR5cGUgeyBOZXN0ZWRGaWVsZENvbmZpZywgRW50aXR5Rm9ybUNvbmZpZyB9IGZyb20gJ0BkeW5hbWljLWVudGl0eS9jb3JlJztcclxuaW1wb3J0IHsgcmVzb2x2ZUxhYmVsIH0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xyXG5pbXBvcnQgeyBEeW5hbWljRmllbGRDb21wb25lbnQgfSBmcm9tICcuLi9mb3JtL2R5bmFtaWMtZmllbGQvZHluYW1pYy1maWVsZC5jb21wb25lbnQnO1xyXG5pbXBvcnQgeyBWYWxpZGF0b3JSZWdpc3RyeVNlcnZpY2UgfSBmcm9tICcuLi9zZXJ2aWNlcy92YWxpZGF0b3ItcmVnaXN0cnkuc2VydmljZSc7XHJcblxyXG4vKiogQURSLTAwODogQXJyYXlGaWVsZENvbXBvbmVudCDigJQgcmVuZGVycyByZXBlYXRpbmcgcm93cyBmb3IgRm9ybUFycmF5IHdpdGggKyBBZGQgSXRlbSBhbmQgUmVtb3ZlICovXHJcbkBDb21wb25lbnQoe1xyXG4gIHNlbGVjdG9yOiAnbmd4LWFycmF5LWZpZWxkJyxcclxuICBzdGFuZGFsb25lOiB0cnVlLFxyXG4gIGltcG9ydHM6IFtSZWFjdGl2ZUZvcm1zTW9kdWxlLCBmb3J3YXJkUmVmKCgpID0+IER5bmFtaWNGaWVsZENvbXBvbmVudCldLFxyXG4gIHRlbXBsYXRlOiBgXHJcbiAgICA8ZGl2IGNsYXNzPVwibmd4LWZpZWxkIG5neC1maWVsZC0tYXJyYXlcIlxyXG4gICAgICBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkXCIgW2F0dHIuZGF0YS1maWVsZC10eXBlXT1cImZpZWxkLnR5cGVcIiBbY2xhc3Mubmd4LWZpZWxkLS1yZWFkb25seV09XCJyZWFkb25seVwiIFtjbGFzcy5uZ3gtZmllbGQtLW1hc2tlZF09XCJtYXNrZWRcIj5cclxuICAgICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktaGVhZGVyXCI+XHJcbiAgICAgICAgPGxhYmVsIGNsYXNzPVwibmd4LWZpZWxkX19sYWJlbFwiPnt7IGxhYmVsIH19PC9sYWJlbD5cclxuICAgICAgICBAaWYgKCFyZWFkb25seSAmJiAhbWFza2VkKSB7XHJcbiAgICAgICAgICA8YnV0dG9uIHR5cGU9XCJidXR0b25cIiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktYWRkLWJ0blwiXHJcbiAgICAgICAgICAgIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLWFkZCdcIiAoY2xpY2spPVwiYWRkSXRlbSgpXCI+XHJcbiAgICAgICAgICAgICsgQWRkIEl0ZW1cclxuICAgICAgICAgIDwvYnV0dG9uPlxyXG4gICAgICAgIH1cclxuICAgICAgPC9kaXY+XHJcblxyXG4gICAgICBAaWYgKG1hc2tlZCkge1xyXG4gICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LWZpZWxkX192YWx1ZSBuZ3gtZmllbGRfX3ZhbHVlLS1tYXNrZWRcIiBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkICsgJy1tYXNrZWQnXCI+WFhYWFhYWFhYPC9zcGFuPlxyXG4gICAgICB9IEBlbHNlIHtcclxuICAgICAgICA8ZGl2IGNsYXNzPVwibmd4LWZpZWxkX19hcnJheS1saXN0XCI+XHJcbiAgICAgICAgICBAZm9yIChpdGVtR3JvdXAgb2YgZm9ybUFycmF5LmNvbnRyb2xzOyB0cmFjayBpZHg7IGxldCBpZHggPSAkaW5kZXgpIHtcclxuICAgICAgICAgICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktaXRlbVwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLXJvdydcIj5cclxuICAgICAgICAgICAgICA8ZGl2IGNsYXNzPVwibmd4LWZpZWxkX19hcnJheS1pdGVtLWhlYWRlclwiPlxyXG4gICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9XCJuZ3gtZmllbGRfX2FycmF5LWl0ZW0tdGl0bGVcIj5JdGVtICN7eyBpZHggKyAxIH19PC9zcGFuPlxyXG4gICAgICAgICAgICAgICAgQGlmICghcmVhZG9ubHkpIHtcclxuICAgICAgICAgICAgICAgICAgPGJ1dHRvbiB0eXBlPVwiYnV0dG9uXCIgY2xhc3M9XCJuZ3gtZmllbGRfX2FycmF5LXJlbW92ZS1idG5cIlxyXG4gICAgICAgICAgICAgICAgICAgIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLXJlbW92ZS0nICsgaWR4XCIgKGNsaWNrKT1cInJlbW92ZUl0ZW0oaWR4KVwiPlxyXG4gICAgICAgICAgICAgICAgICAgIFJlbW92ZVxyXG4gICAgICAgICAgICAgICAgICA8L2J1dHRvbj5cclxuICAgICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgICA8L2Rpdj5cclxuICAgICAgICAgICAgICA8ZGl2IGNsYXNzPVwibmd4LWZpZWxkX19hcnJheS1pdGVtLWZpZWxkc1wiPlxyXG4gICAgICAgICAgICAgICAgQGZvciAoY2hpbGQgb2YgZmllbGQuY2hpbGRyZW4gPz8gW107IHRyYWNrIGNoaWxkLmlkKSB7XHJcbiAgICAgICAgICAgICAgICAgIEBpZiAoY2hpbGQudmlzaWJpbGl0eSAhPT0gZmFsc2UpIHtcclxuICAgICAgICAgICAgICAgICAgICA8bmd4LWR5bmFtaWMtZmllbGRcclxuICAgICAgICAgICAgICAgICAgICAgIFtmaWVsZF09XCJjaGlsZFwiXHJcbiAgICAgICAgICAgICAgICAgICAgICBbY29udHJvbF09XCJnZXRJdGVtQ2hpbGRDb250cm9sKGl0ZW1Hcm91cCwgY2hpbGQuaWQpXCJcclxuICAgICAgICAgICAgICAgICAgICAgIFtjb25maWddPVwiY29uZmlnXCJcclxuICAgICAgICAgICAgICAgICAgICAgIFtsYW5ndWFnZV09XCJsYW5ndWFnZVwiXHJcbiAgICAgICAgICAgICAgICAgICAgICBbcmVhZG9ubHldPVwicmVhZG9ubHkgfHwgISFjaGlsZC5yZWFkb25seVwiXHJcbiAgICAgICAgICAgICAgICAgICAgICBbdXNlclJvbGVzXT1cInVzZXJSb2xlc1wiXHJcbiAgICAgICAgICAgICAgICAgICAgLz5cclxuICAgICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgIDwvZGl2PlxyXG4gICAgICAgICAgICA8L2Rpdj5cclxuICAgICAgICAgIH1cclxuICAgICAgICAgIEBpZiAoZm9ybUFycmF5LmNvbnRyb2xzLmxlbmd0aCA9PT0gMCkge1xyXG4gICAgICAgICAgICA8ZGl2IGNsYXNzPVwibmd4LWZpZWxkX19hcnJheS1lbXB0eVwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLWVtcHR5J1wiPk5vIGl0ZW1zIGFkZGVkIHlldC48L2Rpdj5cclxuICAgICAgICAgIH1cclxuICAgICAgICA8L2Rpdj5cclxuICAgICAgfVxyXG4gICAgPC9kaXY+XHJcbiAgYCxcclxuICBzdHlsZXM6IFtcclxuICAgIGBcclxuICAgICAgLm5neC1maWVsZC0tYXJyYXkge1xyXG4gICAgICAgIGJvcmRlcjogMXB4IHNvbGlkICNlMmU4ZjA7XHJcbiAgICAgICAgYm9yZGVyLXJhZGl1czogOHB4O1xyXG4gICAgICAgIHBhZGRpbmc6IDE2cHg7XHJcbiAgICAgICAgbWFyZ2luLWJvdHRvbTogMTZweDtcclxuICAgICAgICBiYWNrZ3JvdW5kOiAjZjhmYWZjO1xyXG4gICAgICB9XHJcbiAgICAgIC5uZ3gtZmllbGRfX2FycmF5LWhlYWRlciB7XHJcbiAgICAgICAgZGlzcGxheTogZmxleDtcclxuICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47XHJcbiAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjtcclxuICAgICAgICBtYXJnaW4tYm90dG9tOiAxMnB4O1xyXG4gICAgICB9XHJcbiAgICAgIC5uZ3gtZmllbGRfX2FycmF5LWFkZC1idG4ge1xyXG4gICAgICAgIGJhY2tncm91bmQ6ICMyNTYzZWI7XHJcbiAgICAgICAgY29sb3I6ICNmZmY7XHJcbiAgICAgICAgYm9yZGVyOiBub25lO1xyXG4gICAgICAgIHBhZGRpbmc6IDZweCAxMnB4O1xyXG4gICAgICAgIGJvcmRlci1yYWRpdXM6IDRweDtcclxuICAgICAgICBmb250LXdlaWdodDogNTAwO1xyXG4gICAgICAgIGN1cnNvcjogcG9pbnRlcjtcclxuICAgICAgfVxyXG4gICAgICAubmd4LWZpZWxkX19hcnJheS1hZGQtYnRuOmhvdmVyIHtcclxuICAgICAgICBiYWNrZ3JvdW5kOiAjMWQ0ZWQ4O1xyXG4gICAgICB9XHJcbiAgICAgIC5uZ3gtZmllbGRfX2FycmF5LWxpc3Qge1xyXG4gICAgICAgIGRpc3BsYXk6IGZsZXg7XHJcbiAgICAgICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjtcclxuICAgICAgICBnYXA6IDEycHg7XHJcbiAgICAgIH1cclxuICAgICAgLm5neC1maWVsZF9fYXJyYXktaXRlbSB7XHJcbiAgICAgICAgYmFja2dyb3VuZDogI2ZmZjtcclxuICAgICAgICBib3JkZXI6IDFweCBzb2xpZCAjY2JkNWUxO1xyXG4gICAgICAgIGJvcmRlci1yYWRpdXM6IDZweDtcclxuICAgICAgICBwYWRkaW5nOiAxMnB4O1xyXG4gICAgICB9XHJcbiAgICAgIC5uZ3gtZmllbGRfX2FycmF5LWl0ZW0taGVhZGVyIHtcclxuICAgICAgICBkaXNwbGF5OiBmbGV4O1xyXG4gICAgICAgIGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjtcclxuICAgICAgICBhbGlnbi1pdGVtczogY2VudGVyO1xyXG4gICAgICAgIG1hcmdpbi1ib3R0b206IDhweDtcclxuICAgICAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgI2YxZjVmOTtcclxuICAgICAgICBwYWRkaW5nLWJvdHRvbTogNnB4O1xyXG4gICAgICB9XHJcbiAgICAgIC5uZ3gtZmllbGRfX2FycmF5LWl0ZW0tdGl0bGUge1xyXG4gICAgICAgIGZvbnQtd2VpZ2h0OiA2MDA7XHJcbiAgICAgICAgZm9udC1zaXplOiAxM3B4O1xyXG4gICAgICAgIGNvbG9yOiAjNDc1NTY5O1xyXG4gICAgICB9XHJcbiAgICAgIC5uZ3gtZmllbGRfX2FycmF5LXJlbW92ZS1idG4ge1xyXG4gICAgICAgIGJhY2tncm91bmQ6ICNlZjQ0NDQ7XHJcbiAgICAgICAgY29sb3I6ICNmZmY7XHJcbiAgICAgICAgYm9yZGVyOiBub25lO1xyXG4gICAgICAgIHBhZGRpbmc6IDRweCA4cHg7XHJcbiAgICAgICAgYm9yZGVyLXJhZGl1czogNHB4O1xyXG4gICAgICAgIGZvbnQtc2l6ZTogMTJweDtcclxuICAgICAgICBjdXJzb3I6IHBvaW50ZXI7XHJcbiAgICAgIH1cclxuICAgICAgLm5neC1maWVsZF9fYXJyYXktZW1wdHkge1xyXG4gICAgICAgIGNvbG9yOiAjOTRhM2I4O1xyXG4gICAgICAgIGZvbnQtc3R5bGU6IGl0YWxpYztcclxuICAgICAgICBmb250LXNpemU6IDEzcHg7XHJcbiAgICAgIH1cclxuICAgIGAsXHJcbiAgXSxcclxufSlcclxuZXhwb3J0IGNsYXNzIEFycmF5RmllbGRDb21wb25lbnQge1xyXG4gIEBJbnB1dCgpIGZpZWxkITogTmVzdGVkRmllbGRDb25maWc7XHJcbiAgQElucHV0KCkgY29udHJvbCE6IEFic3RyYWN0Q29udHJvbDtcclxuICBASW5wdXQoKSBjb25maWchOiBFbnRpdHlGb3JtQ29uZmlnO1xyXG4gIEBJbnB1dCgpIGxhbmd1YWdlOiBzdHJpbmcgPSAnZW4nO1xyXG4gIEBJbnB1dCgpIHJlYWRvbmx5OiBib29sZWFuID0gZmFsc2U7XHJcbiAgQElucHV0KCkgbWFza2VkOiBib29sZWFuID0gZmFsc2U7XHJcbiAgQElucHV0KCkgdXNlclJvbGVzOiBzdHJpbmdbXSA9IFtdO1xyXG5cclxuICBwcml2YXRlIHJlYWRvbmx5IGZiID0gaW5qZWN0KEZvcm1CdWlsZGVyKTtcclxuICBwcml2YXRlIHJlYWRvbmx5IHZhbGlkYXRvclJlZ2lzdHJ5ID0gaW5qZWN0KFZhbGlkYXRvclJlZ2lzdHJ5U2VydmljZSk7XHJcblxyXG4gIGdldCBsYWJlbCgpOiBzdHJpbmcge1xyXG4gICAgcmV0dXJuIHJlc29sdmVMYWJlbCh0aGlzLmZpZWxkPy5sYWJlbCwgdGhpcy5sYW5ndWFnZSk7XHJcbiAgfVxyXG5cclxuICBnZXQgZm9ybUFycmF5KCk6IEZvcm1BcnJheSB7XHJcbiAgICByZXR1cm4gdGhpcy5jb250cm9sIGFzIEZvcm1BcnJheTtcclxuICB9XHJcblxyXG4gIGdldEl0ZW1DaGlsZENvbnRyb2woaXRlbUdyb3VwOiBBYnN0cmFjdENvbnRyb2wsIGNoaWxkSWQ6IHN0cmluZyk6IEFic3RyYWN0Q29udHJvbCB7XHJcbiAgICByZXR1cm4gKGl0ZW1Hcm91cCBhcyBGb3JtR3JvdXApLmdldChjaGlsZElkKSBhcyBBYnN0cmFjdENvbnRyb2w7XHJcbiAgfVxyXG5cclxuICBhZGRJdGVtKCk6IHZvaWQge1xyXG4gICAgY29uc3QgY29udHJvbHM6IFJlY29yZDxzdHJpbmcsIGFueT4gPSB7fTtcclxuICAgIGZvciAoY29uc3QgY2hpbGQgb2YgdGhpcy5maWVsZC5jaGlsZHJlbiA/PyBbXSkge1xyXG4gICAgICBjb25zdCB2YWxpZGF0b3JzID0gdGhpcy52YWxpZGF0b3JSZWdpc3RyeS5yZXNvbHZlRnJvbUNvbmZpZyhjaGlsZC52YWxpZGF0b3JzKTtcclxuICAgICAgY29udHJvbHNbY2hpbGQuaWRdID0gW2NoaWxkLmRlZmF1bHRWYWx1ZSA/PyBudWxsLCB2YWxpZGF0b3JzXTtcclxuICAgIH1cclxuICAgIGNvbnN0IGdyb3VwID0gdGhpcy5mYi5ncm91cChjb250cm9scyk7XHJcbiAgICB0aGlzLmZvcm1BcnJheS5wdXNoKGdyb3VwKTtcclxuICB9XHJcblxyXG4gIHJlbW92ZUl0ZW0oaW5kZXg6IG51bWJlcik6IHZvaWQge1xyXG4gICAgdGhpcy5mb3JtQXJyYXkucmVtb3ZlQXQoaW5kZXgpO1xyXG4gIH1cclxufVxyXG4iXX0=
160
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXJyYXktZmllbGQuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9maWVsZC10eXBlcy9hcnJheS1maWVsZC5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUNyRSxPQUFPLEVBQThCLFdBQVcsRUFBYSxtQkFBbUIsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBRXpHLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNwRCxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSwrQ0FBK0MsQ0FBQztBQUN0RixPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSx3Q0FBd0MsQ0FBQzs7QUFFbEYscUdBQXFHO0FBNEhyRyxNQUFNLE9BQU8sbUJBQW1CO0lBQ3JCLEtBQUssQ0FBcUI7SUFDMUIsT0FBTyxDQUFtQjtJQUMxQixNQUFNLENBQW9CO0lBQzFCLFFBQVEsR0FBVyxJQUFJLENBQUM7SUFDeEIsUUFBUSxHQUFZLEtBQUssQ0FBQztJQUMxQixNQUFNLEdBQVksS0FBSyxDQUFDO0lBQ3hCLFNBQVMsR0FBYSxFQUFFLENBQUM7SUFFakIsRUFBRSxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUN6QixpQkFBaUIsR0FBRyxNQUFNLENBQUMsd0JBQXdCLENBQUMsQ0FBQztJQUV0RSxJQUFJLEtBQUs7UUFDUCxPQUFPLFlBQVksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDeEQsQ0FBQztJQUVELElBQUksU0FBUztRQUNYLE9BQU8sSUFBSSxDQUFDLE9BQW9CLENBQUM7SUFDbkMsQ0FBQztJQUVELG1CQUFtQixDQUFDLFNBQTBCLEVBQUUsT0FBZTtRQUM3RCxPQUFRLFNBQXVCLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBb0IsQ0FBQztJQUNsRSxDQUFDO0lBRUQsT0FBTztRQUNMLE1BQU0sUUFBUSxHQUF3QixFQUFFLENBQUM7UUFDekMsS0FBSyxNQUFNLEtBQUssSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLFFBQVEsSUFBSSxFQUFFLEVBQUUsQ0FBQztZQUM5QyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsaUJBQWlCLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1lBQzlFLFFBQVEsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsWUFBWSxJQUFJLElBQUksRUFBRSxVQUFVLENBQUMsQ0FBQztRQUNoRSxDQUFDO1FBQ0QsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDdEMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDN0IsQ0FBQztJQUVELFVBQVUsQ0FBQyxLQUFhO1FBQ3RCLElBQUksQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ2pDLENBQUM7d0dBcENVLG1CQUFtQjs0RkFBbkIsbUJBQW1CLG1PQXZIcEI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0RULDZrQ0FuRFMsbUJBQW1CLG9EQUFtQixxQkFBcUI7OzRGQXdIMUQsbUJBQW1CO2tCQTNIL0IsU0FBUzsrQkFDRSxpQkFBaUIsY0FDZixJQUFJLFdBQ1AsQ0FBQyxtQkFBbUIsRUFBRSxVQUFVLENBQUMsR0FBRyxFQUFFLENBQUMscUJBQXFCLENBQUMsQ0FBQyxZQUM3RDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FrRFQ7OEJBc0VRLEtBQUs7c0JBQWIsS0FBSztnQkFDRyxPQUFPO3NCQUFmLEtBQUs7Z0JBQ0csTUFBTTtzQkFBZCxLQUFLO2dCQUNHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBQ0csUUFBUTtzQkFBaEIsS0FBSztnQkFDRyxNQUFNO3NCQUFkLEtBQUs7Z0JBQ0csU0FBUztzQkFBakIsS0FBSyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbXBvbmVudCwgSW5wdXQsIGluamVjdCwgZm9yd2FyZFJlZiB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQWJzdHJhY3RDb250cm9sLCBGb3JtQXJyYXksIEZvcm1CdWlsZGVyLCBGb3JtR3JvdXAsIFJlYWN0aXZlRm9ybXNNb2R1bGUgfSBmcm9tICdAYW5ndWxhci9mb3Jtcyc7XG5pbXBvcnQgdHlwZSB7IE5lc3RlZEZpZWxkQ29uZmlnLCBFbnRpdHlGb3JtQ29uZmlnIH0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuaW1wb3J0IHsgcmVzb2x2ZUxhYmVsIH0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuaW1wb3J0IHsgRHluYW1pY0ZpZWxkQ29tcG9uZW50IH0gZnJvbSAnLi4vZm9ybS9keW5hbWljLWZpZWxkL2R5bmFtaWMtZmllbGQuY29tcG9uZW50JztcbmltcG9ydCB7IFZhbGlkYXRvclJlZ2lzdHJ5U2VydmljZSB9IGZyb20gJy4uL3NlcnZpY2VzL3ZhbGlkYXRvci1yZWdpc3RyeS5zZXJ2aWNlJztcblxuLyoqIEFEUi0wMDg6IEFycmF5RmllbGRDb21wb25lbnQg4oCUIHJlbmRlcnMgcmVwZWF0aW5nIHJvd3MgZm9yIEZvcm1BcnJheSB3aXRoICsgQWRkIEl0ZW0gYW5kIFJlbW92ZSAqL1xuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiAnbmd4LWFycmF5LWZpZWxkJyxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW1JlYWN0aXZlRm9ybXNNb2R1bGUsIGZvcndhcmRSZWYoKCkgPT4gRHluYW1pY0ZpZWxkQ29tcG9uZW50KV0sXG4gIHRlbXBsYXRlOiBgXG4gICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZCBuZ3gtZmllbGQtLWFycmF5XCJcbiAgICAgIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWRcIiBbYXR0ci5kYXRhLWZpZWxkLXR5cGVdPVwiZmllbGQudHlwZVwiIFtjbGFzcy5uZ3gtZmllbGQtLXJlYWRvbmx5XT1cInJlYWRvbmx5XCIgW2NsYXNzLm5neC1maWVsZC0tbWFza2VkXT1cIm1hc2tlZFwiPlxuICAgICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktaGVhZGVyXCI+XG4gICAgICAgIDxsYWJlbCBjbGFzcz1cIm5neC1maWVsZF9fbGFiZWxcIj57eyBsYWJlbCB9fTwvbGFiZWw+XG4gICAgICAgIEBpZiAoIXJlYWRvbmx5ICYmICFtYXNrZWQpIHtcbiAgICAgICAgICA8YnV0dG9uIHR5cGU9XCJidXR0b25cIiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktYWRkLWJ0blwiXG4gICAgICAgICAgICBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkICsgJy1hZGQnXCIgKGNsaWNrKT1cImFkZEl0ZW0oKVwiPlxuICAgICAgICAgICAgKyBBZGQgSXRlbVxuICAgICAgICAgIDwvYnV0dG9uPlxuICAgICAgICB9XG4gICAgICA8L2Rpdj5cblxuICAgICAgQGlmIChtYXNrZWQpIHtcbiAgICAgICAgPHNwYW4gY2xhc3M9XCJuZ3gtZmllbGRfX3ZhbHVlIG5neC1maWVsZF9fdmFsdWUtLW1hc2tlZFwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLW1hc2tlZCdcIj5YWFhYWFhYWFg8L3NwYW4+XG4gICAgICB9IEBlbHNlIHtcbiAgICAgICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktbGlzdFwiPlxuICAgICAgICAgIEBmb3IgKGl0ZW1Hcm91cCBvZiBmb3JtQXJyYXkuY29udHJvbHM7IHRyYWNrIGlkeDsgbGV0IGlkeCA9ICRpbmRleCkge1xuICAgICAgICAgICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktaXRlbVwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLXJvdydcIj5cbiAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktaXRlbS1oZWFkZXJcIj5cbiAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktaXRlbS10aXRsZVwiPkl0ZW0gI3t7IGlkeCArIDEgfX08L3NwYW4+XG4gICAgICAgICAgICAgICAgQGlmICghcmVhZG9ubHkpIHtcbiAgICAgICAgICAgICAgICAgIDxidXR0b24gdHlwZT1cImJ1dHRvblwiIGNsYXNzPVwibmd4LWZpZWxkX19hcnJheS1yZW1vdmUtYnRuXCJcbiAgICAgICAgICAgICAgICAgICAgW2F0dHIuZGF0YS10ZXN0aWRdPVwiJ2ZpZWxkLScgKyBmaWVsZC5pZCArICctcmVtb3ZlLScgKyBpZHhcIiAoY2xpY2spPVwicmVtb3ZlSXRlbShpZHgpXCI+XG4gICAgICAgICAgICAgICAgICAgIFJlbW92ZVxuICAgICAgICAgICAgICAgICAgPC9idXR0b24+XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZF9fYXJyYXktaXRlbS1maWVsZHNcIj5cbiAgICAgICAgICAgICAgICBAZm9yIChjaGlsZCBvZiBmaWVsZC5jaGlsZHJlbiA/PyBbXTsgdHJhY2sgY2hpbGQuaWQpIHtcbiAgICAgICAgICAgICAgICAgIEBpZiAoY2hpbGQudmlzaWJpbGl0eSAhPT0gZmFsc2UpIHtcbiAgICAgICAgICAgICAgICAgICAgPG5neC1keW5hbWljLWZpZWxkXG4gICAgICAgICAgICAgICAgICAgICAgW2ZpZWxkXT1cImNoaWxkXCJcbiAgICAgICAgICAgICAgICAgICAgICBbY29udHJvbF09XCJnZXRJdGVtQ2hpbGRDb250cm9sKGl0ZW1Hcm91cCwgY2hpbGQuaWQpXCJcbiAgICAgICAgICAgICAgICAgICAgICBbY29uZmlnXT1cImNvbmZpZ1wiXG4gICAgICAgICAgICAgICAgICAgICAgW2xhbmd1YWdlXT1cImxhbmd1YWdlXCJcbiAgICAgICAgICAgICAgICAgICAgICBbcmVhZG9ubHldPVwicmVhZG9ubHkgfHwgISFjaGlsZC5yZWFkb25seVwiXG4gICAgICAgICAgICAgICAgICAgICAgW3VzZXJSb2xlc109XCJ1c2VyUm9sZXNcIlxuICAgICAgICAgICAgICAgICAgICAvPlxuICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgPC9kaXY+XG4gICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICB9XG4gICAgICAgICAgQGlmIChmb3JtQXJyYXkuY29udHJvbHMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICA8ZGl2IGNsYXNzPVwibmd4LWZpZWxkX19hcnJheS1lbXB0eVwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLWVtcHR5J1wiPk5vIGl0ZW1zIGFkZGVkIHlldC48L2Rpdj5cbiAgICAgICAgICB9XG4gICAgICAgIDwvZGl2PlxuICAgICAgfVxuICAgIDwvZGl2PlxuICBgLFxuICBzdHlsZXM6IFtcbiAgICBgXG4gICAgICAubmd4LWZpZWxkLS1hcnJheSB7XG4gICAgICAgIGJvcmRlcjogMXB4IHNvbGlkICNlMmU4ZjA7XG4gICAgICAgIGJvcmRlci1yYWRpdXM6IDhweDtcbiAgICAgICAgcGFkZGluZzogMTZweDtcbiAgICAgICAgbWFyZ2luLWJvdHRvbTogMTZweDtcbiAgICAgICAgYmFja2dyb3VuZDogI2Y4ZmFmYztcbiAgICAgIH1cbiAgICAgIC5uZ3gtZmllbGRfX2FycmF5LWhlYWRlciB7XG4gICAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICAgIGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjtcbiAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgICAgICAgbWFyZ2luLWJvdHRvbTogMTJweDtcbiAgICAgIH1cbiAgICAgIC5uZ3gtZmllbGRfX2FycmF5LWFkZC1idG4ge1xuICAgICAgICBiYWNrZ3JvdW5kOiAjMjU2M2ViO1xuICAgICAgICBjb2xvcjogI2ZmZjtcbiAgICAgICAgYm9yZGVyOiBub25lO1xuICAgICAgICBwYWRkaW5nOiA2cHggMTJweDtcbiAgICAgICAgYm9yZGVyLXJhZGl1czogNHB4O1xuICAgICAgICBmb250LXdlaWdodDogNTAwO1xuICAgICAgICBjdXJzb3I6IHBvaW50ZXI7XG4gICAgICB9XG4gICAgICAubmd4LWZpZWxkX19hcnJheS1hZGQtYnRuOmhvdmVyIHtcbiAgICAgICAgYmFja2dyb3VuZDogIzFkNGVkODtcbiAgICAgIH1cbiAgICAgIC5uZ3gtZmllbGRfX2FycmF5LWxpc3Qge1xuICAgICAgICBkaXNwbGF5OiBmbGV4O1xuICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuICAgICAgICBnYXA6IDEycHg7XG4gICAgICB9XG4gICAgICAubmd4LWZpZWxkX19hcnJheS1pdGVtIHtcbiAgICAgICAgYmFja2dyb3VuZDogI2ZmZjtcbiAgICAgICAgYm9yZGVyOiAxcHggc29saWQgI2NiZDVlMTtcbiAgICAgICAgYm9yZGVyLXJhZGl1czogNnB4O1xuICAgICAgICBwYWRkaW5nOiAxMnB4O1xuICAgICAgfVxuICAgICAgLm5neC1maWVsZF9fYXJyYXktaXRlbS1oZWFkZXIge1xuICAgICAgICBkaXNwbGF5OiBmbGV4O1xuICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47XG4gICAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG4gICAgICAgIG1hcmdpbi1ib3R0b206IDhweDtcbiAgICAgICAgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkICNmMWY1Zjk7XG4gICAgICAgIHBhZGRpbmctYm90dG9tOiA2cHg7XG4gICAgICB9XG4gICAgICAubmd4LWZpZWxkX19hcnJheS1pdGVtLXRpdGxlIHtcbiAgICAgICAgZm9udC13ZWlnaHQ6IDYwMDtcbiAgICAgICAgZm9udC1zaXplOiAxM3B4O1xuICAgICAgICBjb2xvcjogIzQ3NTU2OTtcbiAgICAgIH1cbiAgICAgIC5uZ3gtZmllbGRfX2FycmF5LXJlbW92ZS1idG4ge1xuICAgICAgICBiYWNrZ3JvdW5kOiAjZWY0NDQ0O1xuICAgICAgICBjb2xvcjogI2ZmZjtcbiAgICAgICAgYm9yZGVyOiBub25lO1xuICAgICAgICBwYWRkaW5nOiA0cHggOHB4O1xuICAgICAgICBib3JkZXItcmFkaXVzOiA0cHg7XG4gICAgICAgIGZvbnQtc2l6ZTogMTJweDtcbiAgICAgICAgY3Vyc29yOiBwb2ludGVyO1xuICAgICAgfVxuICAgICAgLm5neC1maWVsZF9fYXJyYXktZW1wdHkge1xuICAgICAgICBjb2xvcjogIzk0YTNiODtcbiAgICAgICAgZm9udC1zdHlsZTogaXRhbGljO1xuICAgICAgICBmb250LXNpemU6IDEzcHg7XG4gICAgICB9XG4gICAgYCxcbiAgXSxcbn0pXG5leHBvcnQgY2xhc3MgQXJyYXlGaWVsZENvbXBvbmVudCB7XG4gIEBJbnB1dCgpIGZpZWxkITogTmVzdGVkRmllbGRDb25maWc7XG4gIEBJbnB1dCgpIGNvbnRyb2whOiBBYnN0cmFjdENvbnRyb2w7XG4gIEBJbnB1dCgpIGNvbmZpZyE6IEVudGl0eUZvcm1Db25maWc7XG4gIEBJbnB1dCgpIGxhbmd1YWdlOiBzdHJpbmcgPSAnZW4nO1xuICBASW5wdXQoKSByZWFkb25seTogYm9vbGVhbiA9IGZhbHNlO1xuICBASW5wdXQoKSBtYXNrZWQ6IGJvb2xlYW4gPSBmYWxzZTtcbiAgQElucHV0KCkgdXNlclJvbGVzOiBzdHJpbmdbXSA9IFtdO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgZmIgPSBpbmplY3QoRm9ybUJ1aWxkZXIpO1xuICBwcml2YXRlIHJlYWRvbmx5IHZhbGlkYXRvclJlZ2lzdHJ5ID0gaW5qZWN0KFZhbGlkYXRvclJlZ2lzdHJ5U2VydmljZSk7XG5cbiAgZ2V0IGxhYmVsKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIHJlc29sdmVMYWJlbCh0aGlzLmZpZWxkPy5sYWJlbCwgdGhpcy5sYW5ndWFnZSk7XG4gIH1cblxuICBnZXQgZm9ybUFycmF5KCk6IEZvcm1BcnJheSB7XG4gICAgcmV0dXJuIHRoaXMuY29udHJvbCBhcyBGb3JtQXJyYXk7XG4gIH1cblxuICBnZXRJdGVtQ2hpbGRDb250cm9sKGl0ZW1Hcm91cDogQWJzdHJhY3RDb250cm9sLCBjaGlsZElkOiBzdHJpbmcpOiBBYnN0cmFjdENvbnRyb2wge1xuICAgIHJldHVybiAoaXRlbUdyb3VwIGFzIEZvcm1Hcm91cCkuZ2V0KGNoaWxkSWQpIGFzIEFic3RyYWN0Q29udHJvbDtcbiAgfVxuXG4gIGFkZEl0ZW0oKTogdm9pZCB7XG4gICAgY29uc3QgY29udHJvbHM6IFJlY29yZDxzdHJpbmcsIGFueT4gPSB7fTtcbiAgICBmb3IgKGNvbnN0IGNoaWxkIG9mIHRoaXMuZmllbGQuY2hpbGRyZW4gPz8gW10pIHtcbiAgICAgIGNvbnN0IHZhbGlkYXRvcnMgPSB0aGlzLnZhbGlkYXRvclJlZ2lzdHJ5LnJlc29sdmVGcm9tQ29uZmlnKGNoaWxkLnZhbGlkYXRvcnMpO1xuICAgICAgY29udHJvbHNbY2hpbGQuaWRdID0gW2NoaWxkLmRlZmF1bHRWYWx1ZSA/PyBudWxsLCB2YWxpZGF0b3JzXTtcbiAgICB9XG4gICAgY29uc3QgZ3JvdXAgPSB0aGlzLmZiLmdyb3VwKGNvbnRyb2xzKTtcbiAgICB0aGlzLmZvcm1BcnJheS5wdXNoKGdyb3VwKTtcbiAgfVxuXG4gIHJlbW92ZUl0ZW0oaW5kZXg6IG51bWJlcik6IHZvaWQge1xuICAgIHRoaXMuZm9ybUFycmF5LnJlbW92ZUF0KGluZGV4KTtcbiAgfVxufVxuIl19