@ogidor/dashboard 1.0.10 → 1.0.12

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Directive, EventEmitter, TemplateRef, Component, ChangeDetectionStrategy, Input, Output, ViewChild, ContentChild, Injectable, NgModule } from '@angular/core';
2
+ import { Directive, EventEmitter, TemplateRef, Component, ChangeDetectionStrategy, Input, Output, ViewChild, ContentChild, Injectable, Pipe, HostListener, NgModule } from '@angular/core';
3
3
  import { BrowserModule } from '@angular/platform-browser';
4
4
  import * as i2 from '@angular/common';
5
5
  import { CommonModule } from '@angular/common';
@@ -887,43 +887,54 @@ const DEFAULT_THEME = {
887
887
  accentColor: '#0a84ff',
888
888
  dangerColor: '#ff453a',
889
889
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
890
- // Header / Tabs
891
890
  tabsContainerColor: 'rgba(44,44,46,0.6)',
892
891
  tabActiveColor: '#3a3a3c',
893
892
  tabActiveTextColor: '#ffffff',
894
893
  tabHoverTextColor: '#e5e5ea',
895
894
  addWidgetButtonTextColor: '#ffffff',
896
- // Pop-out
897
895
  popoutTitleColor: '#ffffff',
898
- // Widget card
899
896
  widgetTitleColor: '#ffffff',
900
897
  dragHandleColor: 'rgba(255,255,255,0.2)',
901
898
  widgetBorderColor: 'rgba(255,255,255,0.07)',
902
899
  widgetButtonBgColor: 'rgba(255,255,255,0.07)',
903
900
  widgetButtonColor: 'rgba(255,255,255,0.5)',
904
- // Grid
905
901
  placeholderColor: '#0a84ff',
906
902
  resizeHandleColor: 'rgba(255,255,255,0.25)',
907
903
  };
