@byuhbll/components 5.0.1 → 5.0.3-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ import { CommonModule, DatePipe, DOCUMENT, LowerCasePipe, NgIf, NgClass } from '
3
3
  import { toSignal, toObservable } from '@angular/core/rxjs-interop';
4
4
  import { HttpClient } from '@angular/common/http';
5
5
  import * as i0 from '@angular/core';
6
- import { Component, ChangeDetectionStrategy, ViewChild, Input, input, EventEmitter, Output, inject, computed, ViewChildren, Pipe, Renderer2, viewChild, HostListener, ElementRef, ViewEncapsulation, booleanAttribute, createComponent, Injectable } from '@angular/core';
6
+ import { Component, ChangeDetectionStrategy, ViewChild, Input, input, EventEmitter, Output, inject, computed, ViewChildren, Pipe, Renderer2, viewChild, HostListener, ElementRef, Injector, runInInjectionContext, effect, ViewEncapsulation, booleanAttribute, createComponent, Injectable, signal } from '@angular/core';
7
7
  import { trigger, transition, group, style, query, animateChild, animate } from '@angular/animations';
8
8
  import { map, of, switchMap, shareReplay, combineLatest, Subject, Subscription } from 'rxjs';
9
9
  import { BreakpointObserver } from '@angular/cdk/layout';
@@ -1302,6 +1302,85 @@ class HeaderWithImpersonationComponent {
1302
1302
  : false);
1303
1303
  this.showImpersonationModal = false;
1304
1304
  this.isImpersonating = computed(() => !!this.parsedToken()?.['impersonator']);
1305
+ // Inactivity timeout for impersonation
1306
+ this.activityEvents = [
1307
+ 'keydown',
1308
+ 'pointerdown',
1309
+ 'wheel',
1310
+ 'scroll',
1311
+ ];
1312
+ this.inactivityTimerId = null;
1313
+ this.inactivityTimeoutMs = 5 * 60 * 1000; // 5 minutes
1314
+ this.debounceTimerId = null;
1315
+ this.debounceDelayMs = 250;
1316
+ this.trackingActive = false;
1317
+ this.injector = inject(Injector);
1318
+ /** Reset the inactivity countdown (no-op if not impersonating) */
1319
+ this.resetInactivityTimer = () => {
1320
+ if (!this.isImpersonating())
1321
+ return;
1322
+ if (this.inactivityTimerId)
1323
+ clearTimeout(this.inactivityTimerId);
1324
+ this.inactivityTimerId = window.setTimeout(() => {
1325
+ this.endImpersonation.emit();
1326
+ this.stopInactivityTracking();
1327
+ }, this.inactivityTimeoutMs);
1328
+ };
1329
+ /** Debounce activity to avoid hammering resets during event storms */
1330
+ this.debouncedResetTimer = () => {
1331
+ if (this.debounceTimerId)
1332
+ clearTimeout(this.debounceTimerId);
1333
+ this.debounceTimerId = window.setTimeout(() => {
1334
+ this.resetInactivityTimer();
1335
+ }, this.debounceDelayMs);
1336
+ };
1337
+ }
1338
+ ngOnInit() {
1339
+ // effect can only be used within an injection context (ex: constructor)
1340
+ runInInjectionContext(this.injector, () => {
1341
+ effect(() => {
1342
+ if (this.isImpersonating()) {
1343
+ if (!this.trackingActive)
1344
+ this.startInactivityTracking();
1345
+ }
1346
+ else {
1347
+ if (this.trackingActive)
1348
+ this.stopInactivityTracking();
1349
+ }
1350
+ });
1351
+ });
1352
+ }
1353
+ ngOnDestroy() {
1354
+ this.stopInactivityTracking();
1355
+ }
1356
+ /** Begin listening and start countdown */
1357
+ startInactivityTracking() {
1358
+ this.activityEvents.forEach((event) => {
1359
+ // 'scroll' on document doesn't bubble; use window + capture to catch nested scrolls
1360
+ const target = event === 'scroll' ? window : document;
1361
+ const options = event === 'scroll'
1362
+ ? { passive: true, capture: true }
1363
+ : { passive: true };
1364
+ target.addEventListener(event, this.debouncedResetTimer, options);
1365
+ });
1366
+ this.trackingActive = true;
1367
+ this.resetInactivityTimer();
1368
+ }
1369
+ /** Remove listeners and clear timers */
1370
+ stopInactivityTracking() {
1371
+ this.activityEvents.forEach((event) => {
1372
+ const target = event === 'scroll' ? window : document;
1373
+ target.removeEventListener(event, this.debouncedResetTimer);
1374
+ });
1375
+ if (this.inactivityTimerId) {
1376
+ clearTimeout(this.inactivityTimerId);
1377
+ this.inactivityTimerId = null;
1378
+ }
1379
+ if (this.debounceTimerId) {
1380
+ clearTimeout(this.debounceTimerId);
1381
+ this.debounceTimerId = null;
1382
+ }
1383
+ this.trackingActive = false;
1305
1384
  }
