angular-data-mapper 1.0.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 (64) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/LICENSE +190 -0
  3. package/PUBLISHING.md +75 -0
  4. package/README.md +214 -0
  5. package/angular.json +121 -0
  6. package/package.json +67 -0
  7. package/projects/demo-app/public/favicon.ico +0 -0
  8. package/projects/demo-app/src/app/app.config.ts +12 -0
  9. package/projects/demo-app/src/app/app.html +36 -0
  10. package/projects/demo-app/src/app/app.routes.ts +62 -0
  11. package/projects/demo-app/src/app/app.scss +65 -0
  12. package/projects/demo-app/src/app/app.ts +11 -0
  13. package/projects/demo-app/src/app/layout/app-layout.component.ts +294 -0
  14. package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.html +87 -0
  15. package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.scss +202 -0
  16. package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.ts +192 -0
  17. package/projects/demo-app/src/app/pages/mappings-page/add-mapping-dialog.component.ts +163 -0
  18. package/projects/demo-app/src/app/pages/mappings-page/mappings-page.component.ts +306 -0
  19. package/projects/demo-app/src/app/pages/schema-creator-page/schema-creator-page.component.ts +88 -0
  20. package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.html +108 -0
  21. package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.scss +317 -0
  22. package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.ts +129 -0
  23. package/projects/demo-app/src/app/services/app-state.service.ts +233 -0
  24. package/projects/demo-app/src/app/services/sample-data.service.ts +228 -0
  25. package/projects/demo-app/src/index.html +15 -0
  26. package/projects/demo-app/src/main.ts +6 -0
  27. package/projects/demo-app/src/styles.scss +54 -0
  28. package/projects/demo-app/tsconfig.app.json +13 -0
  29. package/projects/ngx-data-mapper/ng-package.json +7 -0
  30. package/projects/ngx-data-mapper/package.json +40 -0
  31. package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.html +183 -0
  32. package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.scss +352 -0
  33. package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.ts +277 -0
  34. package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.html +174 -0
  35. package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.scss +357 -0
  36. package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.ts +258 -0
  37. package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.html +139 -0
  38. package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.scss +213 -0
  39. package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.ts +261 -0
  40. package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.html +199 -0
  41. package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.scss +321 -0
  42. package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.ts +618 -0
  43. package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.html +67 -0
  44. package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.scss +97 -0
  45. package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.ts +105 -0
  46. package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.html +552 -0
  47. package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.scss +824 -0
  48. package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.ts +730 -0
  49. package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.html +82 -0
  50. package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.scss +352 -0
  51. package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.ts +225 -0
  52. package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.html +346 -0
  53. package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.scss +511 -0
  54. package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.ts +368 -0
  55. package/projects/ngx-data-mapper/src/lib/models/json-schema.model.ts +164 -0
  56. package/projects/ngx-data-mapper/src/lib/models/schema.model.ts +173 -0
  57. package/projects/ngx-data-mapper/src/lib/services/mapping.service.ts +615 -0
  58. package/projects/ngx-data-mapper/src/lib/services/schema-parser.service.ts +270 -0
  59. package/projects/ngx-data-mapper/src/lib/services/svg-connector.service.ts +135 -0
  60. package/projects/ngx-data-mapper/src/lib/services/transformation.service.ts +453 -0
  61. package/projects/ngx-data-mapper/src/public-api.ts +22 -0
  62. package/projects/ngx-data-mapper/tsconfig.lib.json +13 -0
  63. package/projects/ngx-data-mapper/tsconfig.lib.prod.json +9 -0
  64. package/tsconfig.json +28 -0
