@zentto/report-designer 1.0.0 → 1.2.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.
@@ -105,7 +105,10 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
105
105
  this._zoom = 1.5; // Default 150% for comfortable editing
106
106
  this._showPageSetup = false;
107
107
  this._showBandMenu = false;
108
+ this._mobileLeftOpen = false;
109
+ this._mobileRightOpen = false;
108
110
  this._guides = { x: [], y: [] };
111
+ this._collapsedSections = new Set();
109
112
  this._pageSetupDraft = null;
110
113
  this._clipboard = [];
111
114
  this._saveTimer = null;
@@ -131,6 +134,14 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
131
134
  get _unit() {
132
135
  return this._layout.pageSize.unit;
133
136
  }
137
+ _toggleSection(section) {
138
+ const next = new Set(this._collapsedSections);
139
+ if (next.has(section))
140
+ next.delete(section);
141
+ else
142
+ next.add(section);
143
+ this._collapsedSections = next;
144
+ }
134
145
  // Helper: single selected element id (for property panel, backward compat)
135
146
  get _selectedElementId() {
136
147
  if (this._selectedElementIds.size === 1) {
@@ -177,6 +188,8 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
177
188
  padding: 6px 12px;
178
189
  background: var(--zrd-panel-bg);
179
190
  border-bottom: 1px solid var(--zrd-border);
191
+ flex-wrap: wrap;
192
+ min-height: 36px;
180
193
  }
181
194
  .top-toolbar button {
182
195
  background: none;
@@ -288,10 +301,12 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
288
301
  background: #d0d0d0;
289
302
  display: flex;
290
303
  justify-content: center;
304
+ -webkit-overflow-scrolling: touch;
291
305
  }
292
306
  .canvas-scroll-container {
293
307
  transform-origin: top center;
294
308
  transition: transform 0.1s ease;
309
+ min-width: min-content;
295
310
  }
296
311
  .canvas-wrapper {
297
312
  position: relative;
@@ -384,6 +399,7 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
384
399
  cursor: pointer;
385
400
  user-select: none;
386
401
  gap: 6px;
402
+ overflow: hidden;
387
403
  }
388
404
  .band-header:hover { filter: brightness(0.95); }
389
405
  .band-header.selected { box-shadow: inset 0 0 0 2px var(--zrd-accent); color: var(--zrd-accent); }
@@ -391,6 +407,10 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
391
407
  display: flex;
392
408
  align-items: center;
393
409
  gap: 6px;
410
+ overflow: hidden;
411
+ white-space: nowrap;
412
+ text-overflow: ellipsis;
413
+ min-width: 0;
394
414
  }
395
415
  .band-type-icon {
396
416
  font-size: 13px;
@@ -505,79 +525,306 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
505
525
  box-sizing: border-box;
506
526
  }
507
527
 
508
- /* ─── Right Panel (Properties) ─── */
528
+ /* ─── Right Panel (Figma-style Properties) ─── */
509
529
  .right-panel {
510
530
  background: var(--zrd-panel-bg);
511
531
  border-left: 1px solid var(--zrd-border);
512
532
  overflow-y: auto;
513
- padding: 12px;
533
+ overflow-x: hidden;
534
+ scrollbar-width: thin;
535
+ scrollbar-color: #ccc transparent;
514
536
  }
