@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';
@@ -885,42 +885,44 @@ const DEFAULT_THEME = {
885
885
  accentColor: '#0a84ff',
886
886
  dangerColor: '#ff453a',
887
887
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
888
- // Header / Tabs
889
888
  tabsContainerColor: 'rgba(44,44,46,0.6)',
890
889
  tabActiveColor: '#3a3a3c',
891
890
  tabActiveTextColor: '#ffffff',
892
891
  tabHoverTextColor: '#e5e5ea',
893
892
  addWidgetButtonTextColor: '#ffffff',
894
- // Pop-out
895
893
  popoutTitleColor: '#ffffff',
896
- // Widget card
897
894
  widgetTitleColor: '#ffffff',
898
895
  dragHandleColor: 'rgba(255,255,255,0.2)',
899
896
  widgetBorderColor: 'rgba(255,255,255,0.07)',
900
897
  widgetButtonBgColor: 'rgba(255,255,255,0.07)',
901
898
  widgetButtonColor: 'rgba(255,255,255,0.5)',
902
- // Grid
903
899
  placeholderColor: '#0a84ff',
904
900
  resizeHandleColor: 'rgba(255,255,255,0.25)',
905
901
  };
902
+ class OverflowActivePipe {
903
+ transform(pages, activePageId) {
904
+ return pages.some(p => p.id === activePageId);
905
+ }
906
+ }
907
+ OverflowActivePipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: OverflowActivePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
908
+ OverflowActivePipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: OverflowActivePipe, name: "overflowActive" });
909
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: OverflowActivePipe, decorators: [{
910
+ type: Pipe,
911
+ args: [{ name: 'overflowActive' }]
912
+ }] });
906
913
  class DashboardComponent {
907
- constructor(stateService) {
914
+ constructor(stateService, cdr, zone) {
908
915
  this.stateService = stateService;
909
- /**
910
- * Emits when the user clicks "Add Widget".
911
- * The consumer should open their own dialog and call `stateService.addWidget(...)`.
912
- */
916
+ this.cdr = cdr;
917
+ this.zone = zone;
913
918
  this.addWidgetRequested = new EventEmitter();
914
- /**
915
- * Emits the Widget when the user clicks the edit (pen) icon on a card.
916
- * The consumer should open their own edit UI and call `stateService.updateWidget(...)`.
917
- */
918
919
  this.editWidgetRequested = new EventEmitter();
919
920
  this.resolvedTheme = { ...DEFAULT_THEME };
920
921
  this.wrapperStyles = {};
921
922
  this.pages = [];
922
923
  this.activePageId = '';
923
924
  this.isPoppedOut = false;
925
+ this.tabsOverflow = false;
924
926
  this.subs = new Subscription();
925
927
  }
926
928
  ngOnChanges(changes) {
@@ -943,8 +945,7 @@ class DashboardComponent {
943
945
  this.subs.add(this.stateService.pages$.subscribe(pages => {
944
946
  this.pages = pages;
945
947
  if (this.isPoppedOut && hash) {
946
- const target = pages.find(p => p.id === hash);
947
- if (target)
948
+ if (pages.find(p => p.id === hash))
948
949
  this.stateService.setActivePage(hash);
949
950
  }
950
951
  this.updateActivePage();
@@ -954,7 +955,24 @@ class DashboardComponent {
954
955
  this.updateActivePage();
955
956
  }));
956
957
  }
957
- ngOnDestroy() { this.subs.unsubscribe(); }
958
+ ngAfterViewInit() {
959
+ if (!this.tabsContainerRef)
960
+ return;
961
+ this.zone.runOutsideAngular(() => {
962
+ this.resizeObserver = new ResizeObserver(() => {
963
+ this.zone.run(() => this.checkOverflow());
964
+ });
965
+ this.resizeObserver.observe(this.tabsContainerRef.nativeElement);
966
+ });
967
+ // Also detect scroll so fade hides when scrolled to end
968
+ this.tabsContainerRef.nativeElement.addEventListener('scroll', () => {
969
+ this.zone.run(() => this.checkOverflow());
970
+ });
971
+ }
972
+ ngOnDestroy() {
973
+ this.subs.unsubscribe();
974
+ this.resizeObserver?.disconnect();
975
+ }
958
976
  onItemChanged(widget) {
959
977
  this.stateService.updateWidgetPosition(this.activePageId, widget.id, widget.x, widget.y, widget.cols, widget.rows);
960
978
  }
@@ -975,6 +993,14 @@ class DashboardComponent {
975
993
  this.stateService.popOutPage(pageId);
976
994
  }
977
995
  serializeLayout() { return this.stateService.serializeLayout(); }
996
+ checkOverflow() {
997
+ const el = this.tabsContainerRef?.nativeElement;
998
+ if (!el)
999
+ return;
1000
+ const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 4;
1001
+ this.tabsOverflow = !atEnd && el.scrollWidth > el.clientWidth;
1002
+ this.cdr.markForCheck();
1003
+ }
978
1004
  applyTheme() {
979
1005
  this.resolvedTheme = { ...DEFAULT_THEME, ...(this.theme ?? {}) };
980
1006
  const t = this.resolvedTheme;
@@ -1003,86 +1029,85 @@ class DashboardComponent {
1003
1029
  }
1004
1030
  updateActivePage() {
1005
1031
  this.activePage = this.pages.find(p => p.id === this.activePageId);
1032
+ // scroll active tab into view
1033
+ setTimeout(() => {
1034
+ if (!this.tabsContainerRef)
1035
+ return;
1036
+ const active = this.tabsContainerRef.nativeElement.querySelector('.tab.active');
1037
+ active?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
1038
+ this.checkOverflow();
1039
+ });
1006
1040
  }
1007
1041
  }
1008
- DashboardComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardComponent, deps: [{ token: DashboardStateService }], target: i0.ɵɵFactoryTarget.Component });
1009
- 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: `
1010
- <!-- ══════════════════════════════════════════════════════════
1011
- POPPED-OUT MODE
1012
- ══════════════════════════════════════════════════════════ -->
1042
+ 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 });
1043
+ 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: `
1044
+ <!-- POPPED-OUT MODE -->
1013
1045
  <ng-container *ngIf="isPoppedOut; else normalMode">
1014
1046
  <div class="popout-wrapper" [ngStyle]="wrapperStyles">
1015
1047
  <header class="popout-header">
1016
1048
  <span class="popout-title">{{ activePage?.name }}</span>
1017
1049
  </header>
1018
- <div class="grid-container">
1019
- <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1020
- <ng-template gridCell let-widget="widget">
1021
- <app-widget-renderer
1022
- [widget]="widget"
1023
- [theme]="resolvedTheme"
1024
- (editRequested)="editWidgetRequested.emit($event)"
1025
- (removeRequested)="onRemoveWidget($event)"
1026
- >
1027
- <ng-container
1028
- *ngIf="cellTemplate"
1029
- [ngTemplateOutlet]="cellTemplate"
1030
- [ngTemplateOutletContext]="{ widget: widget }">
1031
- </ng-container>
1032
- </app-widget-renderer>
1033
- </ng-template>
1034
- </app-grid>
1035
- </div>
1050
+ <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1051
+ <ng-template gridCell let-widget="widget">
1052
+ <app-widget-renderer [widget]="widget" [theme]="resolvedTheme"
1053
+ (editRequested)="editWidgetRequested.emit($event)"
1054
+ (removeRequested)="onRemoveWidget($event)">
1055
+ <ng-container *ngIf="cellTemplate"
1056
+ [ngTemplateOutlet]="cellTemplate"
1057
+ [ngTemplateOutletContext]="{ widget: widget }">
1058
+ </ng-container>
1059
+ </app-widget-renderer>
1060
+ </ng-template>
1061
+ </app-grid>
1036
1062
  </div>
1037
1063
  </ng-container>
1038
1064
 
1039
- <!-- ══════════════════════════════════════════════════════════
1040
- NORMAL MODE
1041
- ══════════════════════════════════════════════════════════ -->
1065
+ <!-- NORMAL MODE -->
1042
1066
  <ng-template #normalMode>
1043
1067
  <div class="dashboard-wrapper" [ngStyle]="wrapperStyles">
1044
1068
  <main class="main-content">
1045
1069
 
1046
1070
  <header class="dashboard-header">
1047
- <div class="tabs-container">
1048
- <div
1049
- *ngFor="let page of pages"
1050
- class="tab"
1051
- [class.active]="page.id === activePageId"
1052
- (click)="onSelectPage(page.id)"
1053
- >
1054
- <span class="tab-name">{{ page.name }}</span>
1055
- <button class="tab-action tab-popout" (click)="onPopOut($event, page.id)" title="Open in new window">
1056
- <i class="la la-external-link-alt"></i>
1057
- </button>
1058
- <button class="tab-action tab-close" *ngIf="pages.length > 1" (click)="onRemovePage($event, page.id)" title="Close">
1059
- <i class="la la-times"></i>
1071
+
1072
+ <!-- Tabs pill — scrollable, fades when overflowing -->
1073
+ <div class="tabs-scroll-track">
1074
+ <div class="tabs-container" #tabsContainer>
1075
+ <div
1076
+ *ngFor="let page of pages"
1077
+ class="tab"
1078
+ [class.active]="page.id === activePageId"
1079
+ (click)="onSelectPage(page.id)"
1080
+ >
1081
+ <span class="tab-name">{{ page.name }}</span>
1082
+ <button class="tab-action tab-popout" (click)="onPopOut($event, page.id)" title="Open in new window">
1083
+ <i class="la la-external-link-alt"></i>
1084
+ </button>
1085
+ <button class="tab-action tab-close" *ngIf="pages.length > 1" (click)="onRemovePage($event, page.id)" title="Close">
1086
+ <i class="la la-times"></i>
1087
+ </button>
1088
+ </div>
1089
+ <button class="btn-add-page" (click)="onAddPage()" title="New workspace">
1090
+ <i class="la la-plus"></i>
1060
1091
  </button>
1061
1092
  </div>
1062
- <button class="btn-add-page" (click)="onAddPage()" title="New workspace">
1063
- <i class="la la-plus"></i>
1064
- </button>
1093
+ <!-- fade hint shown when scrollable -->
1094
+ <div class="tabs-fade-right" *ngIf="tabsOverflow"></div>
1065
1095
  </div>
1066
1096
 
1067
- <!-- Add Widget button — consumer decides what happens -->
1068
1097
  <button class="btn-add-widget" (click)="addWidgetRequested.emit()" title="Add widget">
1069
1098
  <i class="la la-plus"></i>
1070
1099
  <span>Add Widget</span>
1071
1100
  </button>
1101
+
1072
1102
  </header>
1073
1103
 
1074
1104
  <div class="grid-container">
1075
1105
  <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1076
1106
  <ng-template gridCell let-widget="widget">
1077
- <app-widget-renderer
1078
- [widget]="widget"
1079
- [theme]="resolvedTheme"
1107
+ <app-widget-renderer [widget]="widget" [theme]="resolvedTheme"
1080
1108
  (editRequested)="editWidgetRequested.emit($event)"
1081
- (removeRequested)="onRemoveWidget($event)"
1082
- >
1083
- <!-- Stamp consumer's cell template (if provided) inside the card body -->
1084
- <ng-container
1085
- *ngIf="cellTemplate"
1109
+ (removeRequested)="onRemoveWidget($event)">
1110
+ <ng-container *ngIf="cellTemplate"
1086
1111
  [ngTemplateOutlet]="cellTemplate"
1087
1112
  [ngTemplateOutletContext]="{ widget: widget }">
1088
1113
  </ng-container>
@@ -1094,86 +1119,77 @@ DashboardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", ver
1094
1119
  </main>
1095
1120
  </div>
1096
1121
  </ng-template>
1097
- `, 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]" }] });
1122
+ `, 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]" }] });
1098
1123
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardComponent, decorators: [{
1099
1124
  type: Component,
1100
1125
  args: [{ selector: 'app-dashboard', template: `
1101
- <!-- ══════════════════════════════════════════════════════════
1102
- POPPED-OUT MODE
1103
- ══════════════════════════════════════════════════════════ -->
1126
+ <!-- POPPED-OUT MODE -->
1104
1127
  <ng-container *ngIf="isPoppedOut; else normalMode">
1105
1128
  <div class="popout-wrapper" [ngStyle]="wrapperStyles">
1106
1129
  <header class="popout-header">
1107
1130
  <span class="popout-title">{{ activePage?.name }}</span>
1108
1131
  </header>
1109
- <div class="grid-container">
1110
- <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1111
- <ng-template gridCell let-widget="widget">
1112
- <app-widget-renderer
1113
- [widget]="widget"
1114
- [theme]="resolvedTheme"
1115
- (editRequested)="editWidgetRequested.emit($event)"
1116
- (removeRequested)="onRemoveWidget($event)"
1117
- >
1118
- <ng-container
1119
- *ngIf="cellTemplate"
1120
- [ngTemplateOutlet]="cellTemplate"
1121
- [ngTemplateOutletContext]="{ widget: widget }">
1122
- </ng-container>
1123
- </app-widget-renderer>
1124
- </ng-template>
1125
- </app-grid>
1126
- </div>
1132
+ <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1133
+ <ng-template gridCell let-widget="widget">
1134
+ <app-widget-renderer [widget]="widget" [theme]="resolvedTheme"
1135
+ (editRequested)="editWidgetRequested.emit($event)"
1136
+ (removeRequested)="onRemoveWidget($event)">
1137
+ <ng-container *ngIf="cellTemplate"
1138
+ [ngTemplateOutlet]="cellTemplate"
1139
+ [ngTemplateOutletContext]="{ widget: widget }">
1140
+ </ng-container>
1141
+ </app-widget-renderer>
1142
+ </ng-template>
1143
+ </app-grid>
1127
1144
  </div>
1128
1145
  </ng-container>
1129
1146
 
1130
- <!-- ══════════════════════════════════════════════════════════
1131
- NORMAL MODE
1132
- ══════════════════════════════════════════════════════════ -->
1147
+ <!-- NORMAL MODE -->
1133
1148
  <ng-template #normalMode>
1134
1149
  <div class="dashboard-wrapper" [ngStyle]="wrapperStyles">
1135
1150
  <main class="main-content">
1136
1151
 
1137
1152
  <header class="dashboard-header">
1138
- <div class="tabs-container">
1139
- <div
1140
- *ngFor="let page of pages"
1141
- class="tab"
1142
- [class.active]="page.id === activePageId"
1143
- (click)="onSelectPage(page.id)"
1144
- >
1145
- <span class="tab-name">{{ page.name }}</span>
1146
- <button class="tab-action tab-popout" (click)="onPopOut($event, page.id)" title="Open in new window">
1147
- <i class="la la-external-link-alt"></i>
1148
- </button>
1149
- <button class="tab-action tab-close" *ngIf="pages.length > 1" (click)="onRemovePage($event, page.id)" title="Close">
1150
- <i class="la la-times"></i>
1153
+
1154
+ <!-- Tabs pill — scrollable, fades when overflowing -->
1155
+ <div class="tabs-scroll-track">
1156
+ <div class="tabs-container" #tabsContainer>
1157
+ <div
1158
+ *ngFor="let page of pages"
1159
+ class="tab"
1160
+ [class.active]="page.id === activePageId"
1161
+ (click)="onSelectPage(page.id)"
1162
+ >
1163
+ <span class="tab-name">{{ page.name }}</span>
1164
+ <button class="tab-action tab-popout" (click)="onPopOut($event, page.id)" title="Open in new window">
1165
+ <i class="la la-external-link-alt"></i>
1166
+ </button>
1167
+ <button class="tab-action tab-close" *ngIf="pages.length > 1" (click)="onRemovePage($event, page.id)" title="Close">
1168
+ <i class="la la-times"></i>
1169
+ </button>
1170
+ </div>
1171
+ <button class="btn-add-page" (click)="onAddPage()" title="New workspace">
1172
+ <i class="la la-plus"></i>
1151
1173
  </button>
1152
1174
  </div>
1153
- <button class="btn-add-page" (click)="onAddPage()" title="New workspace">
1154
- <i class="la la-plus"></i>
1155
- </button>
1175
+ <!-- fade hint shown when scrollable -->
1176
+ <div class="tabs-fade-right" *ngIf="tabsOverflow"></div>
1156
1177
  </div>
1157
1178
 
1158
- <!-- Add Widget button — consumer decides what happens -->
1159
1179
  <button class="btn-add-widget" (click)="addWidgetRequested.emit()" title="Add widget">
1160
1180
  <i class="la la-plus"></i>
1161
1181
  <span>Add Widget</span>
1162
1182
  </button>
1183
+
1163
1184
  </header>
1164
1185
 
1165
1186
  <div class="grid-container">
1166
1187
  <app-grid [widgets]="activePage?.widgets || []" (itemChanged)="onItemChanged($event)">
1167
1188
  <ng-template gridCell let-widget="widget">
1168
- <app-widget-renderer
1169
- [widget]="widget"
1170
- [theme]="resolvedTheme"
1189
+ <app-widget-renderer [widget]="widget" [theme]="resolvedTheme"
1171
1190
  (editRequested)="editWidgetRequested.emit($event)"
1172
- (removeRequested)="onRemoveWidget($event)"
1173
- >
1174
- <!-- Stamp consumer's cell template (if provided) inside the card body -->
1175
- <ng-container
1176
- *ngIf="cellTemplate"
1191
+ (removeRequested)="onRemoveWidget($event)">
1192
+ <ng-container *ngIf="cellTemplate"
1177
1193
  [ngTemplateOutlet]="cellTemplate"
1178
1194
  [ngTemplateOutletContext]="{ widget: widget }">
1179
1195
  </ng-container>
@@ -1185,8 +1201,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1185
1201
  </main>
1186
1202
  </div>
1187
1203
  </ng-template>
1188
- `, 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"] }]
1189
- }], ctorParameters: function () { return [{ type: DashboardStateService }]; }, propDecorators: { initialLayout: [{
1204
+ `, 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"] }]
1205
+ }], ctorParameters: function () { return [{ type: DashboardStateService }, { type: i0.ChangeDetectorRef }, { type: i0.NgZone }]; }, propDecorators: { initialLayout: [{
1190
1206
  type: Input
1191
1207
  }], theme: [{
1192
1208
  type: Input
@@ -1197,15 +1213,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1197
1213
  }], cellTemplate: [{
1198
1214
  type: ContentChild,
1199
1215
  args: [GridCellDirective, { read: TemplateRef }]
1216
+ }], tabsContainerRef: [{
1217
+ type: ViewChild,
1218
+ args: ['tabsContainer']
1200
1219
  }] } });
1201
1220
 
1202
1221
  class DashboardModule {
1203
1222
  }
1204
1223
  DashboardModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1205
1224
  DashboardModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, declarations: [DashboardComponent,
1225
+ OverflowActivePipe,
1206
1226
  WidgetRendererComponent,
1207
1227
  CustomGridComponent,
1208
1228
  GridCellDirective], imports: [CommonModule], exports: [DashboardComponent,
1229
+ OverflowActivePipe,
1209
1230
  WidgetRendererComponent,
1210
1231
  CustomGridComponent,
1211
1232
  GridCellDirective] });
@@ -1215,6 +1236,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1215
1236
  args: [{
1216
1237
  declarations: [
1217
1238
  DashboardComponent,
1239
+ OverflowActivePipe,
1218
1240
  WidgetRendererComponent,
1219
1241
  CustomGridComponent,
1220
1242
  GridCellDirective,
@@ -1225,6 +1247,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1225
1247
  providers: [DashboardStateService],
1226
1248
  exports: [
1227
1249
  DashboardComponent,
1250
+ OverflowActivePipe,
1228
1251
  WidgetRendererComponent,
1229
1252
  CustomGridComponent,
1230
1253
  GridCellDirective,
@@ -1256,5 +1279,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1256
1279
  * Generated bundle index. Do not edit.
1257
1280
  */
1258
1281
 
1259
- export { AppModule, CustomGridComponent, DashboardComponent, DashboardModule, DashboardStateService, GridCellDirective, WidgetRendererComponent };
1282
+ export { AppModule, CustomGridComponent, DashboardComponent, DashboardModule, DashboardStateService, GridCellDirective, OverflowActivePipe, WidgetRendererComponent };
1260
1283
  //# sourceMappingURL=ogidor-dashboard.mjs.map