@@ -0,0 +1,261 @@
1
+ import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import { MatButtonModule } from '@angular/material/button';
5
+ import { MatIconModule } from '@angular/material/icon';
6
+ import { MatSelectModule } from '@angular/material/select';
7
+ import { MatInputModule } from '@angular/material/input';
8
+ import { MatFormFieldModule } from '@angular/material/form-field';
9
+ import { MatRadioModule } from '@angular/material/radio';
10
+ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
11
+ import { MatTooltipModule } from '@angular/material/tooltip';
12
+ import {
13
+ FilterCondition,
14
+ FilterGroup,
15
+ FilterItem,
16
+ FilterOperator,
17
+ } from '../../models/schema.model';
18
+
19
+ interface OperatorOption {
20
+ value: FilterOperator;
21
+ label: string;
22
+ needsValue: boolean;
23
+ }
24
+
25
+ export interface ConditionField {
26
+ path: string;
27
+ name: string;
28
+ type: 'string' | 'number' | 'boolean';
29
+ }
30
+
31
+ @Component({
32
+ selector: 'condition-builder',
33
+ standalone: true,
34
+ imports: [
35
+ CommonModule,
36
+ FormsModule,
37
+ MatButtonModule,
38
+ MatIconModule,
39
+ MatSelectModule,
40
+ MatInputModule,
41
+ MatFormFieldModule,
42
+ MatRadioModule,
43
+ MatSlideToggleModule,
44
+ MatTooltipModule,
45
+ ],
46
+ templateUrl: './condition-builder.component.html',
47
+ styleUrl: './condition-builder.component.scss',
48
+ })
49
+ export class ConditionBuilderComponent implements OnInit {
50
+ // Available fields for conditions (optional - if not provided, uses 'value' as the only field)
51
+ @Input() fields: ConditionField[] = [];
52
+
53
+ // The condition group to edit
54
+ @Input() condition: FilterGroup | null = null;
55
+
56
+ // Whether to show a compact/inline version
57
+ @Input() compact = false;
58
+
59
+ // Emits when condition changes
60
+ @Output() conditionChange = new EventEmitter<FilterGroup>();
61
+
62
+ rootGroup!: FilterGroup;
63
+
64
+ // Default field for transformation conditions (the input value)
65
+ private defaultField: ConditionField = { path: 'value', name: 'Input value', type: 'string' };
66
+
67
+ // Operators by type
68
+ stringOperators: OperatorOption[] = [
69
+ { value: 'equals', label: 'equals', needsValue: true },
70
+ { value: 'notEquals', label: 'not equals', needsValue: true },
71
+ { value: 'contains', label: 'contains', needsValue: true },
72
+ { value: 'notContains', label: 'not contain', needsValue: true },
73
+ { value: 'startsWith', label: 'starts with', needsValue: true },
74
+ { value: 'endsWith', label: 'ends with', needsValue: true },
75
+ { value: 'isEmpty', label: 'is empty', needsValue: false },
76
+ { value: 'isNotEmpty', label: 'is not empty', needsValue: false },
77
+ ];
78
+
79
+ numberOperators: OperatorOption[] = [
80
+ { value: 'equals', label: 'equals', needsValue: true },
81
+ { value: 'notEquals', label: 'not equals', needsValue: true },
82
+ { value: 'greaterThan', label: '>', needsValue: true },
83
+ { value: 'lessThan', label: '<', needsValue: true },
84
+ { value: 'greaterThanOrEqual', label: '>=', needsValue: true },
85
+ { value: 'lessThanOrEqual', label: '<=', needsValue: true },
86
+ ];
87
+
88
+ booleanOperators: OperatorOption[] = [
89
+ { value: 'isTrue', label: 'is true', needsValue: false },
90
+ { value: 'isFalse', label: 'is false', needsValue: false },
91
+ ];
92
+
93
+ ngOnInit(): void {
94
+ if (this.condition) {
95
+ this.rootGroup = this.cloneGroup(this.condition);
96
+ } else {
97
+ this.rootGroup = this.createEmptyGroup();
98
+ }
99
+ }
100
+
101
+ get availableFields(): ConditionField[] {
102
+ return this.fields.length > 0 ? this.fields : [this.defaultField];
103
+ }
104
+
105
+ get showFieldSelector(): boolean {
106
+ return this.fields.length > 1;
107
+ }
108
+
109
+ private createEmptyGroup(): FilterGroup {
110
+ return {
111
+ id: this.generateId(),
112
+ type: 'group',
113
+ logic: 'and',
114
+ children: [],
115
+ };
116
+ }
117
+
118
+ private generateId(): string {
119
+ return `cond-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
120
+ }
121
+
122
+ private cloneGroup(group: FilterGroup): FilterGroup {
123
+ return {
124
+ ...group,
125
+ children: group.children.map((child) =>
126
+ child.type === 'group' ? this.cloneGroup(child) : { ...child }
127
+ ),
128
+ };
129
+ }
130
+
131
+ getOperatorsForField(fieldPath: string): OperatorOption[] {
132
+ const field = this.availableFields.find((f) => f.path === fieldPath);
133
+ if (!field) return this.stringOperators;
134
+
135
+ switch (field.type) {
136
+ case 'number':
137
+ return this.numberOperators;
138
+ case 'boolean':
139
+ return this.booleanOperators;
140
+ default:
141
+ return this.stringOperators;
142
+ }
143
+ }
144
+
145
+ operatorNeedsValue(operator: FilterOperator): boolean {
146
+ const allOperators = [...this.stringOperators, ...this.numberOperators, ...this.booleanOperators];
147
+ const op = allOperators.find((o) => o.value === operator);
148
+ return op?.needsValue ?? true;
149
+ }
150
+
151
+ isCondition(item: FilterItem): item is FilterCondition {
152
+ return item.type === 'condition';
153
+ }
154
+
155
+ isGroup(item: FilterItem): item is FilterGroup {
156
+ return item.type === 'group';
157
+ }
158
+
159
+ addCondition(group: FilterGroup): void {
160
+ const firstField = this.availableFields[0];
161
+ const newCondition: FilterCondition = {
162
+ id: this.generateId(),
163
+ type: 'condition',
164
+ field: firstField.path,
165
+ fieldName: firstField.name,
166
+ operator: 'isNotEmpty',
167
+ value: '',
168
+ valueType: firstField.type,
169
+ };
170
+ group.children = [...group.children, newCondition];
171
+ this.emitChange();
172
+ }
173
+
174
+ addGroup(parentGroup: FilterGroup): void {
175
+ const newGroup: FilterGroup = {
176
+ id: this.generateId(),
177
+ type: 'group',
178
+ logic: parentGroup.logic === 'and' ? 'or' : 'and',
179
+ children: [],
180
+ };
181
+ parentGroup.children = [...parentGroup.children, newGroup];
182
+ this.emitChange();
183
+ }
184
+
185
+ removeItem(parentGroup: FilterGroup, itemId: string): void {
186
+ parentGroup.children = parentGroup.children.filter((c) => c.id !== itemId);
187
+ this.emitChange();
188
+ }
189
+
190
+ onFieldChange(condition: FilterCondition, fieldPath: string): void {
191
+ const field = this.availableFields.find((f) => f.path === fieldPath);
192
+ if (field) {
193
+ condition.field = fieldPath;
194
+ condition.fieldName = field.name;
195
+ condition.valueType = field.type;
196
+ const operators = this.getOperatorsForField(fieldPath);
197
+ condition.operator = operators[0].value;
198
+ condition.value = '';
199
+ }
200
+ this.emitChange();
201
+ }
202
+
203
+ onOperatorChange(condition: FilterCondition, operator: FilterOperator): void {
204
+ condition.operator = operator;
205
+ if (!this.operatorNeedsValue(operator)) {
206
+ condition.value = '';
207
+ }
208
+ this.emitChange();
209
+ }
210
+
211
+ onValueChange(condition: FilterCondition, value: string | boolean): void {
212
+ if (condition.valueType === 'number') {
213
+ condition.value = parseFloat(value as string) || 0;
214
+ } else {
215
+ condition.value = value;
216
+ }
217
+ this.emitChange();
218
+ }
219
+
220
+ onLogicChange(group: FilterGroup, logic: 'and' | 'or'): void {
221
+ group.logic = logic;
222
+ this.emitChange();
223
+ }
224
+
225
+ private emitChange(): void {
226
+ this.rootGroup = this.cloneGroup(this.rootGroup);
227
+ this.conditionChange.emit(this.rootGroup);
228
+ }
229
+
230
+ getConditionSummary(): string {
231
+ return this.summarizeGroup(this.rootGroup);
232
+ }
233
+
234
+ private summarizeGroup(group: FilterGroup): string {
235
+ if (group.children.length === 0) return '';
236
+
237
+ const parts = group.children.map(child => {
238
+ if (child.type === 'condition') {
239
+ return this.summarizeCondition(child);
240
+ } else {
241
+ return `(${this.summarizeGroup(child)})`;
242
+ }
243
+ });
244
+
245
+ return parts.join(` ${group.logic.toUpperCase()} `);
246
+ }
247
+
248
+ private summarizeCondition(cond: FilterCondition): string {
249
+ const opLabel = this.getOperatorLabel(cond.operator);
250
+ if (this.operatorNeedsValue(cond.operator)) {
251
+ return `${opLabel} "${cond.value}"`;
252
+ }
253
+ return opLabel;
254
+ }
255
+
256
+ private getOperatorLabel(operator: FilterOperator): string {
257
+ const allOperators = [...this.stringOperators, ...this.numberOperators, ...this.booleanOperators];
258
+ const op = allOperators.find((o) => o.value === operator);
259
+ return op?.label || operator;
260
+ }
261
+ }
@@ -0,0 +1,199 @@
1
+ <div class="data-mapper">
2
+ <!-- Header Toolbar -->
3
+ <div class="mapper-toolbar">
4
+ <div class="toolbar-title">
5
+ <mat-icon>account_tree</mat-icon>
6
+ <span>Data Mapper</span>
7
+ </div>
8
+ <div class="toolbar-actions">
9
+ <span class="mapping-count">{{ mappings().length }} mapping(s)</span>
10
+ <button
11
+ mat-icon-button
12
+ matTooltip="Clear all mappings"
13
+ (click)="clearAllMappings()"
14
+ [disabled]="mappings().length === 0"
15
+ >
16
+ <mat-icon>delete_sweep</mat-icon>
17
+ </button>
18
+ </div>
19
+ </div>
20
+
21
+ <!-- Main Mapper Area -->
22
+ <div class="mapper-container" #svgContainer>
23
+ <!-- Source Schema Panel -->
24
+ <div class="schema-panel source-panel">
25
+ <schema-tree
26
+ [schema]="sourceSchemaForTree()"
27
+ [side]="'source'"
28
+ [mappings]="mappings()"
29
+ (fieldDragStart)="onFieldDragStart($event)"
30
+ (fieldPositionsChanged)="onSourcePositionsChanged($event)"
31
+ (sourceDrop)="onSourceFieldDrop($event)"
32
+ ></schema-tree>
33
+ </div>
34
+
35
+ <!-- SVG Connection Layer -->
36
+ <svg class="connection-layer" #svgElement>
37
+ <!-- Existing connections -->
38
+ @for (connection of connections(); track trackByConnectionId($index, connection)) {
39
+ <g class="connection-group" [class.selected]="connection.isSelected" [class.array-mapping]="connection.isArrayMapping" [class.array-to-object-mapping]="connection.isArrayToObjectMapping" [class.being-dragged]="connection.isBeingDragged">
40
+ <!-- Connection paths -->
41
+ @for (path of connection.paths; track $index) {
42
+ <!-- Shadow path for glow effect -->
43
+ <path
44
+ [attr.d]="path"
45
+ fill="none"
46
+ [attr.stroke]="connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : (connection.isSelected ? '#8b5cf6' : '#6366f1'))"
47
+ stroke-width="6"
48
+ stroke-opacity="0.2"
49
+ stroke-linecap="round"
50
+ />
51
+ <!-- Main connection path -->
52
+ <path
53
+ [attr.d]="path"
54
+ class="connection-path"
55
+ [class.selected]="connection.isSelected"
56
+ [class.array-path]="connection.isArrayMapping"
57
+ [class.array-to-object-path]="connection.isArrayToObjectMapping"
58
+ [attr.stroke]="connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : (connection.isSelected ? '#8b5cf6' : '#6366f1'))"
59
+ fill="none"
60
+ stroke-width="2.5"
61
+ stroke-linecap="round"
62
+ [attr.stroke-dasharray]="connection.isArrayMapping || connection.isArrayToObjectMapping ? '8,4' : 'none'"
63
+ (click)="onConnectionClick(connection, $event)"
64
+ />
65
+ <!-- Invisible wider path for easier clicking -->
66
+ <path
67
+ [attr.d]="path"
68
+ class="connection-hitbox"
69
+ fill="none"
70
+ stroke="transparent"
71
+ stroke-width="20"
72
+ (click)="onConnectionClick(connection, $event)"
73
+ />
74
+ }
75
+
76
+ <!-- Transformation node -->
77
+ <g
78
+ class="transformation-node"
79
+ [class.has-transformation]="connection.hasTransformation"
80
+ [class.is-array-mapping]="connection.isArrayMapping"
81
+ [class.is-array-to-object-mapping]="connection.isArrayToObjectMapping"
82
+ [attr.transform]="'translate(' + connection.midPoint.x + ',' + connection.midPoint.y + ')'"
83
+ (click)="onTransformationNodeClick(connection, $event)"
84
+ >
85
+ <circle r="14" class="node-bg" [attr.fill]="connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : '')" [attr.stroke]="connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : '')" />
86
+ <text
87
+ class="node-icon"
88
+ text-anchor="middle"
89
+ dominant-baseline="central"
90
+ font-family="Material Icons"
91
+ font-size="16"
92
+ [attr.fill]="connection.isArrayMapping || connection.isArrayToObjectMapping ? 'white' : ''"
93
+ >
94
+ {{ getTransformationIcon(connection.mappingId) }}
95
+ </text>
96
+ </g>
97
+
98
+ <!-- Source endpoint circles (draggable) -->
99
+ @for (sourcePoint of connection.sourcePoints; track $index) {
100
+ <circle
101
+ class="endpoint-handle source-endpoint"
102
+ [attr.cx]="sourcePoint.x"
103
+ [attr.cy]="sourcePoint.y"
104
+ r="6"
105
+ [class.selected]="connection.isSelected"
106
+ (mousedown)="onEndpointDragStart(connection, 'source', $index, $event)"
107
+ />
108
+ }
109
+
110
+ <!-- Target endpoint circle (draggable) -->
111
+ <circle
112
+ class="endpoint-handle target-endpoint"
113
+ [attr.cx]="connection.targetPoint.x"
114
+ [attr.cy]="connection.targetPoint.y"
115
+ r="6"
116
+ [class.selected]="connection.isSelected"
117
+ (mousedown)="onEndpointDragStart(connection, 'target', 0, $event)"
118
+ />
119
+ </g>
120
+ }
121
+
122
+ <!-- Drag preview path -->
123
+ @if (dragPath()) {
124
+ <path
125
+ [attr.d]="dragPath()"
126
+ class="drag-path"
127
+ fill="none"
128
+ stroke="#6366f1"
129
+ stroke-width="2.5"
130
+ stroke-dasharray="8,4"
131
+ stroke-linecap="round"
132
+ />
133
+ }
134
+ </svg>
135
+
136
+ <!-- Target Schema Panel -->
137
+ <div class="schema-panel target-panel">
138
+ <schema-tree
139
+ [schema]="targetSchemaForTree()"
140
+ [side]="'target'"
141
+ [mappings]="mappings()"
142
+ [defaultValues]="defaultValues()"
143
+ (fieldDrop)="onFieldDrop($event)"
144
+ (fieldPositionsChanged)="onTargetPositionsChanged($event)"
145
+ (fieldDefaultValueClick)="onDefaultValueClick($event)"
146
+ ></schema-tree>
147
+ </div>
148
+ </div>
149
+
150
+ <!-- Instructions -->
151
+ @if (mappings().length === 0) {
152
+ <div class="empty-state">
153
+ <mat-icon>drag_indicator</mat-icon>
154
+ <p>Drag fields from the source schema to the target schema to create mappings</p>
155
+ </div>
156
+ }
157
+ </div>
158
+
159
+ <!-- Transformation Popover -->
160
+ @if (showPopover() && selectedMapping()) {
161
+ <transformation-popover
162
+ [mapping]="selectedMapping()!"
163
+ [position]="popoverPosition()!"
164
+ [sampleData]="sampleData"
165
+ (save)="onPopoverSave($event)"
166
+ (delete)="onPopoverDelete()"
167
+ (close)="closePopover()"
168
+ ></transformation-popover>
169
+ }
170
+
171
+ <!-- Array Filter Modal -->
172
+ @if (showArrayFilterModal() && selectedArrayMapping()) {
173
+ <array-filter-modal
174
+ [arrayMapping]="selectedArrayMapping()!"
175
+ (save)="onArrayFilterSave($event)"
176
+ (close)="closeArrayFilterModal()"
177
+ ></array-filter-modal>
178
+ }
179
+
180
+ <!-- Array Selector Modal (for array-to-object) -->
181
+ @if (showArraySelectorModal() && selectedArrayToObjectMapping()) {
182
+ <array-selector-modal
183
+ [mapping]="selectedArrayToObjectMapping()!"
184
+ (save)="onArraySelectorSave($event)"
185
+ (close)="closeArraySelectorModal()"
186
+ ></array-selector-modal>
187
+ }
188
+
189
+ <!-- Default Value Popover -->
190
+ @if (showDefaultValuePopover() && selectedDefaultValueField()) {
191
+ <default-value-popover
192
+ [field]="selectedDefaultValueField()!"
193
+ [existingValue]="getExistingDefaultValue(selectedDefaultValueField()!.id)"
194
+ [position]="defaultValuePopoverPosition()!"
195
+ (save)="onDefaultValueSave($event)"
196
+ (delete)="onDefaultValueDelete()"
197
+ (close)="closeDefaultValuePopover()"
198
+ ></default-value-popover>
199
+ }