537
+ .right-panel::-webkit-scrollbar { width: 6px; }
538
+ .right-panel::-webkit-scrollbar-thumb { background: #ccc; border-radius: 3px; }
539
+
515
540
  .prop-section {
516
- margin-bottom: 12px;
541
+ border-bottom: 1px solid var(--zrd-border);
542
+ padding: 10px 12px;
543
+ }
544
+ .prop-section:last-child { border-bottom: none; }
545
+
546
+ .prop-section-header {
547
+ display: flex;
548
+ align-items: center;
549
+ justify-content: space-between;
550
+ cursor: pointer;
551
+ user-select: none;
552
+ margin-bottom: 8px;
517
553
  }
518
- .prop-section h4 {
554
+ .prop-section-header h4 {
519
555
  font-size: 11px;
556
+ font-weight: 600;
520
557
  text-transform: uppercase;
558
+ letter-spacing: 0.5px;
559
+ color: var(--zrd-text);
560
+ margin: 0;
561
+ }
562
+ .prop-section-header .collapse-icon {
563
+ font-size: 10px;
521
564
  color: var(--zrd-text-muted);
522
- margin-bottom: 6px;
565
+ transition: transform 0.15s;
566
+ }
567
+ .prop-section-header .collapse-icon.collapsed {
568
+ transform: rotate(-90deg);
569
+ }
570
+ .prop-section-body {
571
+ display: grid;
572
+ gap: 4px;
523
573
  }
574
+ .prop-section-body.collapsed {
575
+ display: none;
576
+ }
577
+
578
+ /* ─── Property Rows ─── */
524
579
  .prop-row {
525
- display: flex;
580
+ display: grid;
581
+ grid-template-columns: 68px 1fr;
526
582
  align-items: center;
527
- gap: 4px;
528
- margin-bottom: 4px;
583
+ gap: 6px;
584
+ min-height: 28px;
585
+ }
586
+ .prop-row-full {
587
+ grid-template-columns: 1fr;
529
588
  }
530
589
  .prop-label {
531
- width: 70px;
532
590
  font-size: 11px;
533
591
  color: var(--zrd-text-muted);
534
- flex-shrink: 0;
592
+ white-space: nowrap;
593
+ overflow: hidden;
594
+ text-overflow: ellipsis;
595
+ padding-left: 2px;
535
596
  }
597
+
598
+ /* ─── Inputs (Figma-style) ─── */
536
599
  .prop-input {
537
- flex: 1;
538
- border: 1px solid var(--zrd-border);
539
- border-radius: 3px;
540
- padding: 3px 6px;
600
+ width: 100%;
601
+ border: 1px solid transparent;
602
+ border-radius: 4px;
603
+ padding: 4px 8px;
541
604
  font-size: 12px;
542
- background: white;
605
+ background: var(--zrd-bg);
543
606
  color: var(--zrd-text);
607
+ transition: border-color 0.15s, background 0.15s;
608
+ outline: none;
609
+ min-width: 0;
544
610
  }
545
- .prop-input:focus { outline: none; border-color: var(--zrd-accent); }
546
- select.prop-input { padding: 2px 4px; }
547
- .prop-unit {
611
+ .prop-input:hover {
612
+ border-color: var(--zrd-border);
613
+ }
614
+ .prop-input:focus {
615
+ border-color: var(--zrd-accent);
616
+ background: white;
617
+ }
618
+ select.prop-input {
619
+ padding: 3px 6px;
620
+ cursor: pointer;
621
+ appearance: none;
622
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23888'/%3E%3C/svg%3E");
623
+ background-repeat: no-repeat;
624
+ background-position: right 6px center;
625
+ padding-right: 20px;
626
+ }
627
+ textarea.prop-input {
628
+ min-height: 52px;
629
+ resize: vertical;
630
+ font-family: 'Consolas', 'Monaco', monospace;
631
+ font-size: 11px;
632
+ line-height: 1.5;
633
+ }
634
+
635
+ /* ─── Compact number inputs (X, Y, W, H grid) ─── */
636
+ .prop-grid-2x2 {
637
+ display: grid;
638
+ grid-template-columns: 1fr 1fr;
639
+ gap: 4px;
640
+ }
641
+ .prop-grid-item {
642
+ display: flex;
643
+ align-items: center;
644
+ background: var(--zrd-bg);
645
+ border: 1px solid transparent;
646
+ border-radius: 4px;
647
+ padding: 2px 6px;
648
+ gap: 4px;
649
+ transition: border-color 0.15s;
650
+ }
651
+ .prop-grid-item:hover { border-color: var(--zrd-border); }
652
+ .prop-grid-item:focus-within { border-color: var(--zrd-accent); }
653
+ .prop-grid-item .prop-grid-label {
548
654
  font-size: 10px;
549
- color: var(--zrd-text-muted);
550
- width: 22px;
655
+ color: var(--zrd-accent);
656
+ font-weight: 600;
657
+ width: 12px;
658
+ text-align: center;
551
659
  flex-shrink: 0;
552
660
  }
661
+ .prop-grid-item input {
662
+ border: none;
663
+ background: transparent;
664
+ width: 100%;
665
+ font-size: 12px;
666
+ color: var(--zrd-text);
667
+ outline: none;
668
+ padding: 2px 0;
669
+ min-width: 0;
670
+ }
553
671
 
554
- .btn-danger {
555
- background: var(--zrd-danger);
556
- color: white;
672
+ /* ─── Color picker (Figma-style swatch + input) ─── */
673
+ .prop-color-row {
674
+ display: flex;
675
+ align-items: center;
676
+ gap: 6px;
677
+ }
678
+ .prop-color-swatch {
679
+ width: 24px;
680
+ height: 24px;
681
+ border-radius: 4px;
682
+ border: 1px solid var(--zrd-border);
683
+ cursor: pointer;
684
+ flex-shrink: 0;
685
+ position: relative;
686
+ overflow: hidden;
687
+ }
688
+ .prop-color-swatch input[type="color"] {
689
+ position: absolute;
690
+ inset: -4px;
691
+ width: calc(100% + 8px);
692
+ height: calc(100% + 8px);
693
+ cursor: pointer;
557
694
  border: none;
695
+ padding: 0;
696
+ }
697
+ .prop-color-hex {
698
+ border: 1px solid transparent;
558
699
  border-radius: 4px;
559
- padding: 4px 10px;
700
+ padding: 4px 8px;
701
+ font-size: 11px;
702
+ font-family: 'Consolas', 'Monaco', monospace;
703
+ background: var(--zrd-bg);
704
+ color: var(--zrd-text);
705
+ width: 80px;
706
+ outline: none;
707
+ }
708
+ .prop-color-hex:hover { border-color: var(--zrd-border); }
709
+ .prop-color-hex:focus { border-color: var(--zrd-accent); }
710
+
711
+ /* ─── Toggle switches ─── */
712
+ .prop-toggle {
713
+ display: flex;
714
+ align-items: center;
715
+ gap: 8px;
716
+ }
717
+ .prop-switch {
718
+ position: relative;
719
+ width: 32px;
720
+ height: 18px;
721
+ border-radius: 9px;
722
+ background: #ccc;
723
+ cursor: pointer;
724
+ transition: background 0.2s;
725
+ flex-shrink: 0;
726
+ }
727
+ .prop-switch.active { background: var(--zrd-accent); }
728
+ .prop-switch::after {
729
+ content: '';
730
+ position: absolute;
731
+ top: 2px;
732
+ left: 2px;
733
+ width: 14px;
734
+ height: 14px;
735
+ border-radius: 50%;
736
+ background: white;
737
+ box-shadow: 0 1px 3px rgba(0,0,0,0.2);
738
+ transition: transform 0.2s;
739
+ }
740
+ .prop-switch.active::after { transform: translateX(14px); }
741
+
742
+ /* ─── Segmented buttons (alignment, etc.) ─── */
743
+ .prop-segmented {
744
+ display: flex;
745
+ border: 1px solid var(--zrd-border);
746
+ border-radius: 4px;
747
+ overflow: hidden;
748
+ }
749
+ .prop-seg-btn {
750
+ flex: 1;
751
+ padding: 4px 0;
752
+ background: var(--zrd-bg);
753
+ border: none;
754
+ border-right: 1px solid var(--zrd-border);
755
+ cursor: pointer;
756
+ font-size: 12px;
757
+ color: var(--zrd-text-muted);
758
+ transition: background 0.15s;
759
+ text-align: center;
760
+ }
761
+ .prop-seg-btn:last-child { border-right: none; }
762
+ .prop-seg-btn:hover { background: var(--zrd-accent-light); }
763
+ .prop-seg-btn.active { background: var(--zrd-accent); color: white; }
764
+
765
+ /* ─── Font size + family row ─── */
766
+ .prop-font-row {
767
+ display: grid;
768
+ grid-template-columns: 1fr 60px;
769
+ gap: 4px;
770
+ }
771
+
772
+ /* ─── Delete button ─── */
773
+ .btn-danger {
774
+ background: none;
775
+ color: var(--zrd-danger);
776
+ border: 1px solid var(--zrd-danger);
777
+ border-radius: 4px;
778
+ padding: 6px 10px;
560
779
  cursor: pointer;
561
780
  font-size: 12px;
562
781
  width: 100%;
782
+ transition: background 0.15s;
563
783
  }
564
- .btn-danger:hover { opacity: 0.9; }
784
+ .btn-danger:hover { background: var(--zrd-danger); color: white; }
565
785
 
786
+ /* ─── Z-order buttons ─── */
787
+ .z-order-buttons {
788
+ display: flex;
789
+ gap: 2px;
790
+ }
566
791
  .btn-small {
567
- background: none;
568
- border: 1px solid var(--zrd-border);
569
- border-radius: 3px;
570
- padding: 3px 8px;
792
+ background: var(--zrd-bg);
793
+ border: 1px solid transparent;
794
+ border-radius: 4px;
795
+ padding: 4px 8px;
571
796
  cursor: pointer;
572
797
  font-size: 11px;
573
798
  color: var(--zrd-text);
799
+ transition: all 0.15s;
574
800
  }
575
- .btn-small:hover { background: var(--zrd-accent-light); }
801
+ .btn-small:hover { background: var(--zrd-accent-light); border-color: var(--zrd-border); }
576
802
 
577
- .z-order-buttons {
578
- display: flex;
803
+ .prop-unit {
804
+ font-size: 10px;
805
+ color: var(--zrd-text-muted);
806
+ width: 20px;
807
+ flex-shrink: 0;
808
+ text-align: center;
809
+ }
810
+
811
+ /* ─── Section-specific colors ─── */
812
+ .prop-section[data-section="position"] .prop-section-header h4 { color: var(--zrd-accent); }
813
+ .prop-section[data-section="style"] .prop-section-header h4 { color: #7c3aed; }
814
+ .prop-section[data-section="visibility"] .prop-section-header h4 { color: #0d9488; }
815
+ .prop-section[data-section="data"] .prop-section-header h4 { color: #ea580c; }
816
+
817
+ /* ─── Element type badge ─── */
818
+ .element-type-badge {
819
+ display: inline-flex;
820
+ align-items: center;
579
821
  gap: 4px;
580
- flex-wrap: wrap;
822
+ padding: 3px 10px;
823
+ border-radius: 10px;
824
+ font-size: 11px;
825
+ font-weight: 600;
826
+ text-transform: uppercase;
827
+ letter-spacing: 0.3px;
581
828
  }
582
829
 
583
830
  /* ─── Data Source Panel ─── */
@@ -607,6 +854,8 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
607
854
  z-index: 1000;
608
855
  min-width: 160px;
609
856
  padding: 4px 0;
857
+ max-height: calc(100vh - 40px);
858
+ overflow-y: auto;
610
859
  }
611
860
  .context-menu-item {
612
861
  padding: 6px 12px;
@@ -695,6 +944,7 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
695
944
  border-bottom: 1px solid var(--zrd-border);
696
945
  font-size: 12px;
697
946
  flex-wrap: wrap;
947
+ min-height: 32px;
698
948
  }
699
949
  .page-setup-group {
700
950
  display: flex;
@@ -778,6 +1028,160 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
778
1028
  font-size: 10px;
779
1029
  font-family: monospace;
780
1030
  }
1031
+
1032
+ /* ─── Mobile Panel Toggle Buttons ─── */
1033
+ .mobile-panel-toggles {
1034
+ display: none;
1035
+ gap: 8px;
1036
+ padding: 8px;
1037
+ }
1038
+ @media (max-width: 768px) {
1039
+ .mobile-panel-toggles {
1040
+ display: flex;
1041
+ }
1042
+ }
1043
+ .mobile-toggle-btn {
1044
+ padding: 6px 12px;
1045
+ border: 1px solid var(--zrd-border);
1046
+ border-radius: 4px;
1047
+ background: var(--zrd-panel-bg);
1048
+ cursor: pointer;
1049
+ font-size: 12px;
1050
+ color: var(--zrd-text);
1051
+ }
1052
+ .mobile-toggle-btn.active {
1053
+ background: var(--zrd-accent);
1054
+ color: white;
1055
+ border-color: var(--zrd-accent);
1056
+ }
1057
+ .mobile-overlay {
1058
+ position: fixed;
1059
+ inset: 0;
1060
+ background: rgba(0,0,0,0.3);
1061
+ z-index: 499;
1062
+ }
1063
+
1064
+ /* ─── Responsive: Grid Layout ─── */
1065
+ @media (max-width: 1024px) {
1066
+ .designer {
1067
+ grid-template-columns: 180px 1fr 220px;
1068
+ }
1069
+ }
1070
+ @media (max-width: 768px) {
1071
+ .designer {
1072
+ grid-template-columns: 1fr;
1073
+ grid-template-rows: auto auto 1fr auto;
1074
+ }
1075
+ .left-panel {
1076
+ display: none;
1077
+ position: fixed;
1078
+ left: 0;
1079
+ top: 0;
1080
+ bottom: 0;
1081
+ width: 260px;
1082
+ z-index: 500;
1083
+ box-shadow: 4px 0 20px rgba(0,0,0,0.2);
1084
+ }
1085
+ .left-panel.mobile-open {
1086
+ display: block;
1087
+ }
1088
+ .right-panel {
1089
+ display: none;
1090
+ position: fixed;
1091
+ right: 0;
1092
+ top: 0;
1093
+ bottom: 0;
1094
+ width: 280px;
1095
+ z-index: 500;
1096
+ box-shadow: -4px 0 20px rgba(0,0,0,0.2);
1097
+ }
1098
+ .right-panel.mobile-open {
1099
+ display: block;
1100
+ }
1101
+ }
1102
+
1103
+ /* ─── Responsive: Top Toolbar ─── */
1104
+ @media (max-width: 768px) {
1105
+ .top-toolbar {
1106
+ padding: 4px 8px;
1107
+ gap: 4px;
1108
+ }
1109
+ .top-toolbar button {
1110
+ padding: 3px 6px;
1111
+ font-size: 11px;
1112
+ }
1113
+ .top-toolbar .report-name {
1114
+ font-size: 12px;
1115
+ max-width: 120px;
1116
+ overflow: hidden;
1117
+ text-overflow: ellipsis;
1118
+ white-space: nowrap;
1119
+ }
1120
+ .zoom-controls {
1121
+ border-left: none;
1122
+ padding-left: 0;
1123
+ margin-left: 0;
1124
+ }
1125
+ }
1126
+
1127
+ /* ─── Responsive: Page Setup Bar ─── */
1128
+ @media (max-width: 900px) {
1129
+ .page-setup-bar {
1130
+ gap: 6px;
1131
+ padding: 4px 8px;
1132
+ }
1133
+ .page-setup-input {
1134
+ width: 36px;
1135
+ }
1136
+ .page-setup-select {
1137
+ max-width: 140px;
1138
+ }
1139
+ }
1140
+ @media (max-width: 600px) {
1141
+ .page-setup-bar {
1142
+ font-size: 11px;
1143
+ }
1144
+ .page-setup-label {
1145
+ display: none;
1146
+ }
1147
+ }
1148
+
1149
+ /* ─── Responsive: Toolbox Touch Targets ─── */
1150
+ @media (max-width: 768px) {
1151
+ .toolbox-item {
1152
+ padding: 12px 10px;
1153
+ min-height: 44px;
1154
+ }
1155
+ .toolbox-icon {
1156
+ width: 32px;
1157
+ height: 32px;
1158
+ font-size: 13px;
1159
+ }
1160
+ }
1161
+
1162
+ /* ─── Responsive: Properties Panel ─── */
1163
+ @media (max-width: 768px) {
1164
+ .prop-label {
1165
+ width: 60px;
1166
+ font-size: 10px;
1167
+ }
1168
+ .prop-input {
1169
+ font-size: 11px;
1170
+ padding: 4px 4px;
1171
+ }
1172
+ }
1173
+
1174
+ /* ─── Responsive: Dialog ─── */
1175
+ @media (max-width: 600px) {
1176
+ .dialog {
1177
+ min-width: auto;
1178
+ max-width: calc(100vw - 32px);
1179
+ margin: 16px;
1180
+ }
1181
+ .dialog-body {
1182
+ padding: 12px;
1183
+ }
1184
+ }
781
1185
  `; }
782
1186
  // ─── Lifecycle ──────────────────────────────────────────────────
783
1187
  connectedCallback() {
@@ -1375,7 +1779,11 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
1375
1779
  ${this._renderTopToolbar()}
1376
1780
  ${this._renderPageSetupBar()}
1377
1781
 
1378
- <div class="left-panel">
1782
+ ${this._mobileLeftOpen || this._mobileRightOpen ? html `
1783
+ <div class="mobile-overlay" @click=${() => { this._mobileLeftOpen = false; this._mobileRightOpen = false; }}></div>
1784
+ ` : nothing}
1785
+
1786
+ <div class="left-panel ${this._mobileLeftOpen ? 'mobile-open' : ''}">
1379
1787
  <div class="panel-tabs">
1380
1788
  <div class="panel-tab ${this._activePanel === 'toolbox' ? 'active' : ''}"
1381
1789
  @click=${() => this._activePanel = 'toolbox'}>Toolbox</div>
@@ -1398,6 +1806,16 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
1398
1806
  this.requestUpdate();
1399
1807
  }
1400
1808
  }}>
1809
+ <div class="mobile-panel-toggles">
1810
+ <button class="mobile-toggle-btn ${this._mobileLeftOpen ? 'active' : ''}"
1811
+ @click=${(e) => { e.stopPropagation(); this._mobileLeftOpen = !this._mobileLeftOpen; this._mobileRightOpen = false; }}>
1812
+ Toolbox
1813
+ </button>
1814
+ <button class="mobile-toggle-btn ${this._mobileRightOpen ? 'active' : ''}"
1815
+ @click=${(e) => { e.stopPropagation(); this._mobileRightOpen = !this._mobileRightOpen; this._mobileLeftOpen = false; }}>
1816
+ Properties
1817
+ </button>
1818
+ </div>
1401
1819
  <div class="canvas-scroll-container" style="transform:scale(${this._zoom});padding:20px;">
1402
1820
  <div class="canvas-wrapper" style="width:${pageWidthPx}px;">
1403
1821
  ${this._renderRuler(pageWidthPx)}
@@ -1415,7 +1833,7 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
1415
1833
  </div>
1416
1834
  </div>
1417
1835
 
1418
- <div class="right-panel">
1836
+ <div class="right-panel ${this._mobileRightOpen ? 'mobile-open' : ''}">
1419
1837
  ${this._renderPropertiesPanel()}
1420
1838
  </div>
1421
1839
  </div>
@@ -2221,87 +2639,121 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
2221
2639
  _renderPropertiesPanel() {
2222
2640
  const el = this._getSelectedElement();
2223
2641
  const unitLabel = this._unit;
2224
- // Multiple selection info
2642
+ const collapsed = this._collapsedSections;
2643
+ const sectionHeader = (key, title) => html `
2644
+ <div class="prop-section-header" @click=${() => this._toggleSection(key)}>
2645
+ <h4>${title}</h4>
2646
+ <span class="collapse-icon ${collapsed.has(key) ? 'collapsed' : ''}">\u25BE</span>
2647
+ </div>
2648
+ `;
2649
+ // ── Multiple selection ──
2225
2650
  if (this._selectedElementIds.size > 1) {
2226
2651
  return html `
2227
2652
  <div class="prop-section">
2228
- <h4>Multiple Selection</h4>
2229
- <p style="font-size:12px;color:var(--zrd-text-muted);">${this._selectedElementIds.size} elements selected</p>
2230
- <p style="font-size:11px;color:var(--zrd-text-muted);margin-top:4px;">Use Ctrl+C/V to copy/paste, Del to delete.</p>
2653
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
2654
+ <span class="element-type-badge" style="background:#fff3e0;color:#e65100;">\u2750 ${this._selectedElementIds.size} selected</span>
2655
+ </div>
2656
+ <p style="font-size:11px;color:var(--zrd-text-muted);">Use Ctrl+C/V to copy/paste, Del to delete.</p>
2231
2657
  </div>
2232
- <div class="prop-section" style="margin-top:20px;">
2658
+ <div class="prop-section">
2233
2659
  <button class="btn-danger" @click=${this._deleteSelectedElements}>Delete All Selected</button>
2234
2660
  </div>
2235
2661
  `;
2236
2662
  }
2237
- // Band properties (Section Expert — Crystal Reports style)
2663
+ // ── Band properties ──
2238
2664
  if (!el && this._selectedBandId) {
2239
2665
  const band = this._layout.bands.find(b => b.id === this._selectedBandId);
2240
2666
  if (!band)
2241
2667
  return html `<div style="padding:12px;color:var(--zrd-text-muted)">Select an element</div>`;
2242
2668
  return html `
2243
2669
  <div class="prop-section">
2244
- <h4>${BAND_ICONS[band.type]} ${BAND_LABELS[band.type]}</h4>
2245
- <div style="font-size:11px;color:var(--zrd-text-muted);margin-bottom:8px;">${band.elements.length} elements</div>
2670
+ <div style="display:flex;align-items:center;gap:8px;">
2671
+ <span style="font-size:16px;">${BAND_ICONS[band.type]}</span>
2672
+ <div>
2673
+ <div style="font-weight:600;font-size:13px;">${BAND_LABELS[band.type]}</div>
2674
+ <div style="font-size:10px;color:var(--zrd-text-muted);">${band.elements.length} elements</div>
2675
+ </div>
2676
+ </div>
2246
2677
  </div>
2247
2678
 
2248
- <div class="prop-section">
2249
- <h4>Dimensions</h4>
2250
- <div class="prop-row">
2251
- <span class="prop-label">Height</span>
2252
- <input class="prop-input" type="number" step="0.5" .value=${String(band.height)}
2253
- @change=${(e) => this._updateBandHeight(band.id, Number(e.target.value))} />
2254
- <span class="prop-unit">${unitLabel}</span>
2255
- </div>
2256
- <div class="prop-row">
2257
- <span class="prop-label">Background</span>
2258
- <input class="prop-input" type="color" .value=${band.backgroundColor || '#ffffff'}
2259
- @change=${(e) => { band.backgroundColor = e.target.value; this._commitChange(); }} />
2679
+ <div class="prop-section" data-section="position">
2680
+ ${sectionHeader('band-dim', 'Dimensions')}
2681
+ <div class="prop-section-body ${collapsed.has('band-dim') ? 'collapsed' : ''}">
2682
+ <div class="prop-row">
2683
+ <span class="prop-label">Height</span>
2684
+ <div style="display:flex;align-items:center;gap:4px;">
2685
+ <input class="prop-input" type="number" step="0.5" .value=${String(band.height)}
2686
+ @change=${(e) => this._updateBandHeight(band.id, Number(e.target.value))} />
2687
+ <span class="prop-unit">${unitLabel}</span>
2688
+ </div>
2689
+ </div>
2690
+ <div class="prop-row">
2691
+ <span class="prop-label">Background</span>
2692
+ <div class="prop-color-row">
2693
+ <div class="prop-color-swatch" style="background:${band.backgroundColor || '#ffffff'}">
2694
+ <input type="color" .value=${band.backgroundColor || '#ffffff'}
2695
+ @change=${(e) => { band.backgroundColor = e.target.value; this._commitChange(); }} />
2696
+ </div>
2697
+ <input class="prop-color-hex" .value=${band.backgroundColor || '#ffffff'}
2698
+ @change=${(e) => { band.backgroundColor = e.target.value; this._commitChange(); }} />
2699
+ </div>
2700
+ </div>
2260
2701
  </div>
2261
2702
  </div>
2262
2703
 
2263
2704
  ${band.type === 'detail' || band.type === 'groupHeader' || band.type === 'groupFooter' ? html `
2264
- <div class="prop-section">
2265
- <h4>Data</h4>
2266
- <div class="prop-row">
2267
- <span class="prop-label">DataSource</span>
2268
- <select class="prop-input" .value=${band.dataSource || ''}
2269
- @change=${(e) => { band.dataSource = e.target.value; this._commitChange(); }}>
2270
- <option value="">-- none --</option>
2271
- ${this._layout.dataSources.map(ds => html `<option value="${ds.id}">${ds.name}</option>`)}
2272
- </select>
2273
- </div>
2274
- ${band.type === 'groupHeader' || band.type === 'groupFooter' ? html `
2705
+ <div class="prop-section" data-section="data">
2706
+ ${sectionHeader('band-data', 'Data')}
2707
+ <div class="prop-section-body ${collapsed.has('band-data') ? 'collapsed' : ''}">
2275
2708
  <div class="prop-row">
2276
- <span class="prop-label">Group By</span>
2277
- <input class="prop-input" .value=${band.groupField || ''}
2278
- placeholder="field name"
2279
- @change=${(e) => { band.groupField = e.target.value; this._commitChange(); }} />
2709
+ <span class="prop-label">DataSource</span>
2710
+ <select class="prop-input" .value=${band.dataSource || ''}
2711
+ @change=${(e) => { band.dataSource = e.target.value; this._commitChange(); }}>
2712
+ <option value="">-- none --</option>
2713
+ ${this._layout.dataSources.map(ds => html `<option value="${ds.id}">${ds.name}</option>`)}
2714
+ </select>
2280
2715
  </div>
2281
- ` : nothing}
2716
+ ${band.type === 'groupHeader' || band.type === 'groupFooter' ? html `
2717
+ <div class="prop-row">
2718
+ <span class="prop-label">Group By</span>
2719
+ <input class="prop-input" .value=${band.groupField || ''}
2720
+ placeholder="field name"
2721
+ @change=${(e) => { band.groupField = e.target.value; this._commitChange(); }} />
2722
+ </div>
2723
+ ` : nothing}
2724
+ </div>
2282
2725
  </div>
2283
2726
  ` : nothing}
2284
2727
 
2285
- <div class="prop-section">
2286
- <h4>Section Expert</h4>
2287
- <div class="prop-row">
2288
- <span class="prop-label">Visible</span>
2289
- <input type="checkbox" .checked=${band.visible !== false}
2290
- @change=${(e) => { band.visible = e.target.checked; this._commitChange(); }} />
2291
- </div>
2292
- <div class="prop-row">
2293
- <span class="prop-label">Keep Together</span>
2294
- <input type="checkbox" .checked=${band.keepTogether === true}
2295
- @change=${(e) => { band.keepTogether = e.target.checked; this._commitChange(); }} />
2296
- </div>
2297
- <div class="prop-row">
2298
- <span class="prop-label">Repeat Every Page</span>
2299
- <input type="checkbox" .checked=${band.repeatOnEveryPage === true}
2300
- @change=${(e) => { band.repeatOnEveryPage = e.target.checked; this._commitChange(); }} />
2728
+ <div class="prop-section" data-section="visibility">
2729
+ ${sectionHeader('band-expert', 'Section Expert')}
2730
+ <div class="prop-section-body ${collapsed.has('band-expert') ? 'collapsed' : ''}">
2731
+ <div class="prop-row">
2732
+ <span class="prop-label">Visible</span>
2733
+ <div class="prop-toggle">
2734
+ <div class="prop-switch ${band.visible !== false ? 'active' : ''}"
2735
+ @click=${() => { band.visible = !(band.visible !== false); this._commitChange(); }}></div>
2736
+ <span style="font-size:11px;color:var(--zrd-text-muted);">${band.visible !== false ? 'On' : 'Off'}</span>
2737
+ </div>
2738
+ </div>
2739
+ <div class="prop-row">
2740
+ <span class="prop-label">Keep Together</span>
2741
+ <div class="prop-toggle">
2742
+ <div class="prop-switch ${band.keepTogether === true ? 'active' : ''}"
2743
+ @click=${() => { band.keepTogether = !band.keepTogether; this._commitChange(); }}></div>
2744
+ </div>
2745
+ </div>
2746
+ <div class="prop-row">
2747
+ <span class="prop-label">Repeat Page</span>
2748
+ <div class="prop-toggle">
2749
+ <div class="prop-switch ${band.repeatOnEveryPage === true ? 'active' : ''}"
2750
+ @click=${() => { band.repeatOnEveryPage = !band.repeatOnEveryPage; this._commitChange(); }}></div>
2751
+ </div>
2752
+ </div>
2301
2753
  </div>
2302
2754
  </div>
2303
2755
 
2304
- <div class="prop-section" style="margin-top:16px;">
2756
+ <div class="prop-section">
2305
2757
  <button class="btn-danger" @click=${() => {
2306
2758
  this._layout.bands = this._layout.bands.filter(b => b.id !== band.id);
2307
2759
  this._selectedBandId = null;
@@ -2310,6 +2762,7 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
2310
2762
  </div>
2311
2763
  `;
2312
2764
  }
2765
+ // ── Nothing selected — help card ──
2313
2766
  if (!el)
2314
2767
  return html `
2315
2768
  <div class="help-card">
@@ -2328,244 +2781,322 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
2328
2781
  </div>
2329
2782
  </div>
2330
2783
  `;
2784
+ // ── Single element selected ──
2785
+ const typeBadgeColors = {
2786
+ text: 'background:#e3f2fd;color:#1565c0;',
2787
+ field: 'background:#e8f5e9;color:#2e7d32;',
2788
+ image: 'background:#f3e5f5;color:#7b1fa2;',
2789
+ line: 'background:#eceff1;color:#546e7a;',
2790
+ rect: 'background:#eceff1;color:#546e7a;',
2791
+ barcode: 'background:#fff3e0;color:#e65100;',
2792
+ pageNumber: 'background:#fce4ec;color:#c62828;',
2793
+ currentDate: 'background:#fff8e1;color:#f57f17;',
2794
+ };
2795
+ const badgeStyle = typeBadgeColors[el.type] || 'background:#f5f5f5;color:#333;';
2331
2796
  return html `
2332
- <div class="prop-section">
2333
- <h4>Position (${unitLabel})</h4>
2334
- <div class="prop-row">
2335
- <span class="prop-label">X</span>
2336
- <input class="prop-input" type="number" step="0.5" .value=${String(el.x)}
2337
- @change=${(e) => this._updateElementProp('x', Number(e.target.value))} />
2338
- <span class="prop-unit">${unitLabel}</span>
2339
- </div>
2340
- <div class="prop-row">
2341
- <span class="prop-label">Y</span>
2342
- <input class="prop-input" type="number" step="0.5" .value=${String(el.y)}
2343
- @change=${(e) => this._updateElementProp('y', Number(e.target.value))} />
2344
- <span class="prop-unit">${unitLabel}</span>
2345
- </div>
2346
- <div class="prop-row">
2347
- <span class="prop-label">Width</span>
2348
- <input class="prop-input" type="number" step="0.5" .value=${String(el.width)}
2349
- @change=${(e) => this._updateElementProp('width', Number(e.target.value))} />
2350
- <span class="prop-unit">${unitLabel}</span>
2351
- </div>
2352
- <div class="prop-row">
2353
- <span class="prop-label">Height</span>
2354
- <input class="prop-input" type="number" step="0.5" .value=${String(el.height)}
2355
- @change=${(e) => this._updateElementProp('height', Number(e.target.value))} />
2356
- <span class="prop-unit">${unitLabel}</span>
2357
- </div>
2797
+ <!-- Element type badge -->
2798
+ <div class="prop-section" style="display:flex;align-items:center;gap:8px;">
2799
+ <span class="element-type-badge" style="${badgeStyle}">${ELEMENT_TYPE_ICONS[el.type] || '?'} ${el.type}</span>
2358
2800
  </div>
2359
2801
 
2360
- <div class="prop-section">
2361
- <h4>Z-Order</h4>
2362
- <div class="z-order-buttons">
2363
- <button class="btn-small" @click=${() => this._changeZOrder(el.id, this._selectedBandId, 'front')} title="Bring to Front">\u2B06 Front</button>
2364
- <button class="btn-small" @click=${() => this._changeZOrder(el.id, this._selectedBandId, 'forward')} title="Bring Forward">\u2191 Fwd</button>
2365
- <button class="btn-small" @click=${() => this._changeZOrder(el.id, this._selectedBandId, 'backward')} title="Send Backward">\u2193 Bwd</button>
2366
- <button class="btn-small" @click=${() => this._changeZOrder(el.id, this._selectedBandId, 'back')} title="Send to Back">\u2B07 Back</button>
2802
+ <!-- Position -->
2803
+ <div class="prop-section" data-section="position">
2804
+ ${sectionHeader('el-pos', 'Position')}
2805
+ <div class="prop-section-body ${collapsed.has('el-pos') ? 'collapsed' : ''}">
2806
+ <div class="prop-grid-2x2">
2807
+ <div class="prop-grid-item">
2808
+ <span class="prop-grid-label">X</span>
2809
+ <input type="number" step="0.5" .value=${String(el.x)}
2810
+ @change=${(e) => this._updateElementProp('x', Number(e.target.value))} />
2811
+ <span class="prop-unit">${unitLabel}</span>
2812
+ </div>
2813
+ <div class="prop-grid-item">
2814
+ <span class="prop-grid-label">Y</span>
2815
+ <input type="number" step="0.5" .value=${String(el.y)}
2816
+ @change=${(e) => this._updateElementProp('y', Number(e.target.value))} />
2817
+ <span class="prop-unit">${unitLabel}</span>
2818
+ </div>
2819
+ <div class="prop-grid-item">
2820
+ <span class="prop-grid-label">W</span>
2821
+ <input type="number" step="0.5" .value=${String(el.width)}
2822
+ @change=${(e) => this._updateElementProp('width', Number(e.target.value))} />
2823
+ <span class="prop-unit">${unitLabel}</span>
2824
+ </div>
2825
+ <div class="prop-grid-item">
2826
+ <span class="prop-grid-label">H</span>
2827
+ <input type="number" step="0.5" .value=${String(el.height)}
2828
+ @change=${(e) => this._updateElementProp('height', Number(e.target.value))} />
2829
+ <span class="prop-unit">${unitLabel}</span>
2830
+ </div>
2831
+ </div>
2367
2832
  </div>
2368
2833
  </div>
2369
2834
 
2370
- <div class="prop-section">
2371
- <h4>Style</h4>
2372
- <div class="prop-row">
2373
- <span class="prop-label">Font Size</span>
2374
- <input class="prop-input" type="number" .value=${String(el.style?.fontSize || 10)}
2375
- @change=${(e) => this._updateElementStyle('fontSize', Number(e.target.value))} />
2376
- </div>
2377
- <div class="prop-row">
2378
- <span class="prop-label">Font</span>
2379
- <select class="prop-input" .value=${el.style?.fontFamily || 'Arial'}
2380
- @change=${(e) => this._updateElementStyle('fontFamily', e.target.value)}>
2381
- <option>Arial</option><option>Times New Roman</option><option>Courier New</option>
2382
- <option>Verdana</option><option>Georgia</option><option>Helvetica</option>
2383
- </select>
2384
- </div>
2385
- <div class="prop-row">
2386
- <span class="prop-label">Bold</span>
2387
- <input type="checkbox" .checked=${el.style?.fontWeight === 'bold'}
2388
- @change=${(e) => this._updateElementStyle('fontWeight', e.target.checked ? 'bold' : 'normal')} />
2389
- </div>
2390
- <div class="prop-row">
2391
- <span class="prop-label">Align</span>
2392
- <select class="prop-input" .value=${el.style?.textAlign || 'left'}
2393
- @change=${(e) => this._updateElementStyle('textAlign', e.target.value)}>
2394
- <option>left</option><option>center</option><option>right</option>
2395
- </select>
2396
- </div>
2397
- <div class="prop-row">
2398
- <span class="prop-label">Color</span>
2399
- <input class="prop-input" type="color" .value=${el.style?.color || '#333333'}
2400
- @change=${(e) => this._updateElementStyle('color', e.target.value)} />
2401
- </div>
2402
- <div class="prop-row">
2403
- <span class="prop-label">Bg Color</span>
2404
- <input class="prop-input" type="color" .value=${el.style?.backgroundColor || '#ffffff'}
2405
- @change=${(e) => this._updateElementStyle('backgroundColor', e.target.value)} />
2406
- </div>
2407
- <div class="prop-row">
2408
- <span class="prop-label">Italic</span>
2409
- <input type="checkbox" .checked=${el.style?.fontStyle === 'italic'}
2410
- @change=${(e) => this._updateElementStyle('fontStyle', e.target.checked ? 'italic' : 'normal')} />
2411
- </div>
2412
- <div class="prop-row">
2413
- <span class="prop-label">Underline</span>
2414
- <input type="checkbox" .checked=${el.style?.textDecoration === 'underline'}
2415
- @change=${(e) => this._updateElementStyle('textDecoration', e.target.checked ? 'underline' : 'none')} />
2416
- </div>
2417
- <div class="prop-row">
2418
- <span class="prop-label">V.Align</span>
2419
- <select class="prop-input" .value=${el.style?.verticalAlign || 'top'}
2420
- @change=${(e) => this._updateElementStyle('verticalAlign', e.target.value)}>
2421
- <option>top</option><option>middle</option><option>bottom</option>
2422
- </select>
2423
- </div>
2424
- <div class="prop-row">
2425
- <span class="prop-label">Border</span>
2426
- <input class="prop-input" .value=${el.style?.borderBottom || ''}
2427
- placeholder="1px solid #ccc"
2428
- @change=${(e) => {
2835
+ <!-- Style -->
2836
+ <div class="prop-section" data-section="style">
2837
+ ${sectionHeader('el-style', 'Style')}
2838
+ <div class="prop-section-body ${collapsed.has('el-style') ? 'collapsed' : ''}">
2839
+ <!-- Font family + size -->
2840
+ <div class="prop-font-row">
2841
+ <select class="prop-input" .value=${el.style?.fontFamily || 'Arial'}
2842
+ @change=${(e) => this._updateElementStyle('fontFamily', e.target.value)}>
2843
+ <option>Arial</option><option>Times New Roman</option><option>Courier New</option>
2844
+ <option>Verdana</option><option>Georgia</option><option>Helvetica</option>
2845
+ </select>
2846
+ <input class="prop-input" type="number" .value=${String(el.style?.fontSize || 10)}
2847
+ @change=${(e) => this._updateElementStyle('fontSize', Number(e.target.value))} />
2848
+ </div>
2849
+
2850
+ <!-- Bold / Italic / Underline segmented -->
2851
+ <div class="prop-row" style="grid-template-columns:68px 1fr;">
2852
+ <span class="prop-label">Format</span>
2853
+ <div class="prop-segmented">
2854
+ <button class="prop-seg-btn ${el.style?.fontWeight === 'bold' ? 'active' : ''}" style="font-weight:bold;"
2855
+ @click=${() => this._updateElementStyle('fontWeight', el.style?.fontWeight === 'bold' ? 'normal' : 'bold')}>B</button>
2856
+ <button class="prop-seg-btn ${el.style?.fontStyle === 'italic' ? 'active' : ''}" style="font-style:italic;"
2857
+ @click=${() => this._updateElementStyle('fontStyle', el.style?.fontStyle === 'italic' ? 'normal' : 'italic')}>I</button>
2858
+ <button class="prop-seg-btn ${el.style?.textDecoration === 'underline' ? 'active' : ''}" style="text-decoration:underline;"
2859
+ @click=${() => this._updateElementStyle('textDecoration', el.style?.textDecoration === 'underline' ? 'none' : 'underline')}>U</button>
2860
+ </div>
2861
+ </div>
2862
+
2863
+ <!-- Horizontal alignment segmented -->
2864
+ <div class="prop-row" style="grid-template-columns:68px 1fr;">
2865
+ <span class="prop-label">Align</span>
2866
+ <div class="prop-segmented">
2867
+ <button class="prop-seg-btn ${(el.style?.textAlign || 'left') === 'left' ? 'active' : ''}"
2868
+ @click=${() => this._updateElementStyle('textAlign', 'left')}>\u2190</button>
2869
+ <button class="prop-seg-btn ${el.style?.textAlign === 'center' ? 'active' : ''}"
2870
+ @click=${() => this._updateElementStyle('textAlign', 'center')}>\u2194</button>
2871
+ <button class="prop-seg-btn ${el.style?.textAlign === 'right' ? 'active' : ''}"
2872
+ @click=${() => this._updateElementStyle('textAlign', 'right')}>\u2192</button>
2873
+ </div>
2874
+ </div>
2875
+
2876
+ <!-- Vertical alignment segmented -->
2877
+ <div class="prop-row" style="grid-template-columns:68px 1fr;">
2878
+ <span class="prop-label">V.Align</span>
2879
+ <div class="prop-segmented">
2880
+ <button class="prop-seg-btn ${(el.style?.verticalAlign || 'top') === 'top' ? 'active' : ''}"
2881
+ @click=${() => this._updateElementStyle('verticalAlign', 'top')}>\u2191</button>
2882
+ <button class="prop-seg-btn ${el.style?.verticalAlign === 'middle' ? 'active' : ''}"
2883
+ @click=${() => this._updateElementStyle('verticalAlign', 'middle')}>\u2195</button>
2884
+ <button class="prop-seg-btn ${el.style?.verticalAlign === 'bottom' ? 'active' : ''}"
2885
+ @click=${() => this._updateElementStyle('verticalAlign', 'bottom')}>\u2193</button>
2886
+ </div>
2887
+ </div>
2888
+
2889
+ <!-- Text color -->
2890
+ <div class="prop-row">
2891
+ <span class="prop-label">Color</span>
2892
+ <div class="prop-color-row">
2893
+ <div class="prop-color-swatch" style="background:${el.style?.color || '#333333'}">
2894
+ <input type="color" .value=${el.style?.color || '#333333'}
2895
+ @change=${(e) => this._updateElementStyle('color', e.target.value)} />
2896
+ </div>
2897
+ <input class="prop-color-hex" .value=${el.style?.color || '#333333'}
2898
+ @change=${(e) => this._updateElementStyle('color', e.target.value)} />
2899
+ </div>
2900
+ </div>
2901
+
2902
+ <!-- Background color -->
2903
+ <div class="prop-row">
2904
+ <span class="prop-label">Bg Color</span>
2905
+ <div class="prop-color-row">
2906
+ <div class="prop-color-swatch" style="background:${el.style?.backgroundColor || '#ffffff'}">
2907
+ <input type="color" .value=${el.style?.backgroundColor || '#ffffff'}
2908
+ @change=${(e) => this._updateElementStyle('backgroundColor', e.target.value)} />
2909
+ </div>
2910
+ <input class="prop-color-hex" .value=${el.style?.backgroundColor || '#ffffff'}
2911
+ @change=${(e) => this._updateElementStyle('backgroundColor', e.target.value)} />
2912
+ </div>
2913
+ </div>
2914
+
2915
+ <!-- Border -->
2916
+ <div class="prop-row">
2917
+ <span class="prop-label">Border</span>
2918
+ <input class="prop-input" .value=${el.style?.borderBottom || ''}
2919
+ placeholder="1px solid #ccc"
2920
+ @change=${(e) => {
2429
2921
  const v = e.target.value;
2430
2922
  this._updateElementStyle('borderTop', v);
2431
2923
  this._updateElementStyle('borderRight', v);
2432
2924
  this._updateElementStyle('borderBottom', v);
2433
2925
  this._updateElementStyle('borderLeft', v);
2434
2926
  }} />
2435
- </div>
2436
- <div class="prop-row">
2437
- <span class="prop-label">Padding</span>
2438
- <input class="prop-input" type="number" .value=${String(el.style?.padding || 0)}
2439
- @change=${(e) => this._updateElementStyle('padding', Number(e.target.value))} />
2440
- </div>
2441
- <div class="prop-row">
2442
- <span class="prop-label">Word Wrap</span>
2443
- <input type="checkbox" .checked=${el.style?.wordWrap === true}
2444
- @change=${(e) => this._updateElementStyle('wordWrap', e.target.checked)} />
2927
+ </div>
2928
+
2929
+ <!-- Padding -->
2930
+ <div class="prop-row">
2931
+ <span class="prop-label">Padding</span>
2932
+ <input class="prop-input" type="number" .value=${String(el.style?.padding || 0)}
2933
+ @change=${(e) => this._updateElementStyle('padding', Number(e.target.value))} />
2934
+ </div>
2935
+
2936
+ <!-- Word wrap toggle -->
2937
+ <div class="prop-row">
2938
+ <span class="prop-label">Word Wrap</span>
2939
+ <div class="prop-toggle">
2940
+ <div class="prop-switch ${el.style?.wordWrap === true ? 'active' : ''}"
2941
+ @click=${() => this._updateElementStyle('wordWrap', !el.style?.wordWrap)}></div>
2942
+ <span style="font-size:11px;color:var(--zrd-text-muted);">${el.style?.wordWrap ? 'On' : 'Off'}</span>
2943
+ </div>
2944
+ </div>
2445
2945
  </div>
2446
2946
  </div>
2447
2947
 
2448
- <div class="prop-section">
2449
- <h4>Visibility</h4>
2450
- <div class="prop-row">
2451
- <span class="prop-label">Visible</span>
2452
- <input type="checkbox" .checked=${el.visible !== false}
2453
- @change=${(e) => this._updateElementProp('visible', e.target.checked)} />
2454
- </div>
2455
- <div class="prop-row">
2456
- <span class="prop-label">Print On</span>
2457
- <select class="prop-input" .value=${el.printOn || 'all'}
2458
- @change=${(e) => this._updateElementProp('printOn', e.target.value)}>
2459
- <option value="all">All Pages</option>
2460
- <option value="first">First Page Only</option>
2461
- <option value="last">Last Page Only</option>
2462
- <option value="odd">Odd Pages</option>
2463
- <option value="even">Even Pages</option>
2464
- </select>
2948
+ <!-- Visibility -->
2949
+ <div class="prop-section" data-section="visibility">
2950
+ ${sectionHeader('el-vis', 'Visibility')}
2951
+ <div class="prop-section-body ${collapsed.has('el-vis') ? 'collapsed' : ''}">
2952
+ <div class="prop-row">
2953
+ <span class="prop-label">Visible</span>
2954
+ <div class="prop-toggle">
2955
+ <div class="prop-switch ${el.visible !== false ? 'active' : ''}"
2956
+ @click=${() => this._updateElementProp('visible', !(el.visible !== false))}></div>
2957
+ <span style="font-size:11px;color:var(--zrd-text-muted);">${el.visible !== false ? 'On' : 'Off'}</span>
2958
+ </div>
2959
+ </div>
2960
+ <div class="prop-row">
2961
+ <span class="prop-label">Print On</span>
2962
+ <select class="prop-input" .value=${el.printOn || 'all'}
2963
+ @change=${(e) => this._updateElementProp('printOn', e.target.value)}>
2964
+ <option value="all">All Pages</option>
2965
+ <option value="first">First Page Only</option>
2966
+ <option value="last">Last Page Only</option>
2967
+ <option value="odd">Odd Pages</option>
2968
+ <option value="even">Even Pages</option>
2969
+ </select>
2970
+ </div>
2465
2971
  </div>
2466
2972
  </div>
2467
2973
 
2974
+ <!-- Type-specific sections -->
2468
2975
  ${el.type === 'text' ? html `
2469
- <div class="prop-section">
2470
- <h4>Content</h4>
2471
- <div class="prop-row">
2472
- <textarea class="prop-input" style="min-height:60px;resize:vertical;" .value=${el.content}
2976
+ <div class="prop-section" data-section="data">
2977
+ ${sectionHeader('el-content', 'Content')}
2978
+ <div class="prop-section-body ${collapsed.has('el-content') ? 'collapsed' : ''}">
2979
+ <textarea class="prop-input" .value=${el.content}
2473
2980
  @change=${(e) => this._updateElementProp('content', e.target.value)}></textarea>
2474
2981
  </div>
2475
2982
  </div>
2476
2983
  ` : nothing}
2477
2984
 
2478
2985
  ${el.type === 'field' ? html `
2479
- <div class="prop-section">
2480
- <h4>Field Binding</h4>
2481
- <div class="prop-row">
2482
- <span class="prop-label">Source</span>
2483
- <select class="prop-input" .value=${el.dataSource || ''}
2484
- @change=${(e) => this._updateElementProp('dataSource', e.target.value)}>
2485
- <option value="">-- select --</option>
2486
- ${this._layout.dataSources.map(ds => html `<option value="${ds.id}">${ds.name}</option>`)}
2487
- </select>
2488
- </div>
2489
- <div class="prop-row">
2490
- <span class="prop-label">Field</span>
2491
- ${this._renderFieldSelect(el)}
2492
- </div>
2493
- <div class="prop-row">
2494
- <span class="prop-label">Format</span>
2495
- <input class="prop-input" .value=${el.format || ''}
2496
- placeholder="$#,##0.00"
2497
- @change=${(e) => this._updateElementProp('format', e.target.value)} />
2498
- </div>
2499
- <div class="prop-row">
2500
- <span class="prop-label">Aggregate</span>
2501
- <select class="prop-input" .value=${el.aggregate || ''}
2502
- @change=${(e) => this._updateElementProp('aggregate', e.target.value || undefined)}>
2503
- <option value="">None</option>
2504
- <option>sum</option><option>avg</option><option>count</option><option>min</option><option>max</option>
2505
- </select>
2986
+ <div class="prop-section" data-section="data">
2987
+ ${sectionHeader('el-field', 'Field Binding')}
2988
+ <div class="prop-section-body ${collapsed.has('el-field') ? 'collapsed' : ''}">
2989
+ <div class="prop-row">
2990
+ <span class="prop-label">Source</span>
2991
+ <select class="prop-input" .value=${el.dataSource || ''}
2992
+ @change=${(e) => this._updateElementProp('dataSource', e.target.value)}>
2993
+ <option value="">-- select --</option>
2994
+ ${this._layout.dataSources.map(ds => html `<option value="${ds.id}">${ds.name}</option>`)}
2995
+ </select>
2996
+ </div>
2997
+ <div class="prop-row">
2998
+ <span class="prop-label">Field</span>
2999
+ ${this._renderFieldSelect(el)}
3000
+ </div>
3001
+ <div class="prop-row">
3002
+ <span class="prop-label">Format</span>
3003
+ <input class="prop-input" .value=${el.format || ''}
3004
+ placeholder="$#,##0.00"
3005
+ @change=${(e) => this._updateElementProp('format', e.target.value)} />
3006
+ </div>
3007
+ <div class="prop-row">
3008
+ <span class="prop-label">Aggregate</span>
3009
+ <select class="prop-input" .value=${el.aggregate || ''}
3010
+ @change=${(e) => this._updateElementProp('aggregate', e.target.value || undefined)}>
3011
+ <option value="">None</option>
3012
+ <option>sum</option><option>avg</option><option>count</option><option>min</option><option>max</option>
3013
+ </select>
3014
+ </div>
2506
3015
  </div>
2507
3016
  </div>
2508
3017
  ` : nothing}
2509
3018
 
2510
3019
  ${el.type === 'image' ? html `
2511
- <div class="prop-section">
2512
- <h4>Image</h4>
2513
- <div class="prop-row">
2514
- <span class="prop-label">URL/Expr</span>
2515
- <input class="prop-input" .value=${el.src || ''}
2516
- @change=${(e) => this._updateElementProp('src', e.target.value)} />
2517
- </div>
2518
- <div class="prop-row">
2519
- <span class="prop-label">Fit</span>
2520
- <select class="prop-input" .value=${el.fit || 'contain'}
2521
- @change=${(e) => this._updateElementProp('fit', e.target.value)}>
2522
- <option>contain</option><option>cover</option><option>fill</option><option>none</option>
2523
- </select>
3020
+ <div class="prop-section" data-section="data">
3021
+ ${sectionHeader('el-image', 'Image')}
3022
+ <div class="prop-section-body ${collapsed.has('el-image') ? 'collapsed' : ''}">
3023
+ <div class="prop-row">
3024
+ <span class="prop-label">URL/Expr</span>
3025
+ <input class="prop-input" .value=${el.src || ''}
3026
+ @change=${(e) => this._updateElementProp('src', e.target.value)} />
3027
+ </div>
3028
+ <div class="prop-row">
3029
+ <span class="prop-label">Fit</span>
3030
+ <select class="prop-input" .value=${el.fit || 'contain'}
3031
+ @change=${(e) => this._updateElementProp('fit', e.target.value)}>
3032
+ <option>contain</option><option>cover</option><option>fill</option><option>none</option>
3033
+ </select>
3034
+ </div>
2524
3035
  </div>
2525
3036
  </div>
2526
3037
  ` : nothing}
2527
3038
 
2528
3039
  ${el.type === 'barcode' ? html `
2529
- <div class="prop-section">
2530
- <h4>Barcode</h4>
2531
- <div class="prop-row">
2532
- <span class="prop-label">Type</span>
2533
- <select class="prop-input" .value=${el.barcodeType || 'qr'}
2534
- @change=${(e) => this._updateElementProp('barcodeType', e.target.value)}>
2535
- <option>qr</option><option>code128</option><option>ean13</option><option>code39</option>
2536
- </select>
2537
- </div>
2538
- <div class="prop-row">
2539
- <span class="prop-label">Value</span>
2540
- <input class="prop-input" .value=${el.value || ''}
2541
- @change=${(e) => this._updateElementProp('value', e.target.value)} />
3040
+ <div class="prop-section" data-section="data">
3041
+ ${sectionHeader('el-barcode', 'Barcode')}
3042
+ <div class="prop-section-body ${collapsed.has('el-barcode') ? 'collapsed' : ''}">
3043
+ <div class="prop-row">
3044
+ <span class="prop-label">Type</span>
3045
+ <select class="prop-input" .value=${el.barcodeType || 'qr'}
3046
+ @change=${(e) => this._updateElementProp('barcodeType', e.target.value)}>
3047
+ <option>qr</option><option>code128</option><option>ean13</option><option>code39</option>
3048
+ </select>
3049
+ </div>
3050
+ <div class="prop-row">
3051
+ <span class="prop-label">Value</span>
3052
+ <input class="prop-input" .value=${el.value || ''}
3053
+ @change=${(e) => this._updateElementProp('value', e.target.value)} />
3054
+ </div>
2542
3055
  </div>
2543
3056
  </div>
2544
3057
  ` : nothing}
2545
3058
 
2546
3059
  ${el.type === 'pageNumber' ? html `
2547
- <div class="prop-section">
2548
- <h4>Page Number</h4>
2549
- <div class="prop-row">
2550
- <span class="prop-label">Format</span>
2551
- <input class="prop-input" .value=${el.format || 'Page {page} of {pages}'}
2552
- @change=${(e) => this._updateElementProp('format', e.target.value)} />
3060
+ <div class="prop-section" data-section="data">
3061
+ ${sectionHeader('el-pagenum', 'Page Number')}
3062
+ <div class="prop-section-body ${collapsed.has('el-pagenum') ? 'collapsed' : ''}">
3063
+ <div class="prop-row">
3064
+ <span class="prop-label">Format</span>
3065
+ <input class="prop-input" .value=${el.format || 'Page {page} of {pages}'}
3066
+ @change=${(e) => this._updateElementProp('format', e.target.value)} />
3067
+ </div>
2553
3068
  </div>
2554
3069
  </div>
2555
3070
  ` : nothing}
2556
3071
 
2557
3072
  ${el.type === 'currentDate' ? html `
2558
- <div class="prop-section">
2559
- <h4>Date Format</h4>
2560
- <div class="prop-row">
2561
- <span class="prop-label">Format</span>
2562
- <input class="prop-input" .value=${el.format || 'dd/MM/yyyy'}
2563
- @change=${(e) => this._updateElementProp('format', e.target.value)} />
3073
+ <div class="prop-section" data-section="data">
3074
+ ${sectionHeader('el-date', 'Date Format')}
3075
+ <div class="prop-section-body ${collapsed.has('el-date') ? 'collapsed' : ''}">
3076
+ <div class="prop-row">
3077
+ <span class="prop-label">Format</span>
3078
+ <input class="prop-input" .value=${el.format || 'dd/MM/yyyy'}
3079
+ @change=${(e) => this._updateElementProp('format', e.target.value)} />
3080
+ </div>
2564
3081
  </div>
2565
3082
  </div>
2566
3083
  ` : nothing}
2567
3084
 
2568
- <div class="prop-section" style="margin-top:20px;">
3085
+ <!-- Z-Order -->
3086
+ <div class="prop-section">
3087
+ ${sectionHeader('el-zorder', 'Z-Order')}
3088
+ <div class="prop-section-body ${collapsed.has('el-zorder') ? 'collapsed' : ''}">
3089
+ <div class="z-order-buttons">
3090
+ <button class="btn-small" @click=${() => this._changeZOrder(el.id, this._selectedBandId, 'front')} title="Bring to Front">\u2B06 Front</button>
3091
+ <button class="btn-small" @click=${() => this._changeZOrder(el.id, this._selectedBandId, 'forward')} title="Bring Forward">\u2191 Fwd</button>
3092
+ <button class="btn-small" @click=${() => this._changeZOrder(el.id, this._selectedBandId, 'backward')} title="Send Backward">\u2193 Bwd</button>
3093
+ <button class="btn-small" @click=${() => this._changeZOrder(el.id, this._selectedBandId, 'back')} title="Send to Back">\u2B07 Back</button>
3094
+ </div>
3095
+ </div>
3096
+ </div>
3097
+
3098
+ <!-- Delete -->
3099
+ <div class="prop-section">
2569
3100
  <button class="btn-danger" @click=${this._deleteSelectedElement}>Delete Element</button>
2570
3101
  </div>
2571
3102
  `;
@@ -2654,9 +3185,18 @@ __decorate([
2654
3185
  __decorate([
2655
3186
  state()
2656
3187
  ], ZenttoReportDesigner.prototype, "_showBandMenu", void 0);
3188
+ __decorate([
3189
+ state()
3190
+ ], ZenttoReportDesigner.prototype, "_mobileLeftOpen", void 0);
3191
+ __decorate([
3192
+ state()
3193
+ ], ZenttoReportDesigner.prototype, "_mobileRightOpen", void 0);
2657
3194
  __decorate([
2658
3195
  state()
2659
3196
  ], ZenttoReportDesigner.prototype, "_guides", void 0);
3197
+ __decorate([
3198
+ state()
3199
+ ], ZenttoReportDesigner.prototype, "_collapsedSections", void 0);
2660
3200
  __decorate([
2661
3201
  state()
2662
3202
  ], ZenttoReportDesigner.prototype, "_pageSetupDraft", void 0);