1306
1385
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: HeaderWithImpersonationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1307
1386
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.1.0", type: HeaderWithImpersonationComponent, isStandalone: true, selector: "lib-header-with-impersonation", inputs: { accessTokenPayload: { classPropertyName: "accessTokenPayload", publicName: "accessTokenPayload", isSignal: true, isRequired: true, transformFunction: null }, oidcBaseUri: { classPropertyName: "oidcBaseUri", publicName: "oidcBaseUri", isSignal: true, isRequired: false, transformFunction: null }, oidcDefaultIdp: { classPropertyName: "oidcDefaultIdp", publicName: "oidcDefaultIdp", isSignal: true, isRequired: false, transformFunction: null }, mainSiteBaseUrl: { classPropertyName: "mainSiteBaseUrl", publicName: "mainSiteBaseUrl", isSignal: true, isRequired: false, transformFunction: null }, personBaseUri: { classPropertyName: "personBaseUri", publicName: "personBaseUri", isSignal: true, isRequired: false, transformFunction: null }, myAccountApiBaseUri: { classPropertyName: "myAccountApiBaseUri", publicName: "myAccountApiBaseUri", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { login: "login", logout: "logout", endImpersonation: "endImpersonation" }, ngImport: i0, template: "<lib-impersonation-banner\n [accessTokenPayload]=\"accessTokenPayload()\"\n (endImpersonation)=\"endImpersonation.emit()\"\n [personBaseUri]=\"personBaseUri()\"\n [myAccountApiBaseUri]=\"myAccountApiBaseUri()\"\n></lib-impersonation-banner>\n<lib-hbll-header\n [name]=\"name()\"\n [showImpersonateButton]=\"showImpersonateButton()\"\n (openImpersonationModal)=\"showImpersonationModal = true\"\n (login)=\"login.emit()\"\n (logout)=\"logout.emit()\"\n [mainsitebaseurl]=\"mainSiteBaseUrl()\"\n/>\n<lib-impersonate-modal\n [showModal]=\"showImpersonationModal\"\n [oidcBaseUri]=\"oidcBaseUri()\"\n [oidcDefaultIdp]=\"oidcDefaultIdp()\"\n [accessTokenPayload]=\"accessTokenPayload()\"\n (dismiss)=\"showImpersonationModal = false\"\n (init)=\"showImpersonationModal = true\"\n></lib-impersonate-modal>\n", styles: [""], dependencies: [{ kind: "component", type: HbllHeaderComponent, selector: "lib-hbll-header", inputs: ["name", "mainsitebaseurl", "showImpersonateButton"], outputs: ["openImpersonationModal", "login", "logout"] }, { kind: "component", type: ImpersonationBannerComponent, selector: "lib-impersonation-banner", inputs: ["accessTokenPayload", "personBaseUri", "myAccountApiBaseUri"], outputs: ["endImpersonation"] }, { kind: "component", type: ImpersonateModalComponent, selector: "lib-impersonate-modal", inputs: ["showModal", "oidcBaseUri", "oidcDefaultIdp", "accessTokenPayload"], outputs: ["dismiss", "init"] }] }); }
@@ -2502,6 +2581,183 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImpor
2502
2581
  args: [{ providedIn: 'root' }]
2503
2582
  }], ctorParameters: () => [{ type: i0.ApplicationRef }, { type: i0.EnvironmentInjector }] });
2504
2583
 
