wuic-framework-lib 1.3.2 → 1.7.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 (27) hide show
  1. package/fesm2022/{wuic-framework-lib-chart-list.component-DAP-TPJR.mjs → wuic-framework-lib-chart-list.component-Bl424n7x.mjs} +49 -37
  2. package/fesm2022/{wuic-framework-lib-code-editor.component-CLMDvOWs.mjs → wuic-framework-lib-code-editor.component-N6HMl2mf.mjs} +441 -277
  3. package/fesm2022/{wuic-framework-lib-designer.component-DA7abiLW.mjs → wuic-framework-lib-designer.component-Bd2fwl6H.mjs} +1948 -641
  4. package/fesm2022/{wuic-framework-lib-first-run-wizard.component-BLrUrfZ9.mjs → wuic-framework-lib-first-run-wizard.component-NN6cWVHL.mjs} +18 -20
  5. package/fesm2022/{wuic-framework-lib-framework-docs.component-DphKhTef.mjs → wuic-framework-lib-framework-docs.component-B867JAYe.mjs} +4205 -380
  6. package/fesm2022/{wuic-framework-lib-pivot-builder.component-BqUUwrJM.mjs → wuic-framework-lib-pivot-builder.component-DWukH0L-.mjs} +578 -66
  7. package/fesm2022/{wuic-framework-lib-prompt-dialog-fallback-host.component-DZrhwMH5.mjs → wuic-framework-lib-prompt-dialog-fallback-host.component-DWO1Rcgv.mjs} +2 -2
  8. package/fesm2022/wuic-framework-lib-report-designer.component-QcrU81m6.mjs +380 -0
  9. package/fesm2022/wuic-framework-lib-report-scaffold-dialog.component-U-zdOoRV.mjs +431 -0
  10. package/fesm2022/{wuic-framework-lib-report-viewer.component-CIN2fY0P.mjs → wuic-framework-lib-report-viewer.component-B4LhA3SL.mjs} +8 -13
  11. package/fesm2022/wuic-framework-lib-scene3d-designer.component-DjOfzKLo.mjs +4813 -0
  12. package/fesm2022/wuic-framework-lib-scene3d-light-baker-gpu-C96T9uIW.mjs +303 -0
  13. package/fesm2022/wuic-framework-lib-scene3d-shader-effect-DjHyCAFK.mjs +2807 -0
  14. package/fesm2022/wuic-framework-lib-scene3d-viewer.component-Dboz1fjl.mjs +1130 -0
  15. package/fesm2022/{wuic-framework-lib-scheduler-list.component-CdcI1Uor.mjs → wuic-framework-lib-scheduler-list.component--wY21R3o.mjs} +169 -53
  16. package/fesm2022/{wuic-framework-lib-spreadsheet-list-sf.component-j1HTRI_P.mjs → wuic-framework-lib-spreadsheet-list-sf.component-Ek30J8v-.mjs} +147 -106
  17. package/fesm2022/{wuic-framework-lib-stimulsoft-license.service-BIyxB4pO.mjs → wuic-framework-lib-stimulsoft-license.service-CsNNSFYs.mjs} +3 -3
  18. package/fesm2022/wuic-framework-lib-timeline-list.component-aqCH4P0G.mjs +805 -0
  19. package/fesm2022/wuic-framework-lib-workflow-designer.component-DMM8BeXm.mjs +9504 -0
  20. package/fesm2022/{wuic-framework-lib-wuic-framework-lib-BRujEwpR.mjs → wuic-framework-lib-wuic-framework-lib-dfLTqam5.mjs} +14713 -8403
  21. package/fesm2022/wuic-framework-lib.mjs +1 -1
  22. package/package.json +5 -2
  23. package/styles/_wuic-tokens.scss +82 -0
  24. package/styles/styles.scss +160 -0
  25. package/types/wuic-framework-lib.d.ts +5971 -3666
  26. package/fesm2022/wuic-framework-lib-report-designer.component-D_qbefud.mjs +0 -194
  27. package/fesm2022/wuic-framework-lib-workflow-designer.component-SE_7ZogW.mjs +0 -5114
@@ -1,21 +1,21 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Pipe, EventEmitter, ElementRef, Renderer2, ChangeDetectorRef, HostListener, Input, Output, Inject, Directive, Component, computed, signal, untracked, inject, NgZone, numberAttribute, DOCUMENT, input, output, isDevMode, InjectionToken, Injector, contentChildren, contentChild, booleanAttribute, effect, HostBinding, ChangeDetectionStrategy } from '@angular/core';
2
+ import { Pipe, EventEmitter, ElementRef, Renderer2, ChangeDetectorRef, HostListener, Input, Output, Inject, Directive, Component, computed, signal, untracked, inject, NgZone, numberAttribute, DOCUMENT, input, output, isDevMode, InjectionToken, Injector, contentChildren, contentChild, booleanAttribute, effect, HostBinding, ChangeDetectionStrategy, ViewChild } from '@angular/core';
3
3
  import * as i10 from 'primeng/splitter';
4
4
  import { SplitterModule } from 'primeng/splitter';
5
5
  import * as i12 from 'primeng/dragdrop';
6
6
  import { DragDropModule } from 'primeng/dragdrop';
7
7
  import { NgClass, NgStyle, NgTemplateOutlet, AsyncPipe, JsonPipe, NgFor, NgIf, NgComponentOutlet } from '@angular/common';
8
- import { W as WtoolboxService, D as DynamicGenericTemplateComponent, a as DynamicCompilerService, b as DataSourceComponent, c as DataRepeaterComponent, F as FilterBarComponent, L as LazyParametricDialogComponent, P as PagerComponent, M as MetadataProviderService, d as ParametricDialogComponent, e as MetadatiColonna, f as DataProviderService, U as UserInfoService, T as TranslationManagerService, g as MetadataEditorService, C as ChatbotHostRegistryService, h as WorkflowRuntimeMetadataService, i as FieldEditorComponent, j as MetadataEditorComponent, A as ArchetypeConfiguratorComponent } from './wuic-framework-lib-wuic-framework-lib-BRujEwpR.mjs';
8
+ import { W as WtoolboxService, D as DynamicGenericTemplateComponent, a as DynamicCompilerService, b as DataSourceComponent, c as DataRepeaterComponent, F as FilterBarComponent, L as LazyParametricDialogComponent, P as PagerComponent, M as MetadataProviderService, t as toHsbaModel, d as clampAlpha, e as toPersistedColor, f as ParametricDialogComponent, g as MetadatiColonna, h as DataProviderService, U as UserInfoService, T as TranslationManagerService, i as MetadataEditorService, C as ChatbotHostRegistryService, j as WorkflowRuntimeMetadataService, k as FieldEditorComponent, l as MetadataEditorComponent, A as ArchetypeConfiguratorComponent } from './wuic-framework-lib-wuic-framework-lib-dfLTqam5.mjs';
9
9
  import { fromEvent, BehaviorSubject, merge, filter, Observable, switchMap, take, map, takeUntil, tap, timeInterval, scan, mergeMap, of, delay, repeat, Subject, startWith, pairwise, skipWhile, combineLatest } from 'rxjs';
10
10
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
11
11
  import { CdkAccordionModule, CdkAccordion, CdkAccordionItem } from '@angular/cdk/accordion';
12
- import * as i6 from 'primeng/select';
12
+ import * as i5 from 'primeng/select';
13
13
  import { SelectModule } from 'primeng/select';
14
14
  import * as i1 from '@angular/forms';
15
15
  import { FormsModule } from '@angular/forms';
16
- import * as i2$1 from 'primeng/colorpicker';
16
+ import * as i6 from 'primeng/colorpicker';
17
17
  import { ColorPickerModule } from 'primeng/colorpicker';
18
- import * as i2$2 from 'primeng/autocomplete';
18
+ import * as i2$1 from 'primeng/autocomplete';
19
19
  import { AutoCompleteModule } from 'primeng/autocomplete';
20
20
  import * as i8 from '@ngx-translate/core';
21
21
  import { TranslateModule } from '@ngx-translate/core';
@@ -33,7 +33,7 @@ import * as i18 from 'primeng/multiselect';
33
33
  import { MultiSelectModule } from 'primeng/multiselect';
34
34
  import * as i2 from 'primeng/dialog';
35
35
  import { DialogModule } from 'primeng/dialog';
36
- import * as i9 from 'primeng/menu';
36
+ import * as i7 from 'primeng/menu';
37
37
  import { MenuModule } from 'primeng/menu';
38
38
  import * as i4$1 from 'primeng/tree';
39
39
  import { TreeModule } from 'primeng/tree';
@@ -43,7 +43,7 @@ class PropConverterPipe {
43
43
  if (value) {
44
44
  if (args.length > 0) {
45
45
  if (args[0] === 'toArray') {
46
- return Array(parseInt(value.toString())).fill(1).map((x, i) => i);
46
+ return new Array(Number.parseInt(value.toString())).fill(1).map((x, i) => i);
47
47
  }
48
48
  if (args[0] === 'fromArray') {
49
49
  return value.length;
@@ -68,8 +68,8 @@ class NgxDraggablePoint {
68
68
  x;
69
69
  y;
70
70
  constructor(x, y) {
71
- this.x = (!!x) ? x : 0;
72
- this.y = (!!y) ? y : 0;
71
+ this.x = x || 0;
72
+ this.y = y || 0;
73
73
  }
74
74
  }
75
75
 
@@ -136,10 +136,10 @@ class NgxDraggableRect {
136
136
  };
137
137
  }
138
138
  constructor(x, y, width, height) {
139
- this.x = (!!x) ? x : 0;
140
- this.y = (!!y) ? y : 0;
141
- this.width = (!!width) ? width : 0;
142
- this.height = (!!height) ? height : 0;
139
+ this.x = x || 0;
140
+ this.y = y || 0;
141
+ this.width = width || 0;
142
+ this.height = height || 0;
143
143
  }
144
144
  }
145
145
 
@@ -175,15 +175,15 @@ class NgxDraggableDomBoundsCheckEvent {
175
175
  * @param isConstrained Whether the element should be constrained or not.
176
176
  */
177
177
  constructor(top, right, bottom, left, elP0, translation, isConstrained) {
178
- this.top = (!!top) ? top : false;
179
- this.right = (!!right) ? right : false;
180
- this.bottom = (!!bottom) ? bottom : false;
181
- this.left = (!!left) ? left : false;
182
- if (!!elP0) {
178
+ this.top = top ?? false;
179
+ this.right = right ?? false;
180
+ this.bottom = bottom ?? false;
181
+ this.left = left ?? false;
182
+ if (elP0) {
183
183
  this.constrainedCenter = elP0;
184
184
  }
185
- this.translation = (!!translation) ? translation : new NgxDraggablePoint(0, 0);
186
- this.isConstrained = (!!isConstrained) ? isConstrained : false;
185
+ this.translation = translation ?? new NgxDraggablePoint(0, 0);
186
+ this.isConstrained = isConstrained ?? false;
187
187
  }
188
188
  }
189
189
 
@@ -217,8 +217,8 @@ class NgxDraggableDomMoveEvent {
217
217
  * @param position The position of the target HTMLElement.
218
218
  */
219
219
  constructor(target, position) {
220
- this._target = (!!target) ? target : undefined;
221
- this._position = (!!position) ? position : undefined;
220
+ this._target = target || undefined;
221
+ this._position = position || undefined;
222
222
  }
223
223
  }
224
224
 
@@ -386,11 +386,11 @@ class NgxDraggableDomUtilities {
386
386
  static getTransformMatrixForElement(el) {
387
387
  // create the numerical matrix we will use
388
388
  const matrix = [1, 0, 0, 1, 0, 0];
389
- if (window) {
389
+ if (globalThis) {
390
390
  // get the computed transform style
391
- let transform = window.getComputedStyle(el, null).getPropertyValue('transform');
391
+ let transform = globalThis.getComputedStyle(el, null).getPropertyValue('transform');
392
392
  // strip non matrix values from the string
393
- transform = transform.replace(/matrix/g, '').replace(/\(/g, '').replace(/\)/g, '').replace(/ /g, '');
393
+ transform = transform.replaceAll('matrix', '').replaceAll('(', '').replaceAll(')', '').replaceAll(' ', '');
394
394
  // if we have a transform set, convert the string matrix to a numerical one
395
395
  if (transform !== 'none') {
396
396
  // split the string based on commas
@@ -427,7 +427,7 @@ class NgxDraggableDomUtilities {
427
427
  */
428
428
  static getTotalRotationForElement(node, rotation = 0) {
429
429
  // if we can't calculate the computed style or we have no node to analyze, return the current calculated rotation
430
- if (!node || !window) {
430
+ if (!node || !globalThis) {
431
431
  return rotation;
432
432
  }
433
433
  // if we have reached the body, stop processing beyond here
@@ -439,6 +439,23 @@ class NgxDraggableDomUtilities {
439
439
  }
440
440
  }
441
441
 
442
+ /**
443
+ * Stato condiviso dello snap-to-grid del designer.
444
+ *
445
+ * Sorgente di verita' NEUTRA (nessun import Angular) letta dalle direttive di
446
+ * drag (`NgxDraggableDomDirective`) e resize (`AngularResizableDirective`) e
447
+ * scritta da `DesignerComponent.toggleSnapToGrid`. Serve perche' quelle
448
+ * direttive sono applicate agli elementi via TEMPLATE DINAMICO generato
449
+ * (`DynamicDashboardTemplateComponent` riscrive `ngxDraggableDom="true"`), dove
450
+ * non e' pratico bindare un `[snapGridSize]` per-elemento. Un modulo neutro
451
+ * evita anche l'import circolare direttiva <-> componente.
452
+ *
453
+ * `gridSize` = passo in px quando lo snap e' attivo, 0 = snap off.
454
+ */
455
+ const DesignerSnapState = {
456
+ gridSize: 0,
457
+ };
458
+
442
459
  class NgxDraggableDomDirective {
443
460
  el;
444
461
  renderer;
@@ -510,7 +527,7 @@ class NgxDraggableDomDirective {
510
527
  return null;
511
528
  }
512
529
  // get the bounding box of the element
513
- const boundsBounds = this.bounds.getBoundingClientRect();
530
+ const boundsBounds = (this.bounds).getBoundingClientRect();
514
531
  return new NgxDraggablePoint(this.scrollLeft + boundsBounds.left + (boundsBounds.width / 2), this.scrollTop + boundsBounds.top + (boundsBounds.height / 2));
515
532
  }
516
533
  /**
@@ -519,7 +536,7 @@ class NgxDraggableDomDirective {
519
536
  * @return The current scroll position of the document in the x direction.
520
537
  */
521
538
  get scrollLeft() {
522
- if (!!window) {
539
+ if (globalThis) {
523
540
  return window.pageXOffset;
524
541
  }
525
542
  else if (!!document && !!document.documentElement) {
@@ -535,7 +552,7 @@ class NgxDraggableDomDirective {
535
552
  * @return The current scroll position of the document in the y direction.
536
553
  */
537
554
  get scrollTop() {
538
- if (!!window) {
555
+ if (globalThis) {
539
556
  return window.pageYOffset;
540
557
  }
541
558
  else if (!!document && !!document.documentElement) {
@@ -637,7 +654,7 @@ class NgxDraggableDomDirective {
637
654
  */
638
655
  onTouchStart(event) {
639
656
  // block multiTouch events if we are configured to do so
640
- if (this.ignoreMultiTouchEvents && event && event.touches && event.touches.length > 1) {
657
+ if (this.ignoreMultiTouchEvents && event?.touches && event.touches.length > 1) {
641
658
  return;
642
659
  }
643
660
  // stop all default behavior and propagation of the event so it is fully consumed by us
@@ -732,13 +749,8 @@ class NgxDraggableDomDirective {
732
749
  * @param event The touch event to handle as a TouchEvent (or any solely for working around issues with Safari).
733
750
  */
734
751
  onTouchEnd(event) {
735
- // stop all default behavior and propagation of the event so it is fully consumed by us
736
- event.stopImmediatePropagation();
737
- // prevent the default mouse behavior on images so that browser image dragging is disabled
738
- if (event.target instanceof HTMLImageElement) {
739
- event.preventDefault();
740
- }
741
- this.putBack();
752
+ // same consume-and-put-back behavior as the mouse release handler
753
+ this.onMouseUp(event);
742
754
  }
743
755
  /**
744
756
  * Event handler for when the element is moved via a touch event.
@@ -747,7 +759,7 @@ class NgxDraggableDomDirective {
747
759
  */
748
760
  onTouchMove(event) {
749
761
  // block multiTouch events if we are configured to do so
750
- if (this.ignoreMultiTouchEvents && event && event.touches && event.touches.length > 1) {
762
+ if (this.ignoreMultiTouchEvents && event?.touches && event.touches.length > 1) {
751
763
  // ensure any element that is being dragged is put back
752
764
  this.putBack(false);
753
765
  return;
@@ -835,13 +847,23 @@ class NgxDraggableDomDirective {
835
847
  // check the bounds based on the element position
836
848
  boundsCheck = this.boundsCheck(new NgxDraggablePoint(dragPosition.x, dragPosition.y));
837
849
  // hold the element in position if we are requested to be constrained
838
- if (boundsCheck && boundsCheck.isConstrained) {
850
+ if (boundsCheck?.isConstrained) {
839
851
  // update the translation using the constrained center point and bounds center
840
852
  translation = boundsCheck.translation;
841
853
  }
842
854
  }
855
+ // Designer snap-to-grid: arrotonda la posizione ASSOLUTA dell'elemento al
856
+ // multiplo di griglia, poi riporta il delta nel translation. `offsetLeft`/
857
+ // `offsetTop` NON includono il transform, quindi (offset + translation) e' la
858
+ // posizione visiva reale sul canvas (offsetParent). No-op se snap off.
859
+ const _snapGrid = DesignerSnapState.gridSize;
860
+ if (_snapGrid && _snapGrid > 0) {
861
+ const _dragEl = this.el.nativeElement;
862
+ translation.x = Math.round((_dragEl.offsetLeft + translation.x) / _snapGrid) * _snapGrid - _dragEl.offsetLeft;
863
+ translation.y = Math.round((_dragEl.offsetTop + translation.y) / _snapGrid) * _snapGrid - _dragEl.offsetTop;
864
+ }
843
865
  // if it is possible, get the transform from the computed style and modify the matrix to maintain transform properties
844
- if (window) {
866
+ if (globalThis) {
845
867
  // update the x and y values as part of the matrix
846
868
  matrix[4] = translation.x;
847
869
  matrix[5] = translation.y;
@@ -888,9 +910,9 @@ class NgxDraggableDomDirective {
888
910
  this.oldZIndex = this.el.nativeElement.style.zIndex ? this.el.nativeElement.style.zIndex : '';
889
911
  this.oldPosition = this.el.nativeElement.style.position ? this.el.nativeElement.style.position : '';
890
912
  // fetch the old z-index and position from computing the style applied to the element
891
- if (window) {
892
- this.oldZIndex = window.getComputedStyle(this.el.nativeElement, null).getPropertyValue('z-index');
893
- this.oldPosition = window.getComputedStyle(this.el.nativeElement, null).getPropertyValue('position');
913
+ if (globalThis) {
914
+ this.oldZIndex = globalThis.getComputedStyle(this.el.nativeElement, null).getPropertyValue('z-index');
915
+ this.oldPosition = globalThis.getComputedStyle(this.el.nativeElement, null).getPropertyValue('position');
894
916
  }
895
917
  // check if old position is draggable
896
918
  if (this.oldPosition && (this.oldPosition === 'absolute' ||
@@ -927,7 +949,7 @@ class NgxDraggableDomDirective {
927
949
  // compute the current rotation of all parent nodes
928
950
  this.computedRotation = NgxDraggableDomUtilities.getTotalRotationForElement(this.el.nativeElement.parentElement);
929
951
  // normalize the start position for the rotation
930
- this.startPosition = NgxDraggableMath.rotatePoint(this.startPosition, (!!boundsCenter) ? boundsCenter : new NgxDraggablePoint(0, 0), -this.computedRotation);
952
+ this.startPosition = NgxDraggableMath.rotatePoint(this.startPosition, boundsCenter ?? new NgxDraggablePoint(0, 0), -this.computedRotation);
931
953
  // get the current transformation matrix and extract the current translation
932
954
  matrix = NgxDraggableDomUtilities.getTransformMatrixForElement(this.el.nativeElement);
933
955
  translation.x = matrix[4];
@@ -936,7 +958,7 @@ class NgxDraggableDomDirective {
936
958
  this.startPosition.x -= translation.x;
937
959
  this.startPosition.y -= translation.y;
938
960
  // reapply the rotation to the start position
939
- this.startPosition = NgxDraggableMath.rotatePoint(this.startPosition, (!!boundsCenter) ? boundsCenter : new NgxDraggablePoint(0, 0), this.computedRotation);
961
+ this.startPosition = NgxDraggableMath.rotatePoint(this.startPosition, boundsCenter ?? new NgxDraggablePoint(0, 0), this.computedRotation);
940
962
  // determine the clientX and clientY position based on the event type
941
963
  const clientX = (event instanceof MouseEvent) ? event.clientX : event.changedTouches[0]?.clientX;
942
964
  const clientY = (event instanceof MouseEvent) ? event.clientY : event.changedTouches[0]?.clientY;
@@ -994,7 +1016,7 @@ class NgxDraggableDomDirective {
994
1016
  if (this.bounds) {
995
1017
  // get the current center point of the element
996
1018
  const elCenter = this.elCenter;
997
- if (!!elCenter) {
1019
+ if (elCenter) {
998
1020
  // check the bounds based on the element position
999
1021
  const boundsCheck = this.boundsCheck(elCenter);
1000
1022
  // emit the edge event so consumers know the current state of the position
@@ -1116,7 +1138,7 @@ class NgxDraggableDomDirective {
1116
1138
  if (isBLOutside && elBL.x >= (checkBounds.left + checkBounds.width) &&
1117
1139
  NgxDraggableMath.getDistanceBetweenPoints(elBL, boundsP0) > greatestConstrainDistance) {
1118
1140
  constrainPoint = new NgxDraggablePoint(elBL.x, elBL.y);
1119
- greatestConstrainDistance = NgxDraggableMath.getDistanceBetweenPoints(elBL, boundsP0);
1141
+ NgxDraggableMath.getDistanceBetweenPoints(elBL, boundsP0);
1120
1142
  }
1121
1143
  // calculate the displacement
1122
1144
  displaceX = checkBounds.left + checkBounds.width - (constrainPoint ? constrainPoint.x : 0);
@@ -1141,7 +1163,7 @@ class NgxDraggableDomDirective {
1141
1163
  if (isBLOutside && elBL.x <= checkBounds.left &&
1142
1164
  NgxDraggableMath.getDistanceBetweenPoints(elBL, boundsP0) > greatestConstrainDistance) {
1143
1165
  constrainPoint = new NgxDraggablePoint(elBL.x, elBL.y);
1144
- greatestConstrainDistance = NgxDraggableMath.getDistanceBetweenPoints(elBL, boundsP0);
1166
+ NgxDraggableMath.getDistanceBetweenPoints(elBL, boundsP0);
1145
1167
  }
1146
1168
  // calculate the displacement
1147
1169
  displaceX = checkBounds.left - (constrainPoint ? constrainPoint.x : 0);
@@ -1169,7 +1191,7 @@ class NgxDraggableDomDirective {
1169
1191
  if (isBLOutside && elBL.y >= (checkBounds.top + checkBounds.height) &&
1170
1192
  NgxDraggableMath.getDistanceBetweenPoints(elBL, boundsP0) > greatestConstrainDistance) {
1171
1193
  constrainPoint = new NgxDraggablePoint(elBL.x, elBL.y);
1172
- greatestConstrainDistance = NgxDraggableMath.getDistanceBetweenPoints(elBL, boundsP0);
1194
+ NgxDraggableMath.getDistanceBetweenPoints(elBL, boundsP0);
1173
1195
  }
1174
1196
  // calculate the displacement
1175
1197
  displaceY = checkBounds.top + checkBounds.height - (constrainPoint ? constrainPoint.y : 0);
@@ -1194,7 +1216,7 @@ class NgxDraggableDomDirective {
1194
1216
  if (isBLOutside && elBL.y <= checkBounds.top &&
1195
1217
  NgxDraggableMath.getDistanceBetweenPoints(elBL, boundsP0) > greatestConstrainDistance) {
1196
1218
  constrainPoint = new NgxDraggablePoint(elBL.x, elBL.y);
1197
- greatestConstrainDistance = NgxDraggableMath.getDistanceBetweenPoints(elBL, boundsP0);
1219
+ NgxDraggableMath.getDistanceBetweenPoints(elBL, boundsP0);
1198
1220
  }
1199
1221
  // calculate the displacement
1200
1222
  displaceY = checkBounds.top - (constrainPoint ? constrainPoint.y : 0);
@@ -1318,7 +1340,7 @@ class ResizeHandle {
1318
1340
  this._handle.removeEventListener('mousedown', this._onResize);
1319
1341
  this._handle.removeEventListener('touchstart', this._onResize);
1320
1342
  if (this.parent && !this.existHandle) {
1321
- this.parent.removeChild(this._handle);
1343
+ this._handle.remove();
1322
1344
  }
1323
1345
  this._handle = null;
1324
1346
  this._onResize = null;
@@ -1352,9 +1374,9 @@ class Position {
1352
1374
  * Fix issue: Multiple phone draggables at the same time
1353
1375
  * https://github.com/xieziyu/angular2-draggable/issues/128
1354
1376
  */
1355
- for (let i = 0; i < e.changedTouches.length; i++) {
1356
- if (e.changedTouches[i].target === el) {
1357
- return new Position(e.changedTouches[i].clientX, e.changedTouches[i].clientY);
1377
+ for (const touch of e.changedTouches) {
1378
+ if (touch.target === el) {
1379
+ return new Position(touch.clientX, touch.clientY);
1358
1380
  }
1359
1381
  }
1360
1382
  }
@@ -1368,13 +1390,13 @@ class Position {
1368
1390
  }
1369
1391
  static getCurrent(el) {
1370
1392
  let pos = new Position(0, 0);
1371
- if (window) {
1372
- const computed = window.getComputedStyle(el);
1393
+ if (globalThis) {
1394
+ const computed = globalThis.getComputedStyle(el);
1373
1395
  if (computed) {
1374
- let x = parseInt(computed.getPropertyValue('left'), 10);
1375
- let y = parseInt(computed.getPropertyValue('top'), 10);
1376
- pos.x = isNaN(x) ? 0 : x;
1377
- pos.y = isNaN(y) ? 0 : y;
1396
+ let x = Number.parseInt(computed.getPropertyValue('left'), 10);
1397
+ let y = Number.parseInt(computed.getPropertyValue('top'), 10);
1398
+ pos.x = Number.isNaN(Number(x)) ? 0 : x;
1399
+ pos.y = Number.isNaN(Number(y)) ? 0 : y;
1378
1400
  }
1379
1401
  return pos;
1380
1402
  }
@@ -1428,11 +1450,11 @@ class Size {
1428
1450
  }
1429
1451
  static getCurrent(el) {
1430
1452
  let size = new Size(0, 0);
1431
- if (window) {
1432
- const computed = window.getComputedStyle(el);
1453
+ if (globalThis) {
1454
+ const computed = globalThis.getComputedStyle(el);
1433
1455
  if (computed) {
1434
- size.width = parseInt(computed.getPropertyValue('width'), 10);
1435
- size.height = parseInt(computed.getPropertyValue('height'), 10);
1456
+ size.width = Number.parseInt(computed.getPropertyValue('width'), 10);
1457
+ size.height = Number.parseInt(computed.getPropertyValue('height'), 10);
1436
1458
  }
1437
1459
  return size;
1438
1460
  }
@@ -1479,7 +1501,7 @@ class HelperBlock {
1479
1501
  }
1480
1502
  remove() {
1481
1503
  if (this.parent && this._added) {
1482
- this.parent.removeChild(this._helper);
1504
+ this._helper.remove();
1483
1505
  this._added = false;
1484
1506
  }
1485
1507
  }
@@ -1666,7 +1688,7 @@ class AngularResizableDirective {
1666
1688
  }
1667
1689
  else {
1668
1690
  let r = Number(this.rzAspectRatio);
1669
- this._aspectRatio = isNaN(r) ? 0 : r;
1691
+ this._aspectRatio = Number.isNaN(Number(r)) ? 0 : r;
1670
1692
  }
1671
1693
  }
1672
1694
  /** Use it to update containment */
@@ -1698,7 +1720,7 @@ class AngularResizableDirective {
1698
1720
  tmpHandleTypes = ['n', 'e', 's', 'w', 'ne', 'se', 'nw', 'sw'];
1699
1721
  }
1700
1722
  else {
1701
- tmpHandleTypes = this.rzHandles.replace(/ /g, '').toLowerCase().split(',');
1723
+ tmpHandleTypes = this.rzHandles.replaceAll(' ', '').toLowerCase().split(',');
1702
1724
  }
1703
1725
  for (let type of tmpHandleTypes) {
1704
1726
  // default handle theme: ng-resizable-$type.
@@ -1725,7 +1747,7 @@ class AngularResizableDirective {
1725
1747
  createHandleByType(type, css) {
1726
1748
  const _el = this.el.nativeElement;
1727
1749
  const _h = this.rzHandleDoms[type] ? this.rzHandleDoms[type].nativeElement : null;
1728
- if (!type.match(/^(se|sw|ne|nw|n|e|s|w)$/)) {
1750
+ if (!/^(se|sw|ne|nw|n|e|s|w)$/.exec(type)) {
1729
1751
  console.error('Invalid handle type:', type);
1730
1752
  return null;
1731
1753
  }
@@ -1759,7 +1781,7 @@ class AngularResizableDirective {
1759
1781
  this.draggingSub.add(fromEvent(document, 'touchmove', { passive: false }).subscribe(event => this.onMouseMove(event)));
1760
1782
  this.draggingSub.add(fromEvent(document, 'mouseup', { passive: false }).subscribe(() => this.onMouseLeave()));
1761
1783
  // fix for issue #164
1762
- let isIEOrEdge = /msie\s|trident\//i.test(window.navigator.userAgent);
1784
+ let isIEOrEdge = /msie\s|trident\//i.test(globalThis.navigator.userAgent);
1763
1785
  if (!isIEOrEdge) {
1764
1786
  this.draggingSub.add(fromEvent(document, 'mouseleave', { passive: false }).subscribe(() => this.onMouseLeave()));
1765
1787
  }
@@ -1835,10 +1857,10 @@ class AngularResizableDirective {
1835
1857
  }
1836
1858
  updateDirection() {
1837
1859
  this._direction = {
1838
- n: !!this._handleResizing.type.match(/n/),
1839
- s: !!this._handleResizing.type.match(/s/),
1840
- w: !!this._handleResizing.type.match(/w/),
1841
- e: !!this._handleResizing.type.match(/e/),
1860
+ n: /n/.test(this._handleResizing.type),
1861
+ s: /s/.test(this._handleResizing.type),
1862
+ w: /w/.test(this._handleResizing.type),
1863
+ e: /e/.test(this._handleResizing.type),
1842
1864
  };
1843
1865
  this._directionChanged = { ...this._direction };
1844
1866
  // if aspect ration should be preserved:
@@ -2002,24 +2024,24 @@ class AngularResizableDirective {
2002
2024
  }
2003
2025
  getBounding() {
2004
2026
  const el = this._containment;
2005
- const computed = window.getComputedStyle(el);
2027
+ const computed = globalThis.getComputedStyle(el);
2006
2028
  if (computed) {
2007
2029
  let p = computed.getPropertyValue('position');
2008
- const nativeEl = window.getComputedStyle(this.el.nativeElement);
2030
+ const nativeEl = globalThis.getComputedStyle(this.el.nativeElement);
2009
2031
  let transforms = nativeEl
2010
2032
  .getPropertyValue('transform')
2011
- .replace(/[^-\d,]/g, '')
2033
+ .replaceAll(/[^-\d,]/g, '')
2012
2034
  .split(',');
2013
2035
  this._bounding = {};
2014
2036
  this._bounding.width = el.clientWidth;
2015
2037
  this._bounding.height = el.clientHeight;
2016
- this._bounding.pr = parseInt(computed.getPropertyValue('padding-right'), 10);
2017
- this._bounding.pb = parseInt(computed.getPropertyValue('padding-bottom'), 10);
2038
+ this._bounding.pr = Number.parseInt(computed.getPropertyValue('padding-right'), 10);
2039
+ this._bounding.pb = Number.parseInt(computed.getPropertyValue('padding-bottom'), 10);
2018
2040
  this._bounding.deltaL = this.el.nativeElement.offsetLeft - this._currPos.x;
2019
2041
  this._bounding.deltaT = this.el.nativeElement.offsetTop - this._currPos.y;
2020
2042
  if (transforms.length >= 6) {
2021
- this._bounding.translateX = parseInt(transforms[4], 10);
2022
- this._bounding.translateY = parseInt(transforms[5], 10);
2043
+ this._bounding.translateX = Number.parseInt(transforms[4], 10);
2044
+ this._bounding.translateY = Number.parseInt(transforms[5], 10);
2023
2045
  }
2024
2046
  else {
2025
2047
  this._bounding.translateX = 0;
@@ -2032,7 +2054,7 @@ class AngularResizableDirective {
2032
2054
  }
2033
2055
  }
2034
2056
  resetBounding() {
2035
- if (this._bounding && this._bounding.position === 'static') {
2057
+ if (this._bounding?.position === 'static') {
2036
2058
  this.renderer.setStyle(this._containment, 'position', 'relative');
2037
2059
  }
2038
2060
  this._bounding = null;
@@ -2048,6 +2070,12 @@ class AngularResizableDirective {
2048
2070
  this._gridSize = { x: this.rzGrid[0], y: this.rzGrid[1] };
2049
2071
  }
2050
2072
  }
2073
+ else if (DesignerSnapState.gridSize && DesignerSnapState.gridSize > 0) {
2074
+ // Designer snap-to-grid (stato condiviso): usa il passo griglia quando lo
2075
+ // snap e' attivo e nessun `rzGrid` esplicito e' bindato (il designer
2076
+ // applica la direttiva via template dinamico, senza binding per-elemento).
2077
+ this._gridSize = { x: DesignerSnapState.gridSize, y: DesignerSnapState.gridSize };
2078
+ }
2051
2079
  }
2052
2080
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AngularResizableDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
2053
2081
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: AngularResizableDirective, isStandalone: true, selector: "[ngResizable]", inputs: { ngResizable: "ngResizable", rzHandles: "rzHandles", rzHandleDoms: "rzHandleDoms", rzAspectRatio: "rzAspectRatio", rzContainment: "rzContainment", rzGrid: "rzGrid", rzMinWidth: "rzMinWidth", rzMinHeight: "rzMinHeight", rzMaxWidth: "rzMaxWidth", rzMaxHeight: "rzMaxHeight", rzScale: "rzScale", preventDefaultEvent: "preventDefaultEvent" }, outputs: { rzStart: "rzStart", rzResizing: "rzResizing", rzStop: "rzStop" }, usesOnChanges: true, ngImport: i0 });
@@ -2104,7 +2132,7 @@ class DataBoundDirective {
2104
2132
  this.cd = cd;
2105
2133
  }
2106
2134
  async ngOnChanges(changes) {
2107
- if (changes['databound'] && changes['databound'].currentValue) {
2135
+ if (changes['databound']?.currentValue) {
2108
2136
  const source = changes['databound'].currentValue;
2109
2137
  if (!(source instanceof BehaviorSubject)) {
2110
2138
  return;
@@ -2125,17 +2153,17 @@ class DataBoundDirective {
2125
2153
  });
2126
2154
  });
2127
2155
  }
2128
- else if (changes['bindingFunction'] && changes['bindingFunction'].currentValue && this.ds) {
2156
+ else if (changes['bindingFunction']?.currentValue && this.ds) {
2129
2157
  new Function('resultInfo, metaInfo, inputs, wtoolbox', this.bindingFunction)(this.ds.resultInfo, this.ds.metaInfo, this.inputs, WtoolboxService);
2130
2158
  this.cd.detectChanges();
2131
2159
  }
2132
- else if (changes['itemTemplate'] && changes['itemTemplate'].currentValue && changes['itemTemplate'].previousValue != changes['itemTemplate'].currentValue) {
2160
+ else if (changes['itemTemplate']?.currentValue && changes['itemTemplate'].previousValue != changes['itemTemplate'].currentValue) {
2133
2161
  this.inputs.itemTemplateComponent = DynamicGenericTemplateComponent.getComponentFromTemplate(this.inputs.itemTemplate || '', '[data-bound] itemTemplate (host-supplied)', String(this.ds?.metaInfo?.tableMetadata?.md_route_name || ''));
2134
2162
  if (this.ds) {
2135
2163
  await this.ds.fetchData();
2136
2164
  }
2137
2165
  }
2138
- else if (changes['clickCallback'] && changes['clickCallback'].currentValue) {
2166
+ else if (changes['clickCallback']?.currentValue) {
2139
2167
  this.inputs._wtoolbox = WtoolboxService;
2140
2168
  this.inputs.clickCallback__fn = new Function('resultInfo, metaInfo, inputs, event, wtoolbox', this.clickCallback);
2141
2169
  this.cd.detectChanges();
@@ -2202,11 +2230,11 @@ class TabViewWrapperComponent {
2202
2230
  });
2203
2231
  }
2204
2232
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: TabViewWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2205
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: TabViewWrapperComponent, isStandalone: true, selector: "p-tabView", inputs: { tabs: "tabs", activeIndex: "activeIndex" }, ngImport: i0, template: "<div data-pc-name=\"tabview\" class=\"p-tabview p-component\">\n <div class=\"p-tabview-nav-container\">\n <div class=\"p-tabview-nav-content\" data-pc-section=\"navcontent\">\n <ul role=\"tablist\" class=\"p-tabview-nav\" data-pc-section=\"nav\">\n @for (tab of tabs; track tab; let i = $index) {\n <li role=\"presentation\" data-p-disabled=\"false\"\n [ngClass]=\"{'p-highlight': activeIndex == i}\">\n <button type=\"button\" role=\"tab\" pripple=\"\" class=\"p-element p-ripple p-tabview-nav-link\" (click)=\"setIndex(i)\"\n [attr.aria-selected]=\"activeIndex == i\" [attr.tabindex]=\"i\" aria-disabled=\"false\"\n [attr.data-pc-index]=\"i\" data-pc-section=\"headeraction\">\n <span class=\"p-tabview-title\">\n {{tab.inputs.header}}\n </span>\n </button>\n </li>\n }\n </ul>\n </div>\n </div>\n <div class=\"p-tabview-panels\">\n <ng-content></ng-content>\n </div>\n</div>", styles: [".p-tabview.p-component{height:100%}.p-tabview .p-tabview-nav-content{scroll-padding-inline:3rem}.p-tabview-nav-content{overflow-x:auto;overflow-y:hidden;scroll-behavior:smooth;scrollbar-width:none;overscroll-behavior:contain auto}.p-tabview-nav{display:inline-flex;min-width:100%;margin:0;padding:0;list-style-type:none;flex:1 1 auto}.p-tabview .p-tabview-nav li .p-tabview-nav-link{border-width:0 0 2px 0;padding:1.25rem;font-weight:700;border-top-right-radius:6px;border-top-left-radius:6px;transition:box-shadow .2s;margin:0 0 -2px}.p-tabview-nav-link{appearance:none;background:transparent;border:none;font:inherit;color:inherit;cursor:pointer;-webkit-user-select:none;user-select:none;display:flex;align-items:center;position:relative;text-decoration:none;overflow:hidden;width:100%}.p-tabview .p-tabview-nav li.p-highlight .p-tabview-nav-link{border-color:var(--p-primary-color, #3B82F6);color:var(--p-primary-color, #3B82F6);background-color:transparent}.p-tabview-panels{height:calc(100% - 60px);overflow:auto}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
2233
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: TabViewWrapperComponent, isStandalone: true, selector: "p-tabView", inputs: { tabs: "tabs", activeIndex: "activeIndex" }, ngImport: i0, template: "<div data-pc-name=\"tabview\" class=\"p-tabview p-component\">\n <div class=\"p-tabview-nav-container\">\n <div class=\"p-tabview-nav-content\" data-pc-section=\"navcontent\">\n <ul role=\"tablist\" class=\"p-tabview-nav\" data-pc-section=\"nav\">\n @for (tab of tabs; track tab; let i = $index) {\n <li role=\"presentation\" data-p-disabled=\"false\"\n [ngClass]=\"{'p-highlight': tab.inputs?.selected}\">\n <button type=\"button\" role=\"tab\" pripple=\"\" class=\"p-element p-ripple p-tabview-nav-link\" (click)=\"setIndex(i)\"\n [attr.aria-selected]=\"tab.inputs?.selected\" [attr.tabindex]=\"i\" aria-disabled=\"false\"\n [attr.data-pc-index]=\"i\" data-pc-section=\"headeraction\">\n <span class=\"p-tabview-title\">\n {{tab.inputs.header}}\n </span>\n </button>\n </li>\n }\n </ul>\n </div>\n </div>\n <div class=\"p-tabview-panels\">\n <ng-content></ng-content>\n </div>\n</div>", styles: [".p-tabview.p-component{height:100%}.p-tabview .p-tabview-nav-content{scroll-padding-inline:3rem}.p-tabview-nav-content{overflow-x:auto;overflow-y:hidden;scroll-behavior:smooth;scrollbar-width:none;overscroll-behavior:contain auto}.p-tabview-nav{display:inline-flex;min-width:100%;margin:0;padding:0;list-style-type:none;flex:1 1 auto}.p-tabview .p-tabview-nav li .p-tabview-nav-link{border-width:0 0 2px 0;padding:1.25rem;font-weight:700;border-top-right-radius:6px;border-top-left-radius:6px;transition:box-shadow .2s;margin:0 0 -2px}.p-tabview-nav-link{appearance:none;background:transparent;border:none;font:inherit;color:inherit;cursor:pointer;-webkit-user-select:none;user-select:none;display:flex;align-items:center;position:relative;text-decoration:none;overflow:hidden;width:100%}.p-tabview .p-tabview-nav li.p-highlight .p-tabview-nav-link{border-color:var(--p-primary-color, #3B82F6);color:var(--p-primary-color, #3B82F6);background-color:transparent}.p-tabview-panels{height:calc(100% - 60px);overflow:auto}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
2206
2234
  }
2207
2235
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: TabViewWrapperComponent, decorators: [{
2208
2236
  type: Component,
2209
- args: [{ selector: 'p-tabView', imports: [NgClass], template: "<div data-pc-name=\"tabview\" class=\"p-tabview p-component\">\n <div class=\"p-tabview-nav-container\">\n <div class=\"p-tabview-nav-content\" data-pc-section=\"navcontent\">\n <ul role=\"tablist\" class=\"p-tabview-nav\" data-pc-section=\"nav\">\n @for (tab of tabs; track tab; let i = $index) {\n <li role=\"presentation\" data-p-disabled=\"false\"\n [ngClass]=\"{'p-highlight': activeIndex == i}\">\n <button type=\"button\" role=\"tab\" pripple=\"\" class=\"p-element p-ripple p-tabview-nav-link\" (click)=\"setIndex(i)\"\n [attr.aria-selected]=\"activeIndex == i\" [attr.tabindex]=\"i\" aria-disabled=\"false\"\n [attr.data-pc-index]=\"i\" data-pc-section=\"headeraction\">\n <span class=\"p-tabview-title\">\n {{tab.inputs.header}}\n </span>\n </button>\n </li>\n }\n </ul>\n </div>\n </div>\n <div class=\"p-tabview-panels\">\n <ng-content></ng-content>\n </div>\n</div>", styles: [".p-tabview.p-component{height:100%}.p-tabview .p-tabview-nav-content{scroll-padding-inline:3rem}.p-tabview-nav-content{overflow-x:auto;overflow-y:hidden;scroll-behavior:smooth;scrollbar-width:none;overscroll-behavior:contain auto}.p-tabview-nav{display:inline-flex;min-width:100%;margin:0;padding:0;list-style-type:none;flex:1 1 auto}.p-tabview .p-tabview-nav li .p-tabview-nav-link{border-width:0 0 2px 0;padding:1.25rem;font-weight:700;border-top-right-radius:6px;border-top-left-radius:6px;transition:box-shadow .2s;margin:0 0 -2px}.p-tabview-nav-link{appearance:none;background:transparent;border:none;font:inherit;color:inherit;cursor:pointer;-webkit-user-select:none;user-select:none;display:flex;align-items:center;position:relative;text-decoration:none;overflow:hidden;width:100%}.p-tabview .p-tabview-nav li.p-highlight .p-tabview-nav-link{border-color:var(--p-primary-color, #3B82F6);color:var(--p-primary-color, #3B82F6);background-color:transparent}.p-tabview-panels{height:calc(100% - 60px);overflow:auto}\n"] }]
2237
+ args: [{ selector: 'p-tabView', imports: [NgClass], template: "<div data-pc-name=\"tabview\" class=\"p-tabview p-component\">\n <div class=\"p-tabview-nav-container\">\n <div class=\"p-tabview-nav-content\" data-pc-section=\"navcontent\">\n <ul role=\"tablist\" class=\"p-tabview-nav\" data-pc-section=\"nav\">\n @for (tab of tabs; track tab; let i = $index) {\n <li role=\"presentation\" data-p-disabled=\"false\"\n [ngClass]=\"{'p-highlight': tab.inputs?.selected}\">\n <button type=\"button\" role=\"tab\" pripple=\"\" class=\"p-element p-ripple p-tabview-nav-link\" (click)=\"setIndex(i)\"\n [attr.aria-selected]=\"tab.inputs?.selected\" [attr.tabindex]=\"i\" aria-disabled=\"false\"\n [attr.data-pc-index]=\"i\" data-pc-section=\"headeraction\">\n <span class=\"p-tabview-title\">\n {{tab.inputs.header}}\n </span>\n </button>\n </li>\n }\n </ul>\n </div>\n </div>\n <div class=\"p-tabview-panels\">\n <ng-content></ng-content>\n </div>\n</div>", styles: [".p-tabview.p-component{height:100%}.p-tabview .p-tabview-nav-content{scroll-padding-inline:3rem}.p-tabview-nav-content{overflow-x:auto;overflow-y:hidden;scroll-behavior:smooth;scrollbar-width:none;overscroll-behavior:contain auto}.p-tabview-nav{display:inline-flex;min-width:100%;margin:0;padding:0;list-style-type:none;flex:1 1 auto}.p-tabview .p-tabview-nav li .p-tabview-nav-link{border-width:0 0 2px 0;padding:1.25rem;font-weight:700;border-top-right-radius:6px;border-top-left-radius:6px;transition:box-shadow .2s;margin:0 0 -2px}.p-tabview-nav-link{appearance:none;background:transparent;border:none;font:inherit;color:inherit;cursor:pointer;-webkit-user-select:none;user-select:none;display:flex;align-items:center;position:relative;text-decoration:none;overflow:hidden;width:100%}.p-tabview .p-tabview-nav li.p-highlight .p-tabview-nav-link{border-color:var(--p-primary-color, #3B82F6);color:var(--p-primary-color, #3B82F6);background-color:transparent}.p-tabview-panels{height:calc(100% - 60px);overflow:auto}\n"] }]
2210
2238
  }], propDecorators: { tabs: [{
2211
2239
  type: Input
2212
2240
  }], activeIndex: [{
@@ -2218,7 +2246,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
2218
2246
  */
2219
2247
  function getPointFromEvent(event) {
2220
2248
  // NOTE: In firefox TouchEvent is only defined for touch capable devices
2221
- const isTouchEvent = (e) => window.TouchEvent && event instanceof TouchEvent;
2249
+ const isTouchEvent = (e) => globalThis.TouchEvent && event instanceof TouchEvent;
2222
2250
  if (isTouchEvent(event)) {
2223
2251
  if (event.changedTouches.length === 0) {
2224
2252
  return undefined;
@@ -3112,17 +3140,17 @@ class SplitComponent {
3112
3140
  else {
3113
3141
  const percentSize = (tempAreasPixelSizes[index] / dragStartContext.totalAreasPixelSize) * 100;
3114
3142
  // Fix javascript only working with float numbers which are inaccurate compared to decimals
3115
- area._internalSize.set(parseFloat(percentSize.toFixed(10)));
3143
+ area._internalSize.set(Number.parseFloat(percentSize.toFixed(10)));
3116
3144
  }
3117
3145
  });
3118
3146
  this.dragProgressSubject.next(this.createDragInteractionEvent(this.draggedGutterIndex()));
3119
3147
  }
3120
3148
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SplitComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3121
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: SplitComponent, isStandalone: true, selector: "p-splitter", inputs: { gutterSize: { classPropertyName: "gutterSize", publicName: "gutterSize", isSignal: true, isRequired: false, transformFunction: null }, gutterStep: { classPropertyName: "gutterStep", publicName: "gutterStep", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, gutterClickDeltaPx: { classPropertyName: "gutterClickDeltaPx", publicName: "gutterClickDeltaPx", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, unit: { classPropertyName: "unit", publicName: "unit", isSignal: true, isRequired: false, transformFunction: null }, gutterAriaLabel: { classPropertyName: "gutterAriaLabel", publicName: "gutterAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, restrictMove: { classPropertyName: "restrictMove", publicName: "restrictMove", isSignal: true, isRequired: false, transformFunction: null }, useTransition: { classPropertyName: "useTransition", publicName: "useTransition", isSignal: true, isRequired: false, transformFunction: null }, gutterDblClickDuration: { classPropertyName: "gutterDblClickDuration", publicName: "gutterDblClickDuration", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { gutterClick: "gutterClick", gutterDblClick: "gutterDblClick", dragStart: "dragStart", dragEnd: "dragEnd", transitionEnd: "transitionEnd" }, host: { properties: { "class": "this.hostClassesBinding", "dir": "this.hostDirBinding" } }, queries: [{ propertyName: "_areas", predicate: SPLIT_AREA_CONTRACT, isSignal: true }, { propertyName: "customGutter", first: true, predicate: SplitGutterDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<ng-content></ng-content>\n@for (area of _areas(); track area) {\n @if (!$last) {\n <div\n #gutter\n class=\"as-split-gutter\"\n role=\"separator\"\n tabindex=\"0\"\n [attr.aria-label]=\"gutterAriaLabel()\"\n [attr.aria-orientation]=\"direction()\"\n [attr.aria-valuemin]=\"getAriaValue(area.minSize())\"\n [attr.aria-valuemax]=\"getAriaValue(area.maxSize())\"\n [attr.aria-valuenow]=\"getAriaValue(area._internalSize())\"\n [attr.aria-valuetext]=\"getAriaAreaSizeText(area)\"\n [ngStyle]=\"getGutterGridStyle($index + 1)\"\n [class.as-dragged]=\"draggedGutterIndex() === $index\"\n asSplitCustomEventsBehavior\n [asSplitCustomMultiClickThreshold]=\"gutterDblClickDuration()\"\n [asSplitCustomClickDeltaInPx]=\"gutterClickDeltaPx()\"\n (asSplitCustomClick)=\"gutterClicked($index)\"\n (asSplitCustomDblClick)=\"gutterDoubleClicked($index)\"\n (asSplitCustomMouseDown)=\"gutterMouseDown($event, gutter, $index, $index, $index + 1)\"\n (asSplitCustomKeyDown)=\"gutterKeyDown($event, $index, $index, $index + 1)\"\n >\n @if (customGutter()?.template) {\n <ng-container *asSplitGutterDynamicInjector=\"$index + 1; let injector\">\n <ng-container\n *ngTemplateOutlet=\"\n customGutter().template;\n context: {\n areaBefore: area,\n areaAfter: _areas()[$index + 1],\n gutterNum: $index + 1,\n first: $first,\n last: $index === _areas().length - 2,\n isDragged: draggedGutterIndex() === $index\n };\n injector: injector\n \"\n ></ng-container>\n </ng-container>\n } @else {\n <div class=\"as-split-gutter-icon\"></div>\n }\n </div>\n }\n}\n", styles: ["@property --as-gutter-background-color{syntax: \"<color>\"; inherits: true; initial-value: #eeeeee;}@property --as-gutter-icon-horizontal{syntax: \"<url>\"; inherits: true; initial-value: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==);}@property --as-gutter-icon-vertical{syntax: \"<url>\"; inherits: true; initial-value: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFCAMAAABl/6zIAAAABlBMVEUAAADMzMzIT8AyAAAAAXRSTlMAQObYZgAAABRJREFUeAFjYGRkwIMJSeMHlBkOABP7AEGzSuPKAAAAAElFTkSuQmCC);}@property --as-gutter-icon-disabled{syntax: \"<url>\"; inherits: true; initial-value: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==);}@property --as-transition-duration{syntax: \"<time>\"; inherits: true; initial-value: .3s;}@property --as-gutter-disabled-cursor{syntax: \"*\"; inherits: true; initial-value: default;}:host{--_as-gutter-background-color: var(--as-gutter-background-color, #eeeeee);--_as-gutter-icon-horizontal: var(--as-gutter-icon-horizontal, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==));--_as-gutter-icon-vertical: var(--as-gutter-icon-vertical, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFCAMAAABl/6zIAAAABlBMVEUAAADMzMzIT8AyAAAAAXRSTlMAQObYZgAAABRJREFUeAFjYGRkwIMJSeMHlBkOABP7AEGzSuPKAAAAAElFTkSuQmCC));--_as-gutter-icon-disabled: var(--as-gutter-icon-disabled, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==));--_as-transition-duration: var(--as-transition-duration, .3s);--_as-gutter-disabled-cursor: var(--as-gutter-disabled-cursor, default)}:host{display:grid;overflow:hidden;height:100%;width:100%}:host(.as-transition){transition:grid-template var(--_as-transition-duration)}.as-split-gutter{background-color:var(--_as-gutter-background-color);display:flex;align-items:center;justify-content:center;touch-action:none}:host(.as-horizontal)>.as-split-gutter{cursor:col-resize;height:100%}:host(.as-vertical)>.as-split-gutter{cursor:row-resize;width:100%}:host(.as-disabled)>.as-split-gutter{cursor:var(--_as-gutter-disabled-cursor)}.as-split-gutter-icon{width:100%;height:100%;background-position:center center;background-repeat:no-repeat}:host(.as-horizontal)>.as-split-gutter>.as-split-gutter-icon{background-image:var(--_as-gutter-icon-horizontal)}:host(.as-vertical)>.as-split-gutter>.as-split-gutter-icon{background-image:var(--_as-gutter-icon-vertical)}:host(.as-disabled)>.as-split-gutter>.as-split-gutter-icon{background-image:var(--_as-gutter-icon-disabled)}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: SplitCustomEventsBehaviorDirective, selector: "[asSplitCustomEventsBehavior]", inputs: ["asSplitCustomMultiClickThreshold", "asSplitCustomClickDeltaInPx"], outputs: ["asSplitCustomMouseDown", "asSplitCustomClick", "asSplitCustomDblClick", "asSplitCustomKeyDown"] }, { kind: "directive", type: SplitGutterDynamicInjectorDirective, selector: "[asSplitGutterDynamicInjector]", inputs: ["asSplitGutterDynamicInjector"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3149
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: SplitComponent, isStandalone: true, selector: "p-splitter", inputs: { gutterSize: { classPropertyName: "gutterSize", publicName: "gutterSize", isSignal: true, isRequired: false, transformFunction: null }, gutterStep: { classPropertyName: "gutterStep", publicName: "gutterStep", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, gutterClickDeltaPx: { classPropertyName: "gutterClickDeltaPx", publicName: "gutterClickDeltaPx", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, unit: { classPropertyName: "unit", publicName: "unit", isSignal: true, isRequired: false, transformFunction: null }, gutterAriaLabel: { classPropertyName: "gutterAriaLabel", publicName: "gutterAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, restrictMove: { classPropertyName: "restrictMove", publicName: "restrictMove", isSignal: true, isRequired: false, transformFunction: null }, useTransition: { classPropertyName: "useTransition", publicName: "useTransition", isSignal: true, isRequired: false, transformFunction: null }, gutterDblClickDuration: { classPropertyName: "gutterDblClickDuration", publicName: "gutterDblClickDuration", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { gutterClick: "gutterClick", gutterDblClick: "gutterDblClick", dragStart: "dragStart", dragEnd: "dragEnd", transitionEnd: "transitionEnd" }, host: { properties: { "class": "this.hostClassesBinding", "dir": "this.hostDirBinding" } }, queries: [{ propertyName: "_areas", predicate: SPLIT_AREA_CONTRACT, isSignal: true }, { propertyName: "customGutter", first: true, predicate: SplitGutterDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<ng-content></ng-content>\n@for (area of _areas(); track area) {\n @if (!$last) {\n <div\n #gutter\n class=\"as-split-gutter\"\n role=\"separator\"\n tabindex=\"0\"\n [attr.aria-label]=\"gutterAriaLabel()\"\n [attr.aria-orientation]=\"direction()\"\n [attr.aria-valuemin]=\"getAriaValue(area.minSize())\"\n [attr.aria-valuemax]=\"getAriaValue(area.maxSize())\"\n [attr.aria-valuenow]=\"getAriaValue(area._internalSize())\"\n [attr.aria-valuetext]=\"getAriaAreaSizeText(area)\"\n [ngStyle]=\"getGutterGridStyle($index + 1)\"\n [class.as-dragged]=\"draggedGutterIndex() === $index\"\n asSplitCustomEventsBehavior\n [asSplitCustomMultiClickThreshold]=\"gutterDblClickDuration()\"\n [asSplitCustomClickDeltaInPx]=\"gutterClickDeltaPx()\"\n (asSplitCustomClick)=\"gutterClicked($index)\"\n (asSplitCustomDblClick)=\"gutterDoubleClicked($index)\"\n (asSplitCustomMouseDown)=\"gutterMouseDown($event, gutter, $index, $index, $index + 1)\"\n (asSplitCustomKeyDown)=\"gutterKeyDown($event, $index, $index, $index + 1)\"\n >\n @if (customGutter()?.template) {\n <ng-container *asSplitGutterDynamicInjector=\"$index + 1; let injector\">\n <ng-container\n *ngTemplateOutlet=\"\n customGutter().template;\n context: {\n areaBefore: area,\n areaAfter: _areas()[$index + 1],\n gutterNum: $index + 1,\n first: $first,\n last: $index === _areas().length - 2,\n isDragged: draggedGutterIndex() === $index\n };\n injector: injector\n \"\n ></ng-container>\n </ng-container>\n } @else {\n <div class=\"as-split-gutter-icon\"></div>\n }\n </div>\n }\n}\n", styles: ["@property --as-gutter-background-color{syntax: \"<color>\"; inherits: true; initial-value: #eeeeee;}@property --as-gutter-icon-horizontal{syntax: \"<url>\"; inherits: true; initial-value: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==);}@property --as-gutter-icon-vertical{syntax: \"<url>\"; inherits: true; initial-value: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFCAMAAABl/6zIAAAABlBMVEUAAADMzMzIT8AyAAAAAXRSTlMAQObYZgAAABRJREFUeAFjYGRkwIMJSeMHlBkOABP7AEGzSuPKAAAAAElFTkSuQmCC);}@property --as-gutter-icon-disabled{syntax: \"<url>\"; inherits: true; initial-value: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==);}@property --as-transition-duration{syntax: \"<time>\"; inherits: true; initial-value: .3s;}@property --as-gutter-disabled-cursor{syntax: \"*\"; inherits: true; initial-value: default;}:host{--_as-gutter-background-color: var(--as-gutter-background-color, #eeeeee);--_as-gutter-icon-horizontal: var(--as-gutter-icon-horizontal, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==));--_as-gutter-icon-vertical: var(--as-gutter-icon-vertical, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFCAMAAABl/6zIAAAABlBMVEUAAADMzMzIT8AyAAAAAXRSTlMAQObYZgAAABRJREFUeAFjYGRkwIMJSeMHlBkOABP7AEGzSuPKAAAAAElFTkSuQmCC));--_as-gutter-icon-disabled: var(--as-gutter-icon-disabled, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==));--_as-transition-duration: var(--as-transition-duration, .3s);--_as-gutter-disabled-cursor: var(--as-gutter-disabled-cursor, default);display:grid;overflow:hidden;height:100%;width:100%}:host(.as-transition){transition:grid-template var(--_as-transition-duration)}.as-split-gutter{background-color:var(--_as-gutter-background-color);display:flex;align-items:center;justify-content:center;touch-action:none}:host(.as-horizontal)>.as-split-gutter{cursor:col-resize;height:100%}:host(.as-vertical)>.as-split-gutter{cursor:row-resize;width:100%}:host(.as-disabled)>.as-split-gutter{cursor:var(--_as-gutter-disabled-cursor)}.as-split-gutter-icon{width:100%;height:100%;background-position:center center;background-repeat:no-repeat}:host(.as-horizontal)>.as-split-gutter>.as-split-gutter-icon{background-image:var(--_as-gutter-icon-horizontal)}:host(.as-vertical)>.as-split-gutter>.as-split-gutter-icon{background-image:var(--_as-gutter-icon-vertical)}:host(.as-disabled)>.as-split-gutter>.as-split-gutter-icon{background-image:var(--_as-gutter-icon-disabled)}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: SplitCustomEventsBehaviorDirective, selector: "[asSplitCustomEventsBehavior]", inputs: ["asSplitCustomMultiClickThreshold", "asSplitCustomClickDeltaInPx"], outputs: ["asSplitCustomMouseDown", "asSplitCustomClick", "asSplitCustomDblClick", "asSplitCustomKeyDown"] }, { kind: "directive", type: SplitGutterDynamicInjectorDirective, selector: "[asSplitGutterDynamicInjector]", inputs: ["asSplitGutterDynamicInjector"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3122
3150
  }
3123
3151
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SplitComponent, decorators: [{
3124
3152
  type: Component,
3125
- args: [{ selector: 'p-splitter', imports: [NgStyle, SplitCustomEventsBehaviorDirective, SplitGutterDynamicInjectorDirective, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>\n@for (area of _areas(); track area) {\n @if (!$last) {\n <div\n #gutter\n class=\"as-split-gutter\"\n role=\"separator\"\n tabindex=\"0\"\n [attr.aria-label]=\"gutterAriaLabel()\"\n [attr.aria-orientation]=\"direction()\"\n [attr.aria-valuemin]=\"getAriaValue(area.minSize())\"\n [attr.aria-valuemax]=\"getAriaValue(area.maxSize())\"\n [attr.aria-valuenow]=\"getAriaValue(area._internalSize())\"\n [attr.aria-valuetext]=\"getAriaAreaSizeText(area)\"\n [ngStyle]=\"getGutterGridStyle($index + 1)\"\n [class.as-dragged]=\"draggedGutterIndex() === $index\"\n asSplitCustomEventsBehavior\n [asSplitCustomMultiClickThreshold]=\"gutterDblClickDuration()\"\n [asSplitCustomClickDeltaInPx]=\"gutterClickDeltaPx()\"\n (asSplitCustomClick)=\"gutterClicked($index)\"\n (asSplitCustomDblClick)=\"gutterDoubleClicked($index)\"\n (asSplitCustomMouseDown)=\"gutterMouseDown($event, gutter, $index, $index, $index + 1)\"\n (asSplitCustomKeyDown)=\"gutterKeyDown($event, $index, $index, $index + 1)\"\n >\n @if (customGutter()?.template) {\n <ng-container *asSplitGutterDynamicInjector=\"$index + 1; let injector\">\n <ng-container\n *ngTemplateOutlet=\"\n customGutter().template;\n context: {\n areaBefore: area,\n areaAfter: _areas()[$index + 1],\n gutterNum: $index + 1,\n first: $first,\n last: $index === _areas().length - 2,\n isDragged: draggedGutterIndex() === $index\n };\n injector: injector\n \"\n ></ng-container>\n </ng-container>\n } @else {\n <div class=\"as-split-gutter-icon\"></div>\n }\n </div>\n }\n}\n", styles: ["@property --as-gutter-background-color{syntax: \"<color>\"; inherits: true; initial-value: #eeeeee;}@property --as-gutter-icon-horizontal{syntax: \"<url>\"; inherits: true; initial-value: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==);}@property --as-gutter-icon-vertical{syntax: \"<url>\"; inherits: true; initial-value: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFCAMAAABl/6zIAAAABlBMVEUAAADMzMzIT8AyAAAAAXRSTlMAQObYZgAAABRJREFUeAFjYGRkwIMJSeMHlBkOABP7AEGzSuPKAAAAAElFTkSuQmCC);}@property --as-gutter-icon-disabled{syntax: \"<url>\"; inherits: true; initial-value: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==);}@property --as-transition-duration{syntax: \"<time>\"; inherits: true; initial-value: .3s;}@property --as-gutter-disabled-cursor{syntax: \"*\"; inherits: true; initial-value: default;}:host{--_as-gutter-background-color: var(--as-gutter-background-color, #eeeeee);--_as-gutter-icon-horizontal: var(--as-gutter-icon-horizontal, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==));--_as-gutter-icon-vertical: var(--as-gutter-icon-vertical, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFCAMAAABl/6zIAAAABlBMVEUAAADMzMzIT8AyAAAAAXRSTlMAQObYZgAAABRJREFUeAFjYGRkwIMJSeMHlBkOABP7AEGzSuPKAAAAAElFTkSuQmCC));--_as-gutter-icon-disabled: var(--as-gutter-icon-disabled, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==));--_as-transition-duration: var(--as-transition-duration, .3s);--_as-gutter-disabled-cursor: var(--as-gutter-disabled-cursor, default)}:host{display:grid;overflow:hidden;height:100%;width:100%}:host(.as-transition){transition:grid-template var(--_as-transition-duration)}.as-split-gutter{background-color:var(--_as-gutter-background-color);display:flex;align-items:center;justify-content:center;touch-action:none}:host(.as-horizontal)>.as-split-gutter{cursor:col-resize;height:100%}:host(.as-vertical)>.as-split-gutter{cursor:row-resize;width:100%}:host(.as-disabled)>.as-split-gutter{cursor:var(--_as-gutter-disabled-cursor)}.as-split-gutter-icon{width:100%;height:100%;background-position:center center;background-repeat:no-repeat}:host(.as-horizontal)>.as-split-gutter>.as-split-gutter-icon{background-image:var(--_as-gutter-icon-horizontal)}:host(.as-vertical)>.as-split-gutter>.as-split-gutter-icon{background-image:var(--_as-gutter-icon-vertical)}:host(.as-disabled)>.as-split-gutter>.as-split-gutter-icon{background-image:var(--_as-gutter-icon-disabled)}\n"] }]
3153
+ args: [{ selector: 'p-splitter', imports: [NgStyle, SplitCustomEventsBehaviorDirective, SplitGutterDynamicInjectorDirective, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>\n@for (area of _areas(); track area) {\n @if (!$last) {\n <div\n #gutter\n class=\"as-split-gutter\"\n role=\"separator\"\n tabindex=\"0\"\n [attr.aria-label]=\"gutterAriaLabel()\"\n [attr.aria-orientation]=\"direction()\"\n [attr.aria-valuemin]=\"getAriaValue(area.minSize())\"\n [attr.aria-valuemax]=\"getAriaValue(area.maxSize())\"\n [attr.aria-valuenow]=\"getAriaValue(area._internalSize())\"\n [attr.aria-valuetext]=\"getAriaAreaSizeText(area)\"\n [ngStyle]=\"getGutterGridStyle($index + 1)\"\n [class.as-dragged]=\"draggedGutterIndex() === $index\"\n asSplitCustomEventsBehavior\n [asSplitCustomMultiClickThreshold]=\"gutterDblClickDuration()\"\n [asSplitCustomClickDeltaInPx]=\"gutterClickDeltaPx()\"\n (asSplitCustomClick)=\"gutterClicked($index)\"\n (asSplitCustomDblClick)=\"gutterDoubleClicked($index)\"\n (asSplitCustomMouseDown)=\"gutterMouseDown($event, gutter, $index, $index, $index + 1)\"\n (asSplitCustomKeyDown)=\"gutterKeyDown($event, $index, $index, $index + 1)\"\n >\n @if (customGutter()?.template) {\n <ng-container *asSplitGutterDynamicInjector=\"$index + 1; let injector\">\n <ng-container\n *ngTemplateOutlet=\"\n customGutter().template;\n context: {\n areaBefore: area,\n areaAfter: _areas()[$index + 1],\n gutterNum: $index + 1,\n first: $first,\n last: $index === _areas().length - 2,\n isDragged: draggedGutterIndex() === $index\n };\n injector: injector\n \"\n ></ng-container>\n </ng-container>\n } @else {\n <div class=\"as-split-gutter-icon\"></div>\n }\n </div>\n }\n}\n", styles: ["@property --as-gutter-background-color{syntax: \"<color>\"; inherits: true; initial-value: #eeeeee;}@property --as-gutter-icon-horizontal{syntax: \"<url>\"; inherits: true; initial-value: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==);}@property --as-gutter-icon-vertical{syntax: \"<url>\"; inherits: true; initial-value: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFCAMAAABl/6zIAAAABlBMVEUAAADMzMzIT8AyAAAAAXRSTlMAQObYZgAAABRJREFUeAFjYGRkwIMJSeMHlBkOABP7AEGzSuPKAAAAAElFTkSuQmCC);}@property --as-gutter-icon-disabled{syntax: \"<url>\"; inherits: true; initial-value: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==);}@property --as-transition-duration{syntax: \"<time>\"; inherits: true; initial-value: .3s;}@property --as-gutter-disabled-cursor{syntax: \"*\"; inherits: true; initial-value: default;}:host{--_as-gutter-background-color: var(--as-gutter-background-color, #eeeeee);--_as-gutter-icon-horizontal: var(--as-gutter-icon-horizontal, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==));--_as-gutter-icon-vertical: var(--as-gutter-icon-vertical, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFCAMAAABl/6zIAAAABlBMVEUAAADMzMzIT8AyAAAAAXRSTlMAQObYZgAAABRJREFUeAFjYGRkwIMJSeMHlBkOABP7AEGzSuPKAAAAAElFTkSuQmCC));--_as-gutter-icon-disabled: var(--as-gutter-icon-disabled, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==));--_as-transition-duration: var(--as-transition-duration, .3s);--_as-gutter-disabled-cursor: var(--as-gutter-disabled-cursor, default);display:grid;overflow:hidden;height:100%;width:100%}:host(.as-transition){transition:grid-template var(--_as-transition-duration)}.as-split-gutter{background-color:var(--_as-gutter-background-color);display:flex;align-items:center;justify-content:center;touch-action:none}:host(.as-horizontal)>.as-split-gutter{cursor:col-resize;height:100%}:host(.as-vertical)>.as-split-gutter{cursor:row-resize;width:100%}:host(.as-disabled)>.as-split-gutter{cursor:var(--_as-gutter-disabled-cursor)}.as-split-gutter-icon{width:100%;height:100%;background-position:center center;background-repeat:no-repeat}:host(.as-horizontal)>.as-split-gutter>.as-split-gutter-icon{background-image:var(--_as-gutter-icon-horizontal)}:host(.as-vertical)>.as-split-gutter>.as-split-gutter-icon{background-image:var(--_as-gutter-icon-vertical)}:host(.as-disabled)>.as-split-gutter>.as-split-gutter-icon{background-image:var(--_as-gutter-icon-disabled)}\n"] }]
3126
3154
  }], ctorParameters: () => [], propDecorators: { _areas: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SPLIT_AREA_CONTRACT), { isSignal: true }] }], customGutter: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SplitGutterDirective), { isSignal: true }] }], gutterSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "gutterSize", required: false }] }], gutterStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "gutterStep", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], gutterClickDeltaPx: [{ type: i0.Input, args: [{ isSignal: true, alias: "gutterClickDeltaPx", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], dir: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], unit: [{ type: i0.Input, args: [{ isSignal: true, alias: "unit", required: false }] }], gutterAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "gutterAriaLabel", required: false }] }], restrictMove: [{ type: i0.Input, args: [{ isSignal: true, alias: "restrictMove", required: false }] }], useTransition: [{ type: i0.Input, args: [{ isSignal: true, alias: "useTransition", required: false }] }], gutterDblClickDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "gutterDblClickDuration", required: false }] }], gutterClick: [{ type: i0.Output, args: ["gutterClick"] }], gutterDblClick: [{ type: i0.Output, args: ["gutterDblClick"] }], dragStart: [{ type: i0.Output, args: ["dragStart"] }], dragEnd: [{ type: i0.Output, args: ["dragEnd"] }], transitionEnd: [{ type: i0.Output, args: ["transitionEnd"] }], hostClassesBinding: [{
3127
3155
  type: HostBinding,
3128
3156
  args: ['class']
@@ -3188,7 +3216,7 @@ class SplitAreaComponent {
3188
3216
  /**
3189
3217
  * Indice corrente per index, usato per posizionamento o navigazione nel componente.
3190
3218
  */
3191
- index = computed(() => this.split._areas().findIndex((area) => area === this), ...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
3219
+ index = computed(() => this.split._areas().indexOf(this), ...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
3192
3220
  /**
3193
3221
  * Proprieta di stato del componente per grid area num, usata dalla logica interna e dal template.
3194
3222
  */
@@ -3352,6 +3380,23 @@ class DynamicDashboardTemplateComponent {
3352
3380
  * does not trigger a spurious commit.
3353
3381
  */
3354
3382
  static lastDesignerGestureTimestamp = 0;
3383
+ /**
3384
+ * Dimensione della griglia di snap (px) per drag/resize nel designer.
3385
+ * 0 = snap disattivato (comportamento libero, default). Impostato staticamente
3386
+ * da DesignerComponent quando l'utente attiva "snap alla griglia": statico per
3387
+ * lo stesso motivo di lastDesignerGestureTimestamp (ogni elemento ha la propria
3388
+ * istanza di template). onMoving/onResizing arrotondano posizione/dimensione al
3389
+ * multiplo piu' vicino quando > 0.
3390
+ */
3391
+ static snapGridSize = 0;
3392
+ /** Arrotonda un valore al multiplo di `snapGridSize` (no-op se snap off). */
3393
+ static snap(value) {
3394
+ const g = DynamicDashboardTemplateComponent.snapGridSize;
3395
+ if (!g || g <= 0) {
3396
+ return value;
3397
+ }
3398
+ return Math.round(value / g) * g;
3399
+ }
3355
3400
  /**
3356
3401
  * Input dal componente padre per inputs; usata nella configurazione e nel rendering del componente.
3357
3402
  */
@@ -3437,7 +3482,7 @@ class DynamicDashboardTemplateComponent {
3437
3482
  let cache = [];
3438
3483
  let str = JSON.stringify(obj, function (key, value) {
3439
3484
  if (typeof value === "object" && value !== null) {
3440
- if (cache.indexOf(value) !== -1) {
3485
+ if (cache.includes(value)) {
3441
3486
  // Circular reference found, discard key
3442
3487
  return;
3443
3488
  }
@@ -3456,12 +3501,8 @@ class DynamicDashboardTemplateComponent {
3456
3501
  * @param parentElement Parametro utilizzato dal metodo nel flusso elaborativo.
3457
3502
  */
3458
3503
  onResizing(event, element, parentElement) {
3459
- if (parentElement) {
3460
- }
3461
- else {
3462
- }
3463
- element.inputs.width = event.size.width + 'px';
3464
- element.inputs.height = event.size.height + 'px';
3504
+ element.inputs.width = DynamicDashboardTemplateComponent.snap(event.size.width) + 'px';
3505
+ element.inputs.height = DynamicDashboardTemplateComponent.snap(event.size.height) + 'px';
3465
3506
  // Mark "designer mutation in progress" so the next window:mouseup in
3466
3507
  // DesignerComponent commits a history snapshot. See the field doc.
3467
3508
  DynamicDashboardTemplateComponent.lastDesignerGestureTimestamp = Date.now();
@@ -3472,13 +3513,9 @@ class DynamicDashboardTemplateComponent {
3472
3513
  * @param element Parametro utilizzato dal metodo nel flusso elaborativo.
3473
3514
  */
3474
3515
  onMoving($event, element) {
3475
- // debugger;
3476
- // element.inputs.left = $event.position.x + 'px';
3477
- // element.inputs.top = $event.position.y + 'px';
3478
- // element.inputs.position = 'absolute';
3479
3516
  let scaleX = 1, skewY = 0, skewX = 0, scaleY = 1;
3480
3517
  if (element.inputs.transform) {
3481
- let matrix = element.inputs.transform.replace('matrix(', '').replace(')', '').split(',').map(x => parseFloat(x));
3518
+ let matrix = element.inputs.transform.replace('matrix(', '').replace(')', '').split(',').map(x => Number.parseFloat(x));
3482
3519
  if (matrix.length >= 1) {
3483
3520
  scaleX = matrix[0];
3484
3521
  }
@@ -3492,6 +3529,11 @@ class DynamicDashboardTemplateComponent {
3492
3529
  scaleY = matrix[3];
3493
3530
  }
3494
3531
  }
3532
+ // La direttiva ngx-draggable-dom snappa gia' la traslazione alla griglia
3533
+ // ASSOLUTA (offsetLeft/Top + delta), quindi $event.position e' gia' allineato.
3534
+ // NON ri-snappare con DynamicDashboardTemplateComponent.snap(): e' un
3535
+ // arrotondamento grid-STEP che, applicato a una traslazione gia' assoluta,
3536
+ // la disallineava (l'offset visto sui box trascinati).
3495
3537
  element.inputs.transform = `matrix(${scaleX}, ${skewY}, ${skewX}, ${scaleY}, ${$event.position.x}, ${$event.position.y})`;
3496
3538
  // Mark "designer mutation in progress" so the next window:mouseup in
3497
3539
  // DesignerComponent commits a history snapshot. See the field doc.
@@ -3624,7 +3666,7 @@ class DynamicDashboardTemplateComponent {
3624
3666
  * Abilita drag/drop solo in contesto designer.
3625
3667
  */
3626
3668
  isDesignerMode() {
3627
- const hash = String(window?.location?.hash || '').toLowerCase();
3669
+ const hash = String(globalThis?.location?.hash || '').toLowerCase();
3628
3670
  return hash.includes('/designer');
3629
3671
  }
3630
3672
  /**
@@ -3640,9 +3682,9 @@ class DynamicDashboardTemplateComponent {
3640
3682
  */
3641
3683
  static getComponentFromTemplate(template, route, templateField) {
3642
3684
  const normalizedTemplate = String(template || '')
3643
- .replace(/wuic-parametric-dialog(?!-lazy)/g, 'wuic-parametric-dialog-lazy')
3644
- .replace(/ngxDraggableDom\s*=\s*\"true\"/g, '[ngxDraggableDom]="isDesignerMode()"')
3645
- .replace(/ngxDraggableDom\s*=\s*\'true\'/g, '[ngxDraggableDom]="isDesignerMode()"');
3685
+ .replaceAll(/wuic-parametric-dialog(?!-lazy)/g, 'wuic-parametric-dialog-lazy')
3686
+ .replaceAll(/ngxDraggableDom\s*=\s*"true"/g, '[ngxDraggableDom]="isDesignerMode()"')
3687
+ .replaceAll(/ngxDraggableDom\s*=\s*'true'/g, '[ngxDraggableDom]="isDesignerMode()"');
3646
3688
  return DynamicCompilerService.compile({
3647
3689
  template: normalizedTemplate,
3648
3690
  baseClass: DynamicDashboardTemplateComponent,
@@ -3724,6 +3766,36 @@ class DashboardComponent {
3724
3766
  * Input dal componente padre per dashboard elements; usata nella configurazione e nel rendering del componente.
3725
3767
  */
3726
3768
  dashboardElements;
3769
+ /**
3770
+ * Chrome "card" opzionale per i widget top-level (surface + bordo + radius
3771
+ * + shadow dai token --wuic-*). Opt-in dal boardcontent wrapper-object
3772
+ * (`{"widgetChrome": true, "dashboardElements": [...]}`): default false per
3773
+ * non alterare l'aspetto delle dashboard esistenti. Parser tollerante
3774
+ * boolean-like come le altre config archetype.
3775
+ */
3776
+ widgetChrome = false;
3777
+ get hasWidgetChrome() {
3778
+ const v = this.widgetChrome;
3779
+ return v === true || v === 1 || String(v).trim().toLowerCase() === 'true' || String(v).trim() === '1';
3780
+ }
3781
+ /**
3782
+ * Layout assoluto vs flusso normale: il "modo" con cui la dashboard e' stata
3783
+ * SALVATA e' codificato per-elemento nel boardcontent (`inputs.position`).
3784
+ * Se almeno un figlio diretto e' `absolute`/`fixed`, questo host deve diventare
3785
+ * il contesto di posizionamento dei figli (altrimenti risalgono al viewport e
3786
+ * finiscono sopra il menu fixed). In flusso normale la classe NON viene
3787
+ * applicata -> nessun cambiamento di layout, retrocompatibile.
3788
+ *
3789
+ * Vale sia top-level (host = area dashboard sotto il menu) sia annidato
3790
+ * (host = dentro il container `position:relative` gia' salvato dal designer):
3791
+ * in entrambi i casi il figlio absolute risolve left/top rispetto a questo box.
3792
+ */
3793
+ get hasAbsoluteLayout() {
3794
+ return (this.dashboardElements || []).some((el) => {
3795
+ const pos = String(el?.inputs?.position || '').trim().toLowerCase();
3796
+ return pos === 'absolute' || pos === 'fixed';
3797
+ });
3798
+ }
3727
3799
  /**
3728
3800
  * function Object() { [native code] }
3729
3801
  */
@@ -3755,13 +3827,21 @@ class DashboardComponent {
3755
3827
  }
3756
3828
  ;
3757
3829
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DashboardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3758
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DashboardComponent, isStandalone: true, selector: "wuic-dashboard", inputs: { dashboardElements: "dashboardElements" }, ngImport: i0, template: "@for (dashboardElement of dashboardElements; track dashboardElement.uniqueName) {\r\n<ng-container *ngComponentOutlet=\"getComponent(dashboardElement); inputs: getComponentInputs(dashboardElement)\"></ng-container>\r\n}\r\n", styles: [""], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }] });
3830
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DashboardComponent, isStandalone: true, selector: "wuic-dashboard", inputs: { dashboardElements: "dashboardElements", widgetChrome: "widgetChrome" }, host: { properties: { "class.wuic-dashboard-widget-chrome": "this.hasWidgetChrome", "class.wuic-dashboard-absolute-layout": "this.hasAbsoluteLayout" } }, ngImport: i0, template: "@for (dashboardElement of dashboardElements; track dashboardElement.uniqueName) {\r\n<ng-container *ngComponentOutlet=\"getComponent(dashboardElement); inputs: getComponentInputs(dashboardElement)\"></ng-container>\r\n}\r\n", styles: [":host(.wuic-dashboard-absolute-layout){position:relative;display:block;width:100%;height:100%}:host(.wuic-dashboard-widget-chrome){display:block;padding:var(--wuic-space-3, 16px)}:host(.wuic-dashboard-widget-chrome)>:not(wuic-data-source):not(.datasource-descriptor){display:block;background:var(--wuic-surface, #ffffff);border:1px solid var(--wuic-border-soft, #e6ebf2);border-radius:var(--wuic-radius-lg, 10px);box-shadow:var(--wuic-shadow-1, 0 1px 3px rgba(15, 23, 42, .08));padding:var(--wuic-space-3, 16px);margin-bottom:var(--wuic-space-3, 16px)}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }] });
3759
3831
  }
3760
3832
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DashboardComponent, decorators: [{
3761
3833
  type: Component,
3762
- args: [{ selector: 'wuic-dashboard', imports: [NgComponentOutlet], template: "@for (dashboardElement of dashboardElements; track dashboardElement.uniqueName) {\r\n<ng-container *ngComponentOutlet=\"getComponent(dashboardElement); inputs: getComponentInputs(dashboardElement)\"></ng-container>\r\n}\r\n" }]
3834
+ args: [{ selector: 'wuic-dashboard', imports: [NgComponentOutlet], template: "@for (dashboardElement of dashboardElements; track dashboardElement.uniqueName) {\r\n<ng-container *ngComponentOutlet=\"getComponent(dashboardElement); inputs: getComponentInputs(dashboardElement)\"></ng-container>\r\n}\r\n", styles: [":host(.wuic-dashboard-absolute-layout){position:relative;display:block;width:100%;height:100%}:host(.wuic-dashboard-widget-chrome){display:block;padding:var(--wuic-space-3, 16px)}:host(.wuic-dashboard-widget-chrome)>:not(wuic-data-source):not(.datasource-descriptor){display:block;background:var(--wuic-surface, #ffffff);border:1px solid var(--wuic-border-soft, #e6ebf2);border-radius:var(--wuic-radius-lg, 10px);box-shadow:var(--wuic-shadow-1, 0 1px 3px rgba(15, 23, 42, .08));padding:var(--wuic-space-3, 16px);margin-bottom:var(--wuic-space-3, 16px)}\n"] }]
3763
3835
  }], ctorParameters: () => [], propDecorators: { dashboardElements: [{
3764
3836
  type: Input
3837
+ }], widgetChrome: [{
3838
+ type: Input
3839
+ }], hasWidgetChrome: [{
3840
+ type: HostBinding,
3841
+ args: ['class.wuic-dashboard-widget-chrome']
3842
+ }], hasAbsoluteLayout: [{
3843
+ type: HostBinding,
3844
+ args: ['class.wuic-dashboard-absolute-layout']
3765
3845
  }] } });
3766
3846
 
3767
3847
  class CssSheetEditorComponent {
@@ -3829,8 +3909,8 @@ class CssSheetEditorComponent {
3829
3909
  if (!this.valore) {
3830
3910
  this.valore = '#FFFFFF';
3831
3911
  }
3832
- this.pickerValue = this.toHsbaModel(this.valore);
3833
- this.alphaValue = this.clampAlpha(this.pickerValue?.a);
3912
+ this.pickerValue = toHsbaModel(this.valore);
3913
+ this.alphaValue = clampAlpha(this.pickerValue?.a);
3834
3914
  }
3835
3915
  /**
3836
3916
  * Gestisce i cambiamenti degli input aggiornando lo stato derivato e le dipendenze del componente.
@@ -4038,11 +4118,11 @@ class CssSheetEditorComponent {
4038
4118
  onPendingColorChanged(value) {
4039
4119
  const raw = value;
4040
4120
  const hsba = {
4041
- ...(raw || {}),
4042
- a: this.clampAlpha(raw?.a ?? this.alphaValue)
4121
+ ...(raw),
4122
+ a: clampAlpha(raw?.a ?? this.alphaValue)
4043
4123
  };
4044
- this.alphaValue = this.clampAlpha(hsba?.a);
4045
- const newValue = this.toPersistedColor(raw);
4124
+ this.alphaValue = clampAlpha(hsba?.a);
4125
+ const newValue = toPersistedColor(raw, this.alphaValue);
4046
4126
  this.pickerValue = raw;
4047
4127
  this.pendingStyleValue = this.valore = newValue;
4048
4128
  }
@@ -4144,7 +4224,7 @@ class CssSheetEditorComponent {
4144
4224
  * @returns Stringa risultante calcolata da `normalizePath` per chiavi/label o valori testuali.
4145
4225
  */
4146
4226
  normalizePath(path) {
4147
- return String(path || '').trim().replace(/\\/g, '/');
4227
+ return String(path || '').trim().replaceAll('\\', '/');
4148
4228
  }
4149
4229
  /**
4150
4230
  * Costruisce il payload/struttura risultante in `buildAssetSheetPath` partendo dai dati correnti.
@@ -4160,11 +4240,11 @@ class CssSheetEditorComponent {
4160
4240
  if (normalizedName.includes('/') || normalizedName.includes('\\')) {
4161
4241
  return '';
4162
4242
  }
4163
- normalizedName = normalizedName.replace(/\s+/g, '-');
4243
+ normalizedName = normalizedName.replaceAll(/\s+/g, '-');
4164
4244
  if (!normalizedName.toLowerCase().endsWith('.css')) {
4165
4245
  normalizedName += '.css';
4166
4246
  }
4167
- const folder = String(this.assetsFolder || '').trim().replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
4247
+ const folder = String(this.assetsFolder || '').trim().replaceAll('\\', '/').replaceAll(/^\/+|\/+$/g, '');
4168
4248
  if (!folder) {
4169
4249
  return '';
4170
4250
  }
@@ -4208,167 +4288,16 @@ class CssSheetEditorComponent {
4208
4288
  return found || null;
4209
4289
  }
4210
4290
  async onAlphaChange(rawAlpha) {
4211
- this.alphaValue = this.clampAlpha(rawAlpha);
4291
+ this.alphaValue = clampAlpha(rawAlpha);
4212
4292
  this.pickerValue = {
4213
- ...(this.pickerValue || {}),
4293
+ ...(this.pickerValue),
4214
4294
  a: this.alphaValue
4215
4295
  };
4216
- const newValue = this.toPersistedColor(this.pickerValue);
4296
+ const newValue = toPersistedColor(this.pickerValue, this.alphaValue);
4217
4297
  this.pendingStyleValue = this.valore = newValue;
4218
4298
  }
4219
- toPersistedColor(raw) {
4220
- if (raw === undefined || raw === null) {
4221
- return '';
4222
- }
4223
- if (typeof raw === 'string') {
4224
- return raw.trim();
4225
- }
4226
- const h = Number(raw?.h);
4227
- const s = Number(raw?.s);
4228
- const b = Number(raw?.b);
4229
- let a = Number(raw?.a);
4230
- if (!Number.isFinite(h) || !Number.isFinite(s) || !Number.isFinite(b)) {
4231
- return String(raw);
4232
- }
4233
- if (!Number.isFinite(a)) {
4234
- a = this.alphaValue;
4235
- }
4236
- a = this.clampAlpha(a);
4237
- return this.hsbaToRgbaString(h, s, b, a);
4238
- }
4239
- toHsbaModel(value) {
4240
- const fallback = { h: 0, s: 0, b: 100, a: 1 };
4241
- if (value === undefined || value === null) {
4242
- return fallback;
4243
- }
4244
- if (typeof value === 'object' && value?.h !== undefined && value?.s !== undefined && value?.b !== undefined) {
4245
- return {
4246
- h: Number(value.h) || 0,
4247
- s: Number(value.s) || 0,
4248
- b: Number(value.b) || 0,
4249
- a: Number.isFinite(Number(value.a)) ? Number(value.a) : 1
4250
- };
4251
- }
4252
- const str = String(value).trim();
4253
- if (!str) {
4254
- return fallback;
4255
- }
4256
- const rgbMatch = str.match(/^rgba?\(([^)]+)\)$/i);
4257
- if (rgbMatch) {
4258
- const parts = rgbMatch[1].split(',').map((x) => x.trim());
4259
- const r = Number(parts[0]);
4260
- const g = Number(parts[1]);
4261
- const b = Number(parts[2]);
4262
- const a = parts.length > 3 ? Number(parts[3]) : 1;
4263
- if (Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b)) {
4264
- return this.rgbaToHsbaModel(r, g, b, Number.isFinite(a) ? a : 1);
4265
- }
4266
- return fallback;
4267
- }
4268
- const hex = str.replace('#', '');
4269
- if (/^[0-9a-f]{3,8}$/i.test(hex)) {
4270
- const normalized = hex.length === 3
4271
- ? hex.split('').map((x) => `${x}${x}`).join('') + 'ff'
4272
- : hex.length === 4
4273
- ? hex.split('').map((x) => `${x}${x}`).join('')
4274
- : hex.length === 6
4275
- ? `${hex}ff`
4276
- : hex;
4277
- const r = parseInt(normalized.slice(0, 2), 16);
4278
- const g = parseInt(normalized.slice(2, 4), 16);
4279
- const b = parseInt(normalized.slice(4, 6), 16);
4280
- const a = parseInt(normalized.slice(6, 8), 16) / 255;
4281
- return this.rgbaToHsbaModel(r, g, b, a);
4282
- }
4283
- return fallback;
4284
- }
4285
- rgbaToHsbaModel(rRaw, gRaw, bRaw, aRaw) {
4286
- const r = Math.max(0, Math.min(255, rRaw)) / 255;
4287
- const g = Math.max(0, Math.min(255, gRaw)) / 255;
4288
- const b = Math.max(0, Math.min(255, bRaw)) / 255;
4289
- const a = Math.max(0, Math.min(1, aRaw));
4290
- const max = Math.max(r, g, b);
4291
- const min = Math.min(r, g, b);
4292
- const delta = max - min;
4293
- let h = 0;
4294
- if (delta !== 0) {
4295
- if (max === r) {
4296
- h = 60 * (((g - b) / delta) % 6);
4297
- }
4298
- else if (max === g) {
4299
- h = 60 * (((b - r) / delta) + 2);
4300
- }
4301
- else {
4302
- h = 60 * (((r - g) / delta) + 4);
4303
- }
4304
- }
4305
- if (h < 0) {
4306
- h += 360;
4307
- }
4308
- const s = max === 0 ? 0 : (delta / max) * 100;
4309
- const v = max * 100;
4310
- return {
4311
- h: Math.round(h),
4312
- s: Math.round(s),
4313
- b: Math.round(v),
4314
- a: Number(a.toFixed(3))
4315
- };
4316
- }
4317
- hsbaToRgbaString(hRaw, sRaw, bRaw, aRaw) {
4318
- const h = ((hRaw % 360) + 360) % 360;
4319
- const s = Math.max(0, Math.min(100, sRaw)) / 100;
4320
- const v = Math.max(0, Math.min(100, bRaw)) / 100;
4321
- const a = Math.max(0, Math.min(1, aRaw));
4322
- const c = v * s;
4323
- const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
4324
- const m = v - c;
4325
- let r1 = 0;
4326
- let g1 = 0;
4327
- let b1 = 0;
4328
- if (h < 60) {
4329
- r1 = c;
4330
- g1 = x;
4331
- b1 = 0;
4332
- }
4333
- else if (h < 120) {
4334
- r1 = x;
4335
- g1 = c;
4336
- b1 = 0;
4337
- }
4338
- else if (h < 180) {
4339
- r1 = 0;
4340
- g1 = c;
4341
- b1 = x;
4342
- }
4343
- else if (h < 240) {
4344
- r1 = 0;
4345
- g1 = x;
4346
- b1 = c;
4347
- }
4348
- else if (h < 300) {
4349
- r1 = x;
4350
- g1 = 0;
4351
- b1 = c;
4352
- }
4353
- else {
4354
- r1 = c;
4355
- g1 = 0;
4356
- b1 = x;
4357
- }
4358
- const r = Math.round((r1 + m) * 255);
4359
- const g = Math.round((g1 + m) * 255);
4360
- const b = Math.round((b1 + m) * 255);
4361
- return `rgba(${r}, ${g}, ${b}, ${Number(a.toFixed(3))})`;
4362
- }
4363
- clampAlpha(value) {
4364
- const parsed = Number(value);
4365
- if (!Number.isFinite(parsed)) {
4366
- return 1;
4367
- }
4368
- return Math.max(0, Math.min(1, parsed));
4369
- }
4370
4299
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: CssSheetEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4371
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: CssSheetEditorComponent, isStandalone: true, selector: "wuic-css-sheet-editor", inputs: { visible: "visible", sheets: "sheets", selectedSheetPath: "selectedSheetPath", styleProps: "styleProps", assetsFolder: "assetsFolder" }, outputs: { visibleChange: "visibleChange", applyEditor: "applyEditor" }, usesOnChanges: true, ngImport: i0, template: "<p-dialog [header]=\"'CSS Editor'\" [(visible)]=\"visible\" (visibleChange)=\"visibleChange.emit($event)\" [modal]=\"true\"\n [closable]=\"true\" [style]=\"{ width: '1100px', maxWidth: '98vw' }\">\n <div class=\"css-editor-layout\">\n <div class=\"css-editor-col\">\n <div class=\"css-editor-row\">\n <label>File CSS</label>\n <p-select [options]=\"draftSheets\" optionLabel=\"SheetPath\" optionValue=\"SheetPath\"\n [(ngModel)]=\"selectedSheetPathDraft\" (ngModelChange)=\"onSelectedSheetChanged()\" appendTo=\"body\"\n [style]=\"{ width: '100%' }\">\n </p-select>\n </div>\n <div class=\"css-editor-row css-editor-inline\">\n <input type=\"text\" [(ngModel)]=\"newSheetName\" pattern=\"^[^/\\\\]+$\"\n [placeholder]=\"'Nuovo file in assets/' + (assetsFolder || '<dash_route>') + ' (solo nome, es. dashboard-custom.css)'\" />\n <p-button label=\"Crea file\" (onClick)=\"addSheet()\" [disabled]=\"!assetsFolder\"></p-button>\n </div>\n @if (!assetsFolder) {\n <div class=\"css-editor-row\">\n <small>Salva prima la dashboard per definire il folder (dash_route).</small>\n </div>\n }\n <div class=\"css-editor-row\">\n <p-button severity=\"danger\" label=\"Rimuovi file selezionato\" (onClick)=\"removeSelectedSheet()\"\n [disabled]=\"!currentSheet\"></p-button>\n </div>\n </div>\n\n @if (currentSheet) {\n <div class=\"css-editor-col\">\n <div class=\"css-editor-row\">\n <label>Classi</label>\n <p-select [options]=\"currentSheet?.classes || []\" optionLabel=\"SelectorString\" optionValue=\"SelectorString\"\n [(ngModel)]=\"selectedClassSelector\" (ngModelChange)=\"onSelectedClassChanged()\" appendTo=\"body\"\n [style]=\"{ width: '100%' }\">\n </p-select>\n </div>\n <div class=\"css-editor-row css-editor-inline\">\n <input type=\"text\" [(ngModel)]=\"newClassName\" placeholder=\"Nuova classe (es. card-title)\" />\n <p-button label=\"Crea classe\" (onClick)=\"addClassToSelectedSheet()\" [disabled]=\"!currentSheet\"></p-button>\n </div>\n <div class=\"css-editor-row\">\n <p-button severity=\"danger\" label=\"Rimuovi classe selezionata\" (onClick)=\"removeSelectedClass()\"\n [disabled]=\"!currentClass\"></p-button>\n </div>\n </div>\n }\n </div>\n\n @if (currentClass) {\n <div class=\"css-editor-styles\">\n <div class=\"css-editor-row css-style-row\" style=\"margin-bottom: 8px;\">\n <p-select [options]=\"styleProps\" optionLabel=\"key\" optionValue=\"key\" [(ngModel)]=\"pendingStyleKey\"\n (ngModelChange)=\"onPendingStyleKeyChanged()\" appendTo=\"body\" [style]=\"{ width: '260px' }\">\n </p-select>\n\n @if (isPropertyDictionary(pendingStyleKey)) {\n <p-select [options]=\"getPropertyValues(pendingStyleKey)\" [(ngModel)]=\"pendingStyleValue\" appendTo=\"body\"\n [style]=\"{ width: '320px' }\">\n </p-select>\n } @else if (isPropertyColor(pendingStyleKey)) {\n\n <div class=\"css-style-color-input\">\n <div class=\"color-editor-root\" [ngStyle]=\"{ '--wuic-color-editor-preview': valore || 'transparent' }\">\n <p-colorPicker format=\"hsb\" [ngModel]=\"pickerValue\" (ngModelChange)=\"onPendingColorChanged($event)\"\n appendTo=\"body\">\n </p-colorPicker>\n </div>\n <input type=\"text\" [(ngModel)]=\"pendingStyleValue\" style=\"width: 252px;\" />\n </div>\n <div class=\"color-editor-alpha-row\">\n <label class=\"color-editor-alpha-label\">Alpha</label>\n <input type=\"range\" min=\"0\" max=\"1\" step=\"0.01\" [ngModel]=\"alphaValue\"\n (ngModelChange)=\"onAlphaChange($event)\" />\n <input type=\"number\" min=\"0\" max=\"1\" step=\"0.01\" [ngModel]=\"alphaValue\"\n (ngModelChange)=\"onAlphaChange($event)\" />\n </div>\n\n } @else {\n <input type=\"text\" [(ngModel)]=\"pendingStyleValue\" style=\"width: 320px;\" />\n }\n\n <p-button label=\"Aggiungi propriet\u00E0 corrente\" (onClick)=\"addStyleToSelectedClass()\"\n [disabled]=\"!currentClass || !pendingStyleKey\"></p-button>\n </div>\n\n @for (style of currentClass.Styles; track style; let i = $index) {\n <div class=\"css-style-row\">\n <div class=\"css-style-readonly-key\">{{ style.Key }}</div>\n <div class=\"css-style-readonly-value\">{{ style.Value }}</div>\n\n <p-button icon=\"pi pi-trash\" severity=\"danger\" [text]=\"true\" (onClick)=\"removeStyle(i)\"></p-button>\n </div>\n }\n </div>\n }\n\n <ng-template pTemplate=\"footer\">\n <p-button label=\"Chiudi\" severity=\"secondary\" (onClick)=\"close()\"></p-button>\n </ng-template>\n</p-dialog>", styles: [".css-editor-layout{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px}.css-editor-col{border:1px solid #d0d0d0;border-radius:6px;padding:12px}.css-editor-row{display:flex;flex-direction:column;gap:8px;margin-bottom:10px}.css-editor-inline{flex-direction:row;align-items:center}.css-editor-inline input{flex:1}.css-editor-styles{border:1px solid #d0d0d0;border-radius:6px;padding:12px;max-height:420px;overflow:auto}.css-style-row{display:flex;align-items:center;gap:8px;margin-bottom:8px}.css-style-readonly-key{width:260px;min-height:32px;display:flex;align-items:center;padding:0 8px;border:1px solid #d0d0d0;border-radius:4px;background:#f9f9f9;font-family:monospace}.css-style-readonly-value{width:320px;min-height:32px;display:flex;align-items:center;padding:0 8px;border:1px solid #d0d0d0;border-radius:4px;background:#f9f9f9;font-family:monospace}.css-style-color-input{display:flex;align-items:center;gap:8px;width:320px}@media(max-width:1024px){.css-editor-layout{grid-template-columns:1fr}.css-style-row{flex-wrap:wrap}}.color-editor-root{--wuic-color-editor-preview: transparent;border:solid 1px}:host ::ng-deep .color-editor-root .p-colorpicker-preview{background-image:linear-gradient(var(--wuic-color-editor-preview),var(--wuic-color-editor-preview)),linear-gradient(45deg,#d5d9e2 25%,transparent 25%),linear-gradient(-45deg,#d5d9e2 25%,transparent 25%),linear-gradient(45deg,transparent 75%,#d5d9e2 75%),linear-gradient(-45deg,transparent 75%,#d5d9e2 75%)!important;background-size:10px 10px!important;background-position:0 0,0 5px,5px -5px,-5px 0!important;background-color:transparent!important}.color-editor-alpha-row{display:flex;align-items:center;gap:.5rem;margin-top:.5rem}.color-editor-alpha-label{min-width:2.8rem;font-size:.85rem}.color-editor-alpha-row input[type=range]{flex:1 1 auto}.color-editor-alpha-row input[type=number]{width:4.5rem}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { 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.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "directive", type: i3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ColorPickerModule }, { kind: "component", type: i2$1.ColorPicker, selector: "p-colorPicker, p-colorpicker, p-color-picker", inputs: ["styleClass", "showTransitionOptions", "hideTransitionOptions", "inline", "format", "tabindex", "inputId", "autoZIndex", "autofocus", "defaultColor", "appendTo", "overlayOptions", "motionOptions"], outputs: ["onChange", "onShow", "onHide"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
4300
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: CssSheetEditorComponent, isStandalone: true, selector: "wuic-css-sheet-editor", inputs: { visible: "visible", sheets: "sheets", selectedSheetPath: "selectedSheetPath", styleProps: "styleProps", assetsFolder: "assetsFolder" }, outputs: { visibleChange: "visibleChange", applyEditor: "applyEditor" }, usesOnChanges: true, ngImport: i0, template: "<p-dialog [header]=\"'CSS Editor'\" [(visible)]=\"visible\" (visibleChange)=\"visibleChange.emit($event)\" [modal]=\"true\"\n [closable]=\"true\" [style]=\"{ width: '1100px', maxWidth: '98vw' }\">\n <div class=\"css-editor-layout\">\n <div class=\"css-editor-col\">\n <div class=\"css-editor-row\">\n <label>File CSS</label>\n <p-select [options]=\"draftSheets\" optionLabel=\"SheetPath\" optionValue=\"SheetPath\"\n [(ngModel)]=\"selectedSheetPathDraft\" (ngModelChange)=\"onSelectedSheetChanged()\" appendTo=\"body\"\n [style]=\"{ width: '100%' }\">\n </p-select>\n </div>\n <div class=\"css-editor-row css-editor-inline\">\n <input type=\"text\" [(ngModel)]=\"newSheetName\" pattern=\"^[^/\\\\]+$\"\n [placeholder]=\"'Nuovo file in assets/' + (assetsFolder || '<dash_route>') + ' (solo nome, es. dashboard-custom.css)'\" />\n <p-button label=\"Crea file\" (onClick)=\"addSheet()\" [disabled]=\"!assetsFolder\"></p-button>\n </div>\n @if (!assetsFolder) {\n <div class=\"css-editor-row\">\n <small>Salva prima la dashboard per definire il folder (dash_route).</small>\n </div>\n }\n <div class=\"css-editor-row\">\n <p-button severity=\"danger\" label=\"Rimuovi file selezionato\" (onClick)=\"removeSelectedSheet()\"\n [disabled]=\"!currentSheet\"></p-button>\n </div>\n </div>\n\n @if (currentSheet) {\n <div class=\"css-editor-col\">\n <div class=\"css-editor-row\">\n <label>Classi</label>\n <p-select [options]=\"currentSheet?.classes || []\" optionLabel=\"SelectorString\" optionValue=\"SelectorString\"\n [(ngModel)]=\"selectedClassSelector\" (ngModelChange)=\"onSelectedClassChanged()\" appendTo=\"body\"\n [style]=\"{ width: '100%' }\">\n </p-select>\n </div>\n <div class=\"css-editor-row css-editor-inline\">\n <input type=\"text\" [(ngModel)]=\"newClassName\" placeholder=\"Nuova classe (es. card-title)\" />\n <p-button label=\"Crea classe\" (onClick)=\"addClassToSelectedSheet()\" [disabled]=\"!currentSheet\"></p-button>\n </div>\n <div class=\"css-editor-row\">\n <p-button severity=\"danger\" label=\"Rimuovi classe selezionata\" (onClick)=\"removeSelectedClass()\"\n [disabled]=\"!currentClass\"></p-button>\n </div>\n </div>\n }\n </div>\n\n @if (currentClass) {\n <div class=\"css-editor-styles\">\n <div class=\"css-editor-row css-style-row\" style=\"margin-bottom: 8px;\">\n <p-select [options]=\"styleProps\" optionLabel=\"key\" optionValue=\"key\" [(ngModel)]=\"pendingStyleKey\"\n (ngModelChange)=\"onPendingStyleKeyChanged()\" appendTo=\"body\" [style]=\"{ width: '260px' }\">\n </p-select>\n\n @if (isPropertyDictionary(pendingStyleKey)) {\n <p-select [options]=\"getPropertyValues(pendingStyleKey)\" [(ngModel)]=\"pendingStyleValue\" appendTo=\"body\"\n [style]=\"{ width: '320px' }\">\n </p-select>\n } @else if (isPropertyColor(pendingStyleKey)) {\n\n <div class=\"css-style-color-input\">\n <div class=\"color-editor-root\" [ngStyle]=\"{ '--wuic-color-editor-preview': valore || 'transparent' }\">\n <p-colorPicker format=\"hsb\" [ngModel]=\"pickerValue\" (ngModelChange)=\"onPendingColorChanged($event)\"\n appendTo=\"body\">\n </p-colorPicker>\n </div>\n <input type=\"text\" [(ngModel)]=\"pendingStyleValue\" style=\"width: 252px;\" />\n </div>\n <div class=\"color-editor-alpha-row\">\n <label class=\"color-editor-alpha-label\">Alpha</label>\n <input type=\"range\" min=\"0\" max=\"1\" step=\"0.01\" [ngModel]=\"alphaValue\"\n (ngModelChange)=\"onAlphaChange($event)\" />\n <input type=\"number\" min=\"0\" max=\"1\" step=\"0.01\" [ngModel]=\"alphaValue\"\n (ngModelChange)=\"onAlphaChange($event)\" />\n </div>\n\n } @else {\n <input type=\"text\" [(ngModel)]=\"pendingStyleValue\" style=\"width: 320px;\" />\n }\n\n <p-button label=\"Aggiungi propriet\u00E0 corrente\" (onClick)=\"addStyleToSelectedClass()\"\n [disabled]=\"!currentClass || !pendingStyleKey\"></p-button>\n </div>\n\n @for (style of currentClass.Styles; track style; let i = $index) {\n <div class=\"css-style-row\">\n <div class=\"css-style-readonly-key\">{{ style.Key }}</div>\n <div class=\"css-style-readonly-value\">{{ style.Value }}</div>\n\n <p-button icon=\"pi pi-trash\" severity=\"danger\" [text]=\"true\" (onClick)=\"removeStyle(i)\"></p-button>\n </div>\n }\n </div>\n }\n\n <ng-template pTemplate=\"footer\">\n <p-button label=\"Chiudi\" severity=\"secondary\" (onClick)=\"close()\"></p-button>\n </ng-template>\n</p-dialog>", styles: [".css-editor-layout{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px}.css-editor-col{border:1px solid #d0d0d0;border-radius:6px;padding:12px}.css-editor-row{display:flex;flex-direction:column;gap:8px;margin-bottom:10px}.css-editor-inline{flex-direction:row;align-items:center}.css-editor-inline input{flex:1}.css-editor-styles{border:1px solid #d0d0d0;border-radius:6px;padding:12px;max-height:420px;overflow:auto}.css-style-row{display:flex;align-items:center;gap:8px;margin-bottom:8px}.css-style-readonly-key{width:260px;min-height:32px;display:flex;align-items:center;padding:0 8px;border:1px solid #d0d0d0;border-radius:4px;background:#f9f9f9;font-family:monospace}.css-style-readonly-value{width:320px;min-height:32px;display:flex;align-items:center;padding:0 8px;border:1px solid #d0d0d0;border-radius:4px;background:#f9f9f9;font-family:monospace}.css-style-color-input{display:flex;align-items:center;gap:8px;width:320px}@media(max-width:1024px){.css-editor-layout{grid-template-columns:1fr}.css-style-row{flex-wrap:wrap}}.color-editor-root{--wuic-color-editor-preview: transparent;border:solid 1px}:host ::ng-deep .color-editor-root .p-colorpicker-preview{background-image:linear-gradient(var(--wuic-color-editor-preview),var(--wuic-color-editor-preview)),linear-gradient(45deg,#d5d9e2 25%,transparent 25%),linear-gradient(-45deg,#d5d9e2 25%,transparent 25%),linear-gradient(45deg,transparent 75%,#d5d9e2 75%),linear-gradient(-45deg,transparent 75%,#d5d9e2 75%)!important;background-size:10px 10px!important;background-position:0 0,0 5px,5px -5px,-5px 0!important;background-color:transparent!important}.color-editor-alpha-row{display:flex;align-items:center;gap:.5rem;margin-top:.5rem}.color-editor-alpha-label{min-width:2.8rem;font-size:.85rem}.color-editor-alpha-row input[type=range]{flex:1 1 auto}.color-editor-alpha-row input[type=number]{width:4.5rem}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { 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.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "directive", type: i3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i5.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ColorPickerModule }, { kind: "component", type: i6.ColorPicker, selector: "p-colorPicker, p-colorpicker, p-color-picker", inputs: ["styleClass", "showTransitionOptions", "hideTransitionOptions", "inline", "format", "tabindex", "inputId", "autoZIndex", "autofocus", "defaultColor", "appendTo", "overlayOptions", "motionOptions"], outputs: ["onChange", "onShow", "onHide"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
4372
4301
  }
4373
4302
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: CssSheetEditorComponent, decorators: [{
4374
4303
  type: Component,
@@ -4479,6 +4408,8 @@ class DesignerComponent {
4479
4408
  reopenDashboardDialogVisible = false;
4480
4409
  reopenDialogSelectedDashboardRoute = null;
4481
4410
  noEdit;
4411
+ /** Chrome card dei widget top-level (flag board-level dal boardcontent wrapper-object). */
4412
+ dashboardWidgetChrome = false;
4482
4413
  currentDashboardRoute = '';
4483
4414
  currentDashboardDescription = '';
4484
4415
  showArchetypeConfigurator = false;
@@ -4527,79 +4458,12 @@ class DesignerComponent {
4527
4458
  this.metadataEditorSrv = metadataEditorSrv;
4528
4459
  this.chatbotHostRegistry = chatbotHostRegistry;
4529
4460
  this.workflowRuntimeMetadata = workflowRuntimeMetadata;
4530
- this.defaultCtxItems = [
4531
- {
4532
- label: this.t('designer.edit_properties', 'Edit properties'),
4533
- icon: 'pi pi-cog',
4534
- command: () => {
4535
- if (this.ctxElement) {
4536
- this.selectDashboardItem(this.ctxElement);
4537
- this.cd.detectChanges();
4538
- }
4539
- }
4540
- },
4541
- {
4542
- label: this.t('designer.remove', 'Remove'),
4543
- icon: 'pi pi-trash',
4544
- command: (menuItem) => {
4545
- if (this.ctxElement) {
4546
- this.removeElementByName(this.ctxElement.uniqueName);
4547
- }
4548
- }
4549
- }
4550
- ];
4461
+ // Context menu elemento: UNICA sorgente per tutti e tre i punti di accesso
4462
+ // (canvas / gerarchia / footer breadcrumb) -> `buildElementContextMenuItems`.
4463
+ // I tre handler `show*ContextMenu` ripopolano il rispettivo array con
4464
+ // l'elemento risolto al click, cosi' le voci sono sempre identiche.
4465
+ this.defaultCtxItems = this.buildElementContextMenuItems(null);
4551
4466
  this.ctxItems.next(this.defaultCtxItems);
4552
- this.footerCtxItems = [
4553
- {
4554
- label: this.t('designer.edit_properties', 'Edit properties'),
4555
- icon: 'pi pi-cog',
4556
- command: () => {
4557
- if (this.footerCtxElement) {
4558
- this.selectDashboardItem(this.footerCtxElement);
4559
- this.cd.detectChanges();
4560
- }
4561
- }
4562
- },
4563
- {
4564
- label: this.t('designer.rename', 'Rename'),
4565
- icon: 'pi pi-pencil',
4566
- command: () => {
4567
- this.renameFooterContextItem();
4568
- }
4569
- },
4570
- {
4571
- label: this.t('designer.remove', 'Remove'),
4572
- icon: 'pi pi-trash',
4573
- command: () => {
4574
- const uniqueName = String(this.footerCtxElement?.uniqueName || '').trim();
4575
- if (uniqueName) {
4576
- this.removeElementByName(uniqueName);
4577
- }
4578
- }
4579
- }
4580
- ];
4581
- this.hierarchyCtxItems = [
4582
- {
4583
- label: this.t('designer.edit_properties', 'Edit properties'),
4584
- icon: 'pi pi-cog',
4585
- command: () => {
4586
- if (this.hierarchyCtxElement) {
4587
- this.selectDashboardItem(this.hierarchyCtxElement);
4588
- this.cd.detectChanges();
4589
- }
4590
- }
4591
- },
4592
- {
4593
- label: this.t('designer.remove', 'Remove'),
4594
- icon: 'pi pi-trash',
4595
- command: () => {
4596
- const uniqueName = String(this.hierarchyCtxElement?.uniqueName || '').trim();
4597
- if (uniqueName) {
4598
- this.removeElementByName(uniqueName);
4599
- }
4600
- }
4601
- }
4602
- ];
4603
4467
  this.htmlStyleString = '[ngClass]="0.cssClass" [ngStyle]="{width: 0.width, maxWidth: 0.maxWidth, height: 0.height, maxHeight: 0.maxHeight, minWidth: 0.minWidth, minHeight: 0.minHeight, position: 0.position, left: 0.left, top: 0.top, float: 0.float, display: 0.display, boxSizing: 0.boxSizing, backgroundColor: 0.backgroundColor, color: 0.color, borderRadius: 0.borderRadius, borderBottomStyle: 0.borderBottomStyle, borderBottomWidth: 0.borderBottomWidth, borderBottomColor: 0.borderBottomColor, borderLeftStyle: 0.borderLeftStyle, borderLeftWidth: 0.borderLeftWidth, borderLeftColor: 0.borderLeftColor, borderRightStyle: 0.borderRightStyle, borderRightWidth: 0.borderRightWidth, borderRightColor: 0.borderRightColor, borderTopStyle: 0.borderTopStyle, borderTopWidth: 0.borderTopWidth, borderTopColor: 0.borderTopColor, paddingTop: 0.paddingTop, paddingRight: 0.paddingRight, paddingBottom: 0.paddingBottom, paddingLeft: 0.paddingLeft, marginTop: 0.marginTop, marginRight: 0.marginRight, marginBottom: 0.marginBottom, marginLeft: 0.marginLeft, fontSize: 0.fontSize, fontFamily: 0.fontFamily, fontWeight: 0.fontWeight, fontStyle: 0.fontStyle, textDecoration: 0.textDecoration, textAlign: 0.textAlign, verticalAlign: 0.verticalAlign, lineHeight: 0.lineHeight, letterSpacing: 0.letterSpacing, wordSpacing: 0.wordSpacing, textTransform: 0.textTransform, textIndent: 0.textIndent, whiteSpace: 0.whiteSpace, wordWrap: 0.wordWrap, wordBreak: 0.wordBreak, overflow: 0.overflow, overflowX: 0.overflowX, overflowY: 0.overflowY, zIndex: 0.zIndex, cursor: 0.cursor, visibility: 0.visibility, opacity: 0.opacity, filter: 0.filter, transform: 0.transform, transformOrigin: 0.transformOrigin, transition: 0.transition, boxShadow: 0.boxShadow, textShadow: 0.textShadow, backgroundPosition: 0.backgroundPosition, backgroundRepeat: 0.backgroundRepeat, backgroundAttachment: 0.backgroundAttachment, backgroundSize: 0.backgroundSize, backgroundImage: 0.backgroundImage, backgroundClip: 0.backgroundClip, backgroundOrigin: 0.backgroundOrigin, backgroundBlendMode: 0.backgroundBlendMode, clip: 0.clip, clipPath: 0.clipPath, clipRule: 0.clipRule, mask: 0.mask, maskType: 0.maskType, maskImage: 0.maskImage, maskMode: 0.maskMode, maskSize: 0.maskSize, maskRepeat: 0.maskRepeat, maskPosition: 0.maskPosition, maskClip: 0.maskClip, maskOrigin: 0.maskOrigin, maskComposite: 0.maskComposite, maskBorder: 0.maskBorder, maskBorderSource: 0.maskBorderSource, maskBorderSlice: 0.maskBorderSlice, maskBorderWidth: 0.maskBorderWidth, maskBorderOutset: 0.maskBorderOutset, maskBorderRepeat: 0.maskBorderRepeat, maskBorderMode: 0.maskBorderMode, maskBorderClip: 0.maskBorderClip, maskBorderOrigin: 0.maskBorderOrigin, maskBorder, tableLayout: 0.tableLayout, borderCollapse: 0.borderCollapse, borderSpacing: 0.borderSpacing, captionSide: 0.captionSide, emptyCells: 0.emptyCells, tableCaption: 0.tableCaption, tableBorder: 0.tableBorder, tableBorderSpacing: 0.tableBorderSpacing, tableBorderCollapse: 0.tableBorderCollapse, listStyle: 0.listStyle}"';
4604
4468
  this.htmlInputProperties = {
4605
4469
  animation: { type: 'string' },
@@ -4908,7 +4772,6 @@ class DesignerComponent {
4908
4772
  size: 50,
4909
4773
  areas: 2,
4910
4774
  resize: (event, splitter) => {
4911
- // let indx = event.gutterNum;
4912
4775
  splitter.nestedComponents.forEach((area, i) => {
4913
4776
  area.inputs.size = event.sizes[i];
4914
4777
  });
@@ -4967,29 +4830,18 @@ class DesignerComponent {
4967
4830
  delete this.accordionAreaInputs['cssClass'];
4968
4831
  this.accordionAreaInputs['height'] = null;
4969
4832
  this.accordionAreaInputs['display'] = 'block';
4970
- // this.accordionAreaInputs['borderStyle'] = 'none';
4971
4833
  this.accordionAreaInputs['borderTopStyle'] = 'none';
4972
4834
  this.accordionAreaInputs['borderLeftStyle'] = 'none';
4973
4835
  this.accordionAreaInputs['borderRightStyle'] = 'none';
4974
4836
  this.accordionAreaInputs['borderBottomStyle'] = 'solid';
4975
4837
  this.accordionAreaInputs['borderBottomWidth'] = '1px';
4976
4838
  this.accordionAreaInputs['borderBottomColor'] = '#e2e8f0';
4977
- // let divCtx = [
4978
- // {
4979
- // label: 'Add DIV',
4980
- // icon: 'pi pi-plus',
4981
- // command: (menuItem) => {
4982
- // debugger;
4983
- // }
4984
- // } as any
4985
- // ];
4986
- // divCtx.push(...this.defaultCtxItems);
4987
4839
  this.availableTools = [
4988
4840
  {
4989
4841
  group: 'HTML',
4990
4842
  toolId: 1,
4991
4843
  name: 'TABLE',
4992
- tag: `<table [attr.id]="uniqueName" ngxDraggableDom="true" (moved)="onMoving($event, this)" ngResizable [rzHandles] = "'se'" (rzResizing)="onResizing($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}><tr *ngFor="let nestedTR of nestedComponents" [attr.id]="nestedTR.uniqueName" ${this.htmlStyleString.replace(/0/g, 'nestedTR.inputs')}><td *ngFor="let nestedTD of nestedTR.nestedComponents" [attr.id]="nestedTD.uniqueName" [attr.colspan]="nestedTD.inputs?.colSpan || null" [attr.rowspan]="nestedTD.inputs?.rowSpan || null" ngResizable [rzHandles] = "'se'" (rzResizing)="onResizing($event, nestedTD)" ${this.htmlStyleString.replace(/0/g, 'nestedTD.inputs')}><wuic-dashboard [dashboardElements]="nestedTD.nestedComponents"></wuic-dashboard></td></tr></table>`,
4844
+ tag: `<table [attr.id]="uniqueName" ngxDraggableDom="true" (moved)="onMoving($event, this)" ngResizable [rzHandles] = "'se'" (rzResizing)="onResizing($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}><tr *ngFor="let nestedTR of nestedComponents" [attr.id]="nestedTR.uniqueName" ${this.htmlStyleString.replaceAll('0', 'nestedTR.inputs')}><td *ngFor="let nestedTD of nestedTR.nestedComponents" [attr.id]="nestedTD.uniqueName" [attr.colspan]="nestedTD.inputs?.colSpan || null" [attr.rowspan]="nestedTD.inputs?.rowSpan || null" ngResizable [rzHandles] = "'se'" (rzResizing)="onResizing($event, nestedTD)" ${this.htmlStyleString.replaceAll('0', 'nestedTD.inputs')}><wuic-dashboard [dashboardElements]="nestedTD.nestedComponents"></wuic-dashboard></td></tr></table>`,
4993
4845
  icon: 'pi pi-table',
4994
4846
  inputProps: tableProps,
4995
4847
  inputs: tableFullInputs,
@@ -5004,7 +4856,7 @@ class DesignerComponent {
5004
4856
  tag: ``,
5005
4857
  icon: 'pi pi-bars',
5006
4858
  inputProps: this.htmlInputProperties,
5007
- inputs: Object.assign({}, tableFullInputs, { cssClass: [] }),
4859
+ inputs: { ...tableFullInputs, cssClass: [] },
5008
4860
  nestedComponents: []
5009
4861
  },
5010
4862
  {
@@ -5014,21 +4866,24 @@ class DesignerComponent {
5014
4866
  name: 'TD',
5015
4867
  tag: ``,
5016
4868
  icon: 'pi pi-bars',
5017
- inputProps: Object.assign({}, this.htmlInputProperties, {
4869
+ inputProps: {
4870
+ ...this.htmlInputProperties,
5018
4871
  colSpan: { type: 'string', propertyCaption: 'Col span' },
5019
4872
  rowSpan: { type: 'string', propertyCaption: 'Row span' }
5020
- }),
5021
- inputs: Object.assign({}, tableFullInputs, {
4873
+ },
4874
+ inputs: {
4875
+ ...tableFullInputs,
5022
4876
  colSpan: '',
5023
- rowSpan: ''
5024
- }, { cssClass: [] }),
4877
+ rowSpan: '',
4878
+ cssClass: []
4879
+ },
5025
4880
  nestedComponents: []
5026
4881
  },
5027
4882
  {
5028
4883
  group: 'HTML',
5029
4884
  toolId: 2,
5030
4885
  name: 'DIV',
5031
- tag: `<div [attr.id]="uniqueName" ngxDraggableDom="true" ngResizable [rzHandles] = "'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}><wuic-dashboard [dashboardElements]="nestedComponents"></wuic-dashboard></div>`,
4886
+ tag: `<div [attr.id]="uniqueName" ngxDraggableDom="true" ngResizable [rzHandles] = "'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}><wuic-dashboard [dashboardElements]="nestedComponents"></wuic-dashboard></div>`,
5032
4887
  icon: 'pi pi-stop',
5033
4888
  inputProps: this.htmlInputProperties,
5034
4889
  inputs: this.htmlInputs,
@@ -5039,7 +4894,7 @@ class DesignerComponent {
5039
4894
  group: 'HTML',
5040
4895
  toolId: 3,
5041
4896
  name: 'SPAN',
5042
- tag: `<span [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}>{{inputs.innerText}}</span>`,
4897
+ tag: `<span [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}>{{inputs.innerText}}</span>`,
5043
4898
  icon: 'pi pi-language',
5044
4899
  inputProps: this.bindableHtmlProps,
5045
4900
  inputs: this.bindableHtmlInputs
@@ -5048,25 +4903,27 @@ class DesignerComponent {
5048
4903
  group: 'HTML',
5049
4904
  toolId: 17,
5050
4905
  name: 'LABEL',
5051
- tag: `<label [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles]="'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}>{{getLabelDisplayValue(inputs)}}</label>`,
4906
+ tag: `<label [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles]="'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}>{{getLabelDisplayValue(inputs)}}</label>`,
5052
4907
  icon: 'pi pi-tag',
5053
- inputProps: Object.assign({}, this.bindableHtmlProps, {
4908
+ inputProps: {
4909
+ ...this.bindableHtmlProps,
5054
4910
  displayField: { type: 'dictionary', values: [''], propertyCaption: 'Display Field' },
5055
4911
  displayFormula: { type: 'button', propertyCaption: 'Display Formula', hideCaption: true, callback: this.openLabelDisplayFormulaEditor.bind(this) }
5056
- }),
5057
- inputs: Object.assign({}, this.bindableHtmlInputs, {
4912
+ },
4913
+ inputs: {
4914
+ ...this.bindableHtmlInputs,
5058
4915
  datasource: null,
5059
4916
  componentRef: null,
5060
4917
  innerText: 'Label',
5061
4918
  displayField: '',
5062
4919
  displayFormula: ''
5063
- })
4920
+ }
5064
4921
  },
5065
4922
  {
5066
4923
  group: 'HTML',
5067
4924
  toolId: 4,
5068
4925
  name: 'Hx',
5069
- tag: `<h1 *ngIf="inputs.headerType == 'h1'" [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}>{{inputs.innerText}}</h1><h2 *ngIf="inputs.headerType == 'h2'" [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}>{{inputs.innerText}}</h2><h3 *ngIf="inputs.headerType == 'h3'" [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}>{{inputs.innerText}}</h3><h4 *ngIf="inputs.headerType == 'h4'" [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}>{{inputs.innerText}}</h4><h5 *ngIf="inputs.headerType == 'h5'" [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}>{{inputs.innerText}}</h5><h6 *ngIf="inputs.headerType == 'h6'" [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}>{{inputs.innerText}}</h6>`,
4926
+ tag: `<h1 *ngIf="inputs.headerType == 'h1'" [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}>{{inputs.innerText}}</h1><h2 *ngIf="inputs.headerType == 'h2'" [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}>{{inputs.innerText}}</h2><h3 *ngIf="inputs.headerType == 'h3'" [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}>{{inputs.innerText}}</h3><h4 *ngIf="inputs.headerType == 'h4'" [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}>{{inputs.innerText}}</h4><h5 *ngIf="inputs.headerType == 'h5'" [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}>{{inputs.innerText}}</h5><h6 *ngIf="inputs.headerType == 'h6'" [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}>{{inputs.innerText}}</h6>`,
5070
4927
  icon: 'pi pi-chevron-up',
5071
4928
  inputProps: this.headerProps,
5072
4929
  inputs: this.headerInputs
@@ -5075,7 +4932,7 @@ class DesignerComponent {
5075
4932
  group: 'HTML',
5076
4933
  toolId: 5,
5077
4934
  name: 'ANCHOR',
5078
- tag: `<a [attr.id]="uniqueName" [href]="inputs.href" [target]="inputs.target" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}>{{inputs.innerText}}</a>`,
4935
+ tag: `<a [attr.id]="uniqueName" [href]="inputs.href" [target]="inputs.target" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}>{{inputs.innerText}}</a>`,
5079
4936
  icon: 'pi pi-link',
5080
4937
  inputProps: this.anchorProps,
5081
4938
  inputs: this.anchorInputs
@@ -5084,7 +4941,7 @@ class DesignerComponent {
5084
4941
  group: 'HTML',
5085
4942
  toolId: 6,
5086
4943
  name: 'IMG',
5087
- tag: `<img [attr.id]="uniqueName" [src]="inputs.src" [alt]="inputs.alt" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')} />`,
4944
+ tag: `<img [attr.id]="uniqueName" [src]="inputs.src" [alt]="inputs.alt" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')} />`,
5088
4945
  icon: 'pi pi-image',
5089
4946
  inputProps: this.imgProps,
5090
4947
  inputs: this.imgInputs
@@ -5093,26 +4950,51 @@ class DesignerComponent {
5093
4950
  group: 'HTML',
5094
4951
  toolId: 18,
5095
4952
  name: 'IFRAME',
5096
- tag: `<iframe [attr.id]="uniqueName" [attr.src]="inputs.src" [attr.frameborder]="inputs.frameBorder" [attr.allowfullscreen]="inputs.allowFullscreen ? '' : null" ngxDraggableDom="true" ngResizable [rzHandles]="'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}></iframe>`,
4953
+ tag: `<iframe [attr.id]="uniqueName" [attr.src]="inputs.src" [attr.frameborder]="inputs.frameBorder" [attr.allowfullscreen]="inputs.allowFullscreen ? '' : null" ngxDraggableDom="true" ngResizable [rzHandles]="'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}></iframe>`,
5097
4954
  icon: 'pi pi-window-maximize',
5098
- inputProps: Object.assign({}, this.htmlInputProperties, {
4955
+ inputProps: {
4956
+ ...this.htmlInputProperties,
5099
4957
  src: { type: 'string', propertyCaption: 'Source URL' },
5100
4958
  frameBorder: { type: 'dictionary', values: ['0', '1'], propertyCaption: 'Frame Border' },
5101
4959
  allowFullscreen: { type: 'boolean', propertyCaption: 'Allow Fullscreen' }
5102
- }),
5103
- inputs: Object.assign({}, this.htmlInputs, {
4960
+ },
4961
+ inputs: {
4962
+ ...this.htmlInputs,
5104
4963
  src: '',
5105
4964
  frameBorder: '0',
5106
4965
  allowFullscreen: false,
5107
4966
  width: '480px',
5108
4967
  height: '280px'
5109
- })
4968
+ }
4969
+ },
4970
+ {
4971
+ group: 'HTML',
4972
+ toolId: 130,
4973
+ name: 'IMAGE',
4974
+ tag: `<wuic-image-wrapper-lazy [attr.id]="uniqueName" [src]="inputs.src" [alt]="inputs.alt" [preview]="inputs.preview" [previewImageSrc]="inputs.previewImageSrc" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}></wuic-image-wrapper-lazy>`,
4975
+ icon: 'pi pi-images',
4976
+ inputProps: {
4977
+ ...this.htmlInputProperties,
4978
+ src: { type: 'string', propertyCaption: 'Source URL' },
4979
+ previewImageSrc: { type: 'string', propertyCaption: 'Preview (full-size) URL' },
4980
+ alt: { type: 'string', propertyCaption: 'Alt text' },
4981
+ preview: { type: 'boolean', propertyCaption: 'Enable lightbox preview' }
4982
+ },
4983
+ inputs: {
4984
+ ...this.htmlInputs,
4985
+ src: '',
4986
+ previewImageSrc: '',
4987
+ alt: '',
4988
+ preview: true,
4989
+ width: '240px',
4990
+ height: '160px'
4991
+ }
5110
4992
  },
5111
4993
  {
5112
4994
  group: 'HTML',
5113
4995
  toolId: 7,
5114
4996
  name: 'UL',
5115
- tag: `<ul [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [itemTemplate]="inputs.itemTemplate" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}>
4997
+ tag: `<ul [attr.id]="uniqueName" [databound]="inputs.datasource?.component" [itemTemplate]="inputs.itemTemplate" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}>
5116
4998
  <li *ngFor="let li of (inputs.datasource?.component?.value?.resultInfo?.dato || (inputs.optionsCsv || '').split(','))">
5117
4999
  <ng-container *ngIf="inputs.displayFormula; else ulItemTemplate">{{getUlItemDisplayValue(inputs, li)}}</ng-container>
5118
5000
  <ng-template #ulItemTemplate>
@@ -5122,28 +5004,30 @@ class DesignerComponent {
5122
5004
  </ul>`,
5123
5005
  icon: 'pi pi-list',
5124
5006
  inputProps: (() => {
5125
- const ulProps = Object.assign({}, this.htmlItemTemplateProps, {
5007
+ const ulProps = {
5008
+ ...this.htmlItemTemplateProps,
5126
5009
  optionsCsv: { type: 'string', propertyCaption: 'Options CSV' },
5127
5010
  valueField: { type: 'dictionary', values: [''], propertyCaption: 'Value Field' },
5128
5011
  textField: { type: 'dictionary', values: [''], propertyCaption: 'Text Field' },
5129
5012
  displayFormula: { type: 'button', propertyCaption: 'Display Formula', hideCaption: true, callback: this.openUlDisplayFormulaEditor.bind(this) }
5130
- });
5013
+ };
5131
5014
  delete ulProps['bindingFunction'];
5132
5015
  return ulProps;
5133
5016
  })(),
5134
- inputs: Object.assign({}, this.bindableHtmlInputs, {
5017
+ inputs: {
5018
+ ...this.bindableHtmlInputs,
5135
5019
  optionsCsv: 'Item 1,Item 2,Item 3',
5136
5020
  valueField: '',
5137
5021
  textField: '',
5138
5022
  displayFormula: ''
5139
- }),
5023
+ },
5140
5024
  nestedComponents: []
5141
5025
  },
5142
5026
  {
5143
5027
  group: 'HTML',
5144
5028
  toolId: 8,
5145
5029
  name: 'BUTTON',
5146
- tag: `<button [attr.id]="uniqueName" (click)="inputs.clickCallback__fn(inputs.datasource?.component?.value?.resultInfo, inputs.datasource?.component?.value?.metaInfo, inputs, $event, inputs._wtoolbox)" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [itemTemplate]="inputs.itemTemplate" [clickCallback]="inputs.clickCallback" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}><ng-container *ngComponentOutlet="inputs.itemTemplateComponent; inputs: { rowData: inputs.templateData, metaInfo: inputs.datasource?.component?.value?.metaInfo, datasource: inputs.datasource?.component?.value }"></ng-container></button>`,
5030
+ tag: `<button [attr.id]="uniqueName" (click)="inputs.clickCallback__fn(inputs.datasource?.component?.value?.resultInfo, inputs.datasource?.component?.value?.metaInfo, inputs, $event, inputs._wtoolbox)" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [itemTemplate]="inputs.itemTemplate" [clickCallback]="inputs.clickCallback" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}><ng-container *ngComponentOutlet="inputs.itemTemplateComponent; inputs: { rowData: inputs.templateData, metaInfo: inputs.datasource?.component?.value?.metaInfo, datasource: inputs.datasource?.component?.value }"></ng-container></button>`,
5147
5031
  icon: 'pi pi-arrow-circle-down',
5148
5032
  inputProps: this.htmlButtonProps,
5149
5033
  inputs: this.bindableHtmlInputs
@@ -5152,58 +5036,64 @@ class DesignerComponent {
5152
5036
  group: 'HTML',
5153
5037
  toolId: 9,
5154
5038
  name: 'INPUT',
5155
- tag: `<input [attr.id]="uniqueName" [type]="inputs.inputType || 'text'" [placeholder]="inputs.placeholder" [readonly]="inputs.readonly" [disabled]="inputs.disabled" [value]="inputs.value" (input)="inputs.value=$any($event.target).value" ngxDraggableDom="true" ngResizable [rzHandles]="'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')} />`,
5039
+ tag: `<input [attr.id]="uniqueName" [type]="inputs.inputType || 'text'" [placeholder]="inputs.placeholder" [readonly]="inputs.readonly" [disabled]="inputs.disabled" [value]="inputs.value" (input)="inputs.value=$any($event.target).value" ngxDraggableDom="true" ngResizable [rzHandles]="'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')} />`,
5156
5040
  icon: 'pi pi-pencil',
5157
- inputProps: Object.assign({}, this.htmlInputProperties, {
5041
+ inputProps: {
5042
+ ...this.htmlInputProperties,
5158
5043
  inputType: { type: 'dictionary', propertyCaption: 'Type', values: ['text', 'number', 'email', 'password', 'search', 'url'] },
5159
5044
  placeholder: { type: 'string', propertyCaption: 'Placeholder' },
5160
5045
  value: { type: 'string', propertyCaption: 'Value' },
5161
5046
  readonly: { type: 'boolean', propertyCaption: 'Readonly' },
5162
5047
  disabled: { type: 'boolean', propertyCaption: 'Disabled' }
5163
- }),
5164
- inputs: Object.assign({}, this.htmlInputs, {
5048
+ },
5049
+ inputs: {
5050
+ ...this.htmlInputs,
5165
5051
  inputType: 'text',
5166
5052
  placeholder: '',
5167
5053
  value: '',
5168
5054
  readonly: false,
5169
5055
  disabled: false,
5170
5056
  height: '32px'
5171
- })
5057
+ }
5172
5058
  },
5173
5059
  {
5174
5060
  group: 'HTML',
5175
5061
  toolId: 10,
5176
5062
  name: 'TEXTAREA',
5177
- tag: `<textarea [attr.id]="uniqueName" [placeholder]="inputs.placeholder" [readonly]="inputs.readonly" [disabled]="inputs.disabled" [rows]="inputs.rows" [value]="inputs.value" (input)="inputs.value=$any($event.target).value" ngxDraggableDom="true" ngResizable [rzHandles]="'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}></textarea>`,
5063
+ tag: `<textarea [attr.id]="uniqueName" [placeholder]="inputs.placeholder" [readonly]="inputs.readonly" [disabled]="inputs.disabled" [rows]="inputs.rows" [value]="inputs.value" (input)="inputs.value=$any($event.target).value" ngxDraggableDom="true" ngResizable [rzHandles]="'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}></textarea>`,
5178
5064
  icon: 'pi pi-align-left',
5179
- inputProps: Object.assign({}, this.htmlInputProperties, {
5065
+ inputProps: {
5066
+ ...this.htmlInputProperties,
5180
5067
  placeholder: { type: 'string', propertyCaption: 'Placeholder' },
5181
5068
  value: { type: 'txt_area', propertyCaption: 'Value' },
5182
5069
  rows: { type: 'string', propertyCaption: 'Rows' },
5183
5070
  readonly: { type: 'boolean', propertyCaption: 'Readonly' },
5184
5071
  disabled: { type: 'boolean', propertyCaption: 'Disabled' }
5185
- }),
5186
- inputs: Object.assign({}, this.htmlInputs, {
5072
+ },
5073
+ inputs: {
5074
+ ...this.htmlInputs,
5187
5075
  placeholder: '',
5188
5076
  value: '',
5189
5077
  rows: '4',
5190
5078
  readonly: false,
5191
5079
  disabled: false,
5192
5080
  height: '96px'
5193
- })
5081
+ }
5194
5082
  },
5195
5083
  {
5196
5084
  group: 'HTML',
5197
5085
  toolId: 11,
5198
5086
  name: 'CHECKBOX',
5199
- tag: `<label [attr.id]="uniqueName" ngxDraggableDom="true" ngResizable [rzHandles]="'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}><input type="checkbox" [checked]="inputs.checked" (change)="inputs.checked=$any($event.target).checked" [disabled]="inputs.disabled" /> {{inputs.label}}</label>`,
5087
+ tag: `<label [attr.id]="uniqueName" ngxDraggableDom="true" ngResizable [rzHandles]="'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}><input type="checkbox" [checked]="inputs.checked" (change)="inputs.checked=$any($event.target).checked" [disabled]="inputs.disabled" /> {{inputs.label}}</label>`,
5200
5088
  icon: 'pi pi-check-square',
5201
- inputProps: Object.assign({}, this.htmlInputProperties, {
5089
+ inputProps: {
5090
+ ...this.htmlInputProperties,
5202
5091
  label: { type: 'string', propertyCaption: 'Label' },
5203
5092
  checked: { type: 'boolean', propertyCaption: 'Checked' },
5204
5093
  disabled: { type: 'boolean', propertyCaption: 'Disabled' }
5205
- }),
5206
- inputs: Object.assign({}, this.htmlInputs, {
5094
+ },
5095
+ inputs: {
5096
+ ...this.htmlInputs,
5207
5097
  label: 'Checkbox',
5208
5098
  checked: false,
5209
5099
  disabled: false,
@@ -5214,16 +5104,17 @@ class DesignerComponent {
5214
5104
  borderRightStyle: 'none',
5215
5105
  borderBottomStyle: 'none',
5216
5106
  borderLeftStyle: 'none'
5217
- })
5107
+ }
5218
5108
  },
5219
5109
  {
5220
5110
  group: 'HTML',
5221
5111
  toolId: 12,
5222
5112
  name: 'SEPARATOR',
5223
- tag: `<hr [attr.id]="uniqueName" ngxDraggableDom="true" ngResizable [rzHandles]="'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')} />`,
5113
+ tag: `<hr [attr.id]="uniqueName" ngxDraggableDom="true" ngResizable [rzHandles]="'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')} />`,
5224
5114
  icon: 'pi pi-minus',
5225
5115
  inputProps: this.htmlInputProperties,
5226
- inputs: Object.assign({}, this.htmlInputs, {
5116
+ inputs: {
5117
+ ...this.htmlInputs,
5227
5118
  width: '100%',
5228
5119
  height: '2px',
5229
5120
  // borderStyle: 'none',
@@ -5233,16 +5124,17 @@ class DesignerComponent {
5233
5124
  borderRightStyle: 'none',
5234
5125
  borderBottomStyle: 'none',
5235
5126
  borderLeftStyle: 'none'
5236
- })
5127
+ }
5237
5128
  },
5238
5129
  {
5239
5130
  group: 'HTML',
5240
5131
  toolId: 19,
5241
5132
  name: 'HR',
5242
- tag: `<hr [attr.id]="uniqueName" ngxDraggableDom="true" ngResizable [rzHandles]="'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')} />`,
5133
+ tag: `<hr [attr.id]="uniqueName" ngxDraggableDom="true" ngResizable [rzHandles]="'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')} />`,
5243
5134
  icon: 'pi pi-minus',
5244
5135
  inputProps: this.htmlInputProperties,
5245
- inputs: Object.assign({}, this.htmlInputs, {
5136
+ inputs: {
5137
+ ...this.htmlInputs,
5246
5138
  width: '100%',
5247
5139
  height: '2px',
5248
5140
  // borderStyle: 'none',
@@ -5252,19 +5144,21 @@ class DesignerComponent {
5252
5144
  borderRightStyle: 'none',
5253
5145
  borderBottomStyle: 'none',
5254
5146
  borderLeftStyle: 'none'
5255
- })
5147
+ }
5256
5148
  },
5257
5149
  {
5258
5150
  group: 'HTML',
5259
5151
  toolId: 13,
5260
5152
  name: 'KPI',
5261
- tag: `<div [attr.id]="uniqueName" ngxDraggableDom="true" ngResizable [rzHandles]="'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}><div style="font-size:12px; opacity:.75;">{{inputs.caption}}</div><div style="font-size:28px; font-weight:700; line-height:1.2;">{{inputs.value}}</div></div>`,
5153
+ tag: `<div [attr.id]="uniqueName" ngxDraggableDom="true" ngResizable [rzHandles]="'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}><div style="font-size:12px; opacity:.75;">{{inputs.caption}}</div><div style="font-size:28px; font-weight:700; line-height:1.2;">{{inputs.value}}</div></div>`,
5262
5154
  icon: 'pi pi-chart-line',
5263
- inputProps: Object.assign({}, this.htmlInputProperties, {
5155
+ inputProps: {
5156
+ ...this.htmlInputProperties,
5264
5157
  caption: { type: 'string', propertyCaption: 'Caption' },
5265
5158
  value: { type: 'string', propertyCaption: 'Value' }
5266
- }),
5267
- inputs: Object.assign({}, this.htmlInputs, {
5159
+ },
5160
+ inputs: {
5161
+ ...this.htmlInputs,
5268
5162
  caption: 'KPI',
5269
5163
  value: '0',
5270
5164
  width: '180px',
@@ -5277,42 +5171,47 @@ class DesignerComponent {
5277
5171
  // borderStyle: 'solid',
5278
5172
  // borderWidth: '1px',
5279
5173
  // borderColor: '#d1d5db'
5280
- })
5174
+ }
5281
5175
  },
5282
5176
  {
5283
5177
  group: 'HTML',
5284
5178
  toolId: 14,
5285
5179
  name: 'DATE',
5286
- tag: `<input [attr.id]="uniqueName" type="date" [readonly]="inputs.readonly" [disabled]="inputs.disabled" [value]="inputs.value" (input)="inputs.value=$any($event.target).value" ngxDraggableDom="true" ngResizable [rzHandles]="'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')} />`,
5180
+ tag: `<input [attr.id]="uniqueName" type="date" [readonly]="inputs.readonly" [disabled]="inputs.disabled" [value]="inputs.value" (input)="inputs.value=$any($event.target).value" ngxDraggableDom="true" ngResizable [rzHandles]="'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')} />`,
5287
5181
  icon: 'pi pi-calendar',
5288
- inputProps: Object.assign({}, this.htmlInputProperties, {
5182
+ inputProps: {
5183
+ ...this.htmlInputProperties,
5289
5184
  value: { type: 'string', propertyCaption: 'Value (yyyy-mm-dd)' },
5290
5185
  readonly: { type: 'boolean', propertyCaption: 'Readonly' },
5291
5186
  disabled: { type: 'boolean', propertyCaption: 'Disabled' }
5292
- }),
5293
- inputs: Object.assign({}, this.htmlInputs, {
5187
+ },
5188
+ inputs: {
5189
+ ...this.htmlInputs,
5294
5190
  value: '',
5295
5191
  readonly: false,
5296
5192
  disabled: false,
5297
5193
  width: '180px',
5298
5194
  height: '32px'
5299
- })
5195
+ }
5300
5196
  },
5301
5197
  {
5302
5198
  group: 'HTML',
5303
5199
  toolId: 15,
5304
5200
  name: 'SELECT',
5305
- tag: `<select [attr.id]="uniqueName" [disabled]="inputs.disabled" [value]="inputs.value" (change)="inputs.value=$any($event.target).value" ngxDraggableDom="true" ngResizable [rzHandles]="'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}><option *ngFor="let opt of (inputs.datasource?.component?.value?.resultInfo?.dato || (inputs.optionsCsv || '').split(','))" [value]="inputs.datasource?.component?.value?.resultInfo?.dato ? (opt?.[inputs.valueField] ?? '') : opt.trim()">{{getSelectOptionLabel(inputs, opt)}}</option></select>`,
5201
+ tag: `<select [attr.id]="uniqueName" [disabled]="inputs.disabled" [value]="inputs.value" (change)="inputs.value=$any($event.target).value" ngxDraggableDom="true" ngResizable [rzHandles]="'e'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}><option *ngFor="let opt of (inputs.datasource?.component?.value?.resultInfo?.dato || (inputs.optionsCsv || '').split(','))" [value]="inputs.datasource?.component?.value?.resultInfo?.dato ? (opt?.[inputs.valueField] ?? '') : opt.trim()">{{getSelectOptionLabel(inputs, opt)}}</option></select>`,
5306
5202
  icon: 'pi pi-list',
5307
- inputProps: Object.assign({}, this.htmlInputProperties, this.dsProperties, {
5203
+ inputProps: {
5204
+ ...this.htmlInputProperties,
5205
+ ...this.dsProperties,
5308
5206
  optionsCsv: { type: 'string', propertyCaption: 'Options CSV' },
5309
5207
  valueField: { type: 'dictionary', values: [''], propertyCaption: 'Value Field' },
5310
5208
  textField: { type: 'dictionary', values: [''], propertyCaption: 'Text Field' },
5311
5209
  displayFormula: { type: 'button', propertyCaption: 'Display Formula', hideCaption: true, callback: this.openSelectDisplayFormulaEditor.bind(this) },
5312
5210
  value: { type: 'string', propertyCaption: 'Selected Value' },
5313
5211
  disabled: { type: 'boolean', propertyCaption: 'Disabled' }
5314
- }),
5315
- inputs: Object.assign({}, this.htmlInputs, {
5212
+ },
5213
+ inputs: {
5214
+ ...this.htmlInputs,
5316
5215
  datasource: null,
5317
5216
  componentRef: null,
5318
5217
  optionsCsv: 'Option 1,Option 2,Option 3',
@@ -5323,15 +5222,17 @@ class DesignerComponent {
5323
5222
  disabled: false,
5324
5223
  width: '180px',
5325
5224
  height: '32px'
5326
- })
5225
+ }
5327
5226
  },
5328
5227
  {
5329
5228
  group: 'HTML',
5330
5229
  toolId: 16,
5331
5230
  name: 'MULTISELECT',
5332
- tag: `<select [attr.id]="uniqueName" multiple="true" [disabled]="inputs.disabled" [size]="inputs.size || 4" (change)="inputs.values = [].slice.call($any($event.target).selectedOptions || []).map(x => x.value)" ngxDraggableDom="true" ngResizable [rzHandles]="'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}><option *ngFor="let opt of (inputs.datasource?.component?.value?.resultInfo?.dato || (inputs.optionsCsv || '').split(','))" [value]="inputs.datasource?.component?.value?.resultInfo?.dato ? (opt?.[inputs.valueField] ?? '') : opt.trim()" [selected]="(inputs.values || []).includes((inputs.datasource?.component?.value?.resultInfo?.dato ? (opt?.[inputs.valueField] ?? '') : opt.trim()) + '')">{{getSelectOptionLabel(inputs, opt)}}</option></select>`,
5231
+ tag: `<select [attr.id]="uniqueName" multiple="true" [disabled]="inputs.disabled" [size]="inputs.size || 4" (change)="inputs.values = [].slice.call($any($event.target).selectedOptions || []).map(x => x.value)" ngxDraggableDom="true" ngResizable [rzHandles]="'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}><option *ngFor="let opt of (inputs.datasource?.component?.value?.resultInfo?.dato || (inputs.optionsCsv || '').split(','))" [value]="inputs.datasource?.component?.value?.resultInfo?.dato ? (opt?.[inputs.valueField] ?? '') : opt.trim()" [selected]="(inputs.values || []).includes((inputs.datasource?.component?.value?.resultInfo?.dato ? (opt?.[inputs.valueField] ?? '') : opt.trim()) + '')">{{getSelectOptionLabel(inputs, opt)}}</option></select>`,
5333
5232
  icon: 'pi pi-check-square',
5334
- inputProps: Object.assign({}, this.htmlInputProperties, this.dsProperties, {
5233
+ inputProps: {
5234
+ ...this.htmlInputProperties,
5235
+ ...this.dsProperties,
5335
5236
  optionsCsv: { type: 'string', propertyCaption: 'Options CSV' },
5336
5237
  valueField: { type: 'dictionary', values: [''], propertyCaption: 'Value Field' },
5337
5238
  textField: { type: 'dictionary', values: [''], propertyCaption: 'Text Field' },
@@ -5339,8 +5240,9 @@ class DesignerComponent {
5339
5240
  values: { type: 'txt_area', propertyCaption: 'Selected Values (array)' },
5340
5241
  size: { type: 'string', propertyCaption: 'Visible Rows' },
5341
5242
  disabled: { type: 'boolean', propertyCaption: 'Disabled' }
5342
- }),
5343
- inputs: Object.assign({}, this.htmlInputs, {
5243
+ },
5244
+ inputs: {
5245
+ ...this.htmlInputs,
5344
5246
  datasource: null,
5345
5247
  componentRef: null,
5346
5248
  optionsCsv: 'Option 1,Option 2,Option 3',
@@ -5352,7 +5254,7 @@ class DesignerComponent {
5352
5254
  disabled: false,
5353
5255
  width: '200px',
5354
5256
  height: '96px'
5355
- })
5257
+ }
5356
5258
  },
5357
5259
  {
5358
5260
  group: 'DATA',
@@ -5427,7 +5329,7 @@ class DesignerComponent {
5427
5329
  name: 'FILTERBAR',
5428
5330
  tag: `<wuic-filter-bar [attr.id]="uniqueName" [datasource]="inputs.datasource?.component"></wuic-filter-bar>`,
5429
5331
  icon: 'pi pi-filter',
5430
- inputProps: Object.assign({}, this.dsProperties),
5332
+ inputProps: { ...this.dsProperties },
5431
5333
  inputs: {
5432
5334
  datasource: null,
5433
5335
  componentRef: null
@@ -5439,10 +5341,11 @@ class DesignerComponent {
5439
5341
  name: 'PAGER',
5440
5342
  tag: `<wuic-pager [attr.id]="uniqueName" [datasource]="inputs.datasource?.component" [pageSize]="inputs.pageSize" [currentPage]="inputs.currentPage"></wuic-pager>`,
5441
5343
  icon: 'pi pi-angle-double-right',
5442
- inputProps: Object.assign({}, this.dsProperties, {
5344
+ inputProps: {
5345
+ ...this.dsProperties,
5443
5346
  pageSize: { type: 'string', propertyCaption: 'Page Size' },
5444
5347
  currentPage: { type: 'string', propertyCaption: 'Current Page' }
5445
- }),
5348
+ },
5446
5349
  inputs: {
5447
5350
  datasource: null,
5448
5351
  componentRef: null,
@@ -5454,12 +5357,12 @@ class DesignerComponent {
5454
5357
  group: 'CONTAINER',
5455
5358
  toolId: 1000,
5456
5359
  name: 'TABVIEW',
5457
- tag: `<p-tabView [attr.id]="uniqueName" [tabs]="nestedComponents" ngxDraggableDom="true" (moved)="onMoving($event, this)" ngResizable [rzHandles]="'se'" (rzResizing)="onResizing($event, this)" ${this.htmlStyleString.replace('[ngStyle]', '[style]').replace(/0/g, 'inputs')}>
5360
+ tag: `<p-tabView [attr.id]="uniqueName" [tabs]="nestedComponents" ngxDraggableDom="true" (moved)="onMoving($event, this)" ngResizable [rzHandles]="'se'" (rzResizing)="onResizing($event, this)" ${this.htmlStyleString.replace('[ngStyle]', '[style]').replaceAll('0', 'inputs')}>
5458
5361
  <wuic-dashboard [dashboardElements]="nestedComponents"></wuic-dashboard>
5459
5362
  </p-tabView>`,
5460
5363
  icon: 'pi pi-folder',
5461
5364
  inputProps: this.htmlInputProperties,
5462
- inputs: Object.assign({}, this.htmlInputs, { cssClass: [] }),
5365
+ inputs: { ...this.htmlInputs, cssClass: [] },
5463
5366
  nestedComponents: [],
5464
5367
  allowedChildren: ['TABPANEL']
5465
5368
  },
@@ -5476,14 +5379,17 @@ class DesignerComponent {
5476
5379
  selected: { type: 'boolean', propertyCaption: 'Selected', hide: true }
5477
5380
  },
5478
5381
  inputs: { header: '<header>' },
5479
- nestedComponents: []
5382
+ nestedComponents: [],
5383
+ // Appena droppato dentro un TABVIEW il tab deve diventare quello attivo,
5384
+ // altrimenti la sua area non e' visibile e non ci si possono droppare figli.
5385
+ onDrop: this.dropTabPanel.bind(this)
5480
5386
  },
5481
5387
  {
5482
5388
  group: 'CONTAINER',
5483
5389
  toolId: 1002,
5484
5390
  name: 'SPLITTER',
5485
- tag: `<p-splitter [attr.id]="uniqueName" [direction]="inputs.direction" (dragEnd)="inputs.resize?.($event, this)" ngxDraggableDom="true" (moved)="onMoving($event, this)" ngResizable [rzHandles]="'se'" (rzResizing)="onResizing($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}>
5486
- <p-splitter-area *ngFor="let nestedArea of nestedComponents" [attr.id]="nestedArea.uniqueName" [size]="nestedArea.inputs.size" ${this.htmlStyleString.replace(/0/g, 'nestedArea.inputs')}>
5391
+ tag: `<p-splitter [attr.id]="uniqueName" [direction]="inputs.direction" (dragEnd)="inputs.resize?.($event, this)" ngxDraggableDom="true" (moved)="onMoving($event, this)" ngResizable [rzHandles]="'se'" (rzResizing)="onResizing($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}>
5392
+ <p-splitter-area *ngFor="let nestedArea of nestedComponents" [attr.id]="nestedArea.uniqueName" [size]="nestedArea.inputs.size" ${this.htmlStyleString.replaceAll('0', 'nestedArea.inputs')}>
5487
5393
  <wuic-dashboard [dashboardElements]="nestedArea.nestedComponents"></wuic-dashboard>
5488
5394
  </p-splitter-area>
5489
5395
  </p-splitter>`,
@@ -5510,9 +5416,9 @@ class DesignerComponent {
5510
5416
  group: 'CONTAINER',
5511
5417
  toolId: 1004,
5512
5418
  name: 'ACCORDION',
5513
- tag: ` <cdk-accordion class="wuic-accordion-tab" [attr.id]="uniqueName" (dragEnd)="inputs.resize?.($event, this)" ngxDraggableDom="true" (moved)="onMoving($event, this)" ngResizable [rzHandles]="'se'" (rzResizing)="onResizing($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}>
5419
+ tag: ` <cdk-accordion class="wuic-accordion-tab" [attr.id]="uniqueName" (dragEnd)="inputs.resize?.($event, this)" ngxDraggableDom="true" (moved)="onMoving($event, this)" ngResizable [rzHandles]="'se'" (rzResizing)="onResizing($event, this)" ${this.htmlStyleString.replaceAll('0', 'inputs')}>
5514
5420
  <cdk-accordion-item *ngFor="let nestedItem of nestedComponents" [attr.id]="nestedItem.uniqueName"
5515
- ${this.htmlStyleString.replace(/0/g, 'nestedItem.inputs')}>
5421
+ ${this.htmlStyleString.replaceAll('0', 'nestedItem.inputs')}>
5516
5422
  <div class="wuic-accordion-header">
5517
5423
  <a class="wuic-accordion-header-link" (click)="inputs.toggle?.($event, this, nestedItem)">{{nestedItem.inputs.header}}</a>
5518
5424
  </div>
@@ -5551,7 +5457,7 @@ class DesignerComponent {
5551
5457
  * oppure ricarica la lista dashboard salvate.
5552
5458
  */
5553
5459
  async ngOnInit() {
5554
- this.lastKnownHashRoute = String(window?.location?.hash || '');
5460
+ this.lastKnownHashRoute = String(globalThis?.location?.hash || '');
5555
5461
  this.refreshAvailableProjectCssClasses();
5556
5462
  // Registra handler chatbot per kind 'designer_inject': il chatbot
5557
5463
  // popover (globale, vedi rag-chatbot.component.ts onApplyAction)
@@ -5702,7 +5608,10 @@ class DesignerComponent {
5702
5608
  let topParent = null;
5703
5609
  const declared = (node && typeof node.parent_unique_name === 'string') ? node.parent_unique_name.trim() : '';
5704
5610
  if (declared) {
5705
- if (declared.startsWith('<') && declared.endsWith('>')) {
5611
+ if (this.isRootPlaceholder(declared)) {
5612
+ topParent = null; // alias esplicito del caso 3: va al ROOT
5613
+ }
5614
+ else if (declared.startsWith('<') && declared.endsWith('>')) {
5706
5615
  if (lastTopLevelInjected) {
5707
5616
  const resolved = this.resolveLayoutPlaceholder(declared, lastTopLevelInjected);
5708
5617
  if (resolved)
@@ -5722,7 +5631,18 @@ class DesignerComponent {
5722
5631
  // Aggiorna il last top-level per i prossimi placeholder. NB: usiamo
5723
5632
  // findLastInjectedNode per pescare l'istanza vera (con onDrop appena
5724
5633
  // eseguito = TR/TD/AREA gia' popolati), non il template clonato.
5725
- lastTopLevelInjected = this.findLastInjectedNode(String(node.tool_name || ''), topParent) || lastTopLevelInjected;
5634
+ // FIX 2026-06-21 (designer-canvas-verify [2], layout 3-colonne): aggiorna
5635
+ // SOLO se il nodo iniettato e' un CONTAINER che auto-crea celle/aree
5636
+ // referenziabili via placeholder (TABLE/SPLITTER/ACCORDION/TABVIEW). I
5637
+ // leaf (DATASOURCE/DATAREPEATER/LABEL/...) NON devono sostituirlo: altrimenti
5638
+ // i placeholder <TD-N>/<AREA-N> dei nodi SUCCESSIVI risolverebbero contro
5639
+ // il leaf invece del container -> errore "Placeholder '<TD-0>' non
5640
+ // risolvibile dentro DATASOURCE__6" e i DATAREPEATER restano 0/3. Cosi'
5641
+ // tutti i nodi di un layout multi-cella risolvono contro lo stesso container.
5642
+ const PLACEHOLDER_CONTAINERS = ['TABLE', 'SPLITTER', 'ACCORDION', 'TABVIEW'];
5643
+ if (PLACEHOLDER_CONTAINERS.includes(String(node.tool_name || '').toUpperCase())) {
5644
+ lastTopLevelInjected = this.findLastInjectedNode(String(node.tool_name || ''), topParent) || lastTopLevelInjected;
5645
+ }
5726
5646
  }
5727
5647
  const rationale = (action?.['rationale'] || '').toString().slice(0, 100);
5728
5648
  return { ok: true, message: `Iniettati ${totalInjected} elementi nel designer${rationale ? ': ' + rationale : ''}.` };
@@ -5768,13 +5688,13 @@ class DesignerComponent {
5768
5688
  // (es. backgroundColor non e' in inputs di default ma e' in
5769
5689
  // htmlInputProperties -> inputProps). Coerente col property panel
5770
5690
  // che legge inputProps per costruire l'UI.
5771
- if (!target.inputProps || !Object.prototype.hasOwnProperty.call(target.inputProps, propName)) {
5691
+ if (!target.inputProps || !Object.hasOwn(target.inputProps, propName)) {
5772
5692
  return { ok: false, message: `set_property: proprieta' '${propName}' non esposta su ${target.name} (${targetName})` };
5773
5693
  }
5774
5694
  // Inizializza inputs[propName] se non esiste, cosi' setValue() canonico
5775
5695
  // (o l'assign fallback) lo trova.
5776
- if (target.inputs && !Object.prototype.hasOwnProperty.call(target.inputs, propName)) {
5777
- target.inputs[propName] = undefined;
5696
+ if (target.inputs && !Object.hasOwn(target.inputs, propName)) {
5697
+ (target.inputs)[propName] = undefined;
5778
5698
  }
5779
5699
  // Trova la toolProp matching (chiave 'key' === propName) per delegare a setValue
5780
5700
  // canonico - applica converter, conditional, hooks specifici (datasource/select).
@@ -5791,8 +5711,8 @@ class DesignerComponent {
5791
5711
  // Fallback: la prop non e' nelle toolProps (raro - inputProps inconsistenti).
5792
5712
  // Assign diretto su inputs[prop], distinguendo BehaviorSubject vs primitivo.
5793
5713
  const cur = target.inputs[propName];
5794
- if (cur && typeof cur.next === 'function' && typeof cur.subscribe === 'function') {
5795
- cur.next(newValue);
5714
+ if (cur && typeof (cur).next === 'function' && typeof (cur).subscribe === 'function') {
5715
+ (cur).next(newValue);
5796
5716
  }
5797
5717
  else {
5798
5718
  target.inputs[propName] = newValue;
@@ -6030,16 +5950,16 @@ class DesignerComponent {
6030
5950
  continue;
6031
5951
  }
6032
5952
  // BehaviorSubject: per datasource estrai uniqueName dell'oggetto wrappato
6033
- if (v && typeof v.subscribe === 'function') {
6034
- const inner = v.value;
5953
+ if (v && typeof (v).subscribe === 'function') {
5954
+ const inner = (v).value;
6035
5955
  const wrappedName = inner?.uniqueName || inner?.component?.uniqueName;
6036
5956
  if (wrappedName)
6037
5957
  parts.push(`${key}=${wrappedName}`);
6038
5958
  continue;
6039
5959
  }
6040
5960
  // Oggetto datasource (dropped-component-list, sync)
6041
- if (v && typeof v === 'object' && v.uniqueName) {
6042
- parts.push(`${key}=${v.uniqueName}`);
5961
+ if (v && typeof v === 'object' && (v).uniqueName) {
5962
+ parts.push(`${key}=${(v).uniqueName}`);
6043
5963
  continue;
6044
5964
  }
6045
5965
  // Primitivi: includi solo se DIVERSI dal default del template
@@ -6246,7 +6166,7 @@ class DesignerComponent {
6246
6166
  if (template.inputs) {
6247
6167
  // Rimuovi le chiavi che node.inputs aveva aggiunto e non c'erano nel backup
6248
6168
  for (const k of Object.keys(template.inputs)) {
6249
- if (!Object.prototype.hasOwnProperty.call(inputsBackup, k)) {
6169
+ if (!Object.hasOwn(inputsBackup, k)) {
6250
6170
  delete template.inputs[k];
6251
6171
  }
6252
6172
  }
@@ -6276,7 +6196,7 @@ class DesignerComponent {
6276
6196
  const rowPct = `${Math.floor(10000 / childCount) / 100}%`;
6277
6197
  for (const tr of justInjected.nestedComponents) {
6278
6198
  if (tr?.inputs)
6279
- tr.inputs['height'] = rowPct;
6199
+ (tr.inputs)['height'] = rowPct;
6280
6200
  }
6281
6201
  }
6282
6202
  else if (toolName === 'SPLITTER') {
@@ -6284,7 +6204,7 @@ class DesignerComponent {
6284
6204
  const sizePct = Math.floor(10000 / childCount) / 100;
6285
6205
  for (const area of justInjected.nestedComponents) {
6286
6206
  if (area?.inputs)
6287
- area.inputs['size'] = sizePct;
6207
+ (area.inputs)['size'] = sizePct;
6288
6208
  }
6289
6209
  }
6290
6210
  }
@@ -6303,7 +6223,7 @@ class DesignerComponent {
6303
6223
  // 2. il target referenziato (es. DATASOURCE_15) deve esistere gia'
6304
6224
  // in flattenedDashboardElements (= iniettato in nodo precedente
6305
6225
  // del layout o gia' nel canvas).
6306
- if (justInjected && justInjected.inputs && droppedBindingKeys.length > 0 && node.inputs) {
6226
+ if (justInjected?.inputs && droppedBindingKeys.length > 0 && node.inputs) {
6307
6227
  for (const key of droppedBindingKeys) {
6308
6228
  const propDef = template.inputProps?.[key];
6309
6229
  if (!propDef)
@@ -6314,7 +6234,7 @@ class DesignerComponent {
6314
6234
  // Se gia' oggetto (LLM mandasse direttamente {uniqueName,component})
6315
6235
  // o BehaviorSubject -> pass-through. Solo stringhe vengono risolte.
6316
6236
  if (typeof raw !== 'string') {
6317
- justInjected.inputs[key] = raw;
6237
+ (justInjected.inputs)[key] = raw;
6318
6238
  continue;
6319
6239
  }
6320
6240
  const resolved = (raw.startsWith('<') && raw.endsWith('>'))
@@ -6381,7 +6301,7 @@ class DesignerComponent {
6381
6301
  }
6382
6302
  catch { /* subs collection non disponibile, sub continuera' a vivere col designer */ }
6383
6303
  }
6384
- justInjected.inputs[key] = {
6304
+ (justInjected.inputs)[key] = {
6385
6305
  uniqueName: refElement.uniqueName,
6386
6306
  [asyncPath]: compBs
6387
6307
  };
@@ -6395,8 +6315,8 @@ class DesignerComponent {
6395
6315
  // non sa quali colonne mostrare e la grid resta vuota anche con
6396
6316
  // datasource bindato correttamente.
6397
6317
  if (justInjected && toolName === 'DATAREPEATER' && justInjected.inputs) {
6398
- const ds = justInjected.inputs['datasource'];
6399
- const action = this.getRepeaterAction(justInjected) || justInjected.inputs['action'] || 'list';
6318
+ const ds = (justInjected.inputs)['datasource'];
6319
+ const action = this.getRepeaterAction(justInjected) || (justInjected.inputs)['action'] || 'list';
6400
6320
  if (ds) {
6401
6321
  try {
6402
6322
  this.propertyTreeBuilder(action, justInjected.inputs);
@@ -6443,7 +6363,10 @@ class DesignerComponent {
6443
6363
  let childParent = injectedUniqueName;
6444
6364
  const declared = (ch && typeof ch.parent_unique_name === 'string') ? ch.parent_unique_name.trim() : '';
6445
6365
  if (declared) {
6446
- if (declared.startsWith('<') && declared.endsWith('>')) {
6366
+ if (this.isRootPlaceholder(declared)) {
6367
+ childParent = null; // alias esplicito del ROOT anche in ricorsione
6368
+ }
6369
+ else if (declared.startsWith('<') && declared.endsWith('>')) {
6447
6370
  // Placeholder semantico: risolvi dentro justInjected
6448
6371
  const resolved = this.resolveLayoutPlaceholder(declared, justInjected);
6449
6372
  if (resolved)
@@ -6479,10 +6402,24 @@ class DesignerComponent {
6479
6402
  * Ritorna l'uniqueName del nodo risolto, o `null` se il pattern non matcha
6480
6403
  * o l'index e' out-of-bounds (es. <TD-3> in tabella 2x2).
6481
6404
  */
6405
+ /**
6406
+ * `<ROOT>` e' l'alias ESPLICITO del caso 3 ("null/missing: va al ROOT"), non un
6407
+ * placeholder container-relative. Senza questo riconoscimento finiva nel ramo
6408
+ * angle-bracket e `resolveLayoutPlaceholder` lo scartava (`'ROOT'.split('-')`
6409
+ * da un solo elemento -> `null`), quindi era IRRISOLVIBILE anche con un
6410
+ * container iniettato prima: un `<ROOT>` faceva abortire l'intero inject.
6411
+ * Caso reale: "aggiungi un bottone Esporta nel canvas" su canvas vuoto.
6412
+ * Nota: se il token arrivasse fino a `drop()` il nodo verrebbe piazzato
6413
+ * correttamente al root (destination = dashboardElements), quindi non e' un
6414
+ * vincolo del canvas — era solo questo ramo a trasformarlo in un rifiuto.
6415
+ */
6416
+ isRootPlaceholder(declared) {
6417
+ return /^<\s*ROOT\s*>$/i.test((declared || '').trim());
6418
+ }
6482
6419
  resolveLayoutPlaceholder(placeholder, parentNode) {
6483
6420
  if (!placeholder || !parentNode)
6484
6421
  return null;
6485
- const inner = placeholder.replace(/^<|>$/g, '').trim();
6422
+ const inner = placeholder.replaceAll(/^<|>$/g, '').trim();
6486
6423
  const parts = inner.split('-');
6487
6424
  if (parts.length < 2)
6488
6425
  return null;
@@ -6491,7 +6428,7 @@ class DesignerComponent {
6491
6428
  if (tag === 'TD') {
6492
6429
  // <TD-N> singolo index OR <TD-R-C> riga/colonna
6493
6430
  if (parts.length === 2) {
6494
- const n = parseInt(parts[1], 10);
6431
+ const n = Number.parseInt(parts[1], 10);
6495
6432
  if (!Number.isFinite(n))
6496
6433
  return null;
6497
6434
  // Row-major lookup: TR[i].TD[j]
@@ -6506,8 +6443,8 @@ class DesignerComponent {
6506
6443
  return null;
6507
6444
  }
6508
6445
  if (parts.length === 3) {
6509
- const r = parseInt(parts[1], 10);
6510
- const c = parseInt(parts[2], 10);
6446
+ const r = Number.parseInt(parts[1], 10);
6447
+ const c = Number.parseInt(parts[2], 10);
6511
6448
  if (!Number.isFinite(r) || !Number.isFinite(c))
6512
6449
  return null;
6513
6450
  const tr = nested[r];
@@ -6519,25 +6456,25 @@ class DesignerComponent {
6519
6456
  return null;
6520
6457
  }
6521
6458
  if (tag === 'TR') {
6522
- const n = parseInt(parts[1], 10);
6459
+ const n = Number.parseInt(parts[1], 10);
6523
6460
  if (!Number.isFinite(n))
6524
6461
  return null;
6525
6462
  return nested[n]?.uniqueName || null;
6526
6463
  }
6527
6464
  if (tag === 'SPLITTER' && parts[1].toUpperCase() === 'AREA') {
6528
- const n = parseInt(parts[2], 10);
6465
+ const n = Number.parseInt(parts[2], 10);
6529
6466
  if (!Number.isFinite(n))
6530
6467
  return null;
6531
6468
  return nested[n]?.uniqueName || null;
6532
6469
  }
6533
6470
  if (tag === 'ACCORDION' && parts[1].toUpperCase() === 'AREA') {
6534
- const n = parseInt(parts[2], 10);
6471
+ const n = Number.parseInt(parts[2], 10);
6535
6472
  if (!Number.isFinite(n))
6536
6473
  return null;
6537
6474
  return nested[n]?.uniqueName || null;
6538
6475
  }
6539
6476
  if (tag === 'TABPANEL') {
6540
- const n = parseInt(parts[1], 10);
6477
+ const n = Number.parseInt(parts[1], 10);
6541
6478
  if (!Number.isFinite(n))
6542
6479
  return null;
6543
6480
  return nested[n]?.uniqueName || null;
@@ -6579,12 +6516,12 @@ class DesignerComponent {
6579
6516
  const trim = placeholder.trim();
6580
6517
  if (!trim.startsWith('<') || !trim.endsWith('>'))
6581
6518
  return null;
6582
- const inner = trim.replace(/^<|>$/g, '').trim();
6583
- const m = inner.match(/^([A-Za-z][A-Za-z0-9_-]*)-(\d+)$/);
6519
+ const inner = trim.replaceAll(/^<|>$/g, '').trim();
6520
+ const m = /^([A-Za-z][A-Za-z0-9_-]*)-(\d+)$/.exec(inner);
6584
6521
  if (!m)
6585
6522
  return null;
6586
6523
  const targetName = m[1].toUpperCase();
6587
- const targetIdx = parseInt(m[2], 10);
6524
+ const targetIdx = Number.parseInt(m[2], 10);
6588
6525
  if (!Number.isFinite(targetIdx) || targetIdx < 0)
6589
6526
  return null;
6590
6527
  const matching = (this.flattenedDashboardElements || [])
@@ -6624,7 +6561,7 @@ class DesignerComponent {
6624
6561
  */
6625
6562
  _designerGestureMouseUp;
6626
6563
  async onWindowHashChange() {
6627
- const nextHash = String(window?.location?.hash || '');
6564
+ const nextHash = String(globalThis?.location?.hash || '');
6628
6565
  const previousHash = this.lastKnownHashRoute;
6629
6566
  if (this.suppressNextHashGuard) {
6630
6567
  this.lastKnownHashRoute = nextHash;
@@ -6643,10 +6580,10 @@ class DesignerComponent {
6643
6580
  return;
6644
6581
  }
6645
6582
  this.suppressNextHashGuard = true;
6646
- window.location.hash = previousHash || '#/';
6583
+ globalThis.location.hash = previousHash || '#/';
6647
6584
  setTimeout(() => {
6648
6585
  this.suppressNextHashGuard = false;
6649
- this.lastKnownHashRoute = String(window?.location?.hash || '');
6586
+ this.lastKnownHashRoute = String(globalThis?.location?.hash || '');
6650
6587
  }, 0);
6651
6588
  }
6652
6589
  onWindowBeforeUnload(event) {
@@ -6757,14 +6694,14 @@ class DesignerComponent {
6757
6694
  }
6758
6695
  try {
6759
6696
  const urlObj = new URL(href, document.baseURI);
6760
- const cleanedPath = String(urlObj.pathname || '').replace(/\\/g, '/');
6697
+ const cleanedPath = String(urlObj.pathname || '').replaceAll('\\', '/');
6761
6698
  const assetsIdx = cleanedPath.toLowerCase().indexOf('/assets/');
6762
6699
  if (assetsIdx >= 0) {
6763
6700
  // Keep relative assets path (e.g. assets/my-dash/file.css) to avoid collisions.
6764
6701
  return cleanedPath.substring(assetsIdx + 1);
6765
6702
  }
6766
6703
  const parts = cleanedPath.split('/');
6767
- return parts[parts.length - 1] || cleanedPath;
6704
+ return parts.at(-1) || cleanedPath;
6768
6705
  }
6769
6706
  catch {
6770
6707
  return href;
@@ -7079,7 +7016,7 @@ class DesignerComponent {
7079
7016
  * Normalizza un path CSS in formato slash `/` e trim.
7080
7017
  */
7081
7018
  normalizeSheetPath(path) {
7082
- return String(path || '').trim().replace(/\\/g, '/');
7019
+ return String(path || '').trim().replaceAll('\\', '/');
7083
7020
  }
7084
7021
  /**
7085
7022
  * Normalizza il payload foglio CSS (path, selector, class names, key/value style)
@@ -7141,7 +7078,7 @@ class DesignerComponent {
7141
7078
  return WtoolboxService.appSettings?.file_path;
7142
7079
  }
7143
7080
  catch {
7144
- return window.location.origin;
7081
+ return globalThis.location.origin;
7145
7082
  }
7146
7083
  };
7147
7084
  const resolveBackendStaticBaseUrl = () => {
@@ -7150,7 +7087,7 @@ class DesignerComponent {
7150
7087
  if (!raw) {
7151
7088
  return '';
7152
7089
  }
7153
- const url = new URL(raw, window.location.origin);
7090
+ const url = new URL(raw, globalThis.location.origin);
7154
7091
  const apiIndex = url.pathname.toLowerCase().indexOf('/api/');
7155
7092
  let staticPath = apiIndex >= 0 ? url.pathname.substring(0, apiIndex) : url.pathname;
7156
7093
  if (!staticPath.endsWith('/')) {
@@ -7163,7 +7100,7 @@ class DesignerComponent {
7163
7100
  }
7164
7101
  };
7165
7102
  const buildCssHref = (baseUrl, relativePath) => {
7166
- const baseUrlObj = new URL(baseUrl || window.location.origin, window.location.origin);
7103
+ const baseUrlObj = new URL(baseUrl || globalThis.location.origin, globalThis.location.origin);
7167
7104
  const basePath = baseUrlObj.pathname.toLowerCase();
7168
7105
  let resolvedRelativePath = relativePath;
7169
7106
  if (basePath.endsWith('/assets/') && resolvedRelativePath.toLowerCase().startsWith('assets/')) {
@@ -7375,6 +7312,8 @@ class DesignerComponent {
7375
7312
  this.applyHistorySnapshot(target, false);
7376
7313
  }
7377
7314
  catch (err) {
7315
+ // Snapshot apply failed — best-effort: keep the designer alive, the
7316
+ // history stacks are already consistent (target popped, previous pushed).
7378
7317
  }
7379
7318
  }
7380
7319
  /**
@@ -7454,11 +7393,53 @@ class DesignerComponent {
7454
7393
  if (this.isTypingTarget(event.target)) {
7455
7394
  return;
7456
7395
  }
7396
+ const rawKey = String(event.key || '');
7457
7397
  const ctrlOrMeta = !!event.ctrlKey || !!event.metaKey;
7398
+ // ---- Scorciatoie SENZA modificatore (feature B) ----
7458
7399
  if (!ctrlOrMeta) {
7400
+ // Delete/Backspace: rimuove l'elemento selezionato (o TUTTI i multi-selezionati).
7401
+ if (rawKey === 'Delete' || rawKey === 'Backspace') {
7402
+ if (this.selectedTools.length > 1) {
7403
+ event.preventDefault();
7404
+ const names = this.selectedTools.map((t) => t.uniqueName);
7405
+ this.clearMultiSelection();
7406
+ names.forEach((n) => this.removeElementByName(n));
7407
+ this.commitHistoryIfChanged();
7408
+ return;
7409
+ }
7410
+ if (this.tool?.uniqueName) {
7411
+ event.preventDefault();
7412
+ this.removeElementByName(this.tool.uniqueName);
7413
+ this.commitHistoryIfChanged();
7414
+ return;
7415
+ }
7416
+ }
7417
+ // Frecce: nudge di posizione dell'elemento selezionato (passo = gridSize se
7418
+ // snap attivo, altrimenti 1px; con Shift un passo piu' grande).
7419
+ const nudges = {
7420
+ ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1]
7421
+ };
7422
+ if (nudges[rawKey] && (this.tool || this.selectedTools.length > 1)) {
7423
+ event.preventDefault();
7424
+ const step = (this.snapToGridEnabled ? this.gridSize : 1) * (event.shiftKey ? 5 : 1);
7425
+ const ux = nudges[rawKey][0] * step, uy = nudges[rawKey][1] * step;
7426
+ if (this.selectedTools.length > 1) {
7427
+ this.nudgeElements(this.selectedTools, ux, uy);
7428
+ }
7429
+ else {
7430
+ this.nudgeSelectedElement(ux, uy);
7431
+ }
7432
+ return;
7433
+ }
7434
+ return;
7435
+ }
7436
+ const key = rawKey.toLowerCase();
7437
+ // Ctrl/Cmd+S: salva (previene il dialog di salvataggio del browser).
7438
+ if (key === 's') {
7439
+ event.preventDefault();
7440
+ void this.saveDashboard();
7459
7441
  return;
7460
7442
  }
7461
- const key = String(event.key || '').toLowerCase();
7462
7443
  if (key === 'z' && event.shiftKey) {
7463
7444
  event.preventDefault();
7464
7445
  this.redo();
@@ -7472,6 +7453,28 @@ class DesignerComponent {
7472
7453
  if (key === 'y') {
7473
7454
  event.preventDefault();
7474
7455
  this.redo();
7456
+ return;
7457
+ }
7458
+ if (key === 'd') {
7459
+ event.preventDefault();
7460
+ void this.duplicateDesignerElement(this.tool);
7461
+ return;
7462
+ }
7463
+ if (key === 'c') {
7464
+ // Non intercettare Ctrl+C se l'utente sta copiando una selezione di testo.
7465
+ const textSelection = (globalThis.window !== undefined && globalThis.getSelection)
7466
+ ? String(globalThis.getSelection() || '')
7467
+ : '';
7468
+ if (textSelection) {
7469
+ return;
7470
+ }
7471
+ event.preventDefault();
7472
+ this.copyDesignerElement(this.tool);
7473
+ return;
7474
+ }
7475
+ if (key === 'v') {
7476
+ event.preventDefault();
7477
+ void this.pasteDesignerElement();
7475
7478
  }
7476
7479
  }
7477
7480
  /**
@@ -7502,9 +7505,7 @@ class DesignerComponent {
7502
7505
  * via touch su designer.
7503
7506
  */
7504
7507
  onWindowTouchend() {
7505
- if (!this.finalizePendingColorHistory()) {
7506
- this.commitHistoryAfterDesignerGestureIfNeeded();
7507
- }
7508
+ this.onWindowMouseup();
7508
7509
  }
7509
7510
  /**
7510
7511
  * Se l'utente ha appena terminato un drag/resize di un elemento del designer
@@ -7697,12 +7698,49 @@ class DesignerComponent {
7697
7698
  {
7698
7699
  separator: true
7699
7700
  },
7701
+ {
7702
+ label: this.t('designer.menu_export_json', 'Esporta JSON'),
7703
+ icon: 'pi pi-download',
7704
+ command: () => this.exportDashboardJson()
7705
+ },
7706
+ {
7707
+ label: this.t('designer.menu_import_json', 'Importa JSON'),
7708
+ icon: 'pi pi-upload',
7709
+ command: () => this.importDashboardJson()
7710
+ },
7711
+ {
7712
+ label: this.t('designer.menu_save_preset', 'Salva come preset'),
7713
+ icon: 'pi pi-bookmark',
7714
+ command: () => this.saveDashboardAsPreset()
7715
+ },
7716
+ {
7717
+ label: this.t('designer.menu_apply_preset', 'Applica preset'),
7718
+ icon: 'pi pi-bookmark-fill',
7719
+ command: () => this.applyDashboardPreset()
7720
+ },
7721
+ {
7722
+ separator: true
7723
+ },
7700
7724
  {
7701
7725
  label: this.hideDatasourcesInCanvas
7702
7726
  ? this.t('designer.menu_show_datasources', 'Mostra datasource nel canvas')
7703
7727
  : this.t('designer.menu_hide_datasources', 'Nascondi datasource nel canvas'),
7704
7728
  icon: this.hideDatasourcesInCanvas ? 'pi pi-eye' : 'pi pi-eye-slash',
7705
7729
  command: () => this.toggleHideDatasourcesInCanvas()
7730
+ },
7731
+ {
7732
+ label: this.snapToGridEnabled
7733
+ ? this.t('designer.menu_snap_off', 'Disattiva snap alla griglia')
7734
+ : this.t('designer.menu_snap_on', 'Attiva snap alla griglia'),
7735
+ icon: this.snapToGridEnabled ? 'pi pi-th-large' : 'pi pi-table',
7736
+ command: () => this.toggleSnapToGrid()
7737
+ },
7738
+ {
7739
+ label: this.absoluteFlowEnabled
7740
+ ? this.t('designer.menu_absflow_off', 'Disattiva flusso assoluto')
7741
+ : this.t('designer.menu_absflow_on', 'Attiva flusso assoluto'),
7742
+ icon: 'pi pi-arrows-alt',
7743
+ command: () => this.toggleAbsoluteFlow()
7706
7744
  }
7707
7745
  ];
7708
7746
  }
@@ -7710,10 +7748,521 @@ class DesignerComponent {
7710
7748
  this.hideDatasourcesInCanvas = !this.hideDatasourcesInCanvas;
7711
7749
  this.refreshGraphActionsMenuItems();
7712
7750
  }
7713
- get droppedDashboardItems() {
7714
- return (this.flattenedDashboardElements || []).filter((item) => !!item?.uniqueName);
7751
+ /**
7752
+ * Flusso "assoluto" (default OFF = flusso normale come sempre). Quando ON, ogni
7753
+ * elemento droppato nasce `position: absolute` con `left/top` dalla posizione
7754
+ * del drop -> non partecipa al flusso, quindi ridimensionarne uno NON sposta gli
7755
+ * altri. E' per-elemento (chi nasce con position:absolute segue il nuovo modello,
7756
+ * gli elementi salvati senza restano a flusso) -> retrocompatibile senza flag globale.
7757
+ */
7758
+ absoluteFlowEnabled = false;
7759
+ toggleAbsoluteFlow() {
7760
+ const turningOn = !this.absoluteFlowEnabled;
7761
+ // Se attivo il flusso assoluto con elementi gia' nel canvas, quelli restano a
7762
+ // flusso: notifico che vanno riposizionati manualmente.
7763
+ if (turningOn && (this.dashboardElements?.length || 0) > 0) {
7764
+ WtoolboxService.messageNotificationService.add({
7765
+ severity: 'warn',
7766
+ summary: this.t('designer.absflow.title', 'Flusso assoluto'),
7767
+ detail: this.t('designer.absflow.existing_manual', 'Gli elementi gia\' presenti restano nel flusso attuale: vanno riposizionati a mano (position/left/top) per adottare il layout assoluto.')
7768
+ });
7769
+ }
7770
+ this.absoluteFlowEnabled = turningOn;
7771
+ this.refreshGraphActionsMenuItems();
7772
+ this.cd.detectChanges();
7715
7773
  }
7716
- get hierarchyTreeNodes() {
7774
+ /**
7775
+ * Posizione del drop relativa a un elemento di riferimento (il canvas per i drop
7776
+ * top-level, oppure il container quando droppo dentro un contenitore), snappata
7777
+ * alla griglia se lo snap e' attivo.
7778
+ */
7779
+ getDropPositionRelativeTo(event, refEl) {
7780
+ const rect = refEl ? refEl.getBoundingClientRect() : null;
7781
+ const oe = (event?.originalEvent) ? event.originalEvent : event;
7782
+ const fallbackCx = (typeof event?.clientX === 'number') ? event.clientX : null;
7783
+ const fallbackCy = (typeof event?.clientY === 'number') ? event.clientY : null;
7784
+ const cx = (typeof oe?.clientX === 'number') ? oe.clientX : fallbackCx;
7785
+ const cy = (typeof oe?.clientY === 'number') ? oe.clientY : fallbackCy;
7786
+ if (!rect || cx === null || cy === null) {
7787
+ return { left: 0, top: 0 };
7788
+ }
7789
+ // Coordinate del cursore relative al box del riferimento (client rect gia'
7790
+ // include l'eventuale bordo/scroll del riferimento: sottraggo il suo scroll).
7791
+ let left = cx - rect.left + (refEl ? refEl.scrollLeft : 0);
7792
+ let top = cy - rect.top + (refEl ? refEl.scrollTop : 0);
7793
+ const g = DesignerSnapState.gridSize;
7794
+ if (g && g > 0) {
7795
+ left = Math.round(left / g) * g;
7796
+ top = Math.round(top / g) * g;
7797
+ }
7798
+ return { left: Math.max(0, Math.round(left)), top: Math.max(0, Math.round(top)) };
7799
+ }
7800
+ /** Snap alla griglia per drag/resize (feature B). Off di default. */
7801
+ snapToGridEnabled = false;
7802
+ /** Passo della griglia di snap in px. */
7803
+ gridSize = 10;
7804
+ /**
7805
+ * Attiva/disattiva lo snap alla griglia: propaga la dimensione griglia alle
7806
+ * istanze DynamicDashboardTemplateComponent (che arrotondano posizione/dimensione
7807
+ * in onMoving/onResizing) e mostra un overlay griglia sul canvas.
7808
+ */
7809
+ toggleSnapToGrid() {
7810
+ this.snapToGridEnabled = !this.snapToGridEnabled;
7811
+ DynamicDashboardTemplateComponent.snapGridSize = this.snapToGridEnabled ? this.gridSize : 0;
7812
+ // Alimenta lo stato condiviso letto dalle direttive drag/resize (lo snap
7813
+ // effettivo su move/resize avviene li', non solo nel bookkeeping onMoving).
7814
+ DesignerSnapState.gridSize = this.snapToGridEnabled ? this.gridSize : 0;
7815
+ // Attivando lo snap, allinea SUBITO alla griglia anche gli elementi gia'
7816
+ // presenti (droppati/spostati prima di attivarlo).
7817
+ if (this.snapToGridEnabled) {
7818
+ this.snapAllExistingElementsToGrid();
7819
+ }
7820
+ this.refreshGraphActionsMenuItems();
7821
+ this.cd.detectChanges();
7822
+ }
7823
+ /** Snappa alla griglia la posizione assoluta di tutti gli elementi top-level presenti. */
7824
+ snapAllExistingElementsToGrid() {
7825
+ const g = DesignerSnapState.gridSize;
7826
+ if (!g || g <= 0) {
7827
+ return;
7828
+ }
7829
+ setTimeout(() => {
7830
+ let changed = false;
7831
+ for (const tool of (this.dashboardElements || [])) {
7832
+ if (this.snapElementAbsoluteToGrid(tool, g)) {
7833
+ changed = true;
7834
+ }
7835
+ }
7836
+ if (changed) {
7837
+ this.commitHistoryIfChanged();
7838
+ this.cd.detectChanges();
7839
+ }
7840
+ }, 0);
7841
+ }
7842
+ /**
7843
+ * Allinea la posizione VISIVA assoluta di un elemento al multiplo di griglia
7844
+ * agendo sulla traslazione del suo `transform` (offsetLeft/Top escludono il
7845
+ * transform -> assoluto = offset + traslazione). Ritorna true se ha modificato.
7846
+ */
7847
+ snapElementAbsoluteToGrid(tool, g) {
7848
+ const dom = tool?.uniqueName ? document.getElementById(tool.uniqueName) : null;
7849
+ if (!dom || !tool.inputs) {
7850
+ return false;
7851
+ }
7852
+ let sx = 1, sy = 0, kx = 0, ky = 1, tx = 0, ty = 0;
7853
+ const m = String(tool.inputs['transform'] || '').match(/matrix\(([^)]+)\)/);
7854
+ if (m) {
7855
+ const p = m[1].split(',').map((x) => Number.parseFloat(x));
7856
+ if (p.length >= 6 && p.every((n) => !Number.isNaN(Number(n)))) {
7857
+ [sx, sy, kx, ky, tx, ty] = p;
7858
+ }
7859
+ }
7860
+ const newTx = Math.round((dom.offsetLeft + tx) / g) * g - dom.offsetLeft;
7861
+ const newTy = Math.round((dom.offsetTop + ty) / g) * g - dom.offsetTop;
7862
+ if (newTx === tx && newTy === ty) {
7863
+ return false;
7864
+ }
7865
+ tool.inputs['transform'] = `matrix(${sx}, ${sy}, ${kx}, ${ky}, ${newTx}, ${newTy})`;
7866
+ return true;
7867
+ }
7868
+ /**
7869
+ * Sposta l'elemento selezionato di (dx, dy) px agendo sulla translation della
7870
+ * matrice `transform` (stesso campo che usa onMoving). Usato dalle frecce.
7871
+ */
7872
+ nudgeSelectedElement(dx, dy) {
7873
+ const el = this.tool;
7874
+ if (!el?.inputs) {
7875
+ return;
7876
+ }
7877
+ let scaleX = 1, skewY = 0, skewX = 0, scaleY = 1, tx = 0, ty = 0;
7878
+ const cur = String(el.inputs['transform'] || '');
7879
+ const m = /matrix\(([^)]+)\)/.exec(cur);
7880
+ if (m) {
7881
+ const parts = m[1].split(',').map((x) => Number.parseFloat(x));
7882
+ if (parts.length >= 6 && parts.every((n) => !Number.isNaN(Number(n)))) {
7883
+ [scaleX, skewY, skewX, scaleY, tx, ty] = parts;
7884
+ }
7885
+ }
7886
+ el.inputs['transform'] = `matrix(${scaleX}, ${skewY}, ${skewX}, ${scaleY}, ${tx + dx}, ${ty + dy})`;
7887
+ this.commitHistoryIfChanged();
7888
+ this.cd.detectChanges();
7889
+ }
7890
+ // ===================== Feature F: preset/template riusabili =====================
7891
+ // Libreria di preset lato client (localStorage): mappa nome -> boardcontent JSON.
7892
+ // Riusa buildSerializedDashboardElements (salva) e loadDashboard (applica), gli
7893
+ // stessi choke-point di save/import. Nessuno schema change.
7894
+ static PRESETS_STORAGE_KEY = 'wuic.designer.presets';
7895
+ readDesignerPresets() {
7896
+ try {
7897
+ const raw = localStorage.getItem(DesignerComponent.PRESETS_STORAGE_KEY);
7898
+ const parsed = raw ? JSON.parse(raw) : {};
7899
+ return (parsed && typeof parsed === 'object') ? parsed : {};
7900
+ }
7901
+ catch {
7902
+ return {};
7903
+ }
7904
+ }
7905
+ writeDesignerPresets(presets) {
7906
+ try {
7907
+ localStorage.setItem(DesignerComponent.PRESETS_STORAGE_KEY, JSON.stringify(presets));
7908
+ }
7909
+ catch { /* quota / storage disabilitato */ }
7910
+ }
7911
+ /** Salva il layout corrente come preset nominato nella libreria locale. */
7912
+ async saveDashboardAsPreset() {
7913
+ const resp = await WtoolboxService.promptDialog(this.t('designer.preset.save_title', 'Salva come preset'), [{ name: 'presetName', caption: this.t('designer.preset.name_caption', 'Nome preset'), type: 'string', value: '', required: true }], null, '400px', '230px');
7914
+ const name = String(resp?.presetName?.value || '').trim();
7915
+ if (!name) {
7916
+ return;
7917
+ }
7918
+ const presets = this.readDesignerPresets();
7919
+ presets[name] = this.buildSerializedDashboardElements();
7920
+ this.writeDesignerPresets(presets);
7921
+ WtoolboxService.messageNotificationService.add({
7922
+ severity: 'success',
7923
+ summary: this.t('success', 'Success'),
7924
+ detail: this.t('designer.preset.saved', 'Preset salvato')
7925
+ });
7926
+ }
7927
+ /** Applica un preset della libreria al canvas (sostituisce il layout corrente). */
7928
+ async applyDashboardPreset() {
7929
+ const presets = this.readDesignerPresets();
7930
+ const names = Object.keys(presets);
7931
+ if (names.length === 0) {
7932
+ WtoolboxService.messageNotificationService.add({
7933
+ severity: 'info',
7934
+ summary: this.t('info', 'Info'),
7935
+ detail: this.t('designer.preset.none', 'Nessun preset salvato')
7936
+ });
7937
+ return;
7938
+ }
7939
+ const resp = await WtoolboxService.promptDialog(this.t('designer.preset.apply_title', 'Applica preset'), [{
7940
+ name: 'preset',
7941
+ caption: this.t('designer.preset.pick_caption', 'Preset'),
7942
+ type: 'dictionary',
7943
+ required: true,
7944
+ dictionaryData: names.map((n) => ({ label: n, value: n }))
7945
+ }], null, '400px', '230px');
7946
+ const chosen = String(resp?.preset?.value || '').trim();
7947
+ if (!chosen || !presets[chosen]) {
7948
+ return;
7949
+ }
7950
+ if ((this.flattenedDashboardElements || []).length > 0) {
7951
+ const ok = await WtoolboxService.confirm({
7952
+ header: this.t('warning', 'Warning'),
7953
+ message: this.t('designer.preset.apply_confirm', 'Applicare il preset sostituira\' il canvas corrente. Continuare?'),
7954
+ acceptLabel: this.t('confirm', 'Confirm'),
7955
+ rejectLabel: this.t('cancel', 'Cancel')
7956
+ });
7957
+ if (!ok) {
7958
+ return;
7959
+ }
7960
+ }
7961
+ try {
7962
+ await this.loadDashboard({
7963
+ boardcontent: presets[chosen],
7964
+ board_route: this.currentDashboardRoute || '',
7965
+ board_des: this.currentDashboardDescription || ''
7966
+ }, true);
7967
+ WtoolboxService.messageNotificationService.add({
7968
+ severity: 'success',
7969
+ summary: this.t('success', 'Success'),
7970
+ detail: this.t('designer.preset.applied', 'Preset applicato')
7971
+ });
7972
+ }
7973
+ catch (error) {
7974
+ console.error('Failed to apply designer preset', error);
7975
+ WtoolboxService.errorHandler?.handleError?.(error);
7976
+ }
7977
+ }
7978
+ /** Clipboard interno per copy/paste di elementi del designer (inject-node). */
7979
+ designerClipboard = null;
7980
+ /**
7981
+ * Converte un elemento del canvas nella shape `node` accettata da
7982
+ * {@link injectLayoutNode} (`{ tool_name, inputs }`), estraendo i soli input
7983
+ * SCALARI (string/number/boolean/array) + i binding datasource (ridotti al
7984
+ * loro `uniqueName`, che il path canonico ri-idrata post-drop). Function e
7985
+ * oggetti complessi vengono scartati: verranno ricostruiti dal template della
7986
+ * palette al drop. Ritorna null se l'elemento non e' copiabile.
7987
+ */
7988
+ buildInjectNodeFromElement(el) {
7989
+ const name = String(el?.name || '').trim();
7990
+ if (!name) {
7991
+ return null;
7992
+ }
7993
+ const inputs = {};
7994
+ const rawInputs = el?.inputs || {};
7995
+ for (const key of Object.keys(rawInputs)) {
7996
+ let v = rawInputs[key];
7997
+ if (v instanceof BehaviorSubject) {
7998
+ v = v.value;
7999
+ }
8000
+ if (typeof v === 'function') {
8001
+ continue;
8002
+ }
8003
+ // Binding datasource (dropped-component): passa il solo uniqueName stringa
8004
+ if (v && typeof v === 'object' && !Array.isArray(v) && 'uniqueName' in v && 'component' in v) {
8005
+ const un = String(v.uniqueName || '').trim();
8006
+ if (un) {
8007
+ inputs[key] = un;
8008
+ }
8009
+ continue;
8010
+ }
8011
+ // Scarta gli altri oggetti complessi non serializzabili come scalari
8012
+ if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
8013
+ continue;
8014
+ }
8015
+ inputs[key] = v;
8016
+ }
8017
+ return { tool_name: name, inputs };
8018
+ }
8019
+ /** Trova l'uniqueName del parent di un elemento nel tree, o null se e' a root. */
8020
+ findParentUniqueName(target) {
8021
+ const targetUn = String(target?.uniqueName || '').trim();
8022
+ if (!targetUn) {
8023
+ return null;
8024
+ }
8025
+ const walk = (nodes) => {
8026
+ for (const n of nodes || []) {
8027
+ const kids = n?.nestedComponents || [];
8028
+ if (kids.some((k) => String(k?.uniqueName || '') === targetUn)) {
8029
+ return String(n?.uniqueName || '') || null;
8030
+ }
8031
+ const deeper = walk(kids);
8032
+ if (deeper !== undefined) {
8033
+ return deeper;
8034
+ }
8035
+ }
8036
+ return undefined;
8037
+ };
8038
+ const res = walk(this.dashboardElements || []);
8039
+ return res ?? null;
8040
+ }
8041
+ /**
8042
+ * Copia l'elemento selezionato (o `ctxElement`) nel clipboard interno.
8043
+ * I contenitori con figli non sono ancora supportati (richiedono il mapping
8044
+ * dei placeholder strutturali): mostra un avviso e non copia.
8045
+ */
8046
+ copyDesignerElement(el) {
8047
+ const target = el || this.tool || this.ctxElement;
8048
+ if (!target) {
8049
+ return;
8050
+ }
8051
+ if (Array.isArray(target.nestedComponents) && target.nestedComponents.length > 0) {
8052
+ WtoolboxService.messageNotificationService.add({
8053
+ severity: 'info',
8054
+ summary: this.t('info', 'Info'),
8055
+ detail: this.t('designer.clipboard.container_unsupported', 'La copia di contenitori con figli non e\' ancora supportata')
8056
+ });
8057
+ return;
8058
+ }
8059
+ // Gli elementi con binding datasource "live" + rendering per-riga
8060
+ // (DATAREPEATER/SELECT/MULTISELECT/UL) non si duplicano ancora in modo
8061
+ // affidabile: il clone via injectLayoutNode ricrea un list-grid il cui
8062
+ // selection model non e' inizializzato ad array -> IsSelectedRowPipe fallisce
8063
+ // (`items.find is not a function`). Fino a quando la re-idratazione del
8064
+ // binding non e' gestita, escludiamo questi tipi con un avviso chiaro.
8065
+ const DATASOURCE_BOUND = new Set(['DATAREPEATER', 'SELECT', 'MULTISELECT', 'UL']);
8066
+ if (DATASOURCE_BOUND.has(String(target.name || ''))) {
8067
+ WtoolboxService.messageNotificationService.add({
8068
+ severity: 'info',
8069
+ summary: this.t('info', 'Info'),
8070
+ detail: this.t('designer.clipboard.datasource_bound_unsupported', 'La duplicazione di componenti legati a un datasource non e\' ancora supportata')
8071
+ });
8072
+ return;
8073
+ }
8074
+ const node = this.buildInjectNodeFromElement(target);
8075
+ if (!node) {
8076
+ return;
8077
+ }
8078
+ this.designerClipboard = node;
8079
+ WtoolboxService.messageNotificationService.add({
8080
+ severity: 'success',
8081
+ summary: this.t('success', 'Success'),
8082
+ detail: this.t('designer.clipboard.copied', 'Elemento copiato')
8083
+ });
8084
+ }
8085
+ /**
8086
+ * Incolla l'elemento dal clipboard. Se `parentUniqueName` e' passato (usato da
8087
+ * `duplicateDesignerElement` per creare un sibling) inietta li'; altrimenti
8088
+ * dentro l'elemento selezionato se e' un contenitore, altrimenti a root.
8089
+ * Riusa il path canonico {@link injectLayoutNode} (drop() reale) per garantire
8090
+ * uniqueName freschi, hydration e history coerenti.
8091
+ */
8092
+ async pasteDesignerElement(parentUniqueName) {
8093
+ if (!this.designerClipboard) {
8094
+ return;
8095
+ }
8096
+ let parent = parentUniqueName ?? null;
8097
+ if (parent === null && this.tool && Array.isArray(this.tool.nestedComponents)) {
8098
+ parent = String(this.tool.uniqueName || '') || null;
8099
+ }
8100
+ try {
8101
+ await this.injectLayoutNode(JSON.parse(JSON.stringify(this.designerClipboard)), parent);
8102
+ this.cd.detectChanges();
8103
+ WtoolboxService.messageNotificationService.add({
8104
+ severity: 'success',
8105
+ summary: this.t('success', 'Success'),
8106
+ detail: this.t('designer.clipboard.pasted', 'Elemento incollato')
8107
+ });
8108
+ }
8109
+ catch (error) {
8110
+ console.error('Failed to paste designer element', error);
8111
+ WtoolboxService.errorHandler?.handleError?.(error);
8112
+ }
8113
+ }
8114
+ /**
8115
+ * Duplica un elemento: lo copia e lo incolla come sibling (stesso parent).
8116
+ */
8117
+ async duplicateDesignerElement(el) {
8118
+ const target = el || this.tool || this.ctxElement;
8119
+ if (!target) {
8120
+ return;
8121
+ }
8122
+ this.copyDesignerElement(target);
8123
+ if (!this.designerClipboard) {
8124
+ // caso contenitore: copyDesignerElement ha gia' avvisato
8125
+ return;
8126
+ }
8127
+ const parentUn = this.findParentUniqueName(target);
8128
+ await this.pasteDesignerElement(parentUn);
8129
+ }
8130
+ /**
8131
+ * Esporta il boardcontent della dashboard corrente come file JSON scaricabile.
8132
+ * Riusa lo stesso choke-point di serializzazione del salvataggio
8133
+ * (`buildSerializedDashboardElements`), quindi il file prodotto e' identico al
8134
+ * `boardcontent` persistito su `dom_board` ed e' re-importabile qui o su un
8135
+ * altro ambiente (import/export tra ambienti).
8136
+ */
8137
+ exportDashboardJson() {
8138
+ try {
8139
+ const raw = this.buildSerializedDashboardElements();
8140
+ let pretty = raw;
8141
+ try {
8142
+ pretty = JSON.stringify(JSON.parse(raw), null, 2);
8143
+ }
8144
+ catch { /* keep raw */ }
8145
+ const routeSlug = String(this.currentDashboardRoute || 'dashboard')
8146
+ .replaceAll(/[^a-zA-Z0-9._-]+/g, '_')
8147
+ .replaceAll(/^_+|_+$/g, '') || 'dashboard';
8148
+ const fileName = `${routeSlug}.dashboard.json`;
8149
+ const blob = new Blob([pretty], { type: 'application/json' });
8150
+ const url = URL.createObjectURL(blob);
8151
+ const anchor = document.createElement('a');
8152
+ anchor.href = url;
8153
+ anchor.download = fileName;
8154
+ document.body.appendChild(anchor);
8155
+ anchor.click();
8156
+ anchor.remove();
8157
+ setTimeout(() => URL.revokeObjectURL(url), 0);
8158
+ WtoolboxService.messageNotificationService.add({
8159
+ severity: 'success',
8160
+ summary: this.t('success', 'Success'),
8161
+ detail: this.t('designer.export_json_success', 'Dashboard JSON exported')
8162
+ });
8163
+ }
8164
+ catch (error) {
8165
+ console.error('Failed to export dashboard JSON', error);
8166
+ WtoolboxService.errorHandler?.handleError?.(error);
8167
+ }
8168
+ }
8169
+ /**
8170
+ * Importa un boardcontent da un file JSON precedentemente esportato.
8171
+ * Apre un file picker nativo (input[type=file] creato al volo, nessun prompt
8172
+ * nativo del browser), valida il JSON, chiede conferma di sovrascrittura del
8173
+ * canvas corrente e infine ricostruisce la dashboard riusando `loadDashboard`
8174
+ * (con `skipServerFetch=true`), lo stesso path usato per aprire una dashboard
8175
+ * salvata — cosi' l'import passa da idratazione/backfill/rebind canonici.
8176
+ */
8177
+ importDashboardJson() {
8178
+ const input = document.createElement('input');
8179
+ input.type = 'file';
8180
+ input.accept = 'application/json,.json';
8181
+ input.style.display = 'none';
8182
+ input.addEventListener('change', () => {
8183
+ const file = input.files?.[0];
8184
+ input.remove();
8185
+ if (!file) {
8186
+ return;
8187
+ }
8188
+ const reader = new FileReader();
8189
+ reader.onload = () => {
8190
+ const text = String(reader.result || '');
8191
+ void this.applyImportedDashboardJson(text);
8192
+ };
8193
+ reader.onerror = () => {
8194
+ WtoolboxService.messageNotificationService.add({
8195
+ severity: 'error',
8196
+ summary: this.t('error', 'Error'),
8197
+ detail: this.t('designer.import_json_read_error', 'Could not read the selected file')
8198
+ });
8199
+ };
8200
+ reader.readAsText(file);
8201
+ });
8202
+ document.body.appendChild(input);
8203
+ input.click();
8204
+ }
8205
+ /**
8206
+ * Valida e applica un boardcontent importato. Estratta da `importDashboardJson`
8207
+ * per essere riusabile (es. incolla da clipboard / test) e testabile.
8208
+ */
8209
+ async applyImportedDashboardJson(text) {
8210
+ let parsed;
8211
+ try {
8212
+ parsed = JSON.parse(text);
8213
+ }
8214
+ catch {
8215
+ WtoolboxService.messageNotificationService.add({
8216
+ severity: 'error',
8217
+ summary: this.t('error', 'Error'),
8218
+ detail: this.t('designer.import_json_invalid', 'The file does not contain valid JSON')
8219
+ });
8220
+ return;
8221
+ }
8222
+ // Accetta sia l'array di elementi top-level sia il wrapper {elements|dashboardElements}
8223
+ const dashboardElements = Array.isArray(parsed?.dashboardElements) ? parsed.dashboardElements : null;
8224
+ const wrappedElements = Array.isArray(parsed?.elements) ? parsed.elements : dashboardElements;
8225
+ const elements = Array.isArray(parsed) ? parsed : wrappedElements;
8226
+ if (!elements) {
8227
+ WtoolboxService.messageNotificationService.add({
8228
+ severity: 'error',
8229
+ summary: this.t('error', 'Error'),
8230
+ detail: this.t('designer.import_json_unrecognized', 'The JSON is not a recognizable dashboard layout')
8231
+ });
8232
+ return;
8233
+ }
8234
+ if ((this.flattenedDashboardElements || []).length > 0) {
8235
+ const ok = await WtoolboxService.confirm({
8236
+ header: this.t('warning', 'Warning'),
8237
+ message: this.t('designer.import_json_confirm', 'Importing will replace the current canvas. Continue?'),
8238
+ acceptLabel: this.t('confirm', 'Confirm'),
8239
+ rejectLabel: this.t('cancel', 'Cancel')
8240
+ });
8241
+ if (!ok) {
8242
+ return;
8243
+ }
8244
+ }
8245
+ try {
8246
+ await this.loadDashboard({
8247
+ boardcontent: JSON.stringify(elements),
8248
+ board_route: this.currentDashboardRoute || '',
8249
+ board_des: this.currentDashboardDescription || ''
8250
+ }, true);
8251
+ WtoolboxService.messageNotificationService.add({
8252
+ severity: 'success',
8253
+ summary: this.t('success', 'Success'),
8254
+ detail: this.t('designer.import_json_success', 'Dashboard JSON imported')
8255
+ });
8256
+ }
8257
+ catch (error) {
8258
+ console.error('Failed to import dashboard JSON', error);
8259
+ WtoolboxService.errorHandler?.handleError?.(error);
8260
+ }
8261
+ }
8262
+ get droppedDashboardItems() {
8263
+ return (this.flattenedDashboardElements || []).filter((item) => !!item?.uniqueName);
8264
+ }
8265
+ get hierarchyTreeNodes() {
7717
8266
  return this.hierarchyTreeNodesState;
7718
8267
  }
7719
8268
  get canUndo() {
@@ -7747,11 +8296,561 @@ class DesignerComponent {
7747
8296
  this.tool = item;
7748
8297
  this.syncHierarchySelectionToCurrentTool();
7749
8298
  }
8299
+ // ===================== Multi-selezione (rubber-band) =====================
8300
+ /** Canvas host (per le coordinate del rettangolo di selezione). */
8301
+ canvasRef;
8302
+ /** Elementi top-level multi-selezionati. `tool` (attivo nel property panel) e' null quando != 1. */
8303
+ selectedTools = [];
8304
+ /** Overlay del rettangolo di selezione (rubber-band). */
8305
+ rubberBandVisible = false;
8306
+ rubberBandStyle = { left: '0px', top: '0px', width: '0px', height: '0px' };
8307
+ rubber = null;
8308
+ canvasRectAtStart = null;
8309
+ boundRubberMove = (e) => this.onRubberMove(e);
8310
+ boundRubberUp = (e) => this.onRubberUp(e);
8311
+ /**
8312
+ * Mousedown sul canvas: avvia la selezione a rettangolo SOLO se parte da area
8313
+ * vuota (nessun elemento sotto il puntatore). Sugli elementi il mousedown e'
8314
+ * consumato dalla direttiva di drag (`stopImmediatePropagation`), quindi qui
8315
+ * arriva solo il click su spazio libero — coerente col vincolo richiesto.
8316
+ */
8317
+ onCanvasMouseDown(event) {
8318
+ if (this.noEdit || !event || event.button !== 0) {
8319
+ return;
8320
+ }
8321
+ const canvasEl = this.canvasRef?.nativeElement;
8322
+ if (!canvasEl || !this.isEmptyCanvasTarget(event.target, canvasEl)) {
8323
+ return;
8324
+ }
8325
+ event.preventDefault();
8326
+ this.clearMultiSelection();
8327
+ this.tool = null;
8328
+ this.canvasRectAtStart = canvasEl.getBoundingClientRect();
8329
+ this.rubber = { startClientX: event.clientX, startClientY: event.clientY };
8330
+ this.rubberBandVisible = true;
8331
+ this.updateRubberBandStyle(event.clientX, event.clientY);
8332
+ globalThis.addEventListener('mousemove', this.boundRubberMove);
8333
+ globalThis.addEventListener('mouseup', this.boundRubberUp);
8334
+ this.cd.detectChanges();
8335
+ }
8336
+ /** true se il target risale fino al canvas senza attraversare un elemento designer (id con '__'). */
8337
+ isEmptyCanvasTarget(target, canvasEl) {
8338
+ let node = target;
8339
+ while (node && node !== canvasEl) {
8340
+ const id = node.getAttribute ? (node.getAttribute('id') || '') : '';
8341
+ if (id.includes('__')) {
8342
+ return false;
8343
+ }
8344
+ node = node.parentElement;
8345
+ }
8346
+ return node === canvasEl;
8347
+ }
8348
+ onRubberMove(event) {
8349
+ if (!this.rubber) {
8350
+ return;
8351
+ }
8352
+ this.updateRubberBandStyle(event.clientX, event.clientY);
8353
+ this.cd.detectChanges();
8354
+ }
8355
+ updateRubberBandStyle(curX, curY) {
8356
+ if (!this.rubber || !this.canvasRectAtStart) {
8357
+ return;
8358
+ }
8359
+ const canvasEl = this.canvasRef?.nativeElement;
8360
+ const r = this.canvasRectAtStart;
8361
+ const sL = canvasEl ? canvasEl.scrollLeft : 0;
8362
+ const sT = canvasEl ? canvasEl.scrollTop : 0;
8363
+ const x1 = Math.min(this.rubber.startClientX, curX), x2 = Math.max(this.rubber.startClientX, curX);
8364
+ const y1 = Math.min(this.rubber.startClientY, curY), y2 = Math.max(this.rubber.startClientY, curY);
8365
+ this.rubberBandStyle = {
8366
+ left: (x1 - r.left + sL) + 'px',
8367
+ top: (y1 - r.top + sT) + 'px',
8368
+ width: (x2 - x1) + 'px',
8369
+ height: (y2 - y1) + 'px',
8370
+ };
8371
+ }
8372
+ onRubberUp(event) {
8373
+ globalThis.removeEventListener('mousemove', this.boundRubberMove);
8374
+ globalThis.removeEventListener('mouseup', this.boundRubberUp);
8375
+ const rubber = this.rubber;
8376
+ this.rubber = null;
8377
+ this.rubberBandVisible = false;
8378
+ if (!rubber) {
8379
+ this.cd.detectChanges();
8380
+ return;
8381
+ }
8382
+ const x1 = Math.min(rubber.startClientX, event.clientX), x2 = Math.max(rubber.startClientX, event.clientX);
8383
+ const y1 = Math.min(rubber.startClientY, event.clientY), y2 = Math.max(rubber.startClientY, event.clientY);
8384
+ const selected = [];
8385
+ // Ignora i "click" (rettangolo minuscolo): niente selezione, solo deselect.
8386
+ if (Math.abs(x2 - x1) >= 3 && Math.abs(y2 - y1) >= 3) {
8387
+ // Hit-test dei soli elementi TOP-LEVEL (spostare/eliminare un top-level porta i figli).
8388
+ for (const el of (this.dashboardElements || [])) {
8389
+ const dom = el?.uniqueName ? document.getElementById(el.uniqueName) : null;
8390
+ if (!dom) {
8391
+ continue;
8392
+ }
8393
+ const b = dom.getBoundingClientRect();
8394
+ if (b.left < x2 && b.right > x1 && b.top < y2 && b.bottom > y1) {
8395
+ selected.push(el);
8396
+ }
8397
+ }
8398
+ }
8399
+ this.setMultiSelection(selected);
8400
+ this.cd.detectChanges();
8401
+ }
8402
+ /** Imposta la multi-selezione: `tool` resta valorizzato solo con esattamente 1 elemento. */
8403
+ setMultiSelection(selected) {
8404
+ this.clearHighlight();
8405
+ this.selectedTools = selected || [];
8406
+ this.tool = this.selectedTools.length === 1 ? this.selectedTools[0] : null;
8407
+ this.applyHighlight();
8408
+ }
8409
+ clearMultiSelection() {
8410
+ this.clearHighlight();
8411
+ this.selectedTools = [];
8412
+ }
8413
+ applyHighlight() {
8414
+ for (const el of (this.selectedTools || [])) {
8415
+ const dom = el?.uniqueName ? document.getElementById(el.uniqueName) : null;
8416
+ if (dom) {
8417
+ dom.classList.add('designer-multi-selected');
8418
+ }
8419
+ }
8420
+ }
8421
+ clearHighlight() {
8422
+ for (const el of (this.selectedTools || [])) {
8423
+ const dom = el?.uniqueName ? document.getElementById(el.uniqueName) : null;
8424
+ if (dom) {
8425
+ dom.classList.remove('designer-multi-selected');
8426
+ }
8427
+ }
8428
+ }
8429
+ /** Applica un delta di traslazione (px) a un insieme di elementi (multi-move via frecce). */
8430
+ nudgeElements(tools, dx, dy) {
8431
+ for (const el of (tools || [])) {
8432
+ if (!el?.inputs) {
8433
+ continue;
8434
+ }
8435
+ let scaleX = 1, skewY = 0, skewX = 0, scaleY = 1, tx = 0, ty = 0;
8436
+ const m = String(el.inputs['transform'] || '').match(/matrix\(([^)]+)\)/);
8437
+ if (m) {
8438
+ const p = m[1].split(',').map((x) => Number.parseFloat(x));
8439
+ if (p.length >= 6 && p.every((n) => !Number.isNaN(Number(n)))) {
8440
+ [scaleX, skewY, skewX, scaleY, tx, ty] = p;
8441
+ }
8442
+ }
8443
+ el.inputs['transform'] = `matrix(${scaleX}, ${skewY}, ${skewX}, ${scaleY}, ${tx + dx}, ${ty + dy})`;
8444
+ }
8445
+ this.commitHistoryIfChanged();
8446
+ this.cd.detectChanges();
8447
+ }
8448
+ // ============ Import dashboard/preset DENTRO un elemento (context menu) ============
8449
+ /** true se il tool e' un container che accetta figli arbitrari (nestedComponents + nessun allowedChildren). */
8450
+ isFreeContainer(tool) {
8451
+ return !!tool && Array.isArray(tool.nestedComponents) && !(tool.allowedChildren);
8452
+ }
8453
+ /** Voci di context menu per importare dashboard/preset dentro un container. */
8454
+ buildImportContextItems(el) {
8455
+ return [
8456
+ { separator: true },
8457
+ { label: this.t('designer.ctx_import_dashboard', 'Importa dashboard qui'), icon: 'pi pi-download', command: () => this.importDashboardIntoElement(el) },
8458
+ { label: this.t('designer.ctx_import_preset', 'Importa preset qui'), icon: 'pi pi-bookmark', command: () => this.importPresetIntoElement(el) },
8459
+ ];
8460
+ }
8461
+ /**
8462
+ * UNICA sorgente del context menu di un elemento, condivisa dai tre punti di
8463
+ * accesso (canvas `showCtx`, gerarchia `showHierarchyNodeContextMenu`, footer
8464
+ * breadcrumb `showFooterContextMenu`). Le `command` operano sull'elemento
8465
+ * passato (risolto al click), non su una variabile globale, quindi il menu si
8466
+ * ricostruisce per-elemento con voci identiche ovunque. Il caller canvas
8467
+ * antepone eventuali voci field-editor (specifiche del click su un campo).
8468
+ */
8469
+ buildElementContextMenuItems(el) {
8470
+ const items = [
8471
+ {
8472
+ label: this.t('designer.edit_properties', 'Edit properties'),
8473
+ icon: 'pi pi-cog',
8474
+ command: () => { if (el) {
8475
+ this.selectDashboardItem(el);
8476
+ this.cd.detectChanges();
8477
+ } }
8478
+ },
8479
+ {
8480
+ label: this.t('designer.duplicate', 'Duplica'),
8481
+ icon: 'pi pi-clone',
8482
+ command: () => { if (el) {
8483
+ void this.duplicateDesignerElement(el);
8484
+ } }
8485
+ },
8486
+ {
8487
+ label: this.t('designer.rename', 'Rename'),
8488
+ icon: 'pi pi-pencil',
8489
+ command: () => { if (el) {
8490
+ void this.renameDesignerElement(el);
8491
+ } }
8492
+ },
8493
+ {
8494
+ label: this.t('designer.remove', 'Remove'),
8495
+ icon: 'pi pi-trash',
8496
+ command: () => { const n = String(el?.uniqueName || '').trim(); if (n) {
8497
+ this.removeElementByName(n);
8498
+ } }
8499
+ }
8500
+ ];
8501
+ // Sposta in nuovo Tab: SOLO se l'elemento e' dentro un TABPANEL di un TABVIEW
8502
+ // (migrazione elemento -> nuovo TABPANEL del TABVIEW padre).
8503
+ if (this.isInsideTabPanel(el)) {
8504
+ items.push({
8505
+ label: this.t('designer.ctx_move_to_new_tab', 'Sposta in nuovo Tab'),
8506
+ icon: 'pi pi-folder-open',
8507
+ command: () => { void this.moveElementToNewTab(el); }
8508
+ });
8509
+ // Sposta in altro tab: solo se il TABVIEW ha piu' di un tab (scelta esclusa il corrente).
8510
+ if (this.hasOtherTabs(el)) {
8511
+ items.push({
8512
+ label: this.t('designer.ctx_move_to_existing_tab_element', 'Sposta in altro tab'),
8513
+ icon: 'pi pi-arrow-right-arrow-left',
8514
+ command: () => { void this.moveElementToExistingTab(el); }
8515
+ });
8516
+ }
8517
+ }
8518
+ // Import dashboard/preset: solo per container liberi (nestedComponents senza allowedChildren).
8519
+ if (this.isFreeContainer(el)) {
8520
+ items.push(...this.buildImportContextItems(el));
8521
+ }
8522
+ return items;
8523
+ }
8524
+ /**
8525
+ * Risolve il contesto tab di un elemento: il TABPANEL che lo contiene e il
8526
+ * TABVIEW che contiene quel pannello. null se l'elemento non e' dentro un tab.
8527
+ */
8528
+ resolveTabContext(el) {
8529
+ if (!el) {
8530
+ return null;
8531
+ }
8532
+ const panelUn = this.findParentUniqueName(el);
8533
+ const panel = panelUn ? this.getElementByName(panelUn) : null;
8534
+ if (!panel || String(panel.name || '').toUpperCase() !== 'TABPANEL') {
8535
+ return null;
8536
+ }
8537
+ const tabViewUn = this.findParentUniqueName(panel);
8538
+ const tabView = tabViewUn ? this.getElementByName(tabViewUn) : null;
8539
+ if (!tabView || String(tabView.name || '').toUpperCase() !== 'TABVIEW') {
8540
+ return null;
8541
+ }
8542
+ return { panel, tabView };
8543
+ }
8544
+ /** true se l'elemento e' dentro un TABPANEL di un TABVIEW (eleggibile a "Sposta in nuovo Tab"). */
8545
+ isInsideTabPanel(el) {
8546
+ return !!this.resolveTabContext(el);
8547
+ }
8548
+ /** true se il TABVIEW che contiene l'elemento ha piu' di un tab (eleggibile a "Sposta in altro tab"). */
8549
+ hasOtherTabs(el) {
8550
+ const ctx = this.resolveTabContext(el);
8551
+ return !!ctx && (ctx.tabView.nestedComponents?.length || 0) > 1;
8552
+ }
8553
+ /**
8554
+ * Sposta l'elemento in un NUOVO tab (TABPANEL) del TABVIEW che lo contiene:
8555
+ * crea un TABPANEL fresco (clonando il template di palette, uniqueName/componentId
8556
+ * nuovi), rimuove l'elemento dal tab corrente e ci mette lo STESSO oggetto (move,
8557
+ * non clone -> binding datasource/stato preservati), poi seleziona il nuovo tab.
8558
+ */
8559
+ async moveElementToNewTab(el) {
8560
+ if (!el) {
8561
+ return;
8562
+ }
8563
+ const ctx = this.resolveTabContext(el);
8564
+ if (!ctx) {
8565
+ return;
8566
+ }
8567
+ const { panel, tabView } = ctx;
8568
+ // Nome del nuovo tab (default: displayName/name dell'elemento).
8569
+ const resp = await WtoolboxService.promptDialog(this.t('designer.ctx_move_to_new_tab', 'Sposta in nuovo Tab'), [{
8570
+ name: 'tabName',
8571
+ caption: this.t('designer.tab_name_caption', 'Nome Tab'),
8572
+ type: 'text',
8573
+ required: true,
8574
+ value: String(el.displayName || el.name || 'Tab').trim()
8575
+ }], '440px', '320px');
8576
+ if (!resp) {
8577
+ return;
8578
+ }
8579
+ const tabName = String(resp?.tabName?.value || '').trim();
8580
+ if (!tabName) {
8581
+ return;
8582
+ }
8583
+ // Nuovo TABPANEL dalla stessa shape del drop (template + id freschi).
8584
+ const template = (this.availableTools || []).find((t) => String(t?.name || '').toUpperCase() === 'TABPANEL');
8585
+ if (!template) {
8586
+ return;
8587
+ }
8588
+ const newPanel = JSON.parse(JSON.stringify(template));
8589
+ newPanel.componentId = this.getNewID();
8590
+ newPanel.uniqueName = newPanel.name + '__' + newPanel.componentId;
8591
+ newPanel.inputs = newPanel.inputs || {};
8592
+ newPanel.inputs['header'] = tabName;
8593
+ newPanel.nestedComponents = [];
8594
+ // Move: rimuovi dall'attuale tab e inserisci lo stesso oggetto nel nuovo.
8595
+ panel.nestedComponents = (panel.nestedComponents || []).filter((k) => String(k?.uniqueName || '') !== String(el.uniqueName || ''));
8596
+ newPanel.nestedComponents.push(el);
8597
+ // Aggancia il nuovo tab al TABVIEW e selezionalo (deseleziona gli altri).
8598
+ tabView.nestedComponents = tabView.nestedComponents || [];
8599
+ tabView.nestedComponents.push(newPanel);
8600
+ this.selectTabPanelExclusive(newPanel);
8601
+ this.flattenedDashboardElements = this.flattenComponentTree(this.dashboardElements);
8602
+ this.commitHistoryIfChanged();
8603
+ this.cd.detectChanges();
8604
+ WtoolboxService.messageNotificationService.add({
8605
+ severity: 'success',
8606
+ summary: this.t('success', 'Success'),
8607
+ detail: this.t('designer.moved_to_new_tab', 'Elemento spostato nel nuovo tab')
8608
+ });
8609
+ }
8610
+ /**
8611
+ * Sposta l'elemento in un ALTRO tab (TABPANEL) gia' esistente dello stesso
8612
+ * TABVIEW: picker dei tab escluso il corrente, poi move dello STESSO oggetto
8613
+ * (binding/stato preservati) nel tab scelto, che viene selezionato.
8614
+ */
8615
+ async moveElementToExistingTab(el) {
8616
+ if (!el) {
8617
+ return;
8618
+ }
8619
+ const ctx = this.resolveTabContext(el);
8620
+ if (!ctx) {
8621
+ return;
8622
+ }
8623
+ const { panel, tabView } = ctx;
8624
+ // Tab candidati: tutti i TABPANEL del TABVIEW tranne quello corrente.
8625
+ const others = (tabView.nestedComponents || []).filter((p) => String(p?.uniqueName || '') !== String(panel.uniqueName || ''));
8626
+ if (!others.length) {
8627
+ return;
8628
+ }
8629
+ const choices = others.map((p, idx) => {
8630
+ const label = String(p?.inputs?.header || p?.displayName || p?.uniqueName || '').trim() || ('Tab ' + (idx + 1));
8631
+ return { label, value: String(p?.uniqueName || '') };
8632
+ });
8633
+ const resp = await WtoolboxService.promptDialog(this.t('designer.ctx_move_to_existing_tab_element', 'Sposta in altro tab'), [{
8634
+ name: 'targetTab',
8635
+ caption: this.t('designer.target_tab_caption', 'Tab di destinazione'),
8636
+ type: 'dictionary',
8637
+ required: true,
8638
+ value: choices[0].value,
8639
+ dictionaryData: choices
8640
+ }], '440px', '320px');
8641
+ if (!resp) {
8642
+ return;
8643
+ }
8644
+ const targetUn = String(resp?.targetTab?.value || '').trim();
8645
+ if (!targetUn) {
8646
+ return;
8647
+ }
8648
+ const targetPanel = (tabView.nestedComponents || []).find((p) => String(p?.uniqueName || '') === targetUn);
8649
+ if (!targetPanel) {
8650
+ return;
8651
+ }
8652
+ // Move: rimuovi dal tab corrente e inserisci lo stesso oggetto nel target.
8653
+ panel.nestedComponents = (panel.nestedComponents || []).filter((k) => String(k?.uniqueName || '') !== String(el.uniqueName || ''));
8654
+ targetPanel.nestedComponents = Array.isArray(targetPanel.nestedComponents) ? targetPanel.nestedComponents : [];
8655
+ targetPanel.nestedComponents.push(el);
8656
+ // Seleziona il tab di destinazione.
8657
+ this.selectTabPanelExclusive(targetPanel);
8658
+ this.flattenedDashboardElements = this.flattenComponentTree(this.dashboardElements);
8659
+ this.commitHistoryIfChanged();
8660
+ this.cd.detectChanges();
8661
+ WtoolboxService.messageNotificationService.add({
8662
+ severity: 'success',
8663
+ summary: this.t('success', 'Success'),
8664
+ detail: this.t('designer.moved_to_tab', 'Elemento spostato nel tab')
8665
+ });
8666
+ }
8667
+ /**
8668
+ * onDrop del TABPANEL: appena droppato dentro un TABVIEW lo rende il tab attivo,
8669
+ * cosi' la sua area diventa visibile e droppabile. Il pannello a questo punto e'
8670
+ * gia' stato inserito in `tabView.nestedComponents` dal drop() (push prima di onDrop).
8671
+ */
8672
+ dropTabPanel(currentTool) {
8673
+ this.selectTabPanelExclusive(currentTool);
8674
+ this.cd.detectChanges();
8675
+ }
8676
+ /**
8677
+ * Seleziona in modo esclusivo un TABPANEL nel TABVIEW che lo contiene
8678
+ * (`inputs.selected = true` sul pannello, `false` sugli altri). No-op se il
8679
+ * parent non e' un TABVIEW. Stesso meccanismo usato da `activateTabPanelForHover`.
8680
+ */
8681
+ selectTabPanelExclusive(panel) {
8682
+ const tabViewUn = this.findParentUniqueName(panel);
8683
+ const tabView = tabViewUn ? this.getElementByName(tabViewUn) : null;
8684
+ if (!tabView || String(tabView.name || '').toUpperCase() !== 'TABVIEW' || !Array.isArray(tabView.nestedComponents)) {
8685
+ return;
8686
+ }
8687
+ tabView.nestedComponents.forEach((p) => {
8688
+ if (!p?.inputs) {
8689
+ p.inputs = {};
8690
+ }
8691
+ p.inputs.selected = String(p?.uniqueName || '') === String(panel?.uniqueName || '');
8692
+ });
8693
+ }
8694
+ /** Importa una dashboard salvata DENTRO l'elemento (nei suoi nestedComponents). */
8695
+ async importDashboardIntoElement(target) {
8696
+ if (!this.isFreeContainer(target)) {
8697
+ return;
8698
+ }
8699
+ const options = (this.savedDashboardOptions || []).map((o) => ({ label: o.label, value: o.value }));
8700
+ if (!options.length) {
8701
+ WtoolboxService.messageNotificationService.add({ severity: 'info', summary: this.t('designer.import.title', 'Import'), detail: this.t('designer.import.no_dashboards', 'Nessuna dashboard salvata') });
8702
+ return;
8703
+ }
8704
+ const resp = await WtoolboxService.promptDialog(this.t('designer.import.pick_dashboard', 'Importa dashboard qui'), [{ name: 'dash', caption: this.t('designer.import.dashboard', 'Dashboard'), type: 'dictionary', dictionaryData: options, value: '', required: true }], null, '440px', '230px');
8705
+ const route = String(resp?.dash?.value ?? '').trim();
8706
+ if (!route) {
8707
+ return;
8708
+ }
8709
+ let boardcontent = null;
8710
+ try {
8711
+ const result = await this.dataSrv.loadDashboard({ user_id: this.userInfo.getuserInfo().user_id, dashRoute: route });
8712
+ const fallbackRows = result ? [result] : [];
8713
+ const resultRows = Array.isArray(result?.results) ? result.results : fallbackRows;
8714
+ const rows = Array.isArray(result) ? result : resultRows;
8715
+ boardcontent = rows?.[0]?.boardcontent ?? null;
8716
+ }
8717
+ catch {
8718
+ boardcontent = null;
8719
+ }
8720
+ await this.importBoardcontentIntoElement(target, boardcontent);
8721
+ }
8722
+ /** Importa un PRESET locale DENTRO l'elemento (nei suoi nestedComponents). */
8723
+ async importPresetIntoElement(target) {
8724
+ if (!this.isFreeContainer(target)) {
8725
+ return;
8726
+ }
8727
+ const presets = this.readDesignerPresets();
8728
+ const names = Object.keys(presets || {});
8729
+ if (!names.length) {
8730
+ WtoolboxService.messageNotificationService.add({ severity: 'info', summary: this.t('designer.import.title', 'Import'), detail: this.t('designer.import.no_presets', 'Nessun preset salvato') });
8731
+ return;
8732
+ }
8733
+ const resp = await WtoolboxService.promptDialog(this.t('designer.import.pick_preset', 'Importa preset qui'), [{ name: 'preset', caption: this.t('designer.import.preset', 'Preset'), type: 'dictionary', dictionaryData: names.map((n) => ({ label: n, value: n })), value: '', required: true }], null, '440px', '230px');
8734
+ const name = String(resp?.preset?.value ?? '').trim();
8735
+ if (!name || !presets[name]) {
8736
+ return;
8737
+ }
8738
+ await this.importBoardcontentIntoElement(target, presets[name]);
8739
+ }
8740
+ /**
8741
+ * Importa un boardcontent (dashboard salvata o preset) DENTRO un container:
8742
+ * rimappa uniqueName/componentId con id freschi (niente collisioni) e i riferimenti
8743
+ * che li puntano (datasource, parentDatasource, dropped-component...), poi riusa la
8744
+ * rehydration completa di loadDashboard (datasource inclusi) in modalita' insert-target.
8745
+ */
8746
+ async importBoardcontentIntoElement(target, boardContentRaw) {
8747
+ if (!target || !boardContentRaw) {
8748
+ WtoolboxService.messageNotificationService.add({ severity: 'warn', summary: this.t('designer.import.title', 'Import'), detail: this.t('designer.import.empty', 'Contenuto vuoto o non valido') });
8749
+ return;
8750
+ }
8751
+ let imported;
8752
+ try {
8753
+ imported = JSON.parse(boardContentRaw);
8754
+ }
8755
+ catch {
8756
+ imported = null;
8757
+ }
8758
+ if (!Array.isArray(imported)) {
8759
+ if (Array.isArray(imported?.dashboardElements)) {
8760
+ imported = imported.dashboardElements;
8761
+ }
8762
+ else if (Array.isArray(imported?.elements)) {
8763
+ imported = imported.elements;
8764
+ }
8765
+ else {
8766
+ imported = [];
8767
+ }
8768
+ }
8769
+ if (!imported.length) {
8770
+ WtoolboxService.messageNotificationService.add({ severity: 'warn', summary: this.t('designer.import.title', 'Import'), detail: this.t('designer.import.empty', 'Contenuto vuoto o non valido') });
8771
+ return;
8772
+ }
8773
+ // 1. Rimappa gli ID con id freschi (getNewID -> uniqueName = name__id) e traccia old->new.
8774
+ const nameMap = new Map();
8775
+ const idMap = new Map();
8776
+ this.remapImportedIds(imported, nameMap, idMap);
8777
+ // 2. Aggiorna i riferimenti agli elementi (datasource ecc.) coi nuovi uniqueName/id.
8778
+ this.remapImportedReferences(imported, nameMap, idMap);
8779
+ // 3. Riusa la rehydration completa di loadDashboard in modalita' insert-target.
8780
+ await this.loadDashboard({ boardcontent: JSON.stringify(imported), board_route: '' }, true, target);
8781
+ WtoolboxService.messageNotificationService.add({ severity: 'success', summary: this.t('designer.import.title', 'Import'), detail: this.t('designer.import.done', 'Contenuto importato nell\'elemento') });
8782
+ }
8783
+ /** Rimappa ricorsivamente componentId/uniqueName degli elementi importati con id freschi. */
8784
+ remapImportedIds(nodes, nameMap, idMap) {
8785
+ for (const el of (nodes || [])) {
8786
+ if (!el || typeof el !== 'object') {
8787
+ continue;
8788
+ }
8789
+ const oldName = String(el.uniqueName || '');
8790
+ const oldId = Number(el.componentId);
8791
+ const newId = this.getNewID();
8792
+ const newName = String(el.name || 'ITEM') + '__' + newId;
8793
+ if (oldName) {
8794
+ nameMap.set(oldName, newName);
8795
+ }
8796
+ if (!Number.isNaN(Number(oldId))) {
8797
+ idMap.set(oldId, newId);
8798
+ }
8799
+ el.componentId = newId;
8800
+ el.uniqueName = newName;
8801
+ if (Array.isArray(el.nestedComponents)) {
8802
+ this.remapImportedIds(el.nestedComponents, nameMap, idMap);
8803
+ }
8804
+ }
8805
+ }
8806
+ /** Aggiorna i riferimenti (oggetti con uniqueName/id) dentro gli inputs, coi nuovi valori. */
8807
+ remapImportedReferences(nodes, nameMap, idMap) {
8808
+ const SKIP = new Set(['nestedComponents', 'inputProps', 'toolProps', 'metaInfo', 'componentRef', 'propertyTree']);
8809
+ const remap = (v, depth) => {
8810
+ if (!v || typeof v !== 'object' || depth > 6) {
8811
+ return;
8812
+ }
8813
+ if (Array.isArray(v)) {
8814
+ for (const it of v) {
8815
+ remap(it, depth + 1);
8816
+ }
8817
+ return;
8818
+ }
8819
+ if (typeof v.uniqueName === 'string' && nameMap.has(v.uniqueName)) {
8820
+ v.uniqueName = nameMap.get(v.uniqueName);
8821
+ if (typeof v.id === 'number' && idMap.has(v.id)) {
8822
+ v.id = idMap.get(v.id);
8823
+ }
8824
+ if (typeof v.componentId === 'number' && idMap.has(v.componentId)) {
8825
+ v.componentId = idMap.get(v.componentId);
8826
+ }
8827
+ }
8828
+ for (const k of Object.keys(v)) {
8829
+ if (SKIP.has(k)) {
8830
+ continue;
8831
+ }
8832
+ remap(v[k], depth + 1);
8833
+ }
8834
+ };
8835
+ const walk = (list) => {
8836
+ for (const el of (list || [])) {
8837
+ if (!el) {
8838
+ continue;
8839
+ }
8840
+ remap(el.inputs, 0);
8841
+ if (Array.isArray(el.nestedComponents)) {
8842
+ walk(el.nestedComponents);
8843
+ }
8844
+ }
8845
+ };
8846
+ walk(nodes);
8847
+ }
7750
8848
  showHierarchyNodeContextMenu(event, node, menu) {
7751
8849
  event.preventDefault();
7752
8850
  event.stopPropagation();
7753
8851
  const uniqueName = String(node?.key || '').trim();
7754
8852
  this.hierarchyCtxElement = uniqueName ? this.getElementByName(uniqueName) : null;
8853
+ this.hierarchyCtxItems = this.buildElementContextMenuItems(this.hierarchyCtxElement);
7755
8854
  menu.show(event);
7756
8855
  }
7757
8856
  onHierarchyNodeSelect(event) {
@@ -8007,13 +9106,14 @@ class DesignerComponent {
8007
9106
  event.preventDefault();
8008
9107
  event.stopPropagation();
8009
9108
  this.footerCtxElement = item;
9109
+ this.footerCtxItems = this.buildElementContextMenuItems(item);
8010
9110
  menu.show(event);
8011
9111
  }
8012
9112
  /**
8013
- * Rinomina l'elemento selezionato nel footer context menu aggiornando `displayName`.
9113
+ * Rinomina un elemento aggiornando `displayName`. Usato dal context menu
9114
+ * unificato (canvas/gerarchia/footer) via `buildElementContextMenuItems`.
8014
9115
  */
8015
- async renameFooterContextItem() {
8016
- const item = this.footerCtxElement;
9116
+ async renameDesignerElement(item) {
8017
9117
  if (!item) {
8018
9118
  return;
8019
9119
  }
@@ -8087,7 +9187,7 @@ class DesignerComponent {
8087
9187
  if (value instanceof BehaviorSubject) {
8088
9188
  return unwrapEntry(value.value);
8089
9189
  }
8090
- if (value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, 'value')) {
9190
+ if (value && typeof value === 'object' && Object.hasOwn(value, 'value')) {
8091
9191
  return unwrapEntry(value.value);
8092
9192
  }
8093
9193
  if (Array.isArray(value)) {
@@ -8151,7 +9251,7 @@ class DesignerComponent {
8151
9251
  if (idValue === undefined || idValue === null || idValue === '') {
8152
9252
  if (!isDeleted && allowMissingIdInsert) {
8153
9253
  const temporaryId = nextTemporaryNumericId(nextList, idKey);
8154
- const rowWithId = Object.assign({}, originalRow || {}, row, { [idKey]: temporaryId });
9254
+ const rowWithId = { ...originalRow, ...row, [idKey]: temporaryId };
8155
9255
  nextList.push(rowWithId);
8156
9256
  }
8157
9257
  return nextList;
@@ -8164,10 +9264,10 @@ class DesignerComponent {
8164
9264
  return nextList;
8165
9265
  }
8166
9266
  if (idx >= 0) {
8167
- nextList[idx] = Object.assign({}, nextList[idx], row);
9267
+ nextList[idx] = { ...nextList[idx], ...row };
8168
9268
  }
8169
9269
  else {
8170
- nextList.push(Object.assign({}, originalRow || {}, row));
9270
+ nextList.push({ ...originalRow, ...row });
8171
9271
  }
8172
9272
  return nextList;
8173
9273
  };
@@ -8199,7 +9299,7 @@ class DesignerComponent {
8199
9299
  else if (normalizedEditorKey === 'CG_Id') {
8200
9300
  const tableMetadata = hostDatasource?.metaInfo?.tableMetadata;
8201
9301
  if (tableMetadata) {
8202
- const normalizedConditionGroup = Object.assign({}, normalizedOriginal || {}, normalizedData || {});
9302
+ const normalizedConditionGroup = { ...normalizedOriginal, ...normalizedData };
8203
9303
  if (normalizedConditionGroup.CG_Id === undefined || normalizedConditionGroup.CG_Id === null || normalizedConditionGroup.CG_Id === '') {
8204
9304
  const fallbackGroupId = normalizedConditionGroup.__guid ?? normalizedOriginal?.__guid;
8205
9305
  if (fallbackGroupId !== undefined && fallbackGroupId !== null && String(fallbackGroupId) !== '') {
@@ -8224,7 +9324,11 @@ class DesignerComponent {
8224
9324
  const linkedGroup = hasGroupId
8225
9325
  ? conditionGroups.find((row) => String(row?.CG_Id ?? row?.FK_CG_Id ?? row?.__guid ?? '') === String(rawGroupId))
8226
9326
  : null;
8227
- const normalizedConditionItem = Object.assign({}, linkedGroup || {}, normalizedOriginal || {}, normalizedData || {});
9327
+ const normalizedConditionItem = {
9328
+ ...(linkedGroup || {}),
9329
+ ...(normalizedOriginal || {}),
9330
+ ...(normalizedData || {})
9331
+ };
8228
9332
  if (hasGroupId) {
8229
9333
  normalizedConditionItem.CG_Id = rawGroupId;
8230
9334
  }
@@ -8252,7 +9356,7 @@ class DesignerComponent {
8252
9356
  WtoolboxService.errorHandler?.handleError(new Error('FK_CG_Id obbligatorio: impossibile salvare Condition Action Group senza gruppo condizione.'));
8253
9357
  return;
8254
9358
  }
8255
- const normalizedActionGroup = Object.assign({}, normalizedOriginal || {}, normalizedData || {});
9359
+ const normalizedActionGroup = { ...normalizedOriginal, ...normalizedData };
8256
9360
  if (hasGroupId) {
8257
9361
  normalizedActionGroup.FK_CG_Id = rawGroupId;
8258
9362
  }
@@ -8292,7 +9396,7 @@ class DesignerComponent {
8292
9396
  WtoolboxService.errorHandler?.handleError(new Error('FK_CAG_Id obbligatorio: impossibile salvare Condition Action Item senza gruppo azione condizione.'));
8293
9397
  return;
8294
9398
  }
8295
- const normalizedActionItem = Object.assign({}, normalizedOriginal || {}, normalizedData || {});
9399
+ const normalizedActionItem = { ...normalizedOriginal, ...normalizedData };
8296
9400
  if (hasActionGroupId) {
8297
9401
  normalizedActionItem.FK_CAG_Id = rawActionGroupId;
8298
9402
  if (normalizedActionItem.CAG_Id === undefined || normalizedActionItem.CAG_Id === null || normalizedActionItem.CAG_Id === '') {
@@ -8355,7 +9459,7 @@ class DesignerComponent {
8355
9459
  publishDatasource(this['datasourceTabelle']);
8356
9460
  if (hostDatasource?.resultInfo?.current) {
8357
9461
  Object.keys(hostDatasource.resultInfo.current).forEach((field) => {
8358
- if (hostTableMetadata && Object.prototype.hasOwnProperty.call(hostTableMetadata, field) && hostDatasource.resultInfo.current[field]?.next) {
9462
+ if (hostTableMetadata && Object.hasOwn(hostTableMetadata, field) && hostDatasource.resultInfo.current[field]?.next) {
8359
9463
  hostDatasource.resultInfo.current[field].next(hostTableMetadata[field]);
8360
9464
  }
8361
9465
  });
@@ -8489,12 +9593,9 @@ class DesignerComponent {
8489
9593
  const fieldCtxItems = (contextField && contextDs)
8490
9594
  ? this.buildFieldEditorContextMenuItems(contextField, contextDs)
8491
9595
  : [];
8492
- if (el && el.ctxItems) {
8493
- this.ctxItems.next([...fieldCtxItems, ...el.ctxItems]);
8494
- }
8495
- else {
8496
- this.ctxItems.next([...fieldCtxItems, ...this.defaultCtxItems]);
8497
- }
9596
+ // Menu elemento dalla sorgente unificata (edit/duplica/rinomina/rimuovi +
9597
+ // import per i container); il canvas antepone le voci field-editor.
9598
+ this.ctxItems.next([...fieldCtxItems, ...this.buildElementContextMenuItems(el)]);
8498
9599
  if (el) {
8499
9600
  this.ctxElement = el;
8500
9601
  }
@@ -8575,18 +9676,21 @@ class DesignerComponent {
8575
9676
  if (!fieldEditorHost) {
8576
9677
  return null;
8577
9678
  }
8578
- const normalize = (v) => String(v || '')
8579
- .replace('*', '')
8580
- .replace(/\s+/g, ' ')
8581
- .trim()
8582
- .toLowerCase();
8583
9679
  return {
8584
9680
  dataFieldId: Number(fieldEditorHost.getAttribute('data-field-id') || 0),
8585
9681
  dataFieldName: String(fieldEditorHost.getAttribute('data-field-name') || '').trim(),
8586
9682
  forAttr: String(fieldEditorHost.querySelector('label.caption')?.getAttribute('for') || '').trim(),
8587
- labelText: normalize(fieldEditorHost.querySelector('label.caption')?.textContent || '')
9683
+ labelText: this.normalizeFieldEditorLabel(fieldEditorHost.querySelector('label.caption')?.textContent || '')
8588
9684
  };
8589
9685
  }
9686
+ /** Normalizzazione condivisa delle label field-editor (no asterisco, spazi collassati, lowercase). */
9687
+ normalizeFieldEditorLabel(v) {
9688
+ return String(v || '')
9689
+ .replace('*', '')
9690
+ .replaceAll(/\s+/g, ' ')
9691
+ .trim()
9692
+ .toLowerCase();
9693
+ }
8590
9694
  matchContextFieldColumn(cols, hints) {
8591
9695
  if (!Array.isArray(cols) || !hints) {
8592
9696
  return null;
@@ -8612,12 +9716,7 @@ class DesignerComponent {
8612
9716
  }
8613
9717
  }
8614
9718
  if (hints.labelText) {
8615
- const normalize = (v) => String(v || '')
8616
- .replace('*', '')
8617
- .replace(/\s+/g, ' ')
8618
- .trim()
8619
- .toLowerCase();
8620
- const byLabel = cols.find((c) => normalize(c?.mc_display_string_in_edit) === hints.labelText);
9719
+ const byLabel = cols.find((c) => this.normalizeFieldEditorLabel(c?.mc_display_string_in_edit) === hints.labelText);
8621
9720
  if (byLabel) {
8622
9721
  return byLabel;
8623
9722
  }
@@ -9307,6 +10406,20 @@ class DesignerComponent {
9307
10406
  applyArchetypeConfigToTool(tool, action, nextConfig) {
9308
10407
  if (!tool || !action || !nextConfig)
9309
10408
  return;
10409
+ // Chart AGGREGATO: con groupInfo/aggregates il backend ritorna il valore per-gruppo nella
10410
+ // colonna `<field>_<AGG>` (es. LatestRecordedPopulation_AVG), NON nel campo grezzo (null nelle
10411
+ // righe gruppo). Il modello/configuratore emette il campo grezzo come dataField + l'aggregato in
10412
+ // `aggregates`: qui colleghiamo i due riscrivendo il dataField dei datasets sul nome aggregato,
10413
+ // cosi' il chart plotta i valori per gruppo (suffix uppercase, coerente con _Metadati_methods).
10414
+ if (Array.isArray((nextConfig).aggregates) && (nextConfig).aggregates.length && (nextConfig).dataOptions?.datasets) {
10415
+ for (const dset of (nextConfig).dataOptions.datasets) {
10416
+ const agg = (nextConfig).aggregates.find((a) => a?.field === dset?.dataField);
10417
+ const sfx = agg?.aggregate ? '_' + String(agg.aggregate).toUpperCase() : '';
10418
+ if (sfx && dset?.dataField && !String(dset.dataField).toUpperCase().endsWith(sfx)) {
10419
+ dset.dataField = dset.dataField + sfx;
10420
+ }
10421
+ }
10422
+ }
9310
10423
  const customKey = 'customProps_' + action;
9311
10424
  tool.inputs[customKey] = this.toBehaviorSubjectsObject(nextConfig);
9312
10425
  const metaInfo = this.getDesignerDatasourceMetaInfo(tool);
@@ -9314,11 +10427,30 @@ class DesignerComponent {
9314
10427
  const extraProps = this.ensureMergedExtraProps(metaInfo);
9315
10428
  extraProps.archetypes = extraProps.archetypes || {};
9316
10429
  const currentArchetype = extraProps.archetypes[action] || {};
9317
- extraProps.archetypes[action] = Object.assign({}, currentArchetype, nextConfig);
10430
+ extraProps.archetypes[action] = { ...currentArchetype, ...nextConfig };
10431
+ // AGGREGAZIONE chart: groupInfo/aggregates sono config DATASOURCE-level (extraProps
10432
+ // top-level, lette in data-source.component initMetadata:1914), non archetype-level. Se
10433
+ // l'archetype_config li porta (es. chart 'popolazione media per provincia'), sollevali a
10434
+ // extraProps top-level cosi' il datasource raggruppa/aggrega lato server nel fetch.
10435
+ if (Array.isArray((nextConfig).groupInfo))
10436
+ extraProps.groupInfo = (nextConfig).groupInfo;
10437
+ if (Array.isArray((nextConfig).aggregates))
10438
+ extraProps.aggregates = (nextConfig).aggregates;
9318
10439
  metaInfo.tableMetadata.extraProps = extraProps;
9319
10440
  metaInfo.tableMetadata.md_props_bag = JSON.stringify(extraProps);
9320
10441
  }
9321
10442
  const ds = tool.inputs?.['datasource']?.component?.value;
10443
+ // Il datasource parsa extraProps.groupInfo/aggregates SOLO all'init metadata: qui (config
10444
+ // applicato a runtime dall'injection/configuratore) li settiamo a mano sull'istanza PRIMA
10445
+ // del fetch, cosi' la fetchData successiva raggruppa/aggrega lato server (un punto per gruppo).
10446
+ if (ds) {
10447
+ if (Array.isArray((nextConfig).groupInfo)) {
10448
+ ds.groupInfo = (nextConfig).groupInfo.map((x) => ({ field: x?.field, groupFormula: x?.groupFormula, dir: x?.dir, groupCount: x?.groupCount }));
10449
+ }
10450
+ if (Array.isArray((nextConfig).aggregates)) {
10451
+ ds.aggregationInfo = (nextConfig).aggregates.map((x) => ({ field: x?.field, aggregate: x?.aggregate }));
10452
+ }
10453
+ }
9322
10454
  if (ds?.fetchData) {
9323
10455
  ds.fetchData();
9324
10456
  }
@@ -9385,7 +10517,7 @@ class DesignerComponent {
9385
10517
  if (!raw) {
9386
10518
  return null;
9387
10519
  }
9388
- const rgbaMatch = raw.match(/^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([0-9]*\.?[0-9]+)\s*\)$/i);
10520
+ const rgbaMatch = /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d*\.?\d+)\s*\)$/i.exec(raw);
9389
10521
  if (rgbaMatch) {
9390
10522
  return {
9391
10523
  r: this.clampRgbChannel(rgbaMatch[1]),
@@ -9394,7 +10526,7 @@ class DesignerComponent {
9394
10526
  a: this.clampColorAlpha(rgbaMatch[4])
9395
10527
  };
9396
10528
  }
9397
- const rgbMatch = raw.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i);
10529
+ const rgbMatch = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i.exec(raw);
9398
10530
  if (rgbMatch) {
9399
10531
  return {
9400
10532
  r: this.clampRgbChannel(rgbMatch[1]),
@@ -9406,26 +10538,26 @@ class DesignerComponent {
9406
10538
  const hex = raw.replace('#', '');
9407
10539
  if (/^[0-9a-f]{3}$/i.test(hex)) {
9408
10540
  return {
9409
- r: parseInt(hex[0] + hex[0], 16),
9410
- g: parseInt(hex[1] + hex[1], 16),
9411
- b: parseInt(hex[2] + hex[2], 16),
10541
+ r: Number.parseInt(hex[0] + hex[0], 16),
10542
+ g: Number.parseInt(hex[1] + hex[1], 16),
10543
+ b: Number.parseInt(hex[2] + hex[2], 16),
9412
10544
  a: 1
9413
10545
  };
9414
10546
  }
9415
10547
  if (/^[0-9a-f]{6}$/i.test(hex)) {
9416
10548
  return {
9417
- r: parseInt(hex.slice(0, 2), 16),
9418
- g: parseInt(hex.slice(2, 4), 16),
9419
- b: parseInt(hex.slice(4, 6), 16),
10549
+ r: Number.parseInt(hex.slice(0, 2), 16),
10550
+ g: Number.parseInt(hex.slice(2, 4), 16),
10551
+ b: Number.parseInt(hex.slice(4, 6), 16),
9420
10552
  a: 1
9421
10553
  };
9422
10554
  }
9423
10555
  if (/^[0-9a-f]{8}$/i.test(hex)) {
9424
10556
  return {
9425
- r: parseInt(hex.slice(0, 2), 16),
9426
- g: parseInt(hex.slice(2, 4), 16),
9427
- b: parseInt(hex.slice(4, 6), 16),
9428
- a: this.clampColorAlpha(parseInt(hex.slice(6, 8), 16) / 255)
10557
+ r: Number.parseInt(hex.slice(0, 2), 16),
10558
+ g: Number.parseInt(hex.slice(2, 4), 16),
10559
+ b: Number.parseInt(hex.slice(4, 6), 16),
10560
+ a: this.clampColorAlpha(Number.parseInt(hex.slice(6, 8), 16) / 255)
9429
10561
  };
9430
10562
  }
9431
10563
  return null;
@@ -9436,14 +10568,14 @@ class DesignerComponent {
9436
10568
  let g = 255;
9437
10569
  let b = 255;
9438
10570
  if (/^[0-9a-f]{3}$/i.test(hex)) {
9439
- r = parseInt(hex[0] + hex[0], 16);
9440
- g = parseInt(hex[1] + hex[1], 16);
9441
- b = parseInt(hex[2] + hex[2], 16);
10571
+ r = Number.parseInt(hex[0] + hex[0], 16);
10572
+ g = Number.parseInt(hex[1] + hex[1], 16);
10573
+ b = Number.parseInt(hex[2] + hex[2], 16);
9442
10574
  }
9443
10575
  else if (/^[0-9a-f]{6}$/i.test(hex)) {
9444
- r = parseInt(hex.slice(0, 2), 16);
9445
- g = parseInt(hex.slice(2, 4), 16);
9446
- b = parseInt(hex.slice(4, 6), 16);
10576
+ r = Number.parseInt(hex.slice(0, 2), 16);
10577
+ g = Number.parseInt(hex.slice(2, 4), 16);
10578
+ b = Number.parseInt(hex.slice(4, 6), 16);
9447
10579
  }
9448
10580
  const alpha = this.clampColorAlpha(alphaRaw);
9449
10581
  return `rgba(${r}, ${g}, ${b}, ${Number(alpha.toFixed(2))})`;
@@ -9536,7 +10668,6 @@ class DesignerComponent {
9536
10668
  }
9537
10669
  if (!prevAction) {
9538
10670
  prevAction = createAction(prevActionCaption, prevActionCallback, prevActiondisableCallback, 'wizard-prev-btn', 1);
9539
- needPublish = true;
9540
10671
  }
9541
10672
  else {
9542
10673
  prevAction.button_image = 'wizard-prev-btn';
@@ -9544,20 +10675,25 @@ class DesignerComponent {
9544
10675
  }
9545
10676
  if (!nextAction) {
9546
10677
  nextAction = createAction(nextActionCaption, nextActionCallback, nextActiondisableCallback, 'wizard-next-btn', 3);
9547
- needPublish = true;
9548
10678
  }
9549
10679
  else {
9550
10680
  nextAction.button_image = 'wizard-next-btn';
9551
10681
  nextAction.ordine = 3;
9552
10682
  }
9553
- // if (needPublish) {
9554
10683
  this.publishDesignerDatasourceState(hostDatasource);
9555
10684
  this.commitHistoryIfChanged();
9556
10685
  // }
9557
10686
  if (!nextAction) {
9558
10687
  return;
9559
10688
  }
9560
- await this.openTableActionEditorDialog(target === 'end' ? completeAction : target === 'prev' ? prevAction : nextAction, hostDatasource);
10689
+ let actionToEdit = nextAction;
10690
+ if (target === 'end') {
10691
+ actionToEdit = completeAction;
10692
+ }
10693
+ else if (target === 'prev') {
10694
+ actionToEdit = prevAction;
10695
+ }
10696
+ await this.openTableActionEditorDialog(actionToEdit, hostDatasource);
9561
10697
  }
9562
10698
  /**
9563
10699
  * Restituisce un Id temporaneo negativo non usato per nuove table action in-memory.
@@ -9731,7 +10867,7 @@ class DesignerComponent {
9731
10867
  async openLabelDisplayFormulaEditor(inputs, tool) {
9732
10868
  const displayField = String(inputs?.displayField || '').trim();
9733
10869
  const defaultFormula = displayField
9734
- ? `return dataItem?.['${displayField.replace(/'/g, "\\'")}'] ?? '';`
10870
+ ? `return dataItem?.['${displayField.replaceAll('\'', "\\'")}'] ?? '';`
9735
10871
  : "return dataItem?.[inputs?.displayField] ?? '';";
9736
10872
  await this.openSelectDisplayFormulaEditor(inputs, tool, defaultFormula);
9737
10873
  }
@@ -9787,11 +10923,10 @@ class DesignerComponent {
9787
10923
  const masterRoute = this.getDatasourceRouteName(masterDatasource || {});
9788
10924
  const detailRoute = this.getDatasourceRouteName(detailDatasource || {});
9789
10925
  const masterPrimaryKey = this.getDatasourcePrimaryKey(masterDatasource || {});
9790
- const masterPkValueExpression = masterPrimaryKey
9791
- ? (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(masterPrimaryKey)
9792
- ? `dataItem?.${masterPrimaryKey}`
9793
- : 'dataItem?.id')
10926
+ const masterPkExpression = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(masterPrimaryKey || '')
10927
+ ? `dataItem?.${masterPrimaryKey}`
9794
10928
  : 'dataItem?.id';
10929
+ const masterPkValueExpression = masterPrimaryKey ? masterPkExpression : 'dataItem?.id';
9795
10930
  const propsBag = {
9796
10931
  customEditorConfig: {
9797
10932
  editorOptions: {
@@ -9821,7 +10956,7 @@ class DesignerComponent {
9821
10956
  : null;
9822
10957
  const detailFkField = fkFromLookup || fkFromNameMatch || (detailColumns.length ? detailColumns[0] : '');
9823
10958
  const defaultFormula = detailFkField
9824
- ? `// Detail columns (use one as first argument of setFilter):\n${detailColumnsHint}\nsetFilter('${detailFkField.replace(/'/g, "\\'")}', ${masterPkValueExpression}, 'eq');`
10959
+ ? `// Detail columns (use one as first argument of setFilter):\n${detailColumnsHint}\nsetFilter('${detailFkField.replaceAll('\'', "\\'")}', ${masterPkValueExpression}, 'eq');`
9825
10960
  : `// Detail columns (use one as first argument of setFilter):\n${detailColumnsHint}\n// Example:\n// setFilter('detail_fk', ${masterPkValueExpression}, 'eq');`;
9826
10961
  const result = await WtoolboxService.promptDialog('Master-Detail Filter Formula', [
9827
10962
  {
@@ -9858,7 +10993,7 @@ class DesignerComponent {
9858
10993
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(col)) {
9859
10994
  return ` ${col}: any;`;
9860
10995
  }
9861
- return ` '${col.replace(/'/g, "\\'")}': any;`;
10996
+ return ` '${col.replaceAll('\'', "\\'")}': any;`;
9862
10997
  }).join('\n');
9863
10998
  };
9864
10999
  const detailCommentRows = detailColumns.length
@@ -9906,7 +11041,7 @@ ${detailCommentRows}
9906
11041
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
9907
11042
  return ` ${key}: any;`;
9908
11043
  }
9909
- return ` '${key.replace(/'/g, "\\'")}': any;`;
11044
+ return ` '${key.replaceAll('\'', "\\'")}': any;`;
9910
11045
  }).join('\n')
9911
11046
  : ' [key: string]: any;';
9912
11047
  const routeComment = routeName ? `route: ${routeName}` : 'route: n/a';
@@ -9998,8 +11133,8 @@ Display formula context:
9998
11133
  propsFromBag = {};
9999
11134
  }
10000
11135
  const current = metaInfo?.tableMetadata?.extraProps || {};
10001
- const merged = Object.assign({}, propsFromBag, current);
10002
- merged.archetypes = Object.assign({}, propsFromBag?.archetypes || {}, current?.archetypes || {});
11136
+ const merged = { ...propsFromBag, ...current };
11137
+ merged.archetypes = { ...propsFromBag?.archetypes, ...current?.archetypes };
10003
11138
  return merged;
10004
11139
  }
10005
11140
  /**
@@ -10035,8 +11170,8 @@ Display formula context:
10035
11170
  propsFromBag = {};
10036
11171
  }
10037
11172
  const current = tableMetadata.extraProps || {};
10038
- const merged = Object.assign({}, propsFromBag, current);
10039
- merged.archetypes = Object.assign({}, propsFromBag?.archetypes || {}, current?.archetypes || {});
11173
+ const merged = { ...propsFromBag, ...current };
11174
+ merged.archetypes = { ...propsFromBag?.archetypes, ...current?.archetypes };
10040
11175
  tableMetadata.extraProps = merged;
10041
11176
  }
10042
11177
  /**
@@ -10051,8 +11186,8 @@ Display formula context:
10051
11186
  const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
10052
11187
  const normalizeDynamicScript = (source) => String(source || '')
10053
11188
  .replace(/^\uFEFF/, '')
10054
- .replace(/\u0000/g, '')
10055
- .replace(/\u2028|\u2029/g, '\n');
11189
+ .replaceAll(/\u0000/g, '')
11190
+ .replaceAll(/[\u2028\u2029]/g, '\n');
10056
11191
  actions.forEach((action) => {
10057
11192
  if (!action || typeof action !== 'object') {
10058
11193
  return;
@@ -10062,7 +11197,7 @@ Display formula context:
10062
11197
  try {
10063
11198
  const body = WtoolboxService.buildAsyncBody(actionScript);
10064
11199
  action.action_callback__fn = actionScript
10065
- ? new AsyncFunction('datasource, metaInfo, record, event, wtoolbox', body)
11200
+ ? (new AsyncFunction('datasource, metaInfo, record, event, wtoolbox', body))
10066
11201
  : async () => { };
10067
11202
  }
10068
11203
  catch {
@@ -10205,10 +11340,6 @@ Display formula context:
10205
11340
  }
10206
11341
  }
10207
11342
  else if (deltaRows < 0) {
10208
- for (let i = tool.nestedComponents.length; i >= Math.abs(deltaRows); i--) {
10209
- let trTool = tool.nestedComponents[i - 1];
10210
- // trTool.nestedComponents = [];
10211
- }
10212
11343
  tool.nestedComponents.splice(deltaRows, Math.abs(deltaRows));
10213
11344
  }
10214
11345
  else if (deltaCols > 0) {
@@ -10267,14 +11398,10 @@ Display formula context:
10267
11398
  }
10268
11399
  }
10269
11400
  else if (deltaAreas < 0) {
10270
- for (let i = tool.nestedComponents.length; i >= Math.abs(deltaAreas); i--) {
10271
- let trTool = tool.nestedComponents[i - 1];
10272
- // trTool.nestedComponents = [];
10273
- }
10274
11401
  tool.nestedComponents.splice(deltaAreas, Math.abs(deltaAreas));
10275
11402
  }
10276
- for (let i = 0; i < tool.nestedComponents.length; i++) {
10277
- tool.nestedComponents[i].inputs['size'] = 100 / tool.nestedComponents.length;
11403
+ for (const nestedComponent of tool.nestedComponents) {
11404
+ nestedComponent.inputs['size'] = 100 / tool.nestedComponents.length;
10278
11405
  }
10279
11406
  this.flattenedDashboardElements = this.flattenComponentTree(this.dashboardElements);
10280
11407
  obj[prop] = newValue;
@@ -10304,14 +11431,10 @@ Display formula context:
10304
11431
  }
10305
11432
  }
10306
11433
  else if (deltaItems < 0) {
10307
- for (let i = tool.nestedComponents.length; i >= Math.abs(deltaItems); i--) {
10308
- let trTool = tool.nestedComponents[i - 1];
10309
- // trTool.nestedComponents = [];
10310
- }
10311
11434
  tool.nestedComponents.splice(deltaItems, Math.abs(deltaItems));
10312
11435
  }
10313
- for (let i = 0; i < tool.nestedComponents.length; i++) {
10314
- tool.nestedComponents[i].inputs['size'] = 100 / tool.nestedComponents.length;
11436
+ for (const nestedComponent of tool.nestedComponents) {
11437
+ nestedComponent.inputs['size'] = 100 / tool.nestedComponents.length;
10315
11438
  }
10316
11439
  this.flattenedDashboardElements = this.flattenComponentTree(this.dashboardElements);
10317
11440
  obj[prop] = newValue;
@@ -10325,7 +11448,8 @@ Display formula context:
10325
11448
  if (currentTool.name == 'TABLE') {
10326
11449
  let tr = this.availableTools.find(x => x.name == 'TR');
10327
11450
  let td = this.availableTools.find(x => x.name == 'TD');
10328
- let table = this.availableTools.find(x => x.name == 'TABLE');
11451
+ let table;
11452
+ this.availableTools.find(x => x.name == 'TABLE');
10329
11453
  for (let i = 1; i <= currentTool.inputs['rows']; i++) {
10330
11454
  let tr1 = JSON.parse(JSON.stringify(tr));
10331
11455
  tr1.inputs.width = undefined;
@@ -10473,16 +11597,14 @@ Display formula context:
10473
11597
  }
10474
11598
  });
10475
11599
  }
11600
+ else if (currentTool.inputProps[prop].type == 'dropped-component') {
11601
+ currentTool.inputs[prop] = { component: new BehaviorSubject(currentTool.inputs[prop]), id: currentTool.componentId, name: currentTool.name, uniqueName: currentTool.uniqueName };
11602
+ }
11603
+ else if (currentTool.inputProps[prop].type == 'function') {
11604
+ currentTool.inputs[prop] = this.draggedPayload.inputs[prop].bind(currentTool.inputs);
11605
+ }
10476
11606
  else {
10477
- if (currentTool.inputProps[prop].type == 'dropped-component') {
10478
- currentTool.inputs[prop] = { component: new BehaviorSubject(currentTool.inputs[prop]), id: currentTool.componentId, name: currentTool.name, uniqueName: currentTool.uniqueName };
10479
- }
10480
- else if (currentTool.inputProps[prop].type == 'function') {
10481
- currentTool.inputs[prop] = this.draggedPayload.inputs[prop].bind(currentTool.inputs);
10482
- }
10483
- else {
10484
- currentTool.inputs[prop] = currentTool.inputs[prop];
10485
- }
11607
+ currentTool.inputs[prop] = currentTool.inputs[prop];
10486
11608
  }
10487
11609
  if (this.draggedPayload.inputProps[prop].conditional) {
10488
11610
  currentTool.inputProps[prop].conditional = this.draggedPayload.inputProps[prop].conditional;
@@ -10512,6 +11634,7 @@ Display formula context:
10512
11634
  });
10513
11635
  }
10514
11636
  let destination = this.dashboardElements;
11637
+ let parentContainerTool = null;
10515
11638
  if (event.toElement) {
10516
11639
  let id = event.toElement.getAttribute("id");
10517
11640
  let parentEl = event.toElement.parentElement;
@@ -10519,14 +11642,14 @@ Display formula context:
10519
11642
  id = parentEl.getAttribute("id");
10520
11643
  parentEl = parentEl.parentElement;
10521
11644
  }
10522
- if (id && id.indexOf("__") > -1) {
10523
- let componentId = parseInt(id.split("__")[1]);
11645
+ if (id?.includes("__")) {
11646
+ let componentId = Number.parseInt(id.split("__")[1]);
10524
11647
  let match = null;
10525
- // let parent = this.dashboardElements;
10526
11648
  match = (this.flattenedDashboardElements || []).find(x => x.componentId == componentId);
10527
11649
  if (match) {
10528
11650
  if (!match.allowedChildren || match.allowedChildren.includes(currentTool.name)) {
10529
11651
  destination = match?.nestedComponents;
11652
+ parentContainerTool = match;
10530
11653
  }
10531
11654
  else {
10532
11655
  WtoolboxService.messageNotificationService.add({
@@ -10552,11 +11675,68 @@ Display formula context:
10552
11675
  currentTool.onDrop(currentTool);
10553
11676
  }
10554
11677
  this.tool = currentTool;
11678
+ // Flusso assoluto (feature, default OFF): l'elemento droppato nasce
11679
+ // position:absolute con left/top dalla posizione del drop -> fuori dal flusso,
11680
+ // quindi ridimensionarne uno non sposta gli altri.
11681
+ if (this.absoluteFlowEnabled) {
11682
+ // Riferimento di posizionamento: il container se droppo dentro uno, altrimenti il canvas.
11683
+ let refEl = this.canvasRef?.nativeElement || null;
11684
+ if (parentContainerTool) {
11685
+ const containerDom = document.getElementById(parentContainerTool.uniqueName);
11686
+ if (containerDom) {
11687
+ refEl = containerDom;
11688
+ // Il container diventa il contesto di posizionamento del figlio absolute
11689
+ // (se non e' gia' posizionato): cosi' left/top del figlio sono relativi a lui.
11690
+ const curPos = String(parentContainerTool.inputs?.['position'] || '').trim();
11691
+ if (!curPos || curPos === 'static') {
11692
+ parentContainerTool.inputs['position'] = 'relative';
11693
+ }
11694
+ }
11695
+ }
11696
+ const pos = this.getDropPositionRelativeTo(event, refEl);
11697
+ currentTool.inputs['position'] = 'absolute';
11698
+ currentTool.inputs['left'] = pos.left + 'px';
11699
+ currentTool.inputs['top'] = pos.top + 'px';
11700
+ }
10555
11701
  this.flattenedDashboardElements = this.flattenComponentTree(this.dashboardElements);
11702
+ // Snap-on-drop SOLO in flusso normale: in modalita' assoluta la posizione e'
11703
+ // gia' settata (left/top) e snappata sopra.
11704
+ if (!this.absoluteFlowEnabled) {
11705
+ this.snapDroppedElementToGrid(currentTool);
11706
+ }
10556
11707
  this.commitHistoryIfChanged();
10557
11708
  this.draggedPayload = null;
10558
11709
  }
10559
11710
  }
11711
+ /**
11712
+ * Snap-on-drop: se lo snap alla griglia e' attivo, allinea la posizione
11713
+ * dell'elemento appena droppato al multiplo di griglia scrivendo un
11714
+ * `inputs.transform` di traslazione (stessa proprieta' usata da `onMoving`,
11715
+ * quindi persiste su save/reload). Deferred perche' l'elemento deve essere
11716
+ * nel DOM per leggere `offsetLeft/offsetTop` (posizione di flusso, senza
11717
+ * transform). No-op se snap off o gia' allineato.
11718
+ */
11719
+ snapDroppedElementToGrid(tool) {
11720
+ const g = DesignerSnapState.gridSize;
11721
+ if (!g || g <= 0 || !tool?.uniqueName) {
11722
+ return;
11723
+ }
11724
+ setTimeout(() => {
11725
+ const el = document.getElementById(tool.uniqueName);
11726
+ if (!el) {
11727
+ return;
11728
+ }
11729
+ const dx = Math.round(el.offsetLeft / g) * g - el.offsetLeft;
11730
+ const dy = Math.round(el.offsetTop / g) * g - el.offsetTop;
11731
+ if (dx === 0 && dy === 0) {
11732
+ return;
11733
+ }
11734
+ tool.inputs = tool.inputs || {};
11735
+ tool.inputs['transform'] = `matrix(1, 0, 0, 1, ${dx}, ${dy})`;
11736
+ this.cd.detectChanges();
11737
+ this.commitHistoryIfChanged();
11738
+ }, 0);
11739
+ }
10560
11740
  /**
10561
11741
  * Genera un nuovo identificativo incrementale per `componentId`.
10562
11742
  */
@@ -10600,12 +11780,15 @@ Display formula context:
10600
11780
  const savedState = this.propertySectionStateByTool[toolKey] || {};
10601
11781
  return sectionOrder
10602
11782
  .filter((key) => grouped[key].length > 0)
10603
- .map((key) => ({
10604
- key: key,
10605
- label: labels[key],
10606
- open: (propFilter && propFilter.length >= 3 && grouped[key].length) ? true : (savedState[key] !== undefined ? !!savedState[key] : openByDefault[key]),
10607
- props: grouped[key]
10608
- }));
11783
+ .map((key) => {
11784
+ const savedOpen = savedState[key] !== undefined ? !!savedState[key] : openByDefault[key];
11785
+ return {
11786
+ key: key,
11787
+ label: labels[key],
11788
+ open: (propFilter && propFilter.length >= 3 && grouped[key].length) ? true : savedOpen,
11789
+ props: grouped[key]
11790
+ };
11791
+ });
10609
11792
  }
10610
11793
  clearPropFilter() {
10611
11794
  this.propFilter = "";
@@ -10648,7 +11831,7 @@ Display formula context:
10648
11831
  return this.getRepeaterAction(tool) !== 'wizard';
10649
11832
  }
10650
11833
  if (propFilter && propFilter.length >= 3) {
10651
- return toolProp.key.toLocaleLowerCase().indexOf(propFilter.toLocaleLowerCase()) < 0;
11834
+ return !toolProp.key.toLocaleLowerCase().includes(propFilter.toLocaleLowerCase());
10652
11835
  }
10653
11836
  return false;
10654
11837
  }
@@ -10764,7 +11947,7 @@ Display formula context:
10764
11947
  return value;
10765
11948
  }
10766
11949
  mergeToolPropDefinition(baseDef, currentDef) {
10767
- const merged = { ...(baseDef || {}), ...(currentDef || {}) };
11950
+ const merged = { ...(baseDef), ...(currentDef) };
10768
11951
  if (!merged.converter && baseDef?.converter) {
10769
11952
  merged.converter = baseDef.converter;
10770
11953
  }
@@ -10790,7 +11973,7 @@ Display formula context:
10790
11973
  if (template?.inputProps) {
10791
11974
  Object.keys(template.inputProps).forEach((key) => {
10792
11975
  element.inputProps[key] = this.mergeToolPropDefinition(template.inputProps[key], element.inputProps[key]);
10793
- if (element.inputs[key] === undefined && template.inputs && template.inputs[key] !== undefined) {
11976
+ if (element.inputs[key] === undefined && template.inputs?.[key] !== undefined) {
10794
11977
  element.inputs[key] = this.cloneInputDefaultValue(template.inputs[key]);
10795
11978
  }
10796
11979
  });
@@ -10845,7 +12028,80 @@ Display formula context:
10845
12028
  * - `elements` (JSON) -> `boardcontent` (TEXT)
10846
12029
  * e aggiorna anche i fogli collegati in `dom_board_sheet`.
10847
12030
  */
12031
+ /**
12032
+ * Lint pre-save del boardcontent: individua problemi di configurazione che
12033
+ * altrimenti emergerebbero solo a runtime. Metodo puro (nessun side-effect),
12034
+ * riusabile e testabile. Rileva:
12035
+ * - uniqueName duplicati (rompono selezione/rimozione/hierarchy tree, key=uniqueName);
12036
+ * - binding datasource orfani (DATAREPEATER/SELECT/MULTISELECT/UL che puntano a
12037
+ * un DATASOURCE.uniqueName inesistente nel layout);
12038
+ * - DATASOURCE senza route impostata.
12039
+ */
12040
+ computeDashboardLintIssues() {
12041
+ const issues = [];
12042
+ const flat = this.flattenComponentTree(this.dashboardElements) || [];
12043
+ // 1. uniqueName duplicati
12044
+ const seen = new Set();
12045
+ const dup = new Set();
12046
+ for (const el of flat) {
12047
+ const un = String(el?.uniqueName || '').trim();
12048
+ if (!un) {
12049
+ continue;
12050
+ }
12051
+ if (seen.has(un)) {
12052
+ dup.add(un);
12053
+ }
12054
+ seen.add(un);
12055
+ }
12056
+ for (const un of dup) {
12057
+ issues.push(this.trslSrv.format(this.t('designer.lint.duplicate_unique_name', "Nome elemento duplicato: '{0}'"), un));
12058
+ }
12059
+ // 2. uniqueName dei DATASOURCE disponibili
12060
+ const dsNames = new Set();
12061
+ for (const el of flat) {
12062
+ if (el?.name === 'DATASOURCE') {
12063
+ const un = String(el?.uniqueName || '').trim();
12064
+ if (un) {
12065
+ dsNames.add(un);
12066
+ }
12067
+ }
12068
+ }
12069
+ // 3. binding orfani + DATASOURCE senza route
12070
+ for (const el of flat) {
12071
+ const name = String(el?.name || '');
12072
+ if (name === 'DATAREPEATER' || name === 'SELECT' || name === 'MULTISELECT' || name === 'UL') {
12073
+ const ds = el?.inputs?.['datasource'];
12074
+ const dsUn = ds && typeof ds === 'object' ? String(ds.uniqueName || '').trim() : '';
12075
+ if (dsUn && !dsNames.has(dsUn)) {
12076
+ issues.push(this.trslSrv.format(this.t('designer.lint.orphan_datasource_binding', "Il componente '{0}' punta a un datasource inesistente ('{1}')"), String(el?.uniqueName || el?.name || ''), dsUn));
12077
+ }
12078
+ }
12079
+ if (name === 'DATASOURCE') {
12080
+ const routeVal = el?.inputs?.['route'];
12081
+ const routeStr = routeVal instanceof BehaviorSubject ? routeVal.value : routeVal;
12082
+ if (!String(routeStr || '').trim()) {
12083
+ issues.push(this.trslSrv.format(this.t('designer.lint.datasource_without_route', "Il datasource '{0}' non ha una route impostata"), String(el?.uniqueName || 'DATASOURCE')));
12084
+ }
12085
+ }
12086
+ }
12087
+ return issues;
12088
+ }
10848
12089
  async saveDashboard() {
12090
+ // Lint pre-save non bloccante: se ci sono problemi, elencali e chiedi conferma.
12091
+ const lintIssues = this.computeDashboardLintIssues();
12092
+ if (lintIssues.length > 0) {
12093
+ const proceed = await WtoolboxService.confirm({
12094
+ header: this.t('designer.lint.title', 'Problemi nel layout'),
12095
+ message: this.t('designer.lint.intro', 'Il layout presenta alcuni problemi:')
12096
+ + ' ' + lintIssues.map((i) => '• ' + i).join(' ')
12097
+ + ' — ' + this.t('designer.lint.confirm_save_anyway', 'Salvare comunque?'),
12098
+ acceptLabel: this.t('confirm', 'Confirm'),
12099
+ rejectLabel: this.t('cancel', 'Cancel')
12100
+ });
12101
+ if (!proceed) {
12102
+ return;
12103
+ }
12104
+ }
10849
12105
  const route = String(this.currentDashboardRoute || '').trim();
10850
12106
  if (route) {
10851
12107
  const payload = {
@@ -10931,14 +12187,18 @@ Display formula context:
10931
12187
  * e ricostruisce lo stato runtime del designer partendo da `dom_board.boardcontent`.
10932
12188
  * Supporta route in forme alias (`board_route`, `boardroute`, `dashRoute`).
10933
12189
  */
10934
- async loadDashboard(dashboard, skipServerFetch = false) {
12190
+ async loadDashboard(dashboard, skipServerFetch = false, insertTarget = null) {
10935
12191
  const dashRoute = String(dashboard?.board_route ??
10936
12192
  dashboard?.boardroute ??
10937
12193
  dashboard?.boardRoute ??
10938
12194
  dashboard?.dashRoute ??
10939
12195
  dashboard ??
10940
12196
  '').trim();
10941
- if (!dashRoute) {
12197
+ // Import/preset (feature C/F) passano boardcontent con route eventualmente
12198
+ // vuota (board non ancora salvata). NON fare reset in quel caso: il reset va
12199
+ // fatto solo quando NON c'e' ne' route ne' contenuto (nuovo grafo / clear).
12200
+ const providedBoardcontent = dashboard?.boardcontent ?? dashboard?.boardContent ?? null;
12201
+ if (!dashRoute && !providedBoardcontent) {
10942
12202
  this.dashboardElements = [];
10943
12203
  this.flattenedDashboardElements = [];
10944
12204
  this.currentDashboardRoute = '';
@@ -10952,8 +12212,10 @@ Display formula context:
10952
12212
  this.cd.detectChanges();
10953
12213
  return;
10954
12214
  }
10955
- this.currentDashboardRoute = dashRoute;
10956
- this.refreshGraphActionsMenuItems();
12215
+ if (!insertTarget) {
12216
+ this.currentDashboardRoute = dashRoute;
12217
+ this.refreshGraphActionsMenuItems();
12218
+ }
10957
12219
  let resultRows = [];
10958
12220
  let result = null;
10959
12221
  if (!skipServerFetch) {
@@ -10962,18 +12224,20 @@ Display formula context:
10962
12224
  dashRoute: dashRoute
10963
12225
  };
10964
12226
  result = await this.dataSrv.loadDashboard(payload);
10965
- resultRows = Array.isArray(result)
10966
- ? result
10967
- : (Array.isArray(result?.results) ? result.results : (result ? [result] : []));
10968
- }
10969
- this.currentDashboardDescription = String(dashboard?.board_des ??
10970
- dashboard?.boarddes ??
10971
- resultRows?.[0]?.board_des ??
10972
- resultRows?.[0]?.boarddes ??
10973
- this.currentDashboardDescription ??
10974
- dashRoute).trim();
10975
- const boardSheetPaths = this.extractDashboardSheetPaths(resultRows, dashboard);
10976
- await this.loadDashboardCssSheets(boardSheetPaths);
12227
+ const singleRows = result ? [result] : [];
12228
+ const wrappedRows = Array.isArray((result)?.results) ? (result).results : singleRows;
12229
+ resultRows = Array.isArray(result) ? result : wrappedRows;
12230
+ }
12231
+ if (!insertTarget) {
12232
+ this.currentDashboardDescription = String(dashboard?.board_des ??
12233
+ dashboard?.boarddes ??
12234
+ resultRows?.[0]?.board_des ??
12235
+ resultRows?.[0]?.boarddes ??
12236
+ this.currentDashboardDescription ??
12237
+ dashRoute).trim();
12238
+ const boardSheetPaths = this.extractDashboardSheetPaths(resultRows, dashboard);
12239
+ await this.loadDashboardCssSheets(boardSheetPaths);
12240
+ }
10977
12241
  const boardContentRaw = resultRows?.[0]?.boardcontent ?? dashboard?.boardcontent ?? dashboard?.boardContent ?? null;
10978
12242
  if (boardContentRaw) {
10979
12243
  let loadedDashboard = [];
@@ -10984,14 +12248,29 @@ Display formula context:
10984
12248
  loadedDashboard = [];
10985
12249
  }
10986
12250
  if (!Array.isArray(loadedDashboard)) {
10987
- loadedDashboard = Array.isArray(loadedDashboard?.dashboardElements)
10988
- ? loadedDashboard.dashboardElements
10989
- : Array.isArray(loadedDashboard?.elements)
10990
- ? loadedDashboard.elements
10991
- : [];
12251
+ // Forma wrapper-object: puo' trasportare flag board-level come
12252
+ // `widgetChrome` (chrome card dei widget, vedi <wuic-dashboard>).
12253
+ if (!insertTarget) {
12254
+ const chrome = loadedDashboard?.widgetChrome;
12255
+ this.dashboardWidgetChrome = chrome === true || String(chrome ?? '').trim().toLowerCase() === 'true' || String(chrome ?? '').trim() === '1';
12256
+ }
12257
+ if (Array.isArray(loadedDashboard?.dashboardElements)) {
12258
+ loadedDashboard = loadedDashboard.dashboardElements;
12259
+ }
12260
+ else if (Array.isArray(loadedDashboard?.elements)) {
12261
+ loadedDashboard = loadedDashboard.elements;
12262
+ }
12263
+ else {
12264
+ loadedDashboard = [];
12265
+ }
12266
+ }
12267
+ else if (!insertTarget) {
12268
+ this.dashboardWidgetChrome = false;
10992
12269
  }
10993
12270
  this.mergeMissingToolPropsRecursive(loadedDashboard);
10994
- this.dashboardComponents = [];
12271
+ if (!insertTarget) {
12272
+ this.dashboardComponents = [];
12273
+ }
10995
12274
  let dsSubs = [];
10996
12275
  let dsElements = [];
10997
12276
  let dsElementCount = (loadedDashboard || []).filter(x => x?.name == 'DATASOURCE').length;
@@ -11044,18 +12323,16 @@ Display formula context:
11044
12323
  uniqueName: element.uniqueName
11045
12324
  });
11046
12325
  }
12326
+ else if (element.inputProps[prop].asyncPath) {
12327
+ const path = element.inputProps[prop].asyncPath;
12328
+ const rawValue = element.inputs[prop] ?? {};
12329
+ element.inputs[prop] = new BehaviorSubject({
12330
+ ...rawValue,
12331
+ [path]: new BehaviorSubject(rawValue?.[path] ?? null)
12332
+ });
12333
+ }
11047
12334
  else {
11048
- if (element.inputProps[prop].asyncPath) {
11049
- const path = element.inputProps[prop].asyncPath;
11050
- const rawValue = element.inputs[prop] ?? {};
11051
- element.inputs[prop] = new BehaviorSubject({
11052
- ...rawValue,
11053
- [path]: new BehaviorSubject(rawValue?.[path] ?? null)
11054
- });
11055
- }
11056
- else {
11057
- element.inputs[prop] = new BehaviorSubject(element.inputs[prop]);
11058
- }
12335
+ element.inputs[prop] = new BehaviorSubject(element.inputs[prop]);
11059
12336
  }
11060
12337
  element.inputs[prop].subscribe((v) => {
11061
12338
  if (v?.component) {
@@ -11089,7 +12366,7 @@ Display formula context:
11089
12366
  }
11090
12367
  }
11091
12368
  }
11092
- if (!dsElements.find(x => x.uniqueName == v.uniqueName)) {
12369
+ if (!dsElements.some(x => x.uniqueName == v.uniqueName)) {
11093
12370
  dsSubs.push(v.component.fetchInfo$);
11094
12371
  dsElements.push(dsElement);
11095
12372
  if (dsElements.length == dsElementCount) {
@@ -11133,18 +12410,34 @@ Display formula context:
11133
12410
  });
11134
12411
  // Ensure a new reference for template control-flow and force refresh when
11135
12412
  // async loading happens outside Angular zone.
11136
- this.dashboardElements = Array.isArray(loadedDashboard) ? [...loadedDashboard] : [];
11137
- this.flattenedDashboardElements = flattened;
12413
+ if (insertTarget) {
12414
+ // Import IN-elemento: inserisci gli elementi importati nei nestedComponents
12415
+ // del target invece di sostituire l'intero canvas.
12416
+ insertTarget.nestedComponents = Array.isArray(insertTarget.nestedComponents) ? insertTarget.nestedComponents : [];
12417
+ insertTarget.nestedComponents.push(...(Array.isArray(loadedDashboard) ? loadedDashboard : []));
12418
+ this.dashboardElements = Array.isArray(this.dashboardElements) ? [...this.dashboardElements] : [];
12419
+ this.flattenedDashboardElements = this.flattenComponentTree(this.dashboardElements);
12420
+ }
12421
+ else {
12422
+ this.dashboardElements = Array.isArray(loadedDashboard) ? [...loadedDashboard] : [];
12423
+ this.flattenedDashboardElements = flattened;
12424
+ }
11138
12425
  this.cd.detectChanges();
11139
12426
  }
11140
- else {
12427
+ else if (!insertTarget) {
11141
12428
  this.dashboardElements = [];
11142
12429
  this.flattenedDashboardElements = [];
11143
12430
  this.currentDashboardRoute = '';
11144
12431
  this.cd.detectChanges();
11145
12432
  console.warn('[Designer] No dashboard content found for route:', dashRoute, result);
11146
12433
  }
11147
- this.resetHistory();
12434
+ if (insertTarget) {
12435
+ // Import in-elemento: NON resettare la history (stiamo aggiungendo), committa.
12436
+ this.commitHistoryIfChanged();
12437
+ }
12438
+ else {
12439
+ this.resetHistory();
12440
+ }
11148
12441
  this.refreshGraphActionsMenuItems();
11149
12442
  this.maxId = (this.flattenedDashboardElements || []).reduce((max, item) => Math.max(max, Number(item?.componentId || 0)), 0);
11150
12443
  }
@@ -11258,11 +12551,15 @@ Display formula context:
11258
12551
  loadedDashboard = [];
11259
12552
  }
11260
12553
  if (!Array.isArray(loadedDashboard)) {
11261
- loadedDashboard = Array.isArray(loadedDashboard?.dashboardElements)
11262
- ? loadedDashboard.dashboardElements
11263
- : Array.isArray(loadedDashboard?.elements)
11264
- ? loadedDashboard.elements
11265
- : [];
12554
+ if (Array.isArray(loadedDashboard?.dashboardElements)) {
12555
+ loadedDashboard = loadedDashboard.dashboardElements;
12556
+ }
12557
+ else if (Array.isArray(loadedDashboard?.elements)) {
12558
+ loadedDashboard = loadedDashboard.elements;
12559
+ }
12560
+ else {
12561
+ loadedDashboard = [];
12562
+ }
11266
12563
  }
11267
12564
  this.mergeMissingToolPropsRecursive(loadedDashboard);
11268
12565
  this.dashboardComponents = [];
@@ -11317,7 +12614,14 @@ Display formula context:
11317
12614
  if (!propDef.async && propDef.asyncPath && (propDef.type === 'dropped-component-list' || propDef.type === 'dropped-component')) {
11318
12615
  const path = propDef.asyncPath;
11319
12616
  const rawValue = element.inputs[prop];
11320
- console.log(`[HYDRATE-v9] ${element.name}.${prop}: rawValue=${rawValue === null ? 'NULL' : rawValue === undefined ? 'UNDEF' : 'object'} uniqueName=${rawValue?.uniqueName} keys=${rawValue ? Object.keys(rawValue) : '[]'}`);
12617
+ let rawValueKind = 'object';
12618
+ if (rawValue === null) {
12619
+ rawValueKind = 'NULL';
12620
+ }
12621
+ else if (rawValue === undefined) {
12622
+ rawValueKind = 'UNDEF';
12623
+ }
12624
+ console.log(`[HYDRATE-v9] ${element.name}.${prop}: rawValue=${rawValueKind} uniqueName=${rawValue?.uniqueName} keys=${rawValue ? Object.keys(rawValue) : '[]'}`);
11321
12625
  if (rawValue && typeof rawValue === 'object' && !(rawValue instanceof BehaviorSubject)) {
11322
12626
  // Only hydrate the component BS — do NOT invent a uniqueName if the
11323
12627
  // snapshot didn't have one. That would create a false binding on undo.
@@ -11380,7 +12684,7 @@ Display formula context:
11380
12684
  }
11381
12685
  }
11382
12686
  }
11383
- if (!dsElements.find(de => de.uniqueName == v.uniqueName)) {
12687
+ if (!dsElements.some(de => de.uniqueName == v.uniqueName)) {
11384
12688
  dsSubs.push(v.component.fetchInfo$);
11385
12689
  dsElements.push(dsElement);
11386
12690
  if (dsElements.length == dsElementCount) {
@@ -11881,10 +13185,10 @@ Display formula context:
11881
13185
  */
11882
13186
  openCurrentDashboardInNewTab() {
11883
13187
  const route = String(this.currentDashboardRoute || '').trim();
11884
- if (!route || typeof window === 'undefined') {
13188
+ if (!route || globalThis.window === undefined) {
11885
13189
  return;
11886
13190
  }
11887
- const base = window.location.href.split('#')[0];
13191
+ const base = globalThis.location.href.split('#')[0];
11888
13192
  const url = `${base}#/${encodeURIComponent(route)}/dashboard`;
11889
13193
  window.open(url, '_blank');
11890
13194
  }
@@ -11904,11 +13208,11 @@ Display formula context:
11904
13208
  // return customProps;
11905
13209
  }
11906
13210
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DesignerComponent, deps: [{ token: i1$1.ActivatedRoute }, { token: DataProviderService }, { token: MetadataProviderService }, { token: UserInfoService }, { token: i0.ChangeDetectorRef }, { token: TranslationManagerService }, { token: MetadataEditorService }, { token: ChatbotHostRegistryService }, { token: WorkflowRuntimeMetadataService }], target: i0.ɵɵFactoryTarget.Component });
11907
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DesignerComponent, isStandalone: true, selector: "wuic-designer", host: { listeners: { "window:hashchange": "onWindowHashChange()", "window:beforeunload": "onWindowBeforeUnload($event)", "window:keydown": "onWindowKeydown($event)", "window:mouseup": "onWindowMouseup()", "window:touchend": "onWindowTouchend()", "window:keyup": "onWindowKeyup()", "window:change": "onWindowChange()" } }, providers: [TreeDragDropService], ngImport: i0, template: "<!-- <button pButton type=\"button\" (click)=\"loadDashboard()\" class=\"mr-2\">{{'load' | translate}}</button> -->\n@if (!noEdit) {\n<div class=\"designer-layout\">\n <div class=\"designer-toolbar\">\n <div class=\"designer-current-dashboard\">{{ currentDashboardTitle }}</div>\n\n <div class=\"designer-toolbar-spacer\"></div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"undo()\" [disabled]=\"!canUndo\">{{ 'designer.undo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"redo()\" [disabled]=\"!canRedo\">{{ 'designer.redo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"refreshGraphActionsMenuItems(); graphActionsMenu.toggle($event)\">{{ 'designer.actions_menu' | translate }}</button>\n <p-menu #graphActionsMenu [popup]=\"true\" [model]=\"graphActionsMenuItems\" appendTo=\"body\"></p-menu>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"openCurrentDashboardInNewTab()\" [disabled]=\"!currentDashboardRoute\">{{\n 'designer.open_current_dashboard' | translate }}</button>\n </div>\n </div>\n <div class=\"designer-main\">\n <p-splitter [style]=\"{ height: '100%' }\" [panelSizes]=\"[15, 85]\" styleClass=\"designer-main-splitter\">\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[35, 65]\"\n [minSizes]=\"[20, 30]\" styleClass=\"designer-left-splitter\">\n <ng-template pTemplate>\n <div class=\"col flex designer-toolbox palette-panel\">\n <ul class=\"tool-groups\">\n @for (toolGroup of availableToolGroups; track toolGroup.group) {\n <li class=\"tool-group-item\">\n <div class=\"tool-group-title\">{{ toolGroup.group }}</div>\n <ul class=\"tool-group-list\">\n @for (tool of toolGroup.tools; track tool) {\n <li pDraggable (onDragStart)=\"dragStart(tool)\" (onDragEnd)=\"dragEnd()\" (onDrag)=\"onDrag($event)\"\n (dblclick)=\"dropToolAtRoot(tool)\">\n <i [ngClass]=\"tool.icon\" [title]=\"tool.name\"></i>\n </li>\n }\n </ul>\n </li>\n }\n </ul>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[68, 32]\"\n [minSizes]=\"[35, 20]\" styleClass=\"designer-left-bottom-splitter\">\n <ng-template pTemplate>\n <div class=\"properties-panel\">\n <p-select [style]=\"{width: '100%'}\" [options]=\"flattenedDashboardElements\" [(ngModel)]=\"tool\"\n optionLabel=\"uniqueName\" dataKey=\"componentId\" appendTo=\"body\" />\n <div class=\"designer-toolbox\">\n\n <span style=\"width: 100%;\">\n <input type=\"text\" style=\"width: calc(100% - 30px);\" [(ngModel)]=\"propFilter\"\n [placeholder]=\"'prop.filter' | translate\" />\n <button style=\"width: 25px\" (click)=\"clearPropFilter()\">X</button>\n </span>\n\n @for (section of getToolPropertySections(tool, propFilter); track section.key) {\n <details class=\"prop-section\" [open]=\"section.open\"\n (toggle)=\"onPropertySectionToggle(tool, section.key, $event)\">\n <summary class=\"prop-section-title\">{{ section.label }}</summary>\n <ul class=\"props\">\n @for (toolProp of section.props; track toolProp.key) {\n <li>\n @if (!toolProp.value.hideCaption) {\n <span>{{ toolProp.value?.propertyCaption ||\n toolProp.key[0].toUpperCase() + toolProp.key.slice(1)\n }}</span>\n }\n <br />\n @if (toolProp.value.type == 'button') {\n <p-button (click)=\"toolProp.value.callback(tool.inputs, toolProp.key, null, tool)\"\n class=\"k-button\" [style]=\"{width: '100%'}\">\n {{toolProp.value.propertyCaption}}\n </p-button>\n }\n @if (toolProp.value.type == 'metaEditor' &&\n tool.inputs['componentRef']?.value?.component) {\n <wuic-metadata-editor [hardcodedDatasource]=\"tool.inputs['componentRef']?.value?.component\"\n [saveCallback]=\"metaEditorSave\"></wuic-metadata-editor>\n }\n @if (toolProp.value.type == 'boolean') {\n <input type=\"checkbox\" [(ngModel)]=\"tool.inputs[toolProp.key]\" />\n }\n @if (toolProp.value.type == 'numberToArray') {\n <input style=\"width: 100%;\" type=\"number\" pInputText [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'txt_area') {\n <textarea style=\"width: 100%;\" [(ngModel)]=\"tool.inputs[toolProp.key]\"\n [ngModelOptions]=\"{ updateOn: 'blur' }\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\"></textarea>\n }\n @if (toolProp.value.type == 'string') {\n <input style=\"width: 100%;\" type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'color') {\n <p-colorPicker [ngModel]=\"getDesignerColorPickerHex(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorPickerHexChange($event, tool, toolProp)\" appendTo=\"body\" />\n <div class=\"designer-color-alpha-row\">\n <span class=\"designer-color-alpha-label\">{{ 'designer.alpha' | translate }}</span>\n <input type=\"range\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n <input type=\"number\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n </div>\n <input type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'dictionary' && !toolProp.value.async) {\n @if (toolProp.key == 'cssFile') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"availableProjectCssFiles\"\n [ngModel]=\"tool.inputs[toolProp.key]\" (ngModelChange)=\"onCssFileChanged($event, tool)\" />\n } @else if (toolProp.key == 'cssClass') {\n <p-multiSelect [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getCssClassOptionsForTool(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"onCssClassChanged($event, tool)\" [filter]=\"true\" [virtualScroll]=\"true\"\n [virtualScrollItemSize]=\"38\" scrollHeight=\"260px\" [showToggleAll]=\"false\" display=\"chip\"\n [maxSelectedLabels]=\"3\" [selectedItemsLabel]=\"'{0} selected'\">\n </p-multiSelect>\n } @else if ((tool.name == 'SELECT' || tool.name == 'MULTISELECT' || tool.name == 'UL') &&\n (toolProp.key == 'valueField' || toolProp.key == 'textField')) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else if (tool.name == 'LABEL' && toolProp.key == 'displayField') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n }\n @if (toolProp.value.type == 'dictionary' && toolProp.value.async) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (tool.name == 'DATAREPEATER' && toolProp.key == 'action' &&\n canOpenArchetypeConfigurator(tool)) {\n <button style=\"margin-top: 8px;\" (click)=\"openArchetypeConfigurator(tool)\">\n {{ 'designer.configure' | translate }} {{ (tool.inputs['action'] | async) || '' }}\n </button>\n }\n @if (toolProp.value.type == 'dropped-component-list') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [placeholder]=\"'select.component' | translate\" [options]=\"elementFilter(toolProp)\"\n [ngModel]=\"getDroppedComponentListModel(tool, toolProp)\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" optionLabel=\"uniqueName\"\n dataKey=\"uniqueName\" />\n }\n @if (toolProp.value.type == 'autocomplete') {\n <p-autoComplete [style]=\"{width: '100%'}\" appendTo=\"body\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\" [forceSelection]=\"true\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" [suggestions]=\"tool.suggestions\"\n [optionLabel]=\"toolProp.value.displayField\" [optionValue]=\"toolProp.value.valueField\"\n (completeMethod)=\"search($event, tool, toolProp)\" [dropdown]=\"true\" dropdownMode=\"current\"\n [multiple]=\"toolProp.value.multiple\" />\n }\n @if (toolProp.value.type == 'propertyTree' && shouldShowPropertyTree(tool)) {\n <ul>\n @for (item of $any(tool.inputs[toolProp.key] | async); track item) {\n <li style=\"padding: 0; margin: 0;\">\n @if (item.col && tool.inputs['customProps_' + (tool.inputs['action'] | async)])\n {\n <wuic-field-editor [field]=\"item.col\"\n [record]=\"tool.inputs['customProps_' + (tool.inputs['action'] | async)]\"\n [metaInfo]=\"tool.inputs['datasource']?.component?.value?.metaInfo\"\n [triggerProp]=\"tool.inputs['action']\"></wuic-field-editor>\n }\n </li>\n }\n </ul>\n }\n </li>\n }\n </ul>\n </details>\n }\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"hierarchy-panel\">\n <div class=\"hierarchy-panel-title\">{{ 'designer.canvas_hierarchy' | translate }}</div>\n <div class=\"hierarchy-tree-wrap\">\n <p-tree [value]=\"hierarchyTreeNodes\" [draggableNodes]=\"true\" [droppableNodes]=\"true\"\n selectionMode=\"single\" [(selection)]=\"hierarchySelection\"\n (onNodeSelect)=\"onHierarchyNodeSelect($event)\" draggableScope=\"designer-hierarchy\"\n droppableScope=\"designer-hierarchy\" (onNodeDrop)=\"onHierarchyNodeDrop($event)\">\n <ng-template let-node pTemplate=\"default\">\n <span class=\"hierarchy-node-label\" [class.active]=\"isHierarchyNodeSelected(node)\"\n (mouseenter)=\"onHierarchyNodeMouseEnter(node)\" (mouseleave)=\"onHierarchyNodeMouseLeave()\"\n (contextmenu)=\"showHierarchyNodeContextMenu($event, node, hierarchyCtxMenu)\">\n {{ node.label }}\n </span>\n </ng-template>\n </p-tree>\n </div>\n <p-contextMenu #hierarchyCtxMenu [model]=\"hierarchyCtxItems\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </ng-template>\n </p-splitter>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"col dashboard-design\" [class.designer-hide-datasources]=\"hideDatasourcesInCanvas\" pDroppable\n (onDrop)=\"drop($event)\" #canvas (contextmenu)=\"showCtx($event, this)\">\n <wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n <p-contextMenu [target]=\"canvas\" [model]=\"ctxItems | async\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </div>\n <div class=\"designer-footer\">\n <div class=\"designer-footer-label\">{{ 'designer.items' | translate }}:</div>\n <div class=\"designer-footer-list\">\n @for (item of droppedDashboardItems; track item.uniqueName) {\n <button type=\"button\" class=\"designer-footer-item\" [class.active]=\"tool?.uniqueName === item?.uniqueName\"\n [attr.data-designer-unique-name]=\"item?.uniqueName\" (mouseenter)=\"setFooterHoverHighlight(item)\"\n (mouseleave)=\"clearFooterHoverHighlight()\" (click)=\"selectDashboardItem(item)\"\n (contextmenu)=\"showFooterContextMenu($event, item, footerCtxMenu)\">\n {{ getDroppedDashboardItemLabel(item) }}\n </button>\n }\n </div>\n <p-contextMenu #footerCtxMenu [model]=\"footerCtxItems\" appendTo=\"body\" />\n </div>\n</div>\n}\n\n@if (noEdit) {\n<wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n}\n\n<wuic-archetype-configurator [visible]=\"showArchetypeConfigurator\"\n (visibleChange)=\"onArchetypeConfigVisibleChange($event)\" [archetype]=\"getActiveArchetypeConfig()\"\n [metaInfo]=\"getArchetypeConfigMetaInfoForTool(archetypeConfigTool)\"\n [value]=\"getArchetypeConfigValueForTool(archetypeConfigTool)\" (applyConfig)=\"onArchetypeConfigApply($event)\">\n</wuic-archetype-configurator>\n\n<wuic-css-sheet-editor [visible]=\"showCssSheetEditor\" (visibleChange)=\"onCssSheetEditorVisibleChange($event)\"\n [sheets]=\"dashboardCssSheets\" [selectedSheetPath]=\"selectedCssEditorSheetPath\" [assetsFolder]=\"currentDashboardRoute\"\n [styleProps]=\"getCssEditorStyleProps()\" (applyEditor)=\"onCssSheetEditorApply($event)\">\n</wuic-css-sheet-editor>\n\n<p-dialog [header]=\"'designer.reopen_dashboard' | translate\" [(visible)]=\"reopenDashboardDialogVisible\" [modal]=\"true\"\n [closable]=\"true\" [draggable]=\"false\" [resizable]=\"false\" [appendTo]=\"'body'\" [style]=\"{ width: '34rem' }\"\n (onHide)=\"cancelReopenDashboardDialog()\">\n <div class=\"route-popup-form\">\n <label for=\"designer-reopen-select\">{{ 'designer.existing_dashboards' | translate }}</label>\n <p-select inputId=\"designer-reopen-select\" [options]=\"savedDashboardOptions\" optionLabel=\"label\" optionValue=\"value\"\n [(ngModel)]=\"reopenDialogSelectedDashboardRoute\" [filter]=\"true\" [showClear]=\"true\" [style]=\"{ width: '100%' }\"\n appendTo=\"body\" />\n\n <div class=\"route-popup-actions\">\n <button type=\"button\" class=\"route-popup-btn\" (click)=\"cancelReopenDashboardDialog()\">{{ 'cancel' | translate\n }}</button>\n <button type=\"button\" class=\"route-popup-btn route-popup-btn-primary\" (click)=\"confirmReopenDashboardDialog()\"\n [disabled]=\"!reopenDialogSelectedDashboardRoute\">{{ 'designer.reopen' | translate }}</button>\n </div>\n </div>\n</p-dialog>", styles: [":host{display:block;width:100%;height:100%;min-width:0}.designer-layout{display:grid;grid-template-rows:auto minmax(0,1fr) auto;height:100%;min-height:0;width:100%;min-width:0}.designer-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:8px;border:1px solid inherit;border-radius:10px;background:transparent;padding:10px 12px;margin-bottom:8px}.designer-current-dashboard{min-width:220px;font-size:.86rem;font-weight:700;color:inherit}.designer-toolbar-spacer{flex:1 1 auto;min-width:16px}.designer-toolbar-menu-group{display:inline-flex}.designer-toolbar-menu-group button{height:32px;border:1px solid #2f67d8;border-radius:6px;background:#2f67d8;color:#fff;padding:0 10px;font-size:.82rem;cursor:pointer}.designer-toolbar-menu-group button[disabled]{opacity:.6;cursor:not-allowed}.designer-main{min-height:0;min-width:0;width:100%;overflow:hidden;direction:ltr}.designer-main-splitter{height:100%;width:100%;min-width:0;direction:ltr}.designer-toolbox{font-size:3rem}.designer-toolbox ul{list-style-type:none;padding:0;margin:0;width:100%}.designer-toolbox li{float:left;margin-left:5px;margin-top:5px}.designer-toolbox .tool-groups{display:block}.designer-toolbox .tool-group-item{float:none;margin-left:0;margin-top:10px;clear:both}.designer-toolbox .tool-group-title{font-size:.8rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;opacity:.7;margin:0 0 4px 6px}.designer-toolbox .tool-group-list{display:flex;flex-wrap:wrap;align-items:flex-start;gap:4px}.designer-toolbox .tool-group-list li{float:none;margin-left:0;margin-top:0}.designer-toolbox .props{font-size:1rem}.designer-toolbox .props li{float:none;padding-bottom:15px}.designer-toolbox .prop-section{width:100%;border:1px solid inherit;border-radius:6px;margin-top:8px;background:transparent;padding:6px 8px 2px}.designer-toolbox .prop-section-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;cursor:pointer;color:inherit;margin-bottom:6px}.palette-panel{height:100%;overflow:auto;width:100%}.designer-color-alpha-row{display:flex;align-items:center;gap:8px;margin-top:6px;margin-bottom:6px}.designer-color-alpha-label{font-size:.8rem;min-width:40px}.designer-color-alpha-row input[type=range]{flex:1 1 auto;min-width:90px}.designer-color-alpha-row input[type=number]{width:72px}.properties-panel{height:100%;overflow:hidden;width:100%;display:flex;flex-direction:column;min-height:0;padding-right:10px;padding-left:10px;padding-top:10px}.properties-panel>.designer-toolbox{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:stable}:host ::ng-deep .designer-left-bottom-splitter .p-splitterpanel{min-height:0!important;overflow:hidden!important}.hierarchy-panel{height:100%;width:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden;padding:6px}.hierarchy-panel-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#4b5563;margin:0 0 6px}.hierarchy-tree-wrap{flex:1 1 auto;min-height:0;border:1px solid inherit;border-radius:6px;background:transparent;overflow:auto;padding:4px}:host ::ng-deep .hierarchy-tree-wrap .p-tree{border:none;padding:0;height:100%;background:transparent}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content{border:1px solid transparent;border-radius:4px;background:transparent!important;color:inherit!important;padding:0!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight{border-color:transparent!important;background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content:hover{background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label{display:inline-block;border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label:hover{border-color:#94a3b8}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label.active{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight .hierarchy-node-label{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}.designer-hierarchy-node{cursor:pointer;border:1px solid transparent;border-radius:4px;padding:2px 6px;font-size:.82rem;line-height:1.25rem}.designer-hierarchy-node-label{cursor:pointer;display:inline-block;width:100%}.designer-hierarchy-node.active{border-color:#3b82f6;background:#eff6ff;color:#1d4ed8}.designer-footer-item.designer-tree-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px}.dashboard-design{height:100%;min-height:0;min-width:0;width:100%;overflow:auto;direction:ltr}:host ::ng-deep .dashboard-design.designer-hide-datasources .datasource-descriptor{display:none!important}.current-dashboard-title{font-weight:600;margin-right:12px}.designer-footer{display:flex;align-items:center;gap:8px;padding:6px 8px;border-top:1px solid inherit;background:transparent;min-height:36px;flex:0 0 auto;width:100%;overflow:auto}.designer-footer-label{font-size:.85rem;font-weight:600;color:inherit;white-space:nowrap}.designer-footer-list{display:flex;align-items:center;gap:6px;overflow-x:auto;overflow-y:hidden;white-space:nowrap;width:100%}.designer-footer-item{border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer;white-space:nowrap}.designer-footer-item.active{border-color:#3b82f6;background:transparent;color:#1d4ed8}:host ::ng-deep .designer-footer-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px;box-shadow:inset 0 0 0 1px #3b82f6!important}.route-popup-form{display:flex;flex-direction:column;gap:8px}.route-popup-form label{font-size:.85rem;font-weight:600;color:#2f3f58}.route-popup-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.route-popup-btn{border:1px solid inherit;background:transparent;color:inherit;border-radius:6px;padding:7px 12px;font-size:.85rem;cursor:pointer}.route-popup-btn-primary{border-color:#2f67d8;background:#2f67d8;color:#fff}.p-colorpicker{border:1px solid black;border-radius:4px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: FormsModule }, { 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.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { 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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SplitterModule }, { kind: "component", type: i10.Splitter, selector: "p-splitter", inputs: ["styleClass", "panelStyleClass", "panelStyle", "stateStorage", "stateKey", "layout", "gutterSize", "step", "minSizes", "panelSizes"], outputs: ["onResizeEnd", "onResizeStart"] }, { kind: "directive", type: i3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i12.Draggable, selector: "[pDraggable]", inputs: ["pDraggable", "dragEffect", "dragHandle", "pDraggableDisabled"], outputs: ["onDragStart", "onDragEnd", "onDrag"] }, { kind: "directive", type: i12.Droppable, selector: "[pDroppable]", inputs: ["pDroppable", "pDroppableDisabled", "dropEffect"], outputs: ["onDragEnter", "onDragLeave", "onDrop"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ColorPickerModule }, { kind: "component", type: i2$1.ColorPicker, selector: "p-colorPicker, p-colorpicker, p-color-picker", inputs: ["styleClass", "showTransitionOptions", "hideTransitionOptions", "inline", "format", "tabindex", "inputId", "autoZIndex", "autofocus", "defaultColor", "appendTo", "overlayOptions", "motionOptions"], outputs: ["onChange", "onShow", "onHide"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i2$2.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "component", type: DashboardComponent, selector: "wuic-dashboard", inputs: ["dashboardElements"] }, { kind: "component", type: FieldEditorComponent, selector: "wuic-field-editor", inputs: ["datasource", "record", "field", "metaInfo", "readOnly", "isFilter", "hideLabel", "forceShowLabel", "operator", "nestedIndex", "triggerProp", "tabIndex", "onInlineCellValueChange"] }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: i14.ContextMenu, selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "component", type: MetadataEditorComponent, selector: "wuic-metadata-editor", inputs: ["datasource", "hardcodedDatasource", "saveCallback", "hideReports", "hideRelatedTableActions", "hideRelatedColumnActions"] }, { kind: "ngmodule", type: MenubarModule }, { kind: "component", type: ArchetypeConfiguratorComponent, selector: "wuic-archetype-configurator", inputs: ["visible", "archetype", "metaInfo", "value"], outputs: ["visibleChange", "applyConfig"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: MultiSelectModule }, { kind: "component", type: i18.MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "styleClass", "panelStyle", "panelStyleClass", "inputId", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "dataKey", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "placeholder", "options", "filterValue", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus", "highlightOnSelect", "size", "variant", "fluid", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "component", type: CssSheetEditorComponent, selector: "wuic-css-sheet-editor", inputs: ["visible", "sheets", "selectedSheetPath", "styleProps", "assetsFolder"], outputs: ["visibleChange", "applyEditor"] }, { kind: "ngmodule", type: MenuModule }, { kind: "component", type: i9.Menu, selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex", "appendTo", "motionOptions"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TreeModule }, { kind: "component", type: i4$1.Tree, selector: "p-tree", inputs: ["value", "selectionMode", "loadingMode", "selection", "styleClass", "contextMenu", "contextMenuSelectionMode", "contextMenuSelection", "draggableScope", "droppableScope", "draggableNodes", "droppableNodes", "metaKeySelection", "propagateSelectionUp", "propagateSelectionDown", "loading", "loadingIcon", "emptyMessage", "ariaLabel", "togglerAriaLabel", "ariaLabelledBy", "validateDrop", "filter", "filterInputAutoFocus", "filterBy", "filterMode", "filterOptions", "filterPlaceholder", "filteredNodes", "filterLocale", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "indentation", "_templateMap", "trackBy", "highlightOnSelect"], outputs: ["selectionChange", "contextMenuSelectionChange", "onNodeSelect", "onNodeUnselect", "onNodeExpand", "onNodeCollapse", "onNodeContextMenuSelect", "onNodeDoubleClick", "onNodeDrop", "onLazyLoad", "onScroll", "onScrollIndexChange", "onFilter"] }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
13211
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DesignerComponent, isStandalone: true, selector: "wuic-designer", host: { listeners: { "window:hashchange": "onWindowHashChange()", "window:beforeunload": "onWindowBeforeUnload($event)", "window:keydown": "onWindowKeydown($event)", "window:mouseup": "onWindowMouseup()", "window:touchend": "onWindowTouchend()", "window:keyup": "onWindowKeyup()", "window:change": "onWindowChange()" } }, providers: [TreeDragDropService], viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true }], ngImport: i0, template: "@if (!noEdit) {\n<div class=\"designer-layout\">\n <div class=\"designer-toolbar\">\n <div class=\"designer-current-dashboard\">{{ currentDashboardTitle }}</div>\n\n <div class=\"designer-toolbar-spacer\"></div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"undo()\" [disabled]=\"!canUndo\">{{ 'designer.undo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"redo()\" [disabled]=\"!canRedo\">{{ 'designer.redo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" data-testid=\"designer-actions-menu-btn\" (click)=\"refreshGraphActionsMenuItems(); graphActionsMenu.toggle($event)\">{{ 'designer.actions_menu' | translate }}</button>\n <p-menu #graphActionsMenu [popup]=\"true\" [model]=\"graphActionsMenuItems\" appendTo=\"body\"></p-menu>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"openCurrentDashboardInNewTab()\" [disabled]=\"!currentDashboardRoute\">{{\n 'designer.open_current_dashboard' | translate }}</button>\n </div>\n </div>\n <div class=\"designer-main\">\n <p-splitter [style]=\"{ height: '100%' }\" [panelSizes]=\"[15, 85]\" styleClass=\"designer-main-splitter\">\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[35, 65]\"\n [minSizes]=\"[20, 30]\" styleClass=\"designer-left-splitter\">\n <ng-template pTemplate>\n <div class=\"col flex designer-toolbox palette-panel\">\n <ul class=\"tool-groups\">\n @for (toolGroup of availableToolGroups; track toolGroup.group) {\n <li class=\"tool-group-item\">\n <div class=\"tool-group-title\">{{ toolGroup.group }}</div>\n <ul class=\"tool-group-list\">\n @for (tool of toolGroup.tools; track tool) {\n <li pDraggable (onDragStart)=\"dragStart(tool)\" (onDragEnd)=\"dragEnd()\" (onDrag)=\"onDrag($event)\"\n (dblclick)=\"dropToolAtRoot(tool)\">\n <i [ngClass]=\"tool.icon\" [title]=\"tool.name\"></i>\n </li>\n }\n </ul>\n </li>\n }\n </ul>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[68, 32]\"\n [minSizes]=\"[35, 20]\" styleClass=\"designer-left-bottom-splitter\">\n <ng-template pTemplate>\n <div class=\"properties-panel\">\n <p-select [style]=\"{width: '100%'}\" [options]=\"flattenedDashboardElements\" [(ngModel)]=\"tool\"\n optionLabel=\"uniqueName\" dataKey=\"componentId\" appendTo=\"body\" />\n <div class=\"designer-toolbox\">\n\n <span style=\"width: 100%;\">\n <input type=\"text\" style=\"width: calc(100% - 30px);\" [(ngModel)]=\"propFilter\"\n [placeholder]=\"'prop.filter' | translate\" />\n <button style=\"width: 25px\" (click)=\"clearPropFilter()\">X</button>\n </span>\n\n @for (section of getToolPropertySections(tool, propFilter); track section.key) {\n <details class=\"prop-section\" [open]=\"section.open\"\n (toggle)=\"onPropertySectionToggle(tool, section.key, $event)\">\n <summary class=\"prop-section-title\">{{ section.label }}</summary>\n <ul class=\"props\">\n @for (toolProp of section.props; track toolProp.key) {\n <li>\n @if (!toolProp.value.hideCaption) {\n <span>{{ toolProp.value?.propertyCaption ||\n toolProp.key[0].toUpperCase() + toolProp.key.slice(1)\n }}</span>\n }\n <br />\n @if (toolProp.value.type == 'button') {\n <p-button (click)=\"toolProp.value.callback(tool.inputs, toolProp.key, null, tool)\"\n class=\"k-button\" [style]=\"{width: '100%'}\">\n {{toolProp.value.propertyCaption}}\n </p-button>\n }\n @if (toolProp.value.type == 'metaEditor' &&\n tool.inputs['componentRef']?.value?.component) {\n <wuic-metadata-editor [hardcodedDatasource]=\"tool.inputs['componentRef']?.value?.component\"\n [saveCallback]=\"metaEditorSave\"></wuic-metadata-editor>\n }\n @if (toolProp.value.type == 'boolean') {\n <input type=\"checkbox\" [(ngModel)]=\"tool.inputs[toolProp.key]\" />\n }\n @if (toolProp.value.type == 'numberToArray') {\n <input style=\"width: 100%;\" type=\"number\" pInputText [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'txt_area') {\n <textarea style=\"width: 100%;\" [(ngModel)]=\"tool.inputs[toolProp.key]\"\n [ngModelOptions]=\"{ updateOn: 'blur' }\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\"></textarea>\n }\n @if (toolProp.value.type == 'string') {\n <!-- updateOn:blur come il txt_area sopra: senza, setValue() scatta\n A OGNI CARATTERE e buildSerializedDashboardElements ri-serializza\n l'intero tree (history da centinaia di KB per tasto, P-D1). -->\n <input style=\"width: 100%;\" type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n [ngModelOptions]=\"{ updateOn: 'blur' }\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'color') {\n <p-colorPicker [ngModel]=\"getDesignerColorPickerHex(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorPickerHexChange($event, tool, toolProp)\" appendTo=\"body\" />\n <div class=\"designer-color-alpha-row\">\n <span class=\"designer-color-alpha-label\">{{ 'designer.alpha' | translate }}</span>\n <input type=\"range\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n <input type=\"number\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n </div>\n <input type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'dictionary' && !toolProp.value.async) {\n @if (toolProp.key == 'cssFile') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"availableProjectCssFiles\"\n [ngModel]=\"tool.inputs[toolProp.key]\" (ngModelChange)=\"onCssFileChanged($event, tool)\" />\n } @else if (toolProp.key == 'cssClass') {\n <p-multiSelect [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getCssClassOptionsForTool(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"onCssClassChanged($event, tool)\" [filter]=\"true\" [virtualScroll]=\"true\"\n [virtualScrollItemSize]=\"38\" scrollHeight=\"260px\" [showToggleAll]=\"false\" display=\"chip\"\n [maxSelectedLabels]=\"3\" [selectedItemsLabel]=\"'{0} selected'\">\n </p-multiSelect>\n } @else if ((tool.name == 'SELECT' || tool.name == 'MULTISELECT' || tool.name == 'UL') &&\n (toolProp.key == 'valueField' || toolProp.key == 'textField')) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else if (tool.name == 'LABEL' && toolProp.key == 'displayField') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n }\n @if (toolProp.value.type == 'dictionary' && toolProp.value.async) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (tool.name == 'DATAREPEATER' && toolProp.key == 'action' &&\n canOpenArchetypeConfigurator(tool)) {\n <button style=\"margin-top: 8px;\" (click)=\"openArchetypeConfigurator(tool)\">\n {{ 'designer.configure' | translate }} {{ (tool.inputs['action'] | async) || '' }}\n </button>\n }\n @if (toolProp.value.type == 'dropped-component-list') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [placeholder]=\"'select.component' | translate\" [options]=\"elementFilter(toolProp)\"\n [ngModel]=\"getDroppedComponentListModel(tool, toolProp)\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" optionLabel=\"uniqueName\"\n dataKey=\"uniqueName\" />\n }\n @if (toolProp.value.type == 'autocomplete') {\n <p-autoComplete [style]=\"{width: '100%'}\" appendTo=\"body\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\" [forceSelection]=\"true\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" [suggestions]=\"tool.suggestions\"\n [optionLabel]=\"toolProp.value.displayField\" [optionValue]=\"toolProp.value.valueField\"\n (completeMethod)=\"search($event, tool, toolProp)\" [dropdown]=\"true\" dropdownMode=\"current\"\n [multiple]=\"toolProp.value.multiple\" />\n }\n @if (toolProp.value.type == 'propertyTree' && shouldShowPropertyTree(tool)) {\n <ul>\n @for (item of $any(tool.inputs[toolProp.key] | async); track item) {\n <li style=\"padding: 0; margin: 0;\">\n @if (item.col && tool.inputs['customProps_' + (tool.inputs['action'] | async)])\n {\n <wuic-field-editor [field]=\"item.col\"\n [record]=\"tool.inputs['customProps_' + (tool.inputs['action'] | async)]\"\n [metaInfo]=\"tool.inputs['datasource']?.component?.value?.metaInfo\"\n [triggerProp]=\"tool.inputs['action']\"></wuic-field-editor>\n }\n </li>\n }\n </ul>\n }\n </li>\n }\n </ul>\n </details>\n }\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"hierarchy-panel\">\n <div class=\"hierarchy-panel-title\">{{ 'designer.canvas_hierarchy' | translate }}</div>\n <div class=\"hierarchy-tree-wrap\">\n <p-tree [value]=\"hierarchyTreeNodes\" [draggableNodes]=\"true\" [droppableNodes]=\"true\"\n selectionMode=\"single\" [(selection)]=\"hierarchySelection\"\n (onNodeSelect)=\"onHierarchyNodeSelect($event)\" draggableScope=\"designer-hierarchy\"\n droppableScope=\"designer-hierarchy\" (onNodeDrop)=\"onHierarchyNodeDrop($event)\">\n <ng-template let-node pTemplate=\"default\">\n <span class=\"hierarchy-node-label\" [class.active]=\"isHierarchyNodeSelected(node)\"\n (mouseenter)=\"onHierarchyNodeMouseEnter(node)\" (mouseleave)=\"onHierarchyNodeMouseLeave()\"\n (contextmenu)=\"showHierarchyNodeContextMenu($event, node, hierarchyCtxMenu)\">\n {{ node.label }}\n </span>\n </ng-template>\n </p-tree>\n </div>\n <p-contextMenu #hierarchyCtxMenu [model]=\"hierarchyCtxItems\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </ng-template>\n </p-splitter>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"col dashboard-design\" [class.designer-hide-datasources]=\"hideDatasourcesInCanvas\"\n [class.designer-snap-grid]=\"snapToGridEnabled\" [style.--wuic-grid-size.px]=\"gridSize\" pDroppable\n (onDrop)=\"drop($event)\" #canvas (mousedown)=\"onCanvasMouseDown($event)\"\n (contextmenu)=\"showCtx($event, this)\">\n <wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n @if (rubberBandVisible) {\n <div class=\"designer-selection-rect\" [style.left]=\"rubberBandStyle.left\" [style.top]=\"rubberBandStyle.top\"\n [style.width]=\"rubberBandStyle.width\" [style.height]=\"rubberBandStyle.height\"></div>\n }\n <p-contextMenu [target]=\"canvas\" [model]=\"ctxItems | async\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </div>\n <div class=\"designer-footer\">\n <div class=\"designer-footer-label\">{{ 'designer.items' | translate }}:</div>\n <div class=\"designer-footer-list\">\n @for (item of droppedDashboardItems; track item.uniqueName) {\n <button type=\"button\" class=\"designer-footer-item\" [class.active]=\"tool?.uniqueName === item?.uniqueName\"\n [attr.data-designer-unique-name]=\"item?.uniqueName\" (mouseenter)=\"setFooterHoverHighlight(item)\"\n (mouseleave)=\"clearFooterHoverHighlight()\" (click)=\"selectDashboardItem(item)\"\n (contextmenu)=\"showFooterContextMenu($event, item, footerCtxMenu)\">\n {{ getDroppedDashboardItemLabel(item) }}\n </button>\n }\n </div>\n <p-contextMenu #footerCtxMenu [model]=\"footerCtxItems\" appendTo=\"body\" />\n </div>\n</div>\n}\n\n@if (noEdit) {\n<wuic-dashboard [dashboardElements]=\"dashboardElements\" [widgetChrome]=\"dashboardWidgetChrome\"></wuic-dashboard>\n}\n\n<wuic-archetype-configurator [visible]=\"showArchetypeConfigurator\"\n (visibleChange)=\"onArchetypeConfigVisibleChange($event)\" [archetype]=\"getActiveArchetypeConfig()\"\n [metaInfo]=\"getArchetypeConfigMetaInfoForTool(archetypeConfigTool)\"\n [value]=\"getArchetypeConfigValueForTool(archetypeConfigTool)\" (applyConfig)=\"onArchetypeConfigApply($event)\">\n</wuic-archetype-configurator>\n\n<wuic-css-sheet-editor [visible]=\"showCssSheetEditor\" (visibleChange)=\"onCssSheetEditorVisibleChange($event)\"\n [sheets]=\"dashboardCssSheets\" [selectedSheetPath]=\"selectedCssEditorSheetPath\" [assetsFolder]=\"currentDashboardRoute\"\n [styleProps]=\"getCssEditorStyleProps()\" (applyEditor)=\"onCssSheetEditorApply($event)\">\n</wuic-css-sheet-editor>\n\n<p-dialog [header]=\"'designer.reopen_dashboard' | translate\" [(visible)]=\"reopenDashboardDialogVisible\" [modal]=\"true\"\n [closable]=\"true\" [draggable]=\"false\" [resizable]=\"false\" [appendTo]=\"'body'\" [style]=\"{ width: '34rem' }\"\n (onHide)=\"cancelReopenDashboardDialog()\">\n <div class=\"route-popup-form\">\n <label for=\"designer-reopen-select\">{{ 'designer.existing_dashboards' | translate }}</label>\n <p-select inputId=\"designer-reopen-select\" [options]=\"savedDashboardOptions\" optionLabel=\"label\" optionValue=\"value\"\n [(ngModel)]=\"reopenDialogSelectedDashboardRoute\" [filter]=\"true\" [showClear]=\"true\" [style]=\"{ width: '100%' }\"\n appendTo=\"body\" />\n\n <div class=\"route-popup-actions\">\n <button type=\"button\" class=\"route-popup-btn\" (click)=\"cancelReopenDashboardDialog()\">{{ 'cancel' | translate\n }}</button>\n <button type=\"button\" class=\"route-popup-btn route-popup-btn-primary\" (click)=\"confirmReopenDashboardDialog()\"\n [disabled]=\"!reopenDialogSelectedDashboardRoute\">{{ 'designer.reopen' | translate }}</button>\n </div>\n </div>\n</p-dialog>", styles: [":host{display:block;width:100%;height:100%;min-width:0}.designer-layout{display:grid;grid-template-rows:auto minmax(0,1fr) auto;height:100%;min-height:0;width:100%;min-width:0}.designer-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:8px;border:1px solid inherit;border-radius:10px;background:transparent;padding:10px 12px;margin-bottom:8px}.designer-current-dashboard{min-width:220px;font-size:.86rem;font-weight:700;color:inherit}.designer-toolbar-spacer{flex:1 1 auto;min-width:16px}.designer-toolbar-menu-group{display:inline-flex}.designer-toolbar-menu-group button{height:32px;border:1px solid #2f67d8;border-radius:6px;background:#2f67d8;color:#fff;padding:0 10px;font-size:.82rem;cursor:pointer}.designer-toolbar-menu-group button[disabled]{opacity:.6;cursor:not-allowed}.designer-main{min-height:0;min-width:0;width:100%;overflow:hidden;direction:ltr}.designer-main-splitter{height:100%;width:100%;min-width:0;direction:ltr}.designer-toolbox{font-size:3rem}.designer-toolbox ul{list-style-type:none;padding:0;margin:0;width:100%}.designer-toolbox li{float:left;margin-left:5px;margin-top:5px}.designer-toolbox .tool-groups{display:block}.designer-toolbox .tool-group-item{float:none;margin-left:0;margin-top:10px;clear:both}.designer-toolbox .tool-group-title{font-size:.8rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;opacity:.7;margin:0 0 4px 6px}.designer-toolbox .tool-group-list{display:flex;flex-wrap:wrap;align-items:flex-start;gap:4px}.designer-toolbox .tool-group-list li{float:none;margin-left:0;margin-top:0}.designer-toolbox .props{font-size:1rem}.designer-toolbox .props li{float:none;padding-bottom:15px}.designer-toolbox .prop-section{width:100%;border:1px solid inherit;border-radius:6px;margin-top:8px;background:transparent;padding:6px 8px 2px}.designer-toolbox .prop-section-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;cursor:pointer;color:inherit;margin-bottom:6px}.palette-panel{height:100%;overflow:auto;width:100%}.designer-color-alpha-row{display:flex;align-items:center;gap:8px;margin-top:6px;margin-bottom:6px}.designer-color-alpha-label{font-size:.8rem;min-width:40px}.designer-color-alpha-row input[type=range]{flex:1 1 auto;min-width:90px}.designer-color-alpha-row input[type=number]{width:72px}.properties-panel{height:100%;overflow:hidden;width:100%;display:flex;flex-direction:column;min-height:0;padding-right:10px;padding-left:10px;padding-top:10px}.properties-panel>.designer-toolbox{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:stable}:host ::ng-deep .designer-left-bottom-splitter .p-splitterpanel{min-height:0!important;overflow:hidden!important}.hierarchy-panel{height:100%;width:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden;padding:6px}.hierarchy-panel-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#4b5563;margin:0 0 6px}.hierarchy-tree-wrap{flex:1 1 auto;min-height:0;border:1px solid inherit;border-radius:6px;background:transparent;overflow:auto;padding:4px}:host ::ng-deep .hierarchy-tree-wrap .p-tree{border:none;padding:0;height:100%;background:transparent}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content{border:1px solid transparent;border-radius:4px;background:transparent!important;color:inherit!important;padding:0!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight{border-color:transparent!important;background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content:hover{background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label{display:inline-block;border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label:hover{border-color:#94a3b8}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label.active{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight .hierarchy-node-label{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}.designer-hierarchy-node{cursor:pointer;border:1px solid transparent;border-radius:4px;padding:2px 6px;font-size:.82rem;line-height:1.25rem}.designer-hierarchy-node-label{cursor:pointer;display:inline-block;width:100%}.designer-hierarchy-node.active{border-color:#3b82f6;background:#eff6ff;color:#1d4ed8}.designer-footer-item.designer-tree-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px}.dashboard-design{height:100%;min-height:0;min-width:0;width:100%;overflow:auto;direction:ltr;position:relative}.designer-selection-rect{position:absolute;border:1px dashed #3b82f6;background:#3b82f61f;pointer-events:none;z-index:10000}:host ::ng-deep .designer-multi-selected{outline:2px solid #3b82f6!important;outline-offset:1px}:host ::ng-deep .dashboard-design.designer-hide-datasources .datasource-descriptor{display:none!important}.current-dashboard-title{font-weight:600;margin-right:12px}.designer-footer{display:flex;align-items:center;gap:8px;padding:6px 8px;border-top:1px solid inherit;background:transparent;min-height:36px;flex:0 0 auto;width:100%;overflow:auto}.designer-footer-label{font-size:.85rem;font-weight:600;color:inherit;white-space:nowrap}.designer-footer-list{display:flex;align-items:center;gap:6px;overflow-x:auto;overflow-y:hidden;white-space:nowrap;width:100%}.designer-footer-item{border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer;white-space:nowrap}.designer-footer-item.active{border-color:#3b82f6;background:transparent;color:#1d4ed8}:host ::ng-deep .designer-footer-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px;box-shadow:inset 0 0 0 1px #3b82f6!important}.route-popup-form{display:flex;flex-direction:column;gap:8px}.route-popup-form label{font-size:.85rem;font-weight:600;color:#2f3f58}.route-popup-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.route-popup-btn{border:1px solid inherit;background:transparent;color:inherit;border-radius:6px;padding:7px 12px;font-size:.85rem;cursor:pointer}.route-popup-btn-primary{border-color:#2f67d8;background:#2f67d8;color:#fff}.p-colorpicker{border:1px solid black;border-radius:4px}.dashboard-design.designer-snap-grid{background-image:linear-gradient(to right,rgba(128,128,128,.16) 1px,transparent 1px),linear-gradient(to bottom,rgba(128,128,128,.16) 1px,transparent 1px);background-size:var(--wuic-grid-size, 10px) var(--wuic-grid-size, 10px)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: FormsModule }, { 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.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { 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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SplitterModule }, { kind: "component", type: i10.Splitter, selector: "p-splitter", inputs: ["styleClass", "panelStyleClass", "panelStyle", "stateStorage", "stateKey", "layout", "gutterSize", "step", "minSizes", "panelSizes"], outputs: ["onResizeEnd", "onResizeStart"] }, { kind: "directive", type: i3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i12.Draggable, selector: "[pDraggable]", inputs: ["pDraggable", "dragEffect", "dragHandle", "pDraggableDisabled"], outputs: ["onDragStart", "onDragEnd", "onDrag"] }, { kind: "directive", type: i12.Droppable, selector: "[pDroppable]", inputs: ["pDroppable", "pDroppableDisabled", "dropEffect"], outputs: ["onDragEnter", "onDragLeave", "onDrop"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i5.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ColorPickerModule }, { kind: "component", type: i6.ColorPicker, selector: "p-colorPicker, p-colorpicker, p-color-picker", inputs: ["styleClass", "showTransitionOptions", "hideTransitionOptions", "inline", "format", "tabindex", "inputId", "autoZIndex", "autofocus", "defaultColor", "appendTo", "overlayOptions", "motionOptions"], outputs: ["onChange", "onShow", "onHide"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i2$1.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "component", type: DashboardComponent, selector: "wuic-dashboard", inputs: ["dashboardElements", "widgetChrome"] }, { kind: "component", type: FieldEditorComponent, selector: "wuic-field-editor", inputs: ["datasource", "record", "field", "metaInfo", "readOnly", "isFilter", "hideLabel", "forceShowLabel", "operator", "nestedIndex", "triggerProp", "tabIndex", "onInlineCellValueChange"] }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: i14.ContextMenu, selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "component", type: MetadataEditorComponent, selector: "wuic-metadata-editor", inputs: ["datasource", "hardcodedDatasource", "saveCallback", "hideReports", "hideRelatedTableActions", "hideRelatedColumnActions"] }, { kind: "ngmodule", type: MenubarModule }, { kind: "component", type: ArchetypeConfiguratorComponent, selector: "wuic-archetype-configurator", inputs: ["visible", "archetype", "metaInfo", "value"], outputs: ["visibleChange", "applyConfig"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: MultiSelectModule }, { kind: "component", type: i18.MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "styleClass", "panelStyle", "panelStyleClass", "inputId", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "dataKey", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "placeholder", "options", "filterValue", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus", "highlightOnSelect", "size", "variant", "fluid", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "component", type: CssSheetEditorComponent, selector: "wuic-css-sheet-editor", inputs: ["visible", "sheets", "selectedSheetPath", "styleProps", "assetsFolder"], outputs: ["visibleChange", "applyEditor"] }, { kind: "ngmodule", type: MenuModule }, { kind: "component", type: i7.Menu, selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex", "appendTo", "motionOptions"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TreeModule }, { kind: "component", type: i4$1.Tree, selector: "p-tree", inputs: ["value", "selectionMode", "loadingMode", "selection", "styleClass", "contextMenu", "contextMenuSelectionMode", "contextMenuSelection", "draggableScope", "droppableScope", "draggableNodes", "droppableNodes", "metaKeySelection", "propagateSelectionUp", "propagateSelectionDown", "loading", "loadingIcon", "emptyMessage", "ariaLabel", "togglerAriaLabel", "ariaLabelledBy", "validateDrop", "filter", "filterInputAutoFocus", "filterBy", "filterMode", "filterOptions", "filterPlaceholder", "filteredNodes", "filterLocale", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "indentation", "_templateMap", "trackBy", "highlightOnSelect"], outputs: ["selectionChange", "contextMenuSelectionChange", "onNodeSelect", "onNodeUnselect", "onNodeExpand", "onNodeCollapse", "onNodeContextMenuSelect", "onNodeDoubleClick", "onNodeDrop", "onLazyLoad", "onScroll", "onScrollIndexChange", "onFilter"] }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
11908
13212
  }
11909
13213
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DesignerComponent, decorators: [{
11910
13214
  type: Component,
11911
- args: [{ selector: 'wuic-designer', imports: [NgClass, TranslateModule, AsyncPipe, FormsModule, SplitterModule, DragDropModule, SelectModule, ColorPickerModule, AutoCompleteModule, DashboardComponent, FieldEditorComponent, ContextMenuModule, MetadataEditorComponent, MenubarModule, ArchetypeConfiguratorComponent, ButtonModule, MultiSelectModule, CssSheetEditorComponent, MenuModule, DialogModule, TreeModule], providers: [TreeDragDropService], template: "<!-- <button pButton type=\"button\" (click)=\"loadDashboard()\" class=\"mr-2\">{{'load' | translate}}</button> -->\n@if (!noEdit) {\n<div class=\"designer-layout\">\n <div class=\"designer-toolbar\">\n <div class=\"designer-current-dashboard\">{{ currentDashboardTitle }}</div>\n\n <div class=\"designer-toolbar-spacer\"></div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"undo()\" [disabled]=\"!canUndo\">{{ 'designer.undo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"redo()\" [disabled]=\"!canRedo\">{{ 'designer.redo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"refreshGraphActionsMenuItems(); graphActionsMenu.toggle($event)\">{{ 'designer.actions_menu' | translate }}</button>\n <p-menu #graphActionsMenu [popup]=\"true\" [model]=\"graphActionsMenuItems\" appendTo=\"body\"></p-menu>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"openCurrentDashboardInNewTab()\" [disabled]=\"!currentDashboardRoute\">{{\n 'designer.open_current_dashboard' | translate }}</button>\n </div>\n </div>\n <div class=\"designer-main\">\n <p-splitter [style]=\"{ height: '100%' }\" [panelSizes]=\"[15, 85]\" styleClass=\"designer-main-splitter\">\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[35, 65]\"\n [minSizes]=\"[20, 30]\" styleClass=\"designer-left-splitter\">\n <ng-template pTemplate>\n <div class=\"col flex designer-toolbox palette-panel\">\n <ul class=\"tool-groups\">\n @for (toolGroup of availableToolGroups; track toolGroup.group) {\n <li class=\"tool-group-item\">\n <div class=\"tool-group-title\">{{ toolGroup.group }}</div>\n <ul class=\"tool-group-list\">\n @for (tool of toolGroup.tools; track tool) {\n <li pDraggable (onDragStart)=\"dragStart(tool)\" (onDragEnd)=\"dragEnd()\" (onDrag)=\"onDrag($event)\"\n (dblclick)=\"dropToolAtRoot(tool)\">\n <i [ngClass]=\"tool.icon\" [title]=\"tool.name\"></i>\n </li>\n }\n </ul>\n </li>\n }\n </ul>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[68, 32]\"\n [minSizes]=\"[35, 20]\" styleClass=\"designer-left-bottom-splitter\">\n <ng-template pTemplate>\n <div class=\"properties-panel\">\n <p-select [style]=\"{width: '100%'}\" [options]=\"flattenedDashboardElements\" [(ngModel)]=\"tool\"\n optionLabel=\"uniqueName\" dataKey=\"componentId\" appendTo=\"body\" />\n <div class=\"designer-toolbox\">\n\n <span style=\"width: 100%;\">\n <input type=\"text\" style=\"width: calc(100% - 30px);\" [(ngModel)]=\"propFilter\"\n [placeholder]=\"'prop.filter' | translate\" />\n <button style=\"width: 25px\" (click)=\"clearPropFilter()\">X</button>\n </span>\n\n @for (section of getToolPropertySections(tool, propFilter); track section.key) {\n <details class=\"prop-section\" [open]=\"section.open\"\n (toggle)=\"onPropertySectionToggle(tool, section.key, $event)\">\n <summary class=\"prop-section-title\">{{ section.label }}</summary>\n <ul class=\"props\">\n @for (toolProp of section.props; track toolProp.key) {\n <li>\n @if (!toolProp.value.hideCaption) {\n <span>{{ toolProp.value?.propertyCaption ||\n toolProp.key[0].toUpperCase() + toolProp.key.slice(1)\n }}</span>\n }\n <br />\n @if (toolProp.value.type == 'button') {\n <p-button (click)=\"toolProp.value.callback(tool.inputs, toolProp.key, null, tool)\"\n class=\"k-button\" [style]=\"{width: '100%'}\">\n {{toolProp.value.propertyCaption}}\n </p-button>\n }\n @if (toolProp.value.type == 'metaEditor' &&\n tool.inputs['componentRef']?.value?.component) {\n <wuic-metadata-editor [hardcodedDatasource]=\"tool.inputs['componentRef']?.value?.component\"\n [saveCallback]=\"metaEditorSave\"></wuic-metadata-editor>\n }\n @if (toolProp.value.type == 'boolean') {\n <input type=\"checkbox\" [(ngModel)]=\"tool.inputs[toolProp.key]\" />\n }\n @if (toolProp.value.type == 'numberToArray') {\n <input style=\"width: 100%;\" type=\"number\" pInputText [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'txt_area') {\n <textarea style=\"width: 100%;\" [(ngModel)]=\"tool.inputs[toolProp.key]\"\n [ngModelOptions]=\"{ updateOn: 'blur' }\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\"></textarea>\n }\n @if (toolProp.value.type == 'string') {\n <input style=\"width: 100%;\" type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'color') {\n <p-colorPicker [ngModel]=\"getDesignerColorPickerHex(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorPickerHexChange($event, tool, toolProp)\" appendTo=\"body\" />\n <div class=\"designer-color-alpha-row\">\n <span class=\"designer-color-alpha-label\">{{ 'designer.alpha' | translate }}</span>\n <input type=\"range\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n <input type=\"number\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n </div>\n <input type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'dictionary' && !toolProp.value.async) {\n @if (toolProp.key == 'cssFile') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"availableProjectCssFiles\"\n [ngModel]=\"tool.inputs[toolProp.key]\" (ngModelChange)=\"onCssFileChanged($event, tool)\" />\n } @else if (toolProp.key == 'cssClass') {\n <p-multiSelect [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getCssClassOptionsForTool(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"onCssClassChanged($event, tool)\" [filter]=\"true\" [virtualScroll]=\"true\"\n [virtualScrollItemSize]=\"38\" scrollHeight=\"260px\" [showToggleAll]=\"false\" display=\"chip\"\n [maxSelectedLabels]=\"3\" [selectedItemsLabel]=\"'{0} selected'\">\n </p-multiSelect>\n } @else if ((tool.name == 'SELECT' || tool.name == 'MULTISELECT' || tool.name == 'UL') &&\n (toolProp.key == 'valueField' || toolProp.key == 'textField')) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else if (tool.name == 'LABEL' && toolProp.key == 'displayField') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n }\n @if (toolProp.value.type == 'dictionary' && toolProp.value.async) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (tool.name == 'DATAREPEATER' && toolProp.key == 'action' &&\n canOpenArchetypeConfigurator(tool)) {\n <button style=\"margin-top: 8px;\" (click)=\"openArchetypeConfigurator(tool)\">\n {{ 'designer.configure' | translate }} {{ (tool.inputs['action'] | async) || '' }}\n </button>\n }\n @if (toolProp.value.type == 'dropped-component-list') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [placeholder]=\"'select.component' | translate\" [options]=\"elementFilter(toolProp)\"\n [ngModel]=\"getDroppedComponentListModel(tool, toolProp)\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" optionLabel=\"uniqueName\"\n dataKey=\"uniqueName\" />\n }\n @if (toolProp.value.type == 'autocomplete') {\n <p-autoComplete [style]=\"{width: '100%'}\" appendTo=\"body\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\" [forceSelection]=\"true\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" [suggestions]=\"tool.suggestions\"\n [optionLabel]=\"toolProp.value.displayField\" [optionValue]=\"toolProp.value.valueField\"\n (completeMethod)=\"search($event, tool, toolProp)\" [dropdown]=\"true\" dropdownMode=\"current\"\n [multiple]=\"toolProp.value.multiple\" />\n }\n @if (toolProp.value.type == 'propertyTree' && shouldShowPropertyTree(tool)) {\n <ul>\n @for (item of $any(tool.inputs[toolProp.key] | async); track item) {\n <li style=\"padding: 0; margin: 0;\">\n @if (item.col && tool.inputs['customProps_' + (tool.inputs['action'] | async)])\n {\n <wuic-field-editor [field]=\"item.col\"\n [record]=\"tool.inputs['customProps_' + (tool.inputs['action'] | async)]\"\n [metaInfo]=\"tool.inputs['datasource']?.component?.value?.metaInfo\"\n [triggerProp]=\"tool.inputs['action']\"></wuic-field-editor>\n }\n </li>\n }\n </ul>\n }\n </li>\n }\n </ul>\n </details>\n }\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"hierarchy-panel\">\n <div class=\"hierarchy-panel-title\">{{ 'designer.canvas_hierarchy' | translate }}</div>\n <div class=\"hierarchy-tree-wrap\">\n <p-tree [value]=\"hierarchyTreeNodes\" [draggableNodes]=\"true\" [droppableNodes]=\"true\"\n selectionMode=\"single\" [(selection)]=\"hierarchySelection\"\n (onNodeSelect)=\"onHierarchyNodeSelect($event)\" draggableScope=\"designer-hierarchy\"\n droppableScope=\"designer-hierarchy\" (onNodeDrop)=\"onHierarchyNodeDrop($event)\">\n <ng-template let-node pTemplate=\"default\">\n <span class=\"hierarchy-node-label\" [class.active]=\"isHierarchyNodeSelected(node)\"\n (mouseenter)=\"onHierarchyNodeMouseEnter(node)\" (mouseleave)=\"onHierarchyNodeMouseLeave()\"\n (contextmenu)=\"showHierarchyNodeContextMenu($event, node, hierarchyCtxMenu)\">\n {{ node.label }}\n </span>\n </ng-template>\n </p-tree>\n </div>\n <p-contextMenu #hierarchyCtxMenu [model]=\"hierarchyCtxItems\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </ng-template>\n </p-splitter>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"col dashboard-design\" [class.designer-hide-datasources]=\"hideDatasourcesInCanvas\" pDroppable\n (onDrop)=\"drop($event)\" #canvas (contextmenu)=\"showCtx($event, this)\">\n <wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n <p-contextMenu [target]=\"canvas\" [model]=\"ctxItems | async\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </div>\n <div class=\"designer-footer\">\n <div class=\"designer-footer-label\">{{ 'designer.items' | translate }}:</div>\n <div class=\"designer-footer-list\">\n @for (item of droppedDashboardItems; track item.uniqueName) {\n <button type=\"button\" class=\"designer-footer-item\" [class.active]=\"tool?.uniqueName === item?.uniqueName\"\n [attr.data-designer-unique-name]=\"item?.uniqueName\" (mouseenter)=\"setFooterHoverHighlight(item)\"\n (mouseleave)=\"clearFooterHoverHighlight()\" (click)=\"selectDashboardItem(item)\"\n (contextmenu)=\"showFooterContextMenu($event, item, footerCtxMenu)\">\n {{ getDroppedDashboardItemLabel(item) }}\n </button>\n }\n </div>\n <p-contextMenu #footerCtxMenu [model]=\"footerCtxItems\" appendTo=\"body\" />\n </div>\n</div>\n}\n\n@if (noEdit) {\n<wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n}\n\n<wuic-archetype-configurator [visible]=\"showArchetypeConfigurator\"\n (visibleChange)=\"onArchetypeConfigVisibleChange($event)\" [archetype]=\"getActiveArchetypeConfig()\"\n [metaInfo]=\"getArchetypeConfigMetaInfoForTool(archetypeConfigTool)\"\n [value]=\"getArchetypeConfigValueForTool(archetypeConfigTool)\" (applyConfig)=\"onArchetypeConfigApply($event)\">\n</wuic-archetype-configurator>\n\n<wuic-css-sheet-editor [visible]=\"showCssSheetEditor\" (visibleChange)=\"onCssSheetEditorVisibleChange($event)\"\n [sheets]=\"dashboardCssSheets\" [selectedSheetPath]=\"selectedCssEditorSheetPath\" [assetsFolder]=\"currentDashboardRoute\"\n [styleProps]=\"getCssEditorStyleProps()\" (applyEditor)=\"onCssSheetEditorApply($event)\">\n</wuic-css-sheet-editor>\n\n<p-dialog [header]=\"'designer.reopen_dashboard' | translate\" [(visible)]=\"reopenDashboardDialogVisible\" [modal]=\"true\"\n [closable]=\"true\" [draggable]=\"false\" [resizable]=\"false\" [appendTo]=\"'body'\" [style]=\"{ width: '34rem' }\"\n (onHide)=\"cancelReopenDashboardDialog()\">\n <div class=\"route-popup-form\">\n <label for=\"designer-reopen-select\">{{ 'designer.existing_dashboards' | translate }}</label>\n <p-select inputId=\"designer-reopen-select\" [options]=\"savedDashboardOptions\" optionLabel=\"label\" optionValue=\"value\"\n [(ngModel)]=\"reopenDialogSelectedDashboardRoute\" [filter]=\"true\" [showClear]=\"true\" [style]=\"{ width: '100%' }\"\n appendTo=\"body\" />\n\n <div class=\"route-popup-actions\">\n <button type=\"button\" class=\"route-popup-btn\" (click)=\"cancelReopenDashboardDialog()\">{{ 'cancel' | translate\n }}</button>\n <button type=\"button\" class=\"route-popup-btn route-popup-btn-primary\" (click)=\"confirmReopenDashboardDialog()\"\n [disabled]=\"!reopenDialogSelectedDashboardRoute\">{{ 'designer.reopen' | translate }}</button>\n </div>\n </div>\n</p-dialog>", styles: [":host{display:block;width:100%;height:100%;min-width:0}.designer-layout{display:grid;grid-template-rows:auto minmax(0,1fr) auto;height:100%;min-height:0;width:100%;min-width:0}.designer-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:8px;border:1px solid inherit;border-radius:10px;background:transparent;padding:10px 12px;margin-bottom:8px}.designer-current-dashboard{min-width:220px;font-size:.86rem;font-weight:700;color:inherit}.designer-toolbar-spacer{flex:1 1 auto;min-width:16px}.designer-toolbar-menu-group{display:inline-flex}.designer-toolbar-menu-group button{height:32px;border:1px solid #2f67d8;border-radius:6px;background:#2f67d8;color:#fff;padding:0 10px;font-size:.82rem;cursor:pointer}.designer-toolbar-menu-group button[disabled]{opacity:.6;cursor:not-allowed}.designer-main{min-height:0;min-width:0;width:100%;overflow:hidden;direction:ltr}.designer-main-splitter{height:100%;width:100%;min-width:0;direction:ltr}.designer-toolbox{font-size:3rem}.designer-toolbox ul{list-style-type:none;padding:0;margin:0;width:100%}.designer-toolbox li{float:left;margin-left:5px;margin-top:5px}.designer-toolbox .tool-groups{display:block}.designer-toolbox .tool-group-item{float:none;margin-left:0;margin-top:10px;clear:both}.designer-toolbox .tool-group-title{font-size:.8rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;opacity:.7;margin:0 0 4px 6px}.designer-toolbox .tool-group-list{display:flex;flex-wrap:wrap;align-items:flex-start;gap:4px}.designer-toolbox .tool-group-list li{float:none;margin-left:0;margin-top:0}.designer-toolbox .props{font-size:1rem}.designer-toolbox .props li{float:none;padding-bottom:15px}.designer-toolbox .prop-section{width:100%;border:1px solid inherit;border-radius:6px;margin-top:8px;background:transparent;padding:6px 8px 2px}.designer-toolbox .prop-section-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;cursor:pointer;color:inherit;margin-bottom:6px}.palette-panel{height:100%;overflow:auto;width:100%}.designer-color-alpha-row{display:flex;align-items:center;gap:8px;margin-top:6px;margin-bottom:6px}.designer-color-alpha-label{font-size:.8rem;min-width:40px}.designer-color-alpha-row input[type=range]{flex:1 1 auto;min-width:90px}.designer-color-alpha-row input[type=number]{width:72px}.properties-panel{height:100%;overflow:hidden;width:100%;display:flex;flex-direction:column;min-height:0;padding-right:10px;padding-left:10px;padding-top:10px}.properties-panel>.designer-toolbox{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:stable}:host ::ng-deep .designer-left-bottom-splitter .p-splitterpanel{min-height:0!important;overflow:hidden!important}.hierarchy-panel{height:100%;width:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden;padding:6px}.hierarchy-panel-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#4b5563;margin:0 0 6px}.hierarchy-tree-wrap{flex:1 1 auto;min-height:0;border:1px solid inherit;border-radius:6px;background:transparent;overflow:auto;padding:4px}:host ::ng-deep .hierarchy-tree-wrap .p-tree{border:none;padding:0;height:100%;background:transparent}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content{border:1px solid transparent;border-radius:4px;background:transparent!important;color:inherit!important;padding:0!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight{border-color:transparent!important;background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content:hover{background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label{display:inline-block;border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label:hover{border-color:#94a3b8}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label.active{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight .hierarchy-node-label{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}.designer-hierarchy-node{cursor:pointer;border:1px solid transparent;border-radius:4px;padding:2px 6px;font-size:.82rem;line-height:1.25rem}.designer-hierarchy-node-label{cursor:pointer;display:inline-block;width:100%}.designer-hierarchy-node.active{border-color:#3b82f6;background:#eff6ff;color:#1d4ed8}.designer-footer-item.designer-tree-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px}.dashboard-design{height:100%;min-height:0;min-width:0;width:100%;overflow:auto;direction:ltr}:host ::ng-deep .dashboard-design.designer-hide-datasources .datasource-descriptor{display:none!important}.current-dashboard-title{font-weight:600;margin-right:12px}.designer-footer{display:flex;align-items:center;gap:8px;padding:6px 8px;border-top:1px solid inherit;background:transparent;min-height:36px;flex:0 0 auto;width:100%;overflow:auto}.designer-footer-label{font-size:.85rem;font-weight:600;color:inherit;white-space:nowrap}.designer-footer-list{display:flex;align-items:center;gap:6px;overflow-x:auto;overflow-y:hidden;white-space:nowrap;width:100%}.designer-footer-item{border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer;white-space:nowrap}.designer-footer-item.active{border-color:#3b82f6;background:transparent;color:#1d4ed8}:host ::ng-deep .designer-footer-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px;box-shadow:inset 0 0 0 1px #3b82f6!important}.route-popup-form{display:flex;flex-direction:column;gap:8px}.route-popup-form label{font-size:.85rem;font-weight:600;color:#2f3f58}.route-popup-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.route-popup-btn{border:1px solid inherit;background:transparent;color:inherit;border-radius:6px;padding:7px 12px;font-size:.85rem;cursor:pointer}.route-popup-btn-primary{border-color:#2f67d8;background:#2f67d8;color:#fff}.p-colorpicker{border:1px solid black;border-radius:4px}\n"] }]
13215
+ args: [{ selector: 'wuic-designer', imports: [NgClass, TranslateModule, AsyncPipe, FormsModule, SplitterModule, DragDropModule, SelectModule, ColorPickerModule, AutoCompleteModule, DashboardComponent, FieldEditorComponent, ContextMenuModule, MetadataEditorComponent, MenubarModule, ArchetypeConfiguratorComponent, ButtonModule, MultiSelectModule, CssSheetEditorComponent, MenuModule, DialogModule, TreeModule], providers: [TreeDragDropService], template: "@if (!noEdit) {\n<div class=\"designer-layout\">\n <div class=\"designer-toolbar\">\n <div class=\"designer-current-dashboard\">{{ currentDashboardTitle }}</div>\n\n <div class=\"designer-toolbar-spacer\"></div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"undo()\" [disabled]=\"!canUndo\">{{ 'designer.undo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"redo()\" [disabled]=\"!canRedo\">{{ 'designer.redo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" data-testid=\"designer-actions-menu-btn\" (click)=\"refreshGraphActionsMenuItems(); graphActionsMenu.toggle($event)\">{{ 'designer.actions_menu' | translate }}</button>\n <p-menu #graphActionsMenu [popup]=\"true\" [model]=\"graphActionsMenuItems\" appendTo=\"body\"></p-menu>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"openCurrentDashboardInNewTab()\" [disabled]=\"!currentDashboardRoute\">{{\n 'designer.open_current_dashboard' | translate }}</button>\n </div>\n </div>\n <div class=\"designer-main\">\n <p-splitter [style]=\"{ height: '100%' }\" [panelSizes]=\"[15, 85]\" styleClass=\"designer-main-splitter\">\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[35, 65]\"\n [minSizes]=\"[20, 30]\" styleClass=\"designer-left-splitter\">\n <ng-template pTemplate>\n <div class=\"col flex designer-toolbox palette-panel\">\n <ul class=\"tool-groups\">\n @for (toolGroup of availableToolGroups; track toolGroup.group) {\n <li class=\"tool-group-item\">\n <div class=\"tool-group-title\">{{ toolGroup.group }}</div>\n <ul class=\"tool-group-list\">\n @for (tool of toolGroup.tools; track tool) {\n <li pDraggable (onDragStart)=\"dragStart(tool)\" (onDragEnd)=\"dragEnd()\" (onDrag)=\"onDrag($event)\"\n (dblclick)=\"dropToolAtRoot(tool)\">\n <i [ngClass]=\"tool.icon\" [title]=\"tool.name\"></i>\n </li>\n }\n </ul>\n </li>\n }\n </ul>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[68, 32]\"\n [minSizes]=\"[35, 20]\" styleClass=\"designer-left-bottom-splitter\">\n <ng-template pTemplate>\n <div class=\"properties-panel\">\n <p-select [style]=\"{width: '100%'}\" [options]=\"flattenedDashboardElements\" [(ngModel)]=\"tool\"\n optionLabel=\"uniqueName\" dataKey=\"componentId\" appendTo=\"body\" />\n <div class=\"designer-toolbox\">\n\n <span style=\"width: 100%;\">\n <input type=\"text\" style=\"width: calc(100% - 30px);\" [(ngModel)]=\"propFilter\"\n [placeholder]=\"'prop.filter' | translate\" />\n <button style=\"width: 25px\" (click)=\"clearPropFilter()\">X</button>\n </span>\n\n @for (section of getToolPropertySections(tool, propFilter); track section.key) {\n <details class=\"prop-section\" [open]=\"section.open\"\n (toggle)=\"onPropertySectionToggle(tool, section.key, $event)\">\n <summary class=\"prop-section-title\">{{ section.label }}</summary>\n <ul class=\"props\">\n @for (toolProp of section.props; track toolProp.key) {\n <li>\n @if (!toolProp.value.hideCaption) {\n <span>{{ toolProp.value?.propertyCaption ||\n toolProp.key[0].toUpperCase() + toolProp.key.slice(1)\n }}</span>\n }\n <br />\n @if (toolProp.value.type == 'button') {\n <p-button (click)=\"toolProp.value.callback(tool.inputs, toolProp.key, null, tool)\"\n class=\"k-button\" [style]=\"{width: '100%'}\">\n {{toolProp.value.propertyCaption}}\n </p-button>\n }\n @if (toolProp.value.type == 'metaEditor' &&\n tool.inputs['componentRef']?.value?.component) {\n <wuic-metadata-editor [hardcodedDatasource]=\"tool.inputs['componentRef']?.value?.component\"\n [saveCallback]=\"metaEditorSave\"></wuic-metadata-editor>\n }\n @if (toolProp.value.type == 'boolean') {\n <input type=\"checkbox\" [(ngModel)]=\"tool.inputs[toolProp.key]\" />\n }\n @if (toolProp.value.type == 'numberToArray') {\n <input style=\"width: 100%;\" type=\"number\" pInputText [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'txt_area') {\n <textarea style=\"width: 100%;\" [(ngModel)]=\"tool.inputs[toolProp.key]\"\n [ngModelOptions]=\"{ updateOn: 'blur' }\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\"></textarea>\n }\n @if (toolProp.value.type == 'string') {\n <!-- updateOn:blur come il txt_area sopra: senza, setValue() scatta\n A OGNI CARATTERE e buildSerializedDashboardElements ri-serializza\n l'intero tree (history da centinaia di KB per tasto, P-D1). -->\n <input style=\"width: 100%;\" type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n [ngModelOptions]=\"{ updateOn: 'blur' }\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'color') {\n <p-colorPicker [ngModel]=\"getDesignerColorPickerHex(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorPickerHexChange($event, tool, toolProp)\" appendTo=\"body\" />\n <div class=\"designer-color-alpha-row\">\n <span class=\"designer-color-alpha-label\">{{ 'designer.alpha' | translate }}</span>\n <input type=\"range\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n <input type=\"number\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n </div>\n <input type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'dictionary' && !toolProp.value.async) {\n @if (toolProp.key == 'cssFile') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"availableProjectCssFiles\"\n [ngModel]=\"tool.inputs[toolProp.key]\" (ngModelChange)=\"onCssFileChanged($event, tool)\" />\n } @else if (toolProp.key == 'cssClass') {\n <p-multiSelect [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getCssClassOptionsForTool(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"onCssClassChanged($event, tool)\" [filter]=\"true\" [virtualScroll]=\"true\"\n [virtualScrollItemSize]=\"38\" scrollHeight=\"260px\" [showToggleAll]=\"false\" display=\"chip\"\n [maxSelectedLabels]=\"3\" [selectedItemsLabel]=\"'{0} selected'\">\n </p-multiSelect>\n } @else if ((tool.name == 'SELECT' || tool.name == 'MULTISELECT' || tool.name == 'UL') &&\n (toolProp.key == 'valueField' || toolProp.key == 'textField')) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else if (tool.name == 'LABEL' && toolProp.key == 'displayField') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n }\n @if (toolProp.value.type == 'dictionary' && toolProp.value.async) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (tool.name == 'DATAREPEATER' && toolProp.key == 'action' &&\n canOpenArchetypeConfigurator(tool)) {\n <button style=\"margin-top: 8px;\" (click)=\"openArchetypeConfigurator(tool)\">\n {{ 'designer.configure' | translate }} {{ (tool.inputs['action'] | async) || '' }}\n </button>\n }\n @if (toolProp.value.type == 'dropped-component-list') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [placeholder]=\"'select.component' | translate\" [options]=\"elementFilter(toolProp)\"\n [ngModel]=\"getDroppedComponentListModel(tool, toolProp)\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" optionLabel=\"uniqueName\"\n dataKey=\"uniqueName\" />\n }\n @if (toolProp.value.type == 'autocomplete') {\n <p-autoComplete [style]=\"{width: '100%'}\" appendTo=\"body\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\" [forceSelection]=\"true\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" [suggestions]=\"tool.suggestions\"\n [optionLabel]=\"toolProp.value.displayField\" [optionValue]=\"toolProp.value.valueField\"\n (completeMethod)=\"search($event, tool, toolProp)\" [dropdown]=\"true\" dropdownMode=\"current\"\n [multiple]=\"toolProp.value.multiple\" />\n }\n @if (toolProp.value.type == 'propertyTree' && shouldShowPropertyTree(tool)) {\n <ul>\n @for (item of $any(tool.inputs[toolProp.key] | async); track item) {\n <li style=\"padding: 0; margin: 0;\">\n @if (item.col && tool.inputs['customProps_' + (tool.inputs['action'] | async)])\n {\n <wuic-field-editor [field]=\"item.col\"\n [record]=\"tool.inputs['customProps_' + (tool.inputs['action'] | async)]\"\n [metaInfo]=\"tool.inputs['datasource']?.component?.value?.metaInfo\"\n [triggerProp]=\"tool.inputs['action']\"></wuic-field-editor>\n }\n </li>\n }\n </ul>\n }\n </li>\n }\n </ul>\n </details>\n }\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"hierarchy-panel\">\n <div class=\"hierarchy-panel-title\">{{ 'designer.canvas_hierarchy' | translate }}</div>\n <div class=\"hierarchy-tree-wrap\">\n <p-tree [value]=\"hierarchyTreeNodes\" [draggableNodes]=\"true\" [droppableNodes]=\"true\"\n selectionMode=\"single\" [(selection)]=\"hierarchySelection\"\n (onNodeSelect)=\"onHierarchyNodeSelect($event)\" draggableScope=\"designer-hierarchy\"\n droppableScope=\"designer-hierarchy\" (onNodeDrop)=\"onHierarchyNodeDrop($event)\">\n <ng-template let-node pTemplate=\"default\">\n <span class=\"hierarchy-node-label\" [class.active]=\"isHierarchyNodeSelected(node)\"\n (mouseenter)=\"onHierarchyNodeMouseEnter(node)\" (mouseleave)=\"onHierarchyNodeMouseLeave()\"\n (contextmenu)=\"showHierarchyNodeContextMenu($event, node, hierarchyCtxMenu)\">\n {{ node.label }}\n </span>\n </ng-template>\n </p-tree>\n </div>\n <p-contextMenu #hierarchyCtxMenu [model]=\"hierarchyCtxItems\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </ng-template>\n </p-splitter>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"col dashboard-design\" [class.designer-hide-datasources]=\"hideDatasourcesInCanvas\"\n [class.designer-snap-grid]=\"snapToGridEnabled\" [style.--wuic-grid-size.px]=\"gridSize\" pDroppable\n (onDrop)=\"drop($event)\" #canvas (mousedown)=\"onCanvasMouseDown($event)\"\n (contextmenu)=\"showCtx($event, this)\">\n <wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n @if (rubberBandVisible) {\n <div class=\"designer-selection-rect\" [style.left]=\"rubberBandStyle.left\" [style.top]=\"rubberBandStyle.top\"\n [style.width]=\"rubberBandStyle.width\" [style.height]=\"rubberBandStyle.height\"></div>\n }\n <p-contextMenu [target]=\"canvas\" [model]=\"ctxItems | async\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </div>\n <div class=\"designer-footer\">\n <div class=\"designer-footer-label\">{{ 'designer.items' | translate }}:</div>\n <div class=\"designer-footer-list\">\n @for (item of droppedDashboardItems; track item.uniqueName) {\n <button type=\"button\" class=\"designer-footer-item\" [class.active]=\"tool?.uniqueName === item?.uniqueName\"\n [attr.data-designer-unique-name]=\"item?.uniqueName\" (mouseenter)=\"setFooterHoverHighlight(item)\"\n (mouseleave)=\"clearFooterHoverHighlight()\" (click)=\"selectDashboardItem(item)\"\n (contextmenu)=\"showFooterContextMenu($event, item, footerCtxMenu)\">\n {{ getDroppedDashboardItemLabel(item) }}\n </button>\n }\n </div>\n <p-contextMenu #footerCtxMenu [model]=\"footerCtxItems\" appendTo=\"body\" />\n </div>\n</div>\n}\n\n@if (noEdit) {\n<wuic-dashboard [dashboardElements]=\"dashboardElements\" [widgetChrome]=\"dashboardWidgetChrome\"></wuic-dashboard>\n}\n\n<wuic-archetype-configurator [visible]=\"showArchetypeConfigurator\"\n (visibleChange)=\"onArchetypeConfigVisibleChange($event)\" [archetype]=\"getActiveArchetypeConfig()\"\n [metaInfo]=\"getArchetypeConfigMetaInfoForTool(archetypeConfigTool)\"\n [value]=\"getArchetypeConfigValueForTool(archetypeConfigTool)\" (applyConfig)=\"onArchetypeConfigApply($event)\">\n</wuic-archetype-configurator>\n\n<wuic-css-sheet-editor [visible]=\"showCssSheetEditor\" (visibleChange)=\"onCssSheetEditorVisibleChange($event)\"\n [sheets]=\"dashboardCssSheets\" [selectedSheetPath]=\"selectedCssEditorSheetPath\" [assetsFolder]=\"currentDashboardRoute\"\n [styleProps]=\"getCssEditorStyleProps()\" (applyEditor)=\"onCssSheetEditorApply($event)\">\n</wuic-css-sheet-editor>\n\n<p-dialog [header]=\"'designer.reopen_dashboard' | translate\" [(visible)]=\"reopenDashboardDialogVisible\" [modal]=\"true\"\n [closable]=\"true\" [draggable]=\"false\" [resizable]=\"false\" [appendTo]=\"'body'\" [style]=\"{ width: '34rem' }\"\n (onHide)=\"cancelReopenDashboardDialog()\">\n <div class=\"route-popup-form\">\n <label for=\"designer-reopen-select\">{{ 'designer.existing_dashboards' | translate }}</label>\n <p-select inputId=\"designer-reopen-select\" [options]=\"savedDashboardOptions\" optionLabel=\"label\" optionValue=\"value\"\n [(ngModel)]=\"reopenDialogSelectedDashboardRoute\" [filter]=\"true\" [showClear]=\"true\" [style]=\"{ width: '100%' }\"\n appendTo=\"body\" />\n\n <div class=\"route-popup-actions\">\n <button type=\"button\" class=\"route-popup-btn\" (click)=\"cancelReopenDashboardDialog()\">{{ 'cancel' | translate\n }}</button>\n <button type=\"button\" class=\"route-popup-btn route-popup-btn-primary\" (click)=\"confirmReopenDashboardDialog()\"\n [disabled]=\"!reopenDialogSelectedDashboardRoute\">{{ 'designer.reopen' | translate }}</button>\n </div>\n </div>\n</p-dialog>", styles: [":host{display:block;width:100%;height:100%;min-width:0}.designer-layout{display:grid;grid-template-rows:auto minmax(0,1fr) auto;height:100%;min-height:0;width:100%;min-width:0}.designer-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:8px;border:1px solid inherit;border-radius:10px;background:transparent;padding:10px 12px;margin-bottom:8px}.designer-current-dashboard{min-width:220px;font-size:.86rem;font-weight:700;color:inherit}.designer-toolbar-spacer{flex:1 1 auto;min-width:16px}.designer-toolbar-menu-group{display:inline-flex}.designer-toolbar-menu-group button{height:32px;border:1px solid #2f67d8;border-radius:6px;background:#2f67d8;color:#fff;padding:0 10px;font-size:.82rem;cursor:pointer}.designer-toolbar-menu-group button[disabled]{opacity:.6;cursor:not-allowed}.designer-main{min-height:0;min-width:0;width:100%;overflow:hidden;direction:ltr}.designer-main-splitter{height:100%;width:100%;min-width:0;direction:ltr}.designer-toolbox{font-size:3rem}.designer-toolbox ul{list-style-type:none;padding:0;margin:0;width:100%}.designer-toolbox li{float:left;margin-left:5px;margin-top:5px}.designer-toolbox .tool-groups{display:block}.designer-toolbox .tool-group-item{float:none;margin-left:0;margin-top:10px;clear:both}.designer-toolbox .tool-group-title{font-size:.8rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;opacity:.7;margin:0 0 4px 6px}.designer-toolbox .tool-group-list{display:flex;flex-wrap:wrap;align-items:flex-start;gap:4px}.designer-toolbox .tool-group-list li{float:none;margin-left:0;margin-top:0}.designer-toolbox .props{font-size:1rem}.designer-toolbox .props li{float:none;padding-bottom:15px}.designer-toolbox .prop-section{width:100%;border:1px solid inherit;border-radius:6px;margin-top:8px;background:transparent;padding:6px 8px 2px}.designer-toolbox .prop-section-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;cursor:pointer;color:inherit;margin-bottom:6px}.palette-panel{height:100%;overflow:auto;width:100%}.designer-color-alpha-row{display:flex;align-items:center;gap:8px;margin-top:6px;margin-bottom:6px}.designer-color-alpha-label{font-size:.8rem;min-width:40px}.designer-color-alpha-row input[type=range]{flex:1 1 auto;min-width:90px}.designer-color-alpha-row input[type=number]{width:72px}.properties-panel{height:100%;overflow:hidden;width:100%;display:flex;flex-direction:column;min-height:0;padding-right:10px;padding-left:10px;padding-top:10px}.properties-panel>.designer-toolbox{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:stable}:host ::ng-deep .designer-left-bottom-splitter .p-splitterpanel{min-height:0!important;overflow:hidden!important}.hierarchy-panel{height:100%;width:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden;padding:6px}.hierarchy-panel-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#4b5563;margin:0 0 6px}.hierarchy-tree-wrap{flex:1 1 auto;min-height:0;border:1px solid inherit;border-radius:6px;background:transparent;overflow:auto;padding:4px}:host ::ng-deep .hierarchy-tree-wrap .p-tree{border:none;padding:0;height:100%;background:transparent}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content{border:1px solid transparent;border-radius:4px;background:transparent!important;color:inherit!important;padding:0!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight{border-color:transparent!important;background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content:hover{background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label{display:inline-block;border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label:hover{border-color:#94a3b8}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label.active{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight .hierarchy-node-label{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}.designer-hierarchy-node{cursor:pointer;border:1px solid transparent;border-radius:4px;padding:2px 6px;font-size:.82rem;line-height:1.25rem}.designer-hierarchy-node-label{cursor:pointer;display:inline-block;width:100%}.designer-hierarchy-node.active{border-color:#3b82f6;background:#eff6ff;color:#1d4ed8}.designer-footer-item.designer-tree-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px}.dashboard-design{height:100%;min-height:0;min-width:0;width:100%;overflow:auto;direction:ltr;position:relative}.designer-selection-rect{position:absolute;border:1px dashed #3b82f6;background:#3b82f61f;pointer-events:none;z-index:10000}:host ::ng-deep .designer-multi-selected{outline:2px solid #3b82f6!important;outline-offset:1px}:host ::ng-deep .dashboard-design.designer-hide-datasources .datasource-descriptor{display:none!important}.current-dashboard-title{font-weight:600;margin-right:12px}.designer-footer{display:flex;align-items:center;gap:8px;padding:6px 8px;border-top:1px solid inherit;background:transparent;min-height:36px;flex:0 0 auto;width:100%;overflow:auto}.designer-footer-label{font-size:.85rem;font-weight:600;color:inherit;white-space:nowrap}.designer-footer-list{display:flex;align-items:center;gap:6px;overflow-x:auto;overflow-y:hidden;white-space:nowrap;width:100%}.designer-footer-item{border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer;white-space:nowrap}.designer-footer-item.active{border-color:#3b82f6;background:transparent;color:#1d4ed8}:host ::ng-deep .designer-footer-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px;box-shadow:inset 0 0 0 1px #3b82f6!important}.route-popup-form{display:flex;flex-direction:column;gap:8px}.route-popup-form label{font-size:.85rem;font-weight:600;color:#2f3f58}.route-popup-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.route-popup-btn{border:1px solid inherit;background:transparent;color:inherit;border-radius:6px;padding:7px 12px;font-size:.85rem;cursor:pointer}.route-popup-btn-primary{border-color:#2f67d8;background:#2f67d8;color:#fff}.p-colorpicker{border:1px solid black;border-radius:4px}.dashboard-design.designer-snap-grid{background-image:linear-gradient(to right,rgba(128,128,128,.16) 1px,transparent 1px),linear-gradient(to bottom,rgba(128,128,128,.16) 1px,transparent 1px);background-size:var(--wuic-grid-size, 10px) var(--wuic-grid-size, 10px)}\n"] }]
11912
13216
  }], ctorParameters: () => [{ type: i1$1.ActivatedRoute }, { type: DataProviderService }, { type: MetadataProviderService }, { type: UserInfoService }, { type: i0.ChangeDetectorRef }, { type: TranslationManagerService }, { type: MetadataEditorService }, { type: ChatbotHostRegistryService }, { type: WorkflowRuntimeMetadataService }], propDecorators: { onWindowHashChange: [{
11913
13217
  type: HostListener,
11914
13218
  args: ['window:hashchange']
@@ -11930,7 +13234,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
11930
13234
  }], onWindowChange: [{
11931
13235
  type: HostListener,
11932
13236
  args: ['window:change']
13237
+ }], canvasRef: [{
13238
+ type: ViewChild,
13239
+ args: ['canvas']
11933
13240
  }] } });
11934
13241
 
11935
13242
  export { DesignerComponent };
11936
- //# sourceMappingURL=wuic-framework-lib-designer.component-DA7abiLW.mjs.map
13243
+ //# sourceMappingURL=wuic-framework-lib-designer.component-Bd2fwl6H.mjs.map