904
+ class OverflowActivePipe {
905
+ transform(pages, activePageId) {
906
+ return pages.some(p => p.id === activePageId);
907
+ }
908
+ }
909
+ OverflowActivePipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: OverflowActivePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
910
+ OverflowActivePipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: OverflowActivePipe, name: "overflowActive" });
911
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: OverflowActivePipe, decorators: [{
912
+ type: Pipe,
913
+ args: [{ name: 'overflowActive' }]
914
+ }] });
908
915
  class DashboardComponent {
909
- constructor(stateService) {
916
+ constructor(stateService, cdr, zone) {
910
917
  this.stateService = stateService;
911
- /**
912
- * Emits when the user clicks "Add Widget".
913
- * The consumer should open their own dialog and call `stateService.addWidget(...)`.
914
- */
918
+ this.cdr = cdr;
919
+ this.zone = zone;
915
920
  this.addWidgetRequested = new EventEmitter();
916
- /**
917
- * Emits the Widget when the user clicks the edit (pen) icon on a card.
918
- * The consumer should open their own edit UI and call `stateService.updateWidget(...)`.
919
- */
920
921
  this.editWidgetRequested = new EventEmitter();
921
922
  this.resolvedTheme = Object.assign({}, DEFAULT_THEME);
922
923
  this.wrapperStyles = {};
923
924
  this.pages = [];
925
+ this.visiblePages = [];
926
+ this.hiddenCount = 0;
924
927
  this.activePageId = '';
925
928
  this.isPoppedOut = false;
929
+ this.tabSwitcherOpen = false;
926
930
  this.subs = new Subscription();
931
+ // Reserved space for the overflow "+N" button and the add "+" button (px)
932
+ this.OVERFLOW_BTN_W = 80;
933
+ this.ADD_BTN_W = 52;
934
+ this.GAP = 6;
935
+ this.PADDING = 12; // 6px each side
936
+ // Cache of measured tab widths (including gap) indexed by page position
937
+ this._tabWidthCache = [];
927
938
  }
928
939
  ngOnChanges(changes) {
929
940
  if (changes['theme'] || changes['initialLayout'])
@@ -945,19 +956,38 @@ class DashboardComponent {
945
956
  this.isPoppedOut = true;
946
957
  this.subs.add(this.stateService.pages$.subscribe(pages => {
947
958
  this.pages = pages;
959
+ this._tabWidthCache = []; // invalidate cache — page list changed
948
960
  if (this.isPoppedOut && hash) {
949
- const target = pages.find(p => p.id === hash);
950
- if (target)
961
+ if (pages.find(p => p.id === hash))
951
962
  this.stateService.setActivePage(hash);
952
963
  }
953
964
  this.updateActivePage();
965
+ this.recalcVisibleTabs();
966
+ // Second pass after Angular has rendered the updated tab elements
967
+ setTimeout(() => this.recalcVisibleTabs());
954
968
  }));
955
969
  this.subs.add(this.stateService.activePageId$.subscribe(id => {
956
970
  this.activePageId = id;
957
971
  this.updateActivePage();
958
972
  }));
959
973
  }
960
- ngOnDestroy() { this.subs.unsubscribe(); }
974
+ ngAfterViewInit() {
975
+ if (!this.tabsTrackRef)
976
+ return;
977
+ this.zone.runOutsideAngular(() => {
978
+ this.resizeObserver = new ResizeObserver(() => {
979
+ this.zone.run(() => this.recalcVisibleTabs());
980
+ });
981
+ this.resizeObserver.observe(this.tabsTrackRef.nativeElement);
982
+ });
983
+ // Initial measurement pass now that the DOM is ready
984
+ setTimeout(() => this.recalcVisibleTabs());
985
+ }
986
+ ngOnDestroy() {
987
+ var _a;
988
+ this.subs.unsubscribe();
989
+ (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
990
+ }
961
991
  onItemChanged(widget) {
962
992
  this.stateService.updateWidgetPosition(this.activePageId, widget.id, widget.x, widget.y, widget.cols, widget.rows);
963
993
  }
@@ -978,6 +1008,85 @@ class DashboardComponent {
978
1008
  this.stateService.popOutPage(pageId);
979
1009
  }
980
1010
  serializeLayout() { return this.stateService.serializeLayout(); }
1011
+ openTabSwitcher() { this.tabSwitcherOpen = true; }
1012
+ closeTabSwitcher() { this.tabSwitcherOpen = false; }
1013
+ onEscapeKey() { this.closeTabSwitcher(); }
1014
+ onSwitcherSelect(pageId) {
1015
+ this.stateService.setActivePage(pageId);
1016
+ this.closeTabSwitcher();
1017
+ }
1018
+ onSwitcherPopOut(event, pageId) {
1019
+ event.stopPropagation();
1020
+ this.stateService.popOutPage(pageId);
1021
+ this.closeTabSwitcher();
1022
+ }
1023
+ onSwitcherRemove(event, pageId) {
1024
+ event.stopPropagation();
1025
+ if (confirm('Remove this workspace?')) {
1026
+ this.stateService.removePage(pageId);
1027
+ if (this.pages.length <= 1)
1028
+ this.closeTabSwitcher();
1029
+ }
1030
+ }
1031
+ onSwitcherAdd() {
1032
+ const name = prompt('Workspace name:', `Workspace ${this.pages.length + 1}`);
1033
+ if (name)
1034
+ this.stateService.addPage(name);
1035
+ }
1036
+ recalcVisibleTabs() {
1037
+ if (!this.tabsTrackRef || !this.pages.length) {
1038
+ this.visiblePages = this.pages;
1039
+ this.hiddenCount = 0;
1040
+ this.cdr.markForCheck();
1041
+ return;
1042
+ }
1043
+ const track = this.tabsTrackRef.nativeElement;
1044
+ const trackW = track.clientWidth;
1045
+ // Space always consumed by the add "+" button + padding
1046
+ const baseReserved = this.ADD_BTN_W + this.GAP + this.PADDING;
1047
+ // Space needed for the overflow "+N" button when shown
1048
+ const overflowSlot = this.OVERFLOW_BTN_W + this.GAP;
1049
+ // Measure each real tab element currently in the DOM.
1050
+ // The DOM always renders all visiblePages tabs, so we may not have all tabs
1051
+ // measured yet — fall back to the previous measured cache or a generous estimate.
1052
+ const tabEls = Array.from(track.querySelectorAll('.tab'));
1053
+ // Build a width map by index (only covers currently visible tabs)
1054
+ const measuredWidths = this.pages.map((_, i) => {
1055
+ var _a;
1056
+ const el = tabEls[i];
1057
+ return el ? el.getBoundingClientRect().width + this.GAP : (_a = this._tabWidthCache[i]) !== null && _a !== void 0 ? _a : 150 + this.GAP;
1058
+ });
1059
+ // Update cache for next call
1060
+ tabEls.forEach((el, i) => {
1061
+ this._tabWidthCache[i] = el.getBoundingClientRect().width + this.GAP;
1062
+ });
1063
+ // How much space the tabs need in total (no overflow button)
1064
+ const totalNeeded = measuredWidths.reduce((s, w) => s + w, 0) - this.GAP; // remove trailing gap
1065
+ if (totalNeeded <= trackW - baseReserved) {
1066
+ // Everything fits — show all
1067
+ this.visiblePages = this.pages;
1068
+ this.hiddenCount = 0;
1069
+ }
1070
+ else {
1071
+ // Not everything fits — fill as many as possible while always leaving room for overflow btn
1072
+ const budget = trackW - baseReserved - overflowSlot;
1073
+ let used = 0;
1074
+ let count = 0;
1075
+ for (let i = 0; i < this.pages.length; i++) {
1076
+ used += measuredWidths[i];
1077
+ if (used - this.GAP <= budget) { // -GAP: last item has no trailing gap
1078
+ count++;
1079
+ }
1080
+ else {
1081
+ break;
1082
+ }
1083
+ }
1084
+ count = Math.max(1, count);
1085
+ this.visiblePages = this.pages.slice(0, count);
1086
+ this.hiddenCount = this.pages.length - count;
1087
+ }
1088
+ this.cdr.markForCheck();
1089
+ }
981
1090
  applyTheme() {
982
1091
  var _a;
983
1092
  this.resolvedTheme = Object.assign(Object.assign({}, DEFAULT_THEME), ((_a = this.theme) !== null && _a !== void 0 ? _a : {}));
@@ -1009,48 +1118,41 @@ class DashboardComponent {
1009
1118
  this.activePage = this.pages.find(p => p.id === this.activePageId);
1010
1119
  }
1011
1120
  }
1012
- DashboardComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardComponent, deps: [{ token: DashboardStateService }], target: i0.ɵɵFactoryTarget.Component });
1013
- DashboardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: DashboardComponent, selector: "app-dashboard", inputs: { initialLayout: "initialLayout", theme: "theme" }, outputs: { addWidgetRequested: "addWidgetRequested", editWidgetRequested: "editWidgetRequested" }, queries: [{ propertyName: "cellTemplate", first: true, predicate: GridCellDirective, descendants: true, read: TemplateRef }], usesOnChanges: true, ngImport: i0, template: `
1014
- <!-- ══════════════════════════════════════════════════════════
1015
- POPPED-OUT MODE
1016
- ══════════════════════════════════════════════════════════ -->
1121
+ DashboardComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardComponent, deps: [{ token: DashboardStateService }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
1122
+ DashboardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: DashboardComponent, selector: "app-dashboard", inputs: { initialLayout: "initialLayout", theme: "theme" }, outputs: { addWidgetRequested: "addWidgetRequested", editWidgetRequested: "editWidgetRequested" }, host: { listeners: { "document:keydown.escape": "onEscapeKey()" } }, queries: [{ propertyName: "cellTemplate", first: true, predicate: GridCellDirective, descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "tabsTrackRef", first: true, predicate: ["tabsTrack"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
1123
+ <!-- POPPED-OUT MODE -->
1017
1124
  <ng-container *ngIf="isPoppedOut; else normalMode">
1018
1125
  <div class="popout-wrapper" [ngStyle]="wrapperStyles">
1019
1126
  <header class="popout-header">
1020
1127
  <span class="popout-title">{{ activePage?.name }}</span>
1021
1128
  </header>
1022
- <div class="grid-container">
1023
- <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1024
- <ng-template gridCell let-widget="widget">
1025
- <app-widget-renderer
1026
- [widget]="widget"
1027
- [theme]="resolvedTheme"
1028
- (editRequested)="editWidgetRequested.emit($event)"
1029
- (removeRequested)="onRemoveWidget($event)"
1030
- >
1031
- <ng-container
1032
- *ngIf="cellTemplate"
1033
- [ngTemplateOutlet]="cellTemplate"
1034
- [ngTemplateOutletContext]="{ widget: widget }">
1035
- </ng-container>
1036
- </app-widget-renderer>
1037
- </ng-template>
1038
- </app-grid>
1039
- </div>
1129
+ <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1130
+ <ng-template gridCell let-widget="widget">
1131
+ <app-widget-renderer [widget]="widget" [theme]="resolvedTheme"
1132
+ (editRequested)="editWidgetRequested.emit($event)"
1133
+ (removeRequested)="onRemoveWidget($event)">
1134
+ <ng-container *ngIf="cellTemplate"
1135
+ [ngTemplateOutlet]="cellTemplate"
1136
+ [ngTemplateOutletContext]="{ widget: widget }">
1137
+ </ng-container>
1138
+ </app-widget-renderer>
1139
+ </ng-template>
1140
+ </app-grid>
1040
1141
  </div>
1041
1142
  </ng-container>
1042
1143
 
1043
- <!-- ══════════════════════════════════════════════════════════
1044
- NORMAL MODE
1045
- ══════════════════════════════════════════════════════════ -->
1144
+ <!-- NORMAL MODE -->
1046
1145
  <ng-template #normalMode>
1047
1146
  <div class="dashboard-wrapper" [ngStyle]="wrapperStyles">
1048
1147
  <main class="main-content">
1049
1148
 
1050
1149
  <header class="dashboard-header">
1051
- <div class="tabs-container">
1150
+
1151
+ <!-- Tab bar -->
1152
+ <div class="tabs-track" #tabsTrack>
1153
+ <!-- Visible tabs -->
1052
1154
  <div
1053
- *ngFor="let page of pages"
1155
+ *ngFor="let page of visiblePages"
1054
1156
  class="tab"
1055
1157
  [class.active]="page.id === activePageId"
1056
1158
  (click)="onSelectPage(page.id)"
@@ -1063,30 +1165,81 @@ DashboardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", ver
1063
1165
  <i class="la la-times"></i>
1064
1166
  </button>
1065
1167
  </div>
1168
+
1169
+ <!-- Overflow button — shown only when there are hidden tabs -->
1170
+ <button
1171
+ *ngIf="hiddenCount > 0"
1172
+ class="btn-overflow"
1173
+ (click)="openTabSwitcher()"
1174
+ title="Show all workspaces"
1175
+ >
1176
+ <span>+{{ hiddenCount }}</span>
1177
+ <i class="la la-th-large"></i>
1178
+ </button>
1179
+
1180
+ <!-- Add page button -->
1066
1181
  <button class="btn-add-page" (click)="onAddPage()" title="New workspace">
1067
1182
  <i class="la la-plus"></i>
1068
1183
  </button>
1069
1184
  </div>
1070
1185
 
1071
- <!-- Add Widget button — consumer decides what happens -->
1072
1186
  <button class="btn-add-widget" (click)="addWidgetRequested.emit()" title="Add widget">
1073
1187
  <i class="la la-plus"></i>
1074
1188
  <span>Add Widget</span>
1075
1189
  </button>
1190
+
1076
1191
  </header>
1077
1192
 
1193
+ <!-- ── Tab-switcher overlay (Chrome mobile style) ── -->
1194
+ <div class="tab-switcher-overlay" *ngIf="tabSwitcherOpen" (click)="closeTabSwitcher()">
1195
+ <div class="tab-switcher-sheet" (click)="$event.stopPropagation()">
1196
+
1197
+ <div class="tab-switcher-header">
1198
+ <span class="tab-switcher-title">{{ pages.length }} Workspace{{ pages.length !== 1 ? 's' : '' }}</span>
1199
+ <button class="tab-switcher-close-btn" (click)="closeTabSwitcher()" title="Close">
1200
+ <i class="la la-times"></i>
1201
+ </button>
1202
+ </div>
1203
+
1204
+ <div class="tab-switcher-grid">
1205
+ <div
1206
+ *ngFor="let page of pages"
1207
+ class="tab-card"
1208
+ [class.active]="page.id === activePageId"
1209
+ (click)="onSwitcherSelect(page.id)"
1210
+ >
1211
+ <div class="tab-card-icon">
1212
+ <i class="la la-th-large"></i>
1213
+ </div>
1214
+ <span class="tab-card-name">{{ page.name }}</span>
1215
+ <div class="tab-card-actions">
1216
+ <button class="tab-card-btn tab-card-popout" (click)="onSwitcherPopOut($event, page.id)" title="Open in new window">
1217
+ <i class="la la-external-link-alt"></i>
1218
+ </button>
1219
+ <button class="tab-card-btn tab-card-remove" *ngIf="pages.length > 1" (click)="onSwitcherRemove($event, page.id)" title="Close">
1220
+ <i class="la la-times"></i>
1221
+ </button>
1222
+ </div>
1223
+ </div>
1224
+ </div>
1225
+
1226
+ <div class="tab-switcher-footer">
1227
+ <button class="tab-switcher-add-btn" (click)="onSwitcherAdd()">
1228
+ <i class="la la-plus"></i>
1229
+ <span>New Workspace</span>
1230
+ </button>
1231
+ </div>
1232
+
1233
+ </div>
1234
+ </div>
1235
+
1078
1236
  <div class="grid-container">
1079
1237
  <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1080
1238
  <ng-template gridCell let-widget="widget">
1081
- <app-widget-renderer
1082
- [widget]="widget"
1083
- [theme]="resolvedTheme"
1239
+ <app-widget-renderer [widget]="widget" [theme]="resolvedTheme"
1084
1240
  (editRequested)="editWidgetRequested.emit($event)"
1085
- (removeRequested)="onRemoveWidget($event)"
1086
- >
1087
- <!-- Stamp consumer's cell template (if provided) inside the card body -->
1088
- <ng-container
1089
- *ngIf="cellTemplate"
1241
+ (removeRequested)="onRemoveWidget($event)">
1242
+ <ng-container *ngIf="cellTemplate"
1090
1243
  [ngTemplateOutlet]="cellTemplate"
1091
1244
  [ngTemplateOutletContext]="{ widget: widget }">
1092
1245
  </ng-container>
@@ -1098,50 +1251,43 @@ DashboardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", ver
1098
1251
  </main>
1099
1252
  </div>
1100
1253
  </ng-template>
1101
- `, isInline: true, styles: [":host{display:flex;flex-direction:column;width:100%;height:100%;min-height:0;--dash-bg: #000000;--dash-panel-bg: #1c1c1e;--dash-card-bg: #2c2c2e;--dash-fore-color: #8e8e93;--dash-accent-color: #0a84ff;--dash-danger-color: #ff453a;--dash-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;--dash-tabs-container-color: rgba(44,44,46,.6);--dash-tab-active-color: #3a3a3c;--dash-tab-active-text: #ffffff;--dash-tab-hover-text: #e5e5ea;--dash-add-widget-text: #ffffff;--dash-popout-title-color: #ffffff;--dash-widget-title-color: #ffffff;--dash-drag-handle-color: rgba(255,255,255,.2);--dash-widget-border-color: rgba(255,255,255,.07);--dash-widget-btn-bg: rgba(255,255,255,.07);--dash-widget-btn-color: rgba(255,255,255,.5);--dash-placeholder-color: #0a84ff;--dash-resize-handle-color: rgba(255,255,255,.25)}.dashboard-wrapper{display:flex;flex:1;min-height:0;overflow:hidden;padding:16px;box-sizing:border-box;background:var(--dash-bg);color:var(--dash-fore-color);font-family:var(--dash-font-family)}.main-content{flex:1;display:flex;flex-direction:column;overflow:hidden;border-radius:40px;padding:20px;background:var(--dash-panel-bg);border:1px solid rgba(255,255,255,.05)}.popout-wrapper{display:flex;flex:1;flex-direction:column;min-height:0;overflow:hidden;padding:16px;box-sizing:border-box;background:var(--dash-bg);color:var(--dash-fore-color);font-family:var(--dash-font-family)}.popout-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;padding:10px 18px;background:var(--dash-panel-bg);border-radius:20px;border:1px solid rgba(255,255,255,.06);flex-shrink:0}.popout-title{font-size:16px;font-weight:700;color:var(--dash-popout-title-color)}.popout-wrapper .grid-container{flex:1;overflow:auto;border-radius:24px;min-height:0;background:var(--dash-panel-bg);padding:16px;border:1px solid rgba(255,255,255,.05)}.dashboard-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;gap:12px}.tabs-container{display:flex;align-items:center;gap:8px;background:var(--dash-tabs-container-color);backdrop-filter:blur(10px);border-radius:25px;padding:6px}.tab{display:flex;align-items:center;gap:4px;padding:8px 14px 8px 18px;border-radius:20px;cursor:pointer;font-size:14px;font-weight:500;color:var(--dash-fore-color);transition:all .3s cubic-bezier(.25,.8,.25,1)}.tab.active{background:var(--dash-tab-active-color);color:var(--dash-tab-active-text)}.tab:hover:not(.active){color:var(--dash-tab-hover-text);background:rgba(255,255,255,.05)}.tab-name{flex:1;white-space:nowrap}.tab-action{background:transparent;border:none;color:var(--dash-fore-color);padding:2px;font-size:11px;cursor:pointer;opacity:0;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;transition:all .2s;flex-shrink:0}.tab:hover .tab-action{opacity:1}.tab-popout:hover{color:var(--dash-accent-color);background:rgba(10,132,255,.18)}.tab-close:hover{color:var(--dash-danger-color);background:rgba(255,69,58,.2)}.btn-add-page{background:transparent;border:none;color:var(--dash-fore-color);padding:8px 16px;cursor:pointer;border-radius:20px;transition:all .2s}.btn-add-page:hover{color:var(--dash-accent-color);background:rgba(10,132,255,.1)}.btn-add-widget{display:flex;align-items:center;gap:6px;background:var(--dash-accent-color);border:none;color:var(--dash-add-widget-text);font-size:14px;font-weight:600;padding:10px 20px;border-radius:22px;cursor:pointer;white-space:nowrap;transition:opacity .2s,transform .15s}.btn-add-widget:hover{opacity:.9;transform:translateY(-1px)}.btn-add-widget:active{transform:translateY(0)}.main-content .grid-container{flex:1;overflow:auto;border-radius:24px;min-height:0}app-grid{background:transparent;display:block;height:100%;width:100%}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: WidgetRendererComponent, selector: "app-widget-renderer", inputs: ["widget", "theme"], outputs: ["editRequested", "removeRequested"] }, { kind: "component", type: CustomGridComponent, selector: "app-grid", inputs: ["widgets", "columns", "gap", "rowHeight", "minItemCols", "minItemRows"], outputs: ["itemChanged", "layoutChanged"] }, { kind: "directive", type: GridCellDirective, selector: "[gridCell]" }] });
1254
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;width:100%;height:100%;min-height:0;--dash-bg: #000000;--dash-panel-bg: #1c1c1e;--dash-card-bg: #2c2c2e;--dash-fore-color: #8e8e93;--dash-accent-color: #0a84ff;--dash-danger-color: #ff453a;--dash-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;--dash-tabs-container-color: rgba(44,44,46,.6);--dash-tab-active-color: #3a3a3c;--dash-tab-active-text: #ffffff;--dash-tab-hover-text: #e5e5ea;--dash-add-widget-text: #ffffff;--dash-popout-title-color: #ffffff;--dash-widget-title-color: #ffffff;--dash-drag-handle-color: rgba(255,255,255,.2);--dash-widget-border-color: rgba(255,255,255,.07);--dash-widget-btn-bg: rgba(255,255,255,.07);--dash-widget-btn-color: rgba(255,255,255,.5);--dash-placeholder-color: #0a84ff;--dash-resize-handle-color: rgba(255,255,255,.25)}.dashboard-wrapper{display:flex;flex:1;min-height:0;overflow:hidden;padding:16px;box-sizing:border-box;background:var(--dash-bg);color:var(--dash-fore-color);font-family:var(--dash-font-family)}.main-content{flex:1;display:flex;flex-direction:column;overflow:hidden;border-radius:40px;padding:20px;background:var(--dash-panel-bg);border:1px solid rgba(255,255,255,.05);position:relative}.popout-wrapper{display:flex;flex:1;flex-direction:column;min-height:0;overflow:hidden;padding:16px;box-sizing:border-box;background:var(--dash-bg);color:var(--dash-fore-color);font-family:var(--dash-font-family)}.popout-header{display:flex;align-items:center;margin-bottom:16px;padding:10px 18px;background:var(--dash-panel-bg);border-radius:20px;border:1px solid rgba(255,255,255,.06);flex-shrink:0}.popout-title{font-size:16px;font-weight:700;color:var(--dash-popout-title-color)}.popout-wrapper app-grid{flex:1;min-height:0;overflow:auto}.dashboard-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;gap:12px;flex-shrink:0}.tabs-track{display:flex;align-items:center;gap:6px;flex:1;min-width:0;background:var(--dash-tabs-container-color);backdrop-filter:blur(10px);border-radius:25px;padding:6px;overflow:hidden}.tab{display:flex;align-items:center;gap:4px;padding:8px 14px 8px 18px;border-radius:20px;cursor:pointer;font-size:14px;font-weight:500;color:var(--dash-fore-color);white-space:nowrap;flex-shrink:0;transition:background .2s,color .2s}.tab.active{background:var(--dash-tab-active-color);color:var(--dash-tab-active-text)}.tab:hover:not(.active){color:var(--dash-tab-hover-text);background:rgba(255,255,255,.05)}.tab-name{white-space:nowrap}.tab-action{background:transparent;border:none;color:var(--dash-fore-color);padding:2px;font-size:11px;cursor:pointer;opacity:0;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;transition:all .2s;flex-shrink:0}.tab:hover .tab-action{opacity:1}.tab-popout:hover{color:var(--dash-accent-color);background:rgba(10,132,255,.18)}.tab-close:hover{color:var(--dash-danger-color);background:rgba(255,69,58,.2)}.btn-overflow{display:inline-flex;align-items:center;gap:5px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.12);border-radius:20px;padding:7px 13px;cursor:pointer;font-size:13px;font-weight:600;color:var(--dash-tab-active-text);white-space:nowrap;flex-shrink:0;transition:background .2s,border-color .2s}.btn-overflow:hover{background:rgba(10,132,255,.18);border-color:var(--dash-accent-color);color:var(--dash-accent-color)}.btn-add-page{background:transparent;border:none;color:var(--dash-fore-color);padding:8px 14px;cursor:pointer;border-radius:20px;flex-shrink:0;transition:all .2s}.btn-add-page:hover{color:var(--dash-accent-color);background:rgba(10,132,255,.1)}.tab-switcher-overlay{position:absolute;inset:0;z-index:1000;background:rgba(0,0,0,.55);backdrop-filter:blur(4px);display:flex;align-items:flex-start;justify-content:center;padding-top:60px;animation:overlay-in .18s ease}@keyframes overlay-in{0%{opacity:0}to{opacity:1}}.tab-switcher-sheet{background:var(--dash-panel-bg);border:1px solid rgba(255,255,255,.08);border-radius:24px;width:min(520px,calc(100% - 32px));max-height:calc(100% - 80px);display:flex;flex-direction:column;overflow:hidden;animation:sheet-in .22s cubic-bezier(.32,1.2,.5,1)}@keyframes sheet-in{0%{transform:translateY(-16px) scale(.97);opacity:0}to{transform:translateY(0) scale(1);opacity:1}}.tab-switcher-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 12px;border-bottom:1px solid rgba(255,255,255,.06);flex-shrink:0}.tab-switcher-title{font-size:15px;font-weight:700;color:var(--dash-tab-active-text)}.tab-switcher-close-btn{background:rgba(255,255,255,.07);border:none;color:var(--dash-fore-color);width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:14px;transition:background .15s,color .15s}.tab-switcher-close-btn:hover{background:rgba(255,69,58,.25);color:var(--dash-danger-color)}.tab-switcher-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px;padding:16px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.15) transparent}.tab-card{display:flex;flex-direction:column;align-items:center;gap:8px;padding:16px 12px 12px;border-radius:16px;background:var(--dash-card-bg);border:1.5px solid transparent;cursor:pointer;transition:border-color .15s,background .15s,transform .12s;position:relative}.tab-card:hover{background:rgba(255,255,255,.06);transform:translateY(-2px)}.tab-card.active{border-color:var(--dash-accent-color);background:rgba(10,132,255,.12)}.tab-card-icon{width:44px;height:44px;border-radius:12px;background:rgba(255,255,255,.07);display:flex;align-items:center;justify-content:center;font-size:20px;color:var(--dash-fore-color)}.tab-card.active .tab-card-icon{background:rgba(10,132,255,.25);color:var(--dash-accent-color)}.tab-card-name{font-size:13px;font-weight:500;color:var(--dash-tab-active-text);text-align:center;word-break:break-word;line-height:1.3}.tab-card-actions{display:flex;gap:4px;opacity:0;transition:opacity .15s}.tab-card:hover .tab-card-actions{opacity:1}.tab-card-btn{background:rgba(255,255,255,.07);border:none;width:26px;height:26px;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:12px;color:var(--dash-fore-color);transition:background .15s,color .15s}.tab-card-popout:hover{background:rgba(10,132,255,.25);color:var(--dash-accent-color)}.tab-card-remove:hover{background:rgba(255,69,58,.25);color:var(--dash-danger-color)}.tab-switcher-footer{padding:12px 16px 16px;border-top:1px solid rgba(255,255,255,.06);flex-shrink:0}.tab-switcher-add-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:11px 20px;border-radius:16px;background:rgba(10,132,255,.15);border:1.5px dashed rgba(10,132,255,.4);color:var(--dash-accent-color);font-size:14px;font-weight:600;cursor:pointer;transition:background .15s,border-color .15s}.tab-switcher-add-btn:hover{background:rgba(10,132,255,.25);border-color:var(--dash-accent-color)}.btn-add-widget{display:flex;align-items:center;gap:6px;background:var(--dash-accent-color);border:none;color:var(--dash-add-widget-text);font-size:14px;font-weight:600;padding:10px 20px;border-radius:22px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:opacity .2s,transform .15s}.btn-add-widget:hover{opacity:.9;transform:translateY(-1px)}.btn-add-widget:active{transform:translateY(0)}.main-content .grid-container{flex:1;overflow:auto;border-radius:24px;min-height:0}app-grid{background:transparent;display:block;height:100%;width:100%}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: WidgetRendererComponent, selector: "app-widget-renderer", inputs: ["widget", "theme"], outputs: ["editRequested", "removeRequested"] }, { kind: "component", type: CustomGridComponent, selector: "app-grid", inputs: ["widgets", "columns", "gap", "rowHeight", "minItemCols", "minItemRows"], outputs: ["itemChanged", "layoutChanged"] }, { kind: "directive", type: GridCellDirective, selector: "[gridCell]" }] });
1102
1255
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardComponent, decorators: [{
1103
1256
  type: Component,
1104
1257
  args: [{ selector: 'app-dashboard', template: `
1105
- <!-- ══════════════════════════════════════════════════════════
1106
- POPPED-OUT MODE
1107
- ══════════════════════════════════════════════════════════ -->
1258
+ <!-- POPPED-OUT MODE -->
1108
1259
  <ng-container *ngIf="isPoppedOut; else normalMode">
1109
1260
  <div class="popout-wrapper" [ngStyle]="wrapperStyles">
1110
1261
  <header class="popout-header">
1111
1262
  <span class="popout-title">{{ activePage?.name }}</span>
1112
1263
  </header>
1113
- <div class="grid-container">
1114
- <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1115
- <ng-template gridCell let-widget="widget">
1116
- <app-widget-renderer
1117
- [widget]="widget"
1118
- [theme]="resolvedTheme"
1119
- (editRequested)="editWidgetRequested.emit($event)"
1120
- (removeRequested)="onRemoveWidget($event)"
1121
- >
1122
- <ng-container
1123
- *ngIf="cellTemplate"
1124
- [ngTemplateOutlet]="cellTemplate"
1125
- [ngTemplateOutletContext]="{ widget: widget }">
1126
- </ng-container>
1127
- </app-widget-renderer>
1128
- </ng-template>
1129
- </app-grid>
1130
- </div>
1264
+ <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1265
+ <ng-template gridCell let-widget="widget">
1266
+ <app-widget-renderer [widget]="widget" [theme]="resolvedTheme"
1267
+ (editRequested)="editWidgetRequested.emit($event)"
1268
+ (removeRequested)="onRemoveWidget($event)">
1269
+ <ng-container *ngIf="cellTemplate"
1270
+ [ngTemplateOutlet]="cellTemplate"
1271
+ [ngTemplateOutletContext]="{ widget: widget }">
1272
+ </ng-container>
1273
+ </app-widget-renderer>
1274
+ </ng-template>
1275
+ </app-grid>
1131
1276
  </div>
1132
1277
  </ng-container>
1133
1278
 
1134
- <!-- ══════════════════════════════════════════════════════════
1135
- NORMAL MODE
1136
- ══════════════════════════════════════════════════════════ -->
1279
+ <!-- NORMAL MODE -->
1137
1280
  <ng-template #normalMode>
1138
1281
  <div class="dashboard-wrapper" [ngStyle]="wrapperStyles">
1139
1282
  <main class="main-content">
1140
1283
 
1141
1284
  <header class="dashboard-header">
1142
- <div class="tabs-container">
1285
+
1286
+ <!-- Tab bar -->
1287
+ <div class="tabs-track" #tabsTrack>
1288
+ <!-- Visible tabs -->
1143
1289
  <div
1144
- *ngFor="let page of pages"
1290
+ *ngFor="let page of visiblePages"
1145
1291
  class="tab"
1146
1292
  [class.active]="page.id === activePageId"
1147
1293
  (click)="onSelectPage(page.id)"
@@ -1154,30 +1300,81 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1154
1300
  <i class="la la-times"></i>
1155
1301
  </button>
1156
1302
  </div>
1303
+
1304
+ <!-- Overflow button — shown only when there are hidden tabs -->
1305
+ <button
1306
+ *ngIf="hiddenCount > 0"
1307
+ class="btn-overflow"
1308
+ (click)="openTabSwitcher()"
1309
+ title="Show all workspaces"
1310
+ >
1311
+ <span>+{{ hiddenCount }}</span>
1312
+ <i class="la la-th-large"></i>
1313
+ </button>
1314
+
1315
+ <!-- Add page button -->
1157
1316
  <button class="btn-add-page" (click)="onAddPage()" title="New workspace">
1158
1317
  <i class="la la-plus"></i>
1159
1318
  </button>
1160
1319
  </div>
1161
1320
 
1162
- <!-- Add Widget button — consumer decides what happens -->
1163
1321
  <button class="btn-add-widget" (click)="addWidgetRequested.emit()" title="Add widget">
1164
1322
  <i class="la la-plus"></i>
1165
1323
  <span>Add Widget</span>
1166
1324
  </button>
1325
+
1167
1326
  </header>
1168
1327
 
1328
+ <!-- ── Tab-switcher overlay (Chrome mobile style) ── -->
1329
+ <div class="tab-switcher-overlay" *ngIf="tabSwitcherOpen" (click)="closeTabSwitcher()">
1330
+ <div class="tab-switcher-sheet" (click)="$event.stopPropagation()">
1331
+
1332
+ <div class="tab-switcher-header">
1333
+ <span class="tab-switcher-title">{{ pages.length }} Workspace{{ pages.length !== 1 ? 's' : '' }}</span>
1334
+ <button class="tab-switcher-close-btn" (click)="closeTabSwitcher()" title="Close">
1335
+ <i class="la la-times"></i>
1336
+ </button>
1337
+ </div>
1338
+
1339
+ <div class="tab-switcher-grid">
1340
+ <div
1341
+ *ngFor="let page of pages"
1342
+ class="tab-card"
1343
+ [class.active]="page.id === activePageId"
1344
+ (click)="onSwitcherSelect(page.id)"
1345
+ >
1346
+ <div class="tab-card-icon">
1347
+ <i class="la la-th-large"></i>
1348
+ </div>
1349
+ <span class="tab-card-name">{{ page.name }}</span>
1350
+ <div class="tab-card-actions">
1351
+ <button class="tab-card-btn tab-card-popout" (click)="onSwitcherPopOut($event, page.id)" title="Open in new window">
1352
+ <i class="la la-external-link-alt"></i>
1353
+ </button>
1354
+ <button class="tab-card-btn tab-card-remove" *ngIf="pages.length > 1" (click)="onSwitcherRemove($event, page.id)" title="Close">
1355
+ <i class="la la-times"></i>
1356
+ </button>
1357
+ </div>
1358
+ </div>
1359
+ </div>
1360
+
1361
+ <div class="tab-switcher-footer">
1362
+ <button class="tab-switcher-add-btn" (click)="onSwitcherAdd()">
1363
+ <i class="la la-plus"></i>
1364
+ <span>New Workspace</span>
1365
+ </button>
1366
+ </div>
1367
+
1368
+ </div>
1369
+ </div>
1370
+
1169
1371
  <div class="grid-container">
1170
1372
  <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1171
1373
  <ng-template gridCell let-widget="widget">
1172
- <app-widget-renderer
1173
- [widget]="widget"
1174
- [theme]="resolvedTheme"
1374
+ <app-widget-renderer [widget]="widget" [theme]="resolvedTheme"
1175
1375
  (editRequested)="editWidgetRequested.emit($event)"
1176
- (removeRequested)="onRemoveWidget($event)"
1177
- >
1178
- <!-- Stamp consumer's cell template (if provided) inside the card body -->
1179
- <ng-container
1180
- *ngIf="cellTemplate"
1376
+ (removeRequested)="onRemoveWidget($event)">
1377
+ <ng-container *ngIf="cellTemplate"
1181
1378
  [ngTemplateOutlet]="cellTemplate"
1182
1379
  [ngTemplateOutletContext]="{ widget: widget }">
1183
1380
  </ng-container>
@@ -1189,8 +1386,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1189
1386
  </main>
1190
1387
  </div>
1191
1388
  </ng-template>
1192
- `, styles: [":host{display:flex;flex-direction:column;width:100%;height:100%;min-height:0;--dash-bg: #000000;--dash-panel-bg: #1c1c1e;--dash-card-bg: #2c2c2e;--dash-fore-color: #8e8e93;--dash-accent-color: #0a84ff;--dash-danger-color: #ff453a;--dash-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;--dash-tabs-container-color: rgba(44,44,46,.6);--dash-tab-active-color: #3a3a3c;--dash-tab-active-text: #ffffff;--dash-tab-hover-text: #e5e5ea;--dash-add-widget-text: #ffffff;--dash-popout-title-color: #ffffff;--dash-widget-title-color: #ffffff;--dash-drag-handle-color: rgba(255,255,255,.2);--dash-widget-border-color: rgba(255,255,255,.07);--dash-widget-btn-bg: rgba(255,255,255,.07);--dash-widget-btn-color: rgba(255,255,255,.5);--dash-placeholder-color: #0a84ff;--dash-resize-handle-color: rgba(255,255,255,.25)}.dashboard-wrapper{display:flex;flex:1;min-height:0;overflow:hidden;padding:16px;box-sizing:border-box;background:var(--dash-bg);color:var(--dash-fore-color);font-family:var(--dash-font-family)}.main-content{flex:1;display:flex;flex-direction:column;overflow:hidden;border-radius:40px;padding:20px;background:var(--dash-panel-bg);border:1px solid rgba(255,255,255,.05)}.popout-wrapper{display:flex;flex:1;flex-direction:column;min-height:0;overflow:hidden;padding:16px;box-sizing:border-box;background:var(--dash-bg);color:var(--dash-fore-color);font-family:var(--dash-font-family)}.popout-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;padding:10px 18px;background:var(--dash-panel-bg);border-radius:20px;border:1px solid rgba(255,255,255,.06);flex-shrink:0}.popout-title{font-size:16px;font-weight:700;color:var(--dash-popout-title-color)}.popout-wrapper .grid-container{flex:1;overflow:auto;border-radius:24px;min-height:0;background:var(--dash-panel-bg);padding:16px;border:1px solid rgba(255,255,255,.05)}.dashboard-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;gap:12px}.tabs-container{display:flex;align-items:center;gap:8px;background:var(--dash-tabs-container-color);backdrop-filter:blur(10px);border-radius:25px;padding:6px}.tab{display:flex;align-items:center;gap:4px;padding:8px 14px 8px 18px;border-radius:20px;cursor:pointer;font-size:14px;font-weight:500;color:var(--dash-fore-color);transition:all .3s cubic-bezier(.25,.8,.25,1)}.tab.active{background:var(--dash-tab-active-color);color:var(--dash-tab-active-text)}.tab:hover:not(.active){color:var(--dash-tab-hover-text);background:rgba(255,255,255,.05)}.tab-name{flex:1;white-space:nowrap}.tab-action{background:transparent;border:none;color:var(--dash-fore-color);padding:2px;font-size:11px;cursor:pointer;opacity:0;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;transition:all .2s;flex-shrink:0}.tab:hover .tab-action{opacity:1}.tab-popout:hover{color:var(--dash-accent-color);background:rgba(10,132,255,.18)}.tab-close:hover{color:var(--dash-danger-color);background:rgba(255,69,58,.2)}.btn-add-page{background:transparent;border:none;color:var(--dash-fore-color);padding:8px 16px;cursor:pointer;border-radius:20px;transition:all .2s}.btn-add-page:hover{color:var(--dash-accent-color);background:rgba(10,132,255,.1)}.btn-add-widget{display:flex;align-items:center;gap:6px;background:var(--dash-accent-color);border:none;color:var(--dash-add-widget-text);font-size:14px;font-weight:600;padding:10px 20px;border-radius:22px;cursor:pointer;white-space:nowrap;transition:opacity .2s,transform .15s}.btn-add-widget:hover{opacity:.9;transform:translateY(-1px)}.btn-add-widget:active{transform:translateY(0)}.main-content .grid-container{flex:1;overflow:auto;border-radius:24px;min-height:0}app-grid{background:transparent;display:block;height:100%;width:100%}\n"] }]
1193
- }], ctorParameters: function () { return [{ type: DashboardStateService }]; }, propDecorators: { initialLayout: [{
1389
+ `, styles: [":host{display:flex;flex-direction:column;width:100%;height:100%;min-height:0;--dash-bg: #000000;--dash-panel-bg: #1c1c1e;--dash-card-bg: #2c2c2e;--dash-fore-color: #8e8e93;--dash-accent-color: #0a84ff;--dash-danger-color: #ff453a;--dash-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;--dash-tabs-container-color: rgba(44,44,46,.6);--dash-tab-active-color: #3a3a3c;--dash-tab-active-text: #ffffff;--dash-tab-hover-text: #e5e5ea;--dash-add-widget-text: #ffffff;--dash-popout-title-color: #ffffff;--dash-widget-title-color: #ffffff;--dash-drag-handle-color: rgba(255,255,255,.2);--dash-widget-border-color: rgba(255,255,255,.07);--dash-widget-btn-bg: rgba(255,255,255,.07);--dash-widget-btn-color: rgba(255,255,255,.5);--dash-placeholder-color: #0a84ff;--dash-resize-handle-color: rgba(255,255,255,.25)}.dashboard-wrapper{display:flex;flex:1;min-height:0;overflow:hidden;padding:16px;box-sizing:border-box;background:var(--dash-bg);color:var(--dash-fore-color);font-family:var(--dash-font-family)}.main-content{flex:1;display:flex;flex-direction:column;overflow:hidden;border-radius:40px;padding:20px;background:var(--dash-panel-bg);border:1px solid rgba(255,255,255,.05);position:relative}.popout-wrapper{display:flex;flex:1;flex-direction:column;min-height:0;overflow:hidden;padding:16px;box-sizing:border-box;background:var(--dash-bg);color:var(--dash-fore-color);font-family:var(--dash-font-family)}.popout-header{display:flex;align-items:center;margin-bottom:16px;padding:10px 18px;background:var(--dash-panel-bg);border-radius:20px;border:1px solid rgba(255,255,255,.06);flex-shrink:0}.popout-title{font-size:16px;font-weight:700;color:var(--dash-popout-title-color)}.popout-wrapper app-grid{flex:1;min-height:0;overflow:auto}.dashboard-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;gap:12px;flex-shrink:0}.tabs-track{display:flex;align-items:center;gap:6px;flex:1;min-width:0;background:var(--dash-tabs-container-color);backdrop-filter:blur(10px);border-radius:25px;padding:6px;overflow:hidden}.tab{display:flex;align-items:center;gap:4px;padding:8px 14px 8px 18px;border-radius:20px;cursor:pointer;font-size:14px;font-weight:500;color:var(--dash-fore-color);white-space:nowrap;flex-shrink:0;transition:background .2s,color .2s}.tab.active{background:var(--dash-tab-active-color);color:var(--dash-tab-active-text)}.tab:hover:not(.active){color:var(--dash-tab-hover-text);background:rgba(255,255,255,.05)}.tab-name{white-space:nowrap}.tab-action{background:transparent;border:none;color:var(--dash-fore-color);padding:2px;font-size:11px;cursor:pointer;opacity:0;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;transition:all .2s;flex-shrink:0}.tab:hover .tab-action{opacity:1}.tab-popout:hover{color:var(--dash-accent-color);background:rgba(10,132,255,.18)}.tab-close:hover{color:var(--dash-danger-color);background:rgba(255,69,58,.2)}.btn-overflow{display:inline-flex;align-items:center;gap:5px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.12);border-radius:20px;padding:7px 13px;cursor:pointer;font-size:13px;font-weight:600;color:var(--dash-tab-active-text);white-space:nowrap;flex-shrink:0;transition:background .2s,border-color .2s}.btn-overflow:hover{background:rgba(10,132,255,.18);border-color:var(--dash-accent-color);color:var(--dash-accent-color)}.btn-add-page{background:transparent;border:none;color:var(--dash-fore-color);padding:8px 14px;cursor:pointer;border-radius:20px;flex-shrink:0;transition:all .2s}.btn-add-page:hover{color:var(--dash-accent-color);background:rgba(10,132,255,.1)}.tab-switcher-overlay{position:absolute;inset:0;z-index:1000;background:rgba(0,0,0,.55);backdrop-filter:blur(4px);display:flex;align-items:flex-start;justify-content:center;padding-top:60px;animation:overlay-in .18s ease}@keyframes overlay-in{0%{opacity:0}to{opacity:1}}.tab-switcher-sheet{background:var(--dash-panel-bg);border:1px solid rgba(255,255,255,.08);border-radius:24px;width:min(520px,calc(100% - 32px));max-height:calc(100% - 80px);display:flex;flex-direction:column;overflow:hidden;animation:sheet-in .22s cubic-bezier(.32,1.2,.5,1)}@keyframes sheet-in{0%{transform:translateY(-16px) scale(.97);opacity:0}to{transform:translateY(0) scale(1);opacity:1}}.tab-switcher-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 12px;border-bottom:1px solid rgba(255,255,255,.06);flex-shrink:0}.tab-switcher-title{font-size:15px;font-weight:700;color:var(--dash-tab-active-text)}.tab-switcher-close-btn{background:rgba(255,255,255,.07);border:none;color:var(--dash-fore-color);width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:14px;transition:background .15s,color .15s}.tab-switcher-close-btn:hover{background:rgba(255,69,58,.25);color:var(--dash-danger-color)}.tab-switcher-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px;padding:16px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.15) transparent}.tab-card{display:flex;flex-direction:column;align-items:center;gap:8px;padding:16px 12px 12px;border-radius:16px;background:var(--dash-card-bg);border:1.5px solid transparent;cursor:pointer;transition:border-color .15s,background .15s,transform .12s;position:relative}.tab-card:hover{background:rgba(255,255,255,.06);transform:translateY(-2px)}.tab-card.active{border-color:var(--dash-accent-color);background:rgba(10,132,255,.12)}.tab-card-icon{width:44px;height:44px;border-radius:12px;background:rgba(255,255,255,.07);display:flex;align-items:center;justify-content:center;font-size:20px;color:var(--dash-fore-color)}.tab-card.active .tab-card-icon{background:rgba(10,132,255,.25);color:var(--dash-accent-color)}.tab-card-name{font-size:13px;font-weight:500;color:var(--dash-tab-active-text);text-align:center;word-break:break-word;line-height:1.3}.tab-card-actions{display:flex;gap:4px;opacity:0;transition:opacity .15s}.tab-card:hover .tab-card-actions{opacity:1}.tab-card-btn{background:rgba(255,255,255,.07);border:none;width:26px;height:26px;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:12px;color:var(--dash-fore-color);transition:background .15s,color .15s}.tab-card-popout:hover{background:rgba(10,132,255,.25);color:var(--dash-accent-color)}.tab-card-remove:hover{background:rgba(255,69,58,.25);color:var(--dash-danger-color)}.tab-switcher-footer{padding:12px 16px 16px;border-top:1px solid rgba(255,255,255,.06);flex-shrink:0}.tab-switcher-add-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:11px 20px;border-radius:16px;background:rgba(10,132,255,.15);border:1.5px dashed rgba(10,132,255,.4);color:var(--dash-accent-color);font-size:14px;font-weight:600;cursor:pointer;transition:background .15s,border-color .15s}.tab-switcher-add-btn:hover{background:rgba(10,132,255,.25);border-color:var(--dash-accent-color)}.btn-add-widget{display:flex;align-items:center;gap:6px;background:var(--dash-accent-color);border:none;color:var(--dash-add-widget-text);font-size:14px;font-weight:600;padding:10px 20px;border-radius:22px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:opacity .2s,transform .15s}.btn-add-widget:hover{opacity:.9;transform:translateY(-1px)}.btn-add-widget:active{transform:translateY(0)}.main-content .grid-container{flex:1;overflow:auto;border-radius:24px;min-height:0}app-grid{background:transparent;display:block;height:100%;width:100%}\n"] }]
1390
+ }], ctorParameters: function () { return [{ type: DashboardStateService }, { type: i0.ChangeDetectorRef }, { type: i0.NgZone }]; }, propDecorators: { initialLayout: [{
1194
1391
  type: Input
1195
1392
  }], theme: [{
1196
1393
  type: Input
@@ -1201,15 +1398,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1201
1398
  }], cellTemplate: [{
1202
1399
  type: ContentChild,
1203
1400
  args: [GridCellDirective, { read: TemplateRef }]
1401
+ }], tabsTrackRef: [{
1402
+ type: ViewChild,
1403
+ args: ['tabsTrack']
1404
+ }], onEscapeKey: [{
1405
+ type: HostListener,
1406
+ args: ['document:keydown.escape']
1204
1407
  }] } });
1205
1408
 
1206
1409
  class DashboardModule {
1207
1410
  }
1208
1411
  DashboardModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1209
1412
  DashboardModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, declarations: [DashboardComponent,
1413
+ OverflowActivePipe,
1210
1414
  WidgetRendererComponent,
1211
1415
  CustomGridComponent,
1212
1416
  GridCellDirective], imports: [CommonModule], exports: [DashboardComponent,
1417
+ OverflowActivePipe,
1213
1418
  WidgetRendererComponent,
1214
1419
  CustomGridComponent,
1215
1420
  GridCellDirective] });
@@ -1219,6 +1424,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1219
1424
  args: [{
1220
1425
  declarations: [
1221
1426
  DashboardComponent,
1427
+ OverflowActivePipe,
1222
1428
  WidgetRendererComponent,
1223
1429
  CustomGridComponent,
1224
1430
  GridCellDirective,
@@ -1229,6 +1435,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1229
1435
  providers: [DashboardStateService],
1230
1436
  exports: [
1231
1437
  DashboardComponent,
1438
+ OverflowActivePipe,
1232
1439
  WidgetRendererComponent,
1233
1440
  CustomGridComponent,
1234
1441
  GridCellDirective,
@@ -1260,5 +1467,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1260
1467
  * Generated bundle index. Do not edit.
1261
1468
  */
1262
1469
 
1263
- export { AppModule, CustomGridComponent, DashboardComponent, DashboardModule, DashboardStateService, GridCellDirective, WidgetRendererComponent };
1470
+ export { AppModule, CustomGridComponent, DashboardComponent, DashboardModule, DashboardStateService, GridCellDirective, OverflowActivePipe, WidgetRendererComponent };
1264
1471
  //# sourceMappingURL=ogidor-dashboard.mjs.map