2584
+ /**
2585
+ * A flexible, reusable button component that supports multiple button types
2586
+ * and various content combinations (icon before, title, icon after).
2587
+ *
2588
+ * @example
2589
+ * ```html
2590
+ * <!-- Primary button with icon and title -->
2591
+ * <app-hbll-button-custom-component
2592
+ * buttonType="primary"
2593
+ * title="Copy Citation"
2594
+ * iconBefore="../../assets/copy.svg"
2595
+ * iconAlt="Copy icon"
2596
+ * (buttonClick)="copyCitation()">
2597
+ * </app-hbll-button-custom-component>
2598
+ *
2599
+ * <!-- Secondary button with title only -->
2600
+ * <app-hbll-button-custom-component
2601
+ * buttonType="secondary"
2602
+ * title="Cancel"
2603
+ * (buttonClick)="cancelAction()">
2604
+ * </app-hbll-button-custom-component>
2605
+ *
2606
+ * <!-- Transparent button with icon after title -->
2607
+ * <app-hbll-button-custom-component
2608
+ * buttonType="transparent"
2609
+ * title="Download"
2610
+ * iconAfter="../../assets/download.svg"
2611
+ * iconAlt="Download icon"
2612
+ * (buttonClick)="downloadFile()">
2613
+ * </app-hbll-button-custom-component>
2614
+ *
2615
+ * <!-- Thin button with reduced padding -->
2616
+ * <app-hbll-button-custom-component
2617
+ * buttonType="primary"
2618
+ * title="Submit"
2619
+ * [isThin]="true"
2620
+ * (buttonClick)="submitForm()">
2621
+ * </app-hbll-button-custom-component>
2622
+ * ```
2623
+ */
2624
+ class ButtonComponent {
2625
+ constructor() {
2626
+ /**
2627
+ * The visual style of the button
2628
+ * @default 'primary'
2629
+ */
2630
+ this.buttonType = 'primary';
2631
+ /**
2632
+ * Whether the button is disabled
2633
+ * @default false
2634
+ */
2635
+ this.disabled = false;
2636
+ /**
2637
+ * Whether the button should have thin padding
2638
+ * @default false
2639
+ */
2640
+ this.isThin = false;
2641
+ /**
2642
+ * Event emitted when the button is clicked or activated via keyboard
2643
+ */
2644
+ this.buttonClick = new EventEmitter();
2645
+ }
2646
+ onButtonClick() {
2647
+ if (!this.disabled) {
2648
+ this.buttonClick.emit();
2649
+ }
2650
+ }
2651
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2652
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.1.0", type: ButtonComponent, isStandalone: true, selector: "lib-button", inputs: { buttonType: "buttonType", title: "title", iconBefore: "iconBefore", iconAfter: "iconAfter", iconAlt: "iconAlt", disabled: "disabled", isThin: "isThin" }, outputs: { buttonClick: "buttonClick" }, ngImport: i0, template: "<button\n type=\"button\"\n [class]=\"'btn btn-' + buttonType + (isThin ? ' btn-thin' : '')\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"title || iconAlt\"\n (click)=\"onButtonClick()\"\n [attr.tabindex]=\"disabled ? -1 : 0\"\n>\n <!-- Icon before title -->\n <span class=\"icon icon-before\" *ngIf=\"iconBefore\">\n <img\n [src]=\"iconBefore\"\n [class]=\"\n disabled\n ? 'disabled-icon'\n : buttonType === 'primary'\n ? 'white-icon'\n : 'dark-icon'\n \"\n [alt]=\"iconAlt || ''\"\n />\n </span>\n\n <!-- Title text -->\n <span class=\"button-title\" *ngIf=\"title\">{{ title }}</span>\n\n <!-- Icon after title -->\n <span class=\"icon icon-after\" *ngIf=\"iconAfter\">\n <img\n [src]=\"iconAfter\"\n [class]=\"\n disabled\n ? 'disabled-icon'\n : buttonType === 'primary'\n ? 'white-icon'\n : 'dark-icon'\n \"\n [alt]=\"iconAlt || ''\"\n />\n </span>\n</button>\n", styles: [".btn{padding:.8rem 2rem;border-radius:.25rem;font-size:1rem;font-weight:600;cursor:pointer;transition:all .2s ease;border:none;display:inline-flex;align-items:center;outline:none}.btn.btn-thin{padding:.4rem 2rem;border-radius:.5rem}.btn:disabled{cursor:not-allowed;color:#767676}.btn:disabled:not(.btn-transparent){background-color:#e7e7e7;border:1px solid #767676}.btn:focus{outline:.125rem solid #b967c7;outline-offset:.125rem}.btn .icon{display:flex;align-items:center;justify-content:center}.btn .icon img{height:1.5rem;width:auto}.btn .icon.icon-before{margin-right:.25rem}.btn .icon.icon-after{margin-left:.25rem}.btn .button-title{flex-shrink:0}.btn-primary{background-color:#0047ba;color:#fff;border:1px solid #0047ba}.btn-primary:hover:not(:disabled){background-color:#003995}.btn-secondary{background-color:#fff;color:#00245d;border:1px solid #0047ba}.btn-secondary:hover:not(:disabled){background-color:#e5edf8}.btn-transparent{background-color:transparent;color:#00245d}.btn-transparent:hover:not(:disabled){background-color:#e5edf8}.white-icon{filter:invert(100%) sepia(0%) saturate(7489%) hue-rotate(149deg) brightness(104%) contrast(100%)}.dark-icon{filter:invert(12%) sepia(44%) saturate(3863%) hue-rotate(207deg) brightness(90%) contrast(105%)}.disabled-icon{filter:invert(48%) sepia(0%) saturate(0%) hue-rotate(146deg) brightness(96%) contrast(88%)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
2653
+ }
2654
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ButtonComponent, decorators: [{
2655
+ type: Component,
2656
+ args: [{ selector: 'lib-button', standalone: true, imports: [CommonModule], template: "<button\n type=\"button\"\n [class]=\"'btn btn-' + buttonType + (isThin ? ' btn-thin' : '')\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"title || iconAlt\"\n (click)=\"onButtonClick()\"\n [attr.tabindex]=\"disabled ? -1 : 0\"\n>\n <!-- Icon before title -->\n <span class=\"icon icon-before\" *ngIf=\"iconBefore\">\n <img\n [src]=\"iconBefore\"\n [class]=\"\n disabled\n ? 'disabled-icon'\n : buttonType === 'primary'\n ? 'white-icon'\n : 'dark-icon'\n \"\n [alt]=\"iconAlt || ''\"\n />\n </span>\n\n <!-- Title text -->\n <span class=\"button-title\" *ngIf=\"title\">{{ title }}</span>\n\n <!-- Icon after title -->\n <span class=\"icon icon-after\" *ngIf=\"iconAfter\">\n <img\n [src]=\"iconAfter\"\n [class]=\"\n disabled\n ? 'disabled-icon'\n : buttonType === 'primary'\n ? 'white-icon'\n : 'dark-icon'\n \"\n [alt]=\"iconAlt || ''\"\n />\n </span>\n</button>\n", styles: [".btn{padding:.8rem 2rem;border-radius:.25rem;font-size:1rem;font-weight:600;cursor:pointer;transition:all .2s ease;border:none;display:inline-flex;align-items:center;outline:none}.btn.btn-thin{padding:.4rem 2rem;border-radius:.5rem}.btn:disabled{cursor:not-allowed;color:#767676}.btn:disabled:not(.btn-transparent){background-color:#e7e7e7;border:1px solid #767676}.btn:focus{outline:.125rem solid #b967c7;outline-offset:.125rem}.btn .icon{display:flex;align-items:center;justify-content:center}.btn .icon img{height:1.5rem;width:auto}.btn .icon.icon-before{margin-right:.25rem}.btn .icon.icon-after{margin-left:.25rem}.btn .button-title{flex-shrink:0}.btn-primary{background-color:#0047ba;color:#fff;border:1px solid #0047ba}.btn-primary:hover:not(:disabled){background-color:#003995}.btn-secondary{background-color:#fff;color:#00245d;border:1px solid #0047ba}.btn-secondary:hover:not(:disabled){background-color:#e5edf8}.btn-transparent{background-color:transparent;color:#00245d}.btn-transparent:hover:not(:disabled){background-color:#e5edf8}.white-icon{filter:invert(100%) sepia(0%) saturate(7489%) hue-rotate(149deg) brightness(104%) contrast(100%)}.dark-icon{filter:invert(12%) sepia(44%) saturate(3863%) hue-rotate(207deg) brightness(90%) contrast(105%)}.disabled-icon{filter:invert(48%) sepia(0%) saturate(0%) hue-rotate(146deg) brightness(96%) contrast(88%)}\n"] }]
2657
+ }], propDecorators: { buttonType: [{
2658
+ type: Input
2659
+ }], title: [{
2660
+ type: Input
2661
+ }], iconBefore: [{
2662
+ type: Input
2663
+ }], iconAfter: [{
2664
+ type: Input
2665
+ }], iconAlt: [{
2666
+ type: Input
2667
+ }], disabled: [{
2668
+ type: Input
2669
+ }], isThin: [{
2670
+ type: Input
2671
+ }], buttonClick: [{
2672
+ type: Output
2673
+ }] } });
2674
+
2675
+ class ButtonGroupItemComponent {
2676
+ constructor() {
2677
+ this.isActive = false;
2678
+ this.disabled = false;
2679
+ this.buttonClick = new EventEmitter();
2680
+ }
2681
+ onClick() {
2682
+ this.buttonClick.emit(this.button.id);
2683
+ }
2684
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ButtonGroupItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2685
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.1.0", type: ButtonGroupItemComponent, isStandalone: true, selector: "lib-button-group-item", inputs: { button: "button", isActive: "isActive", disabled: "disabled" }, outputs: { buttonClick: "buttonClick" }, ngImport: i0, template: "<button\n type=\"button\"\n class=\"tab-btn\"\n [class.active]=\"isActive\"\n [disabled]=\"disabled\"\n (click)=\"onClick()\"\n role=\"tab\"\n [attr.aria-pressed]=\"isActive\"\n [attr.aria-label]=\"button.title\"\n [attr.tabindex]=\"disabled ? -1 : 0\"\n>\n @if (button.icon) {\n <span class=\"icon\">\n <img\n [src]=\"button.icon\"\n [class]=\"disabled ? 'disabled-icon' : isActive ? 'white-icon' : 'dark-icon'\"\n [alt]=\"button.iconAlt || ''\"\n />\n </span>\n }\n\n @if (button.title) {\n <span class=\"button-title\">{{ button.title }}</span>\n }\n</button>\n", styles: [".tab-btn{padding:.5rem;font-size:1rem;font-weight:400;color:#00245d;background-color:#fff;cursor:pointer;position:relative;border:1px solid #d0d0d0;border-left:none;border-radius:0;transition:all .2s ease;display:inline-flex;align-items:center;justify-content:center;gap:.25rem;height:2.3rem}.tab-btn:not(:disabled):hover{background-color:#e5edf8}.tab-btn.active{background-color:#0047ba;color:#fff;z-index:10}.tab-btn.active:not(:disabled):hover{background-color:#003995}.tab-btn:focus{outline:.125rem solid #b967c7;outline-offset:.125rem}.tab-btn:disabled{cursor:not-allowed;color:#767676;background-color:#e7e7e7;border:1px solid #767676}.tab-btn .icon{display:flex;align-items:center;justify-content:center}.tab-btn .icon img{height:1rem;width:auto}.tab-btn .button-title{flex-shrink:0}.white-icon{filter:invert(100%) sepia(0%) saturate(7489%) hue-rotate(149deg) brightness(104%) contrast(100%)}.dark-icon{filter:invert(12%) sepia(44%) saturate(3863%) hue-rotate(207deg) brightness(90%) contrast(105%)}.disabled-icon{filter:invert(48%) sepia(0%) saturate(0%) hue-rotate(146deg) brightness(96%) contrast(88%)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] }); }
2686
+ }
2687
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ButtonGroupItemComponent, decorators: [{
2688
+ type: Component,
2689
+ args: [{ selector: 'lib-button-group-item', standalone: true, imports: [CommonModule], template: "<button\n type=\"button\"\n class=\"tab-btn\"\n [class.active]=\"isActive\"\n [disabled]=\"disabled\"\n (click)=\"onClick()\"\n role=\"tab\"\n [attr.aria-pressed]=\"isActive\"\n [attr.aria-label]=\"button.title\"\n [attr.tabindex]=\"disabled ? -1 : 0\"\n>\n @if (button.icon) {\n <span class=\"icon\">\n <img\n [src]=\"button.icon\"\n [class]=\"disabled ? 'disabled-icon' : isActive ? 'white-icon' : 'dark-icon'\"\n [alt]=\"button.iconAlt || ''\"\n />\n </span>\n }\n\n @if (button.title) {\n <span class=\"button-title\">{{ button.title }}</span>\n }\n</button>\n", styles: [".tab-btn{padding:.5rem;font-size:1rem;font-weight:400;color:#00245d;background-color:#fff;cursor:pointer;position:relative;border:1px solid #d0d0d0;border-left:none;border-radius:0;transition:all .2s ease;display:inline-flex;align-items:center;justify-content:center;gap:.25rem;height:2.3rem}.tab-btn:not(:disabled):hover{background-color:#e5edf8}.tab-btn.active{background-color:#0047ba;color:#fff;z-index:10}.tab-btn.active:not(:disabled):hover{background-color:#003995}.tab-btn:focus{outline:.125rem solid #b967c7;outline-offset:.125rem}.tab-btn:disabled{cursor:not-allowed;color:#767676;background-color:#e7e7e7;border:1px solid #767676}.tab-btn .icon{display:flex;align-items:center;justify-content:center}.tab-btn .icon img{height:1rem;width:auto}.tab-btn .button-title{flex-shrink:0}.white-icon{filter:invert(100%) sepia(0%) saturate(7489%) hue-rotate(149deg) brightness(104%) contrast(100%)}.dark-icon{filter:invert(12%) sepia(44%) saturate(3863%) hue-rotate(207deg) brightness(90%) contrast(105%)}.disabled-icon{filter:invert(48%) sepia(0%) saturate(0%) hue-rotate(146deg) brightness(96%) contrast(88%)}\n"] }]
2690
+ }], propDecorators: { button: [{
2691
+ type: Input,
2692
+ args: [{ required: true }]
2693
+ }], isActive: [{
2694
+ type: Input
2695
+ }], disabled: [{
2696
+ type: Input
2697
+ }], buttonClick: [{
2698
+ type: Output
2699
+ }] } });
2700
+
2701
+ class ButtonGroupComponent {
2702
+ constructor() {
2703
+ // Input: Whether the button group is disabled
2704
+ this.disabled = false;
2705
+ // Output: Emits the id of the active button when changed
2706
+ this.activeButtonChange = new EventEmitter();
2707
+ // Internal state: Currently active button id
2708
+ this.activeButtonId = signal('');
2709
+ }
2710
+ ngOnInit() {
2711
+ this.initializeActiveButton();
2712
+ }
2713
+ ngOnChanges(changes) {
2714
+ // When buttons or initialActiveId change (important for web components)
2715
+ if (changes['buttons'] || changes['initialActiveId']) {
2716
+ this.initializeActiveButton();
2717
+ }
2718
+ }
2719
+ initializeActiveButton() {
2720
+ // Set initial active button
2721
+ const initial = this.initialActiveId;
2722
+ if (initial) {
2723
+ this.activeButtonId.set(initial);
2724
+ }
2725
+ else {
2726
+ // Find the first active button from the config
2727
+ const activeButton = this.buttons?.find((btn) => btn.isActive);
2728
+ if (activeButton) {
2729
+ this.activeButtonId.set(activeButton.id);
2730
+ }
2731
+ }
2732
+ }
2733
+ // Handle button click
2734
+ onButtonClick(buttonId) {
2735
+ if (!this.disabled) {
2736
+ this.activeButtonId.set(buttonId);
2737
+ this.activeButtonChange.emit(buttonId);
2738
+ }
2739
+ }
2740
+ // Check if a button is active
2741
+ isButtonActive(buttonId) {
2742
+ return this.activeButtonId() === buttonId;
2743
+ }
2744
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ButtonGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2745
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.1.0", type: ButtonGroupComponent, isStandalone: true, selector: "lib-button-group", inputs: { buttons: "buttons", initialActiveId: "initialActiveId", disabled: "disabled" }, outputs: { activeButtonChange: "activeButtonChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"button-group\" role=\"tablist\">\n @for (button of buttons; track button.id) {\n <lib-button-group-item\n [button]=\"button\"\n [isActive]=\"isButtonActive(button.id)\"\n [disabled]=\"disabled\"\n (buttonClick)=\"onButtonClick($event)\"\n class=\"button-group-item\"\n />\n }\n</div>\n", styles: [".button-group{display:flex}.button-group lib-button-group-item:first-child::ng-deep .tab-btn{border-radius:.25rem 0 0 .25rem;border-left:1px solid #d0d0d0}.button-group lib-button-group-item:first-child::ng-deep .tab-btn:disabled{border-left:1px solid #767676}.button-group lib-button-group-item:last-child::ng-deep .tab-btn{border-radius:0 .25rem .25rem 0}@media (max-width: 640px){.button-group{overflow-x:auto;-webkit-overflow-scrolling:touch}}\n"], dependencies: [{ kind: "component", type: ButtonGroupItemComponent, selector: "lib-button-group-item", inputs: ["button", "isActive", "disabled"], outputs: ["buttonClick"] }] }); }
2746
+ }
2747
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ButtonGroupComponent, decorators: [{
2748
+ type: Component,
2749
+ args: [{ selector: 'lib-button-group', standalone: true, imports: [ButtonGroupItemComponent], template: "<div class=\"button-group\" role=\"tablist\">\n @for (button of buttons; track button.id) {\n <lib-button-group-item\n [button]=\"button\"\n [isActive]=\"isButtonActive(button.id)\"\n [disabled]=\"disabled\"\n (buttonClick)=\"onButtonClick($event)\"\n class=\"button-group-item\"\n />\n }\n</div>\n", styles: [".button-group{display:flex}.button-group lib-button-group-item:first-child::ng-deep .tab-btn{border-radius:.25rem 0 0 .25rem;border-left:1px solid #d0d0d0}.button-group lib-button-group-item:first-child::ng-deep .tab-btn:disabled{border-left:1px solid #767676}.button-group lib-button-group-item:last-child::ng-deep .tab-btn{border-radius:0 .25rem .25rem 0}@media (max-width: 640px){.button-group{overflow-x:auto;-webkit-overflow-scrolling:touch}}\n"] }]
2750
+ }], propDecorators: { buttons: [{
2751
+ type: Input,
2752
+ args: [{ required: true }]
2753
+ }], initialActiveId: [{
2754
+ type: Input
2755
+ }], disabled: [{
2756
+ type: Input
2757
+ }], activeButtonChange: [{
2758
+ type: Output
2759
+ }] } });
2760
+
2505
2761
  /*
2506
2762
  * Public API Surface of components
2507
2763
  */
