@ogidor/dashboard 1.0.9 → 1.0.11

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, 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,42 +887,44 @@ 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 = [];
924
925
  this.activePageId = '';
925
926
  this.isPoppedOut = false;
927
+ this.tabsOverflow = false;
926
928
  this.subs = new Subscription();
927
929
  }
928
930
  ngOnChanges(changes) {
@@ -946,8 +948,7 @@ class DashboardComponent {
946
948
  this.subs.add(this.stateService.pages$.subscribe(pages => {
947
949
  this.pages = pages;
948
950
  if (this.isPoppedOut && hash) {
949
- const target = pages.find(p => p.id === hash);
950
- if (target)
951
+ if (pages.find(p => p.id === hash))
951
952
  this.stateService.setActivePage(hash);
952
953
  }
953
954
  this.updateActivePage();
@@ -957,7 +958,25 @@ class DashboardComponent {
957
958
  this.updateActivePage();
958
959
  }));
959
960
  }
960
- ngOnDestroy() { this.subs.unsubscribe(); }
961
+ ngAfterViewInit() {
962
+ if (!this.tabsContainerRef)
963
+ return;
964
+ this.zone.runOutsideAngular(() => {
965
+ this.resizeObserver = new ResizeObserver(() => {
966
+ this.zone.run(() => this.checkOverflow());
967
+ });
968
+ this.resizeObserver.observe(this.tabsContainerRef.nativeElement);
969
+ });
970
+ // Also detect scroll so fade hides when scrolled to end
971
+ this.tabsContainerRef.nativeElement.addEventListener('scroll', () => {
972
+ this.zone.run(() => this.checkOverflow());
973
+ });
974
+ }
975
+ ngOnDestroy() {
976
+ var _a;
977
+ this.subs.unsubscribe();
978
+ (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
979
+ }
961
980
  onItemChanged(widget) {
962
981
  this.stateService.updateWidgetPosition(this.activePageId, widget.id, widget.x, widget.y, widget.cols, widget.rows);
963
982
  }
@@ -978,6 +997,15 @@ class DashboardComponent {
978
997
  this.stateService.popOutPage(pageId);
979
998
  }
980
999
  serializeLayout() { return this.stateService.serializeLayout(); }
1000
+ checkOverflow() {
1001
+ var _a;
1002
+ const el = (_a = this.tabsContainerRef) === null || _a === void 0 ? void 0 : _a.nativeElement;
1003
+ if (!el)
1004
+ return;
1005
+ const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 4;
1006
+ this.tabsOverflow = !atEnd && el.scrollWidth > el.clientWidth;
1007
+ this.cdr.markForCheck();
1008
+ }
981
1009
  applyTheme() {
982
1010
  var _a;
983
1011
  this.resolvedTheme = Object.assign(Object.assign({}, DEFAULT_THEME), ((_a = this.theme) !== null && _a !== void 0 ? _a : {}));
@@ -1007,86 +1035,85 @@ class DashboardComponent {
1007
1035
  }
1008
1036
  updateActivePage() {
1009
1037
  this.activePage = this.pages.find(p => p.id === this.activePageId);
1038
+ // scroll active tab into view
1039
+ setTimeout(() => {
1040
+ if (!this.tabsContainerRef)
1041
+ return;
1042
+ const active = this.tabsContainerRef.nativeElement.querySelector('.tab.active');
1043
+ active === null || active === void 0 ? void 0 : active.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
1044
+ this.checkOverflow();
1045
+ });
1010
1046
  }
1011
1047
  }
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
- ══════════════════════════════════════════════════════════ -->
1048
+ 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 });
1049
+ 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 }], viewQueries: [{ propertyName: "tabsContainerRef", first: true, predicate: ["tabsContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
1050
+ <!-- POPPED-OUT MODE -->
1017
1051
  <ng-container *ngIf="isPoppedOut; else normalMode">
1018
1052
  <div class="popout-wrapper" [ngStyle]="wrapperStyles">
1019
1053
  <header class="popout-header">
1020
1054
  <span class="popout-title">{{ activePage?.name }}</span>
1021
1055
  </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>
1056
+ <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1057
+ <ng-template gridCell let-widget="widget">
1058
+ <app-widget-renderer [widget]="widget" [theme]="resolvedTheme"
1059
+ (editRequested)="editWidgetRequested.emit($event)"
1060
+ (removeRequested)="onRemoveWidget($event)">
1061
+ <ng-container *ngIf="cellTemplate"
1062
+ [ngTemplateOutlet]="cellTemplate"
1063
+ [ngTemplateOutletContext]="{ widget: widget }">
1064
+ </ng-container>
1065
+ </app-widget-renderer>
1066
+ </ng-template>
1067
+ </app-grid>
1040
1068
  </div>
1041
1069
  </ng-container>
1042
1070
 
1043
- <!-- ══════════════════════════════════════════════════════════
1044
- NORMAL MODE
1045
- ══════════════════════════════════════════════════════════ -->
1071
+ <!-- NORMAL MODE -->
1046
1072
  <ng-template #normalMode>
1047
1073
  <div class="dashboard-wrapper" [ngStyle]="wrapperStyles">
1048
1074
  <main class="main-content">
1049
1075
 
1050
1076
  <header class="dashboard-header">
1051
- <div class="tabs-container">
1052
- <div
1053
- *ngFor="let page of pages"
1054
- class="tab"
1055
- [class.active]="page.id === activePageId"
1056
- (click)="onSelectPage(page.id)"
1057
- >
1058
- <span class="tab-name">{{ page.name }}</span>
1059
- <button class="tab-action tab-popout" (click)="onPopOut($event, page.id)" title="Open in new window">
1060
- <i class="la la-external-link-alt"></i>
1061
- </button>
1062
- <button class="tab-action tab-close" *ngIf="pages.length > 1" (click)="onRemovePage($event, page.id)" title="Close">
1063
- <i class="la la-times"></i>
1077
+
1078
+ <!-- Tabs pill — scrollable, fades when overflowing -->
1079
+ <div class="tabs-scroll-track">
1080
+ <div class="tabs-container" #tabsContainer>
1081
+ <div
1082
+ *ngFor="let page of pages"
1083
+ class="tab"
1084
+ [class.active]="page.id === activePageId"
1085
+ (click)="onSelectPage(page.id)"
1086
+ >
1087
+ <span class="tab-name">{{ page.name }}</span>
1088
+ <button class="tab-action tab-popout" (click)="onPopOut($event, page.id)" title="Open in new window">
1089
+ <i class="la la-external-link-alt"></i>
1090
+ </button>
1091
+ <button class="tab-action tab-close" *ngIf="pages.length > 1" (click)="onRemovePage($event, page.id)" title="Close">
1092
+ <i class="la la-times"></i>
1093
+ </button>
1094
+ </div>
1095
+ <button class="btn-add-page" (click)="onAddPage()" title="New workspace">
1096
+ <i class="la la-plus"></i>
1064
1097
  </button>
1065
1098
  </div>
1066
- <button class="btn-add-page" (click)="onAddPage()" title="New workspace">
1067
- <i class="la la-plus"></i>
1068
- </button>
1099
+ <!-- fade hint shown when scrollable -->
1100
+ <div class="tabs-fade-right" *ngIf="tabsOverflow"></div>
1069
1101
  </div>
1070
1102
 
1071
- <!-- Add Widget button — consumer decides what happens -->
1072
1103
  <button class="btn-add-widget" (click)="addWidgetRequested.emit()" title="Add widget">
1073
1104
  <i class="la la-plus"></i>
1074
1105
  <span>Add Widget</span>
1075
1106
  </button>
1107
+
1076
1108
  </header>
1077
1109
 
1078
1110
  <div class="grid-container">
1079
1111
  <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1080
1112
  <ng-template gridCell let-widget="widget">
1081
- <app-widget-renderer
1082
- [widget]="widget"
1083
- [theme]="resolvedTheme"
1113
+ <app-widget-renderer [widget]="widget" [theme]="resolvedTheme"
1084
1114
  (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"
1115
+ (removeRequested)="onRemoveWidget($event)">
1116
+ <ng-container *ngIf="cellTemplate"
1090
1117
  [ngTemplateOutlet]="cellTemplate"
1091
1118
  [ngTemplateOutletContext]="{ widget: widget }">
1092
1119
  </ng-container>
@@ -1098,86 +1125,77 @@ DashboardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", ver
1098
1125
  </main>
1099
1126
  </div>
1100
1127
  </ng-template>
1101
- `, isInline: true, styles: [":host{display:block;--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{position:fixed;inset:0;display:flex;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{position:fixed;inset:0;display:flex;flex-direction:column;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]" }] });
1128
+ `, 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;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-scroll-track{position:relative;flex:1;min-width:0}.tabs-container{display:flex;align-items:center;gap:8px;background:var(--dash-tabs-container-color);backdrop-filter:blur(10px);border-radius:25px;padding:6px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none}.tabs-container::-webkit-scrollbar{display:none}.tabs-fade-right{position:absolute;top:0;right:0;width:60px;height:100%;border-radius:0 25px 25px 0;pointer-events:none;background:linear-gradient(to right,transparent,var(--dash-panel-bg))}.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:all .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{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;flex-shrink:0;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;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
1129
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardComponent, decorators: [{
1103
1130
  type: Component,
1104
1131
  args: [{ selector: 'app-dashboard', template: `
1105
- <!-- ══════════════════════════════════════════════════════════
1106
- POPPED-OUT MODE
1107
- ══════════════════════════════════════════════════════════ -->
1132
+ <!-- POPPED-OUT MODE -->
1108
1133
  <ng-container *ngIf="isPoppedOut; else normalMode">
1109
1134
  <div class="popout-wrapper" [ngStyle]="wrapperStyles">
1110
1135
  <header class="popout-header">
1111
1136
  <span class="popout-title">{{ activePage?.name }}</span>
1112
1137
  </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>
1138
+ <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1139
+ <ng-template gridCell let-widget="widget">
1140
+ <app-widget-renderer [widget]="widget" [theme]="resolvedTheme"
1141
+ (editRequested)="editWidgetRequested.emit($event)"
1142
+ (removeRequested)="onRemoveWidget($event)">
1143
+ <ng-container *ngIf="cellTemplate"
1144
+ [ngTemplateOutlet]="cellTemplate"
1145
+ [ngTemplateOutletContext]="{ widget: widget }">
1146
+ </ng-container>
1147
+ </app-widget-renderer>
1148
+ </ng-template>
1149
+ </app-grid>
1131
1150
  </div>
1132
1151
  </ng-container>
1133
1152
 
1134
- <!-- ══════════════════════════════════════════════════════════
1135
- NORMAL MODE
1136
- ══════════════════════════════════════════════════════════ -->
1153
+ <!-- NORMAL MODE -->
1137
1154
  <ng-template #normalMode>
1138
1155
  <div class="dashboard-wrapper" [ngStyle]="wrapperStyles">
1139
1156
  <main class="main-content">
1140
1157
 
1141
1158
  <header class="dashboard-header">
1142
- <div class="tabs-container">
1143
- <div
1144
- *ngFor="let page of pages"
1145
- class="tab"
1146
- [class.active]="page.id === activePageId"
1147
- (click)="onSelectPage(page.id)"
1148
- >
1149
- <span class="tab-name">{{ page.name }}</span>
1150
- <button class="tab-action tab-popout" (click)="onPopOut($event, page.id)" title="Open in new window">
1151
- <i class="la la-external-link-alt"></i>
1152
- </button>
1153
- <button class="tab-action tab-close" *ngIf="pages.length > 1" (click)="onRemovePage($event, page.id)" title="Close">
1154
- <i class="la la-times"></i>
1159
+
1160
+ <!-- Tabs pill — scrollable, fades when overflowing -->
1161
+ <div class="tabs-scroll-track">
1162
+ <div class="tabs-container" #tabsContainer>
1163
+ <div
1164
+ *ngFor="let page of pages"
1165
+ class="tab"
1166
+ [class.active]="page.id === activePageId"
1167
+ (click)="onSelectPage(page.id)"
1168
+ >
1169
+ <span class="tab-name">{{ page.name }}</span>
1170
+ <button class="tab-action tab-popout" (click)="onPopOut($event, page.id)" title="Open in new window">
1171
+ <i class="la la-external-link-alt"></i>
1172
+ </button>
1173
+ <button class="tab-action tab-close" *ngIf="pages.length > 1" (click)="onRemovePage($event, page.id)" title="Close">
1174
+ <i class="la la-times"></i>
1175
+ </button>
1176
+ </div>
1177
+ <button class="btn-add-page" (click)="onAddPage()" title="New workspace">
1178
+ <i class="la la-plus"></i>
1155
1179
  </button>
1156
1180
  </div>
1157
- <button class="btn-add-page" (click)="onAddPage()" title="New workspace">
1158
- <i class="la la-plus"></i>
1159
- </button>
1181
+ <!-- fade hint shown when scrollable -->
1182
+ <div class="tabs-fade-right" *ngIf="tabsOverflow"></div>
1160
1183
  </div>
1161
1184
 
1162
- <!-- Add Widget button — consumer decides what happens -->
1163
1185
  <button class="btn-add-widget" (click)="addWidgetRequested.emit()" title="Add widget">
1164
1186
  <i class="la la-plus"></i>
1165
1187
  <span>Add Widget</span>
1166
1188
  </button>
1189
+
1167
1190
  </header>
1168
1191
 
1169
1192
  <div class="grid-container">
1170
1193
  <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1171
1194
  <ng-template gridCell let-widget="widget">
1172
- <app-widget-renderer
1173
- [widget]="widget"
1174
- [theme]="resolvedTheme"
1195
+ <app-widget-renderer [widget]="widget" [theme]="resolvedTheme"
1175
1196
  (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"
1197
+ (removeRequested)="onRemoveWidget($event)">
1198
+ <ng-container *ngIf="cellTemplate"
1181
1199
  [ngTemplateOutlet]="cellTemplate"
1182
1200
  [ngTemplateOutletContext]="{ widget: widget }">
1183
1201
  </ng-container>
@@ -1189,8 +1207,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1189
1207
  </main>
1190
1208
  </div>
1191
1209
  </ng-template>
1192
- `, styles: [":host{display:block;--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{position:fixed;inset:0;display:flex;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{position:fixed;inset:0;display:flex;flex-direction:column;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: [{
1210
+ `, 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;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-scroll-track{position:relative;flex:1;min-width:0}.tabs-container{display:flex;align-items:center;gap:8px;background:var(--dash-tabs-container-color);backdrop-filter:blur(10px);border-radius:25px;padding:6px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none}.tabs-container::-webkit-scrollbar{display:none}.tabs-fade-right{position:absolute;top:0;right:0;width:60px;height:100%;border-radius:0 25px 25px 0;pointer-events:none;background:linear-gradient(to right,transparent,var(--dash-panel-bg))}.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:all .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{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;flex-shrink:0;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;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"] }]
1211
+ }], ctorParameters: function () { return [{ type: DashboardStateService }, { type: i0.ChangeDetectorRef }, { type: i0.NgZone }]; }, propDecorators: { initialLayout: [{
1194
1212
  type: Input
1195
1213
  }], theme: [{
1196
1214
  type: Input
@@ -1201,15 +1219,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1201
1219
  }], cellTemplate: [{
1202
1220
  type: ContentChild,
1203
1221
  args: [GridCellDirective, { read: TemplateRef }]
1222
+ }], tabsContainerRef: [{
1223
+ type: ViewChild,
1224
+ args: ['tabsContainer']
1204
1225
  }] } });
1205
1226
 
1206
1227
  class DashboardModule {
1207
1228
  }
1208
1229
  DashboardModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1209
1230
  DashboardModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, declarations: [DashboardComponent,
1231
+ OverflowActivePipe,
1210
1232
  WidgetRendererComponent,
1211
1233
  CustomGridComponent,
1212
1234
  GridCellDirective], imports: [CommonModule], exports: [DashboardComponent,
1235
+ OverflowActivePipe,
1213
1236
  WidgetRendererComponent,
1214
1237
  CustomGridComponent,
1215
1238
  GridCellDirective] });
@@ -1219,6 +1242,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1219
1242
  args: [{
1220
1243
  declarations: [
1221
1244
  DashboardComponent,
1245
+ OverflowActivePipe,
1222
1246
  WidgetRendererComponent,
1223
1247
  CustomGridComponent,
1224
1248
  GridCellDirective,
@@ -1229,6 +1253,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1229
1253
  providers: [DashboardStateService],
1230
1254
  exports: [
1231
1255
  DashboardComponent,
1256
+ OverflowActivePipe,
1232
1257
  WidgetRendererComponent,
1233
1258
  CustomGridComponent,
1234
1259
  GridCellDirective,
@@ -1260,5 +1285,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1260
1285
  * Generated bundle index. Do not edit.
1261
1286
  */
1262
1287
 
1263
- export { AppModule, CustomGridComponent, DashboardComponent, DashboardModule, DashboardStateService, GridCellDirective, WidgetRendererComponent };
1288
+ export { AppModule, CustomGridComponent, DashboardComponent, DashboardModule, DashboardStateService, GridCellDirective, OverflowActivePipe, WidgetRendererComponent };
1264
1289
  //# sourceMappingURL=ogidor-dashboard.mjs.map