@@ -2510,5 +2766,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImpor
2510
2766
  * Generated bundle index. Do not edit.
2511
2767
  */
2512
2768
 
2513
- export { ADVANCED_SEARCH_FIELD_MAP, ADVANCED_SEARCH_OPTIONS, ADVANCED_SEARCH_QUALIFIER_MAP, HbllFooterComponent, HbllHeaderComponent, HbllItemTypeIconPipe, HeaderWithImpersonationComponent, ImpersonateModalComponent, ImpersonateUserPipe, ImpersonationBannerComponent, LIBRARY_HOURS_API_URL, SnackbarComponent, SnackbarService, SsSearchBarComponent, StatusButtonComponent, defaultOidcBaseUri, defaultOidcDefaultIdp, getUserStatusFromRoles, isAdvancedSearchExternalFieldOption, isAdvancedSearchFieldOption, isAdvancedSearchLocalFieldOption, isSearchScope };
2769
+ export { ADVANCED_SEARCH_FIELD_MAP, ADVANCED_SEARCH_OPTIONS, ADVANCED_SEARCH_QUALIFIER_MAP, ButtonComponent, ButtonGroupComponent, HbllFooterComponent, HbllHeaderComponent, HbllItemTypeIconPipe, HeaderWithImpersonationComponent, ImpersonateModalComponent, ImpersonateUserPipe, ImpersonationBannerComponent, LIBRARY_HOURS_API_URL, SnackbarComponent, SnackbarService, SsSearchBarComponent, StatusButtonComponent, defaultOidcBaseUri, defaultOidcDefaultIdp, getUserStatusFromRoles, isAdvancedSearchExternalFieldOption, isAdvancedSearchFieldOption, isAdvancedSearchLocalFieldOption, isSearchScope };
2514
2770
  //# sourceMappingURL=byuhbll-components.mjs.map