@byuhbll/components 6.0.0-rc.0 → 6.0.0-rc.0.5
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.
- package/fesm2022/byuhbll-components.mjs +357 -5
- package/fesm2022/byuhbll-components.mjs.map +1 -1
- package/lib/button/button.component.d.ts +66 -0
- package/lib/button-group/button-group.component.d.ts +46 -0
- package/lib/header-with-impersonation/header-with-impersonation.component.d.ts +19 -2
- package/lib/impersonate-modal/impersonate-modal.component.d.ts +1 -1
- package/lib/subatomic-components/button-group-item/button-group-item.component.d.ts +18 -0
- package/package.json +1 -1
- package/public-api.d.ts +3 -0
|
@@ -3,7 +3,7 @@ import { CommonModule, DatePipe, DOCUMENT, NgIf, NgClass } from '@angular/common
|
|
|
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 { Input, ViewChild, ChangeDetectionStrategy, Component, input, EventEmitter, Output, inject, computed, ViewChildren, Pipe, Renderer2, viewChild, HostListener, ElementRef, ViewEncapsulation, booleanAttribute, createComponent, Injectable } from '@angular/core';
|
|
6
|
+
import { Input, ViewChild, ChangeDetectionStrategy, Component, input, EventEmitter, Output, inject, computed, ViewChildren, Pipe, Renderer2, viewChild, HostListener, ElementRef, Injector, runInInjectionContext, effect, ViewEncapsulation, booleanAttribute, createComponent, Injectable, signal, ContentChildren } from '@angular/core';
|
|
7
7
|
import { trigger, transition, group, style, query, animate, animateChild } from '@angular/animations';
|
|
8
8
|
import { map, of, switchMap, shareReplay, combineLatest, Subject, Subscription } from 'rxjs';
|
|
9
9
|
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
@@ -1095,7 +1095,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
1095
1095
|
}] } });
|
|
1096
1096
|
|
|
1097
1097
|
const defaultOidcBaseUri = 'https://keycloak.lib.byu.edu/';
|
|
1098
|
-
const defaultOidcDefaultIdp = '
|
|
1098
|
+
const defaultOidcDefaultIdp = 'ces';
|
|
1099
1099
|
class ImpersonateUserPipe {
|
|
1100
1100
|
transform(user) {
|
|
1101
1101
|
return `${user.name} (${user.netId || 'Unknown'})${user.restricted ? ' — Restricted' : ''}`;
|
|
@@ -1293,6 +1293,93 @@ class HeaderWithImpersonationComponent {
|
|
|
1293
1293
|
: false);
|
|
1294
1294
|
this.showImpersonationModal = false;
|
|
1295
1295
|
this.isImpersonating = computed(() => !!this.parsedToken()?.['impersonator']);
|
|
1296
|
+
// Inactivity timeout for impersonation
|
|
1297
|
+
this.activityEvents = [
|
|
1298
|
+
'keydown',
|
|
1299
|
+
'pointerdown',
|
|
1300
|
+
'wheel',
|
|
1301
|
+
'scroll',
|
|
1302
|
+
];
|
|
1303
|
+
this.inactivityTimerId = null;
|
|
1304
|
+
this.inactivityTimeoutMs = 5 * 60 * 1000; // 5 minutes
|
|
1305
|
+
this.debounceTimerId = null;
|
|
1306
|
+
this.debounceDelayMs = 250;
|
|
1307
|
+
this.trackingActive = false;
|
|
1308
|
+
this.injector = inject(Injector);
|
|
1309
|
+
/** Reset the inactivity countdown (no-op if not impersonating) */
|
|
1310
|
+
this.resetInactivityTimer = () => {
|
|
1311
|
+
if (!this.isImpersonating())
|
|
1312
|
+
return;
|
|
1313
|
+
if (this.inactivityTimerId)
|
|
1314
|
+
clearTimeout(this.inactivityTimerId);
|
|
1315
|
+
this.inactivityTimerId = window.setTimeout(() => {
|
|
1316
|
+
this.endImpersonation.emit();
|
|
1317
|
+
this.stopInactivityTracking();
|
|
1318
|
+
}, this.inactivityTimeoutMs);
|
|
1319
|
+
};
|
|
1320
|
+
/** Debounce activity to avoid hammering resets during event storms */
|
|
1321
|
+
this.debouncedResetTimer = () => {
|
|
1322
|
+
if (this.debounceTimerId)
|
|
1323
|
+
clearTimeout(this.debounceTimerId);
|
|
1324
|
+
this.debounceTimerId = window.setTimeout(() => {
|
|
1325
|
+
this.resetInactivityTimer();
|
|
1326
|
+
}, this.debounceDelayMs);
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
ngOnInit() {
|
|
1330
|
+
// effect can only be used within an injection context (ex: constructor)
|
|
1331
|
+
runInInjectionContext(this.injector, () => {
|
|
1332
|
+
effect(() => {
|
|
1333
|
+
const impersonating = this.isImpersonating();
|
|
1334
|
+
const token = this.parsedToken();
|
|
1335
|
+
// when token refreshes after 5 minutes, it leaves a small moment where there is no token
|
|
1336
|
+
// this prevents dropping inactivtiy timer during that blip
|
|
1337
|
+
if (!token)
|
|
1338
|
+
return;
|
|
1339
|
+
if (impersonating) {
|
|
1340
|
+
if (!this.trackingActive)
|
|
1341
|
+
this.startInactivityTracking();
|
|
1342
|
+
}
|
|
1343
|
+
else {
|
|
1344
|
+
if (this.trackingActive)
|
|
1345
|
+
this.stopInactivityTracking();
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
ngOnDestroy() {
|
|
1351
|
+
this.stopInactivityTracking();
|
|
1352
|
+
}
|
|
1353
|
+
/** Begin listening and start countdown */
|
|
1354
|
+
startInactivityTracking() {
|
|
1355
|
+
this.activityEvents.forEach((event) => {
|
|
1356
|
+
// 'scroll' on document doesn't bubble; use window + capture to catch nested scrolls
|
|
1357
|
+
const target = event === 'scroll' ? window : document;
|
|
1358
|
+
const options = event === 'scroll'
|
|
1359
|
+
? { passive: true, capture: true }
|
|
1360
|
+
: { passive: true };
|
|
1361
|
+
target.addEventListener(event, this.debouncedResetTimer, options);
|
|
1362
|
+
});
|
|
1363
|
+
this.trackingActive = true;
|
|
1364
|
+
this.resetInactivityTimer();
|
|
1365
|
+
}
|
|
1366
|
+
/** Remove listeners and clear timers */
|
|
1367
|
+
stopInactivityTracking() {
|
|
1368
|
+
this.activityEvents.forEach((event) => {
|
|
1369
|
+
const target = event === 'scroll' ? window : document;
|
|
1370
|
+
// IMPORTANT: capture must match how it was added for scroll
|
|
1371
|
+
const options = event === 'scroll' ? { capture: true } : undefined;
|
|
1372
|
+
target.removeEventListener(event, this.debouncedResetTimer, options);
|
|
1373
|
+
});
|
|
1374
|
+
if (this.inactivityTimerId) {
|
|
1375
|
+
clearTimeout(this.inactivityTimerId);
|
|
1376
|
+
this.inactivityTimerId = null;
|
|
1377
|
+
}
|
|
1378
|
+
if (this.debounceTimerId) {
|
|
1379
|
+
clearTimeout(this.debounceTimerId);
|
|
1380
|
+
this.debounceTimerId = null;
|
|
1381
|
+
}
|
|
1382
|
+
this.trackingActive = false;
|
|
1296
1383
|
}
|
|
1297
1384
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: HeaderWithImpersonationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1298
1385
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.17", 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"] }] }); }
|
|
@@ -2350,11 +2437,11 @@ class SnackbarComponent {
|
|
|
2350
2437
|
}
|
|
2351
2438
|
}
|
|
2352
2439
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SnackbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2353
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: SnackbarComponent, isStandalone: true, selector: "lib-snackbar", inputs: { purpose: "purpose", title: "title", description: "description", fieldPlaceholder: "fieldPlaceholder", titleIcon: "titleIcon", showTitle: ["showTitle", "showTitle", booleanAttribute], showTitleIcon: ["showTitleIcon", "showTitleIcon", booleanAttribute], titleIconFilled: ["titleIconFilled", "titleIconFilled", booleanAttribute], showClose: ["showClose", "showClose", booleanAttribute], showProgressBar: ["showProgressBar", "showProgressBar", booleanAttribute], showField: ["showField", "showField", booleanAttribute], showButton: ["showButton", "showButton", booleanAttribute], autoDismiss: ["autoDismiss", "autoDismiss", booleanAttribute], visible: ["visible", "visible", booleanAttribute], progressMs: "progressMs", y: "y", statusButtonInputs: "statusButtonInputs" }, outputs: { closed: "closed", visibleChange: "visibleChange", buttonClicked: "buttonClicked" }, usesOnChanges: true, ngImport: i0, template: "@if (visible) {\n <section\n class=\"snackbar\"\n [ngClass]=\"{\n 'is-default': purpose === 'default',\n 'is-info': purpose === 'info',\n 'is-success': purpose === 'success',\n 'is-error': purpose === 'error',\n 'is-warning': purpose === 'warning',\n 'is-open': isOpen,\n }\"\n role=\"status\"\n aria-live=\"polite\"\n [style.--snackbar-progress-duration]=\"progressMs + 'ms'\"\n [style.top.px]=\"y\"\n >\n <div class=\"snackbar__left\">\n @if(showTitle) {\n <div class=\"snackbar__title-row\">\n <span \n [ngClass]=\"{\n 'snackbar__title-icon': true,\n 'has-icon': showTitle && showTitleIcon\n }\"\n aria-hidden=\"true\"\n >\n @if (showTitle && showTitleIcon) {\n <i [ngClass]=\"titleIconFilled ? 'material-icons' : 'material-icons-outlined'\"\n id=\"snackbar-title-icon\"\n aria-hidden=\"true\"\n >\n {{ titleIcon }}\n </i>\n }\n </span>\n @if (showTitle) {\n <h3 class=\"snackbar__title\">{{ title }}</h3>\n }\n </div>\n }\n <p class=\"snackbar__desc\">\n {{ description }}\n </p>\n @if (showField) {\n <input class=\"snackbar__input\" type=\"text\" [placeholder]=\"fieldPlaceholder\" />\n }\n @if (showButton) {\n <lib-status-button\n label={{statusButtonInputs?.label}}\n [status]=\"status\"\n leftIcon={{statusButtonInputs?.leftIcon}}\n rightIcon={{statusButtonInputs?.rightIcon}}\n [hideLeftIcon]=\"statusButtonInputs?.hideLeftIcon\"\n [hideRightIcon]=\"statusButtonInputs?.hideRightIcon\"\n [clearVariant]=\"statusButtonInputs?.clearVariant\"\n (click)=\"close(true)\"\n ></lib-status-button>\n\n }\n </div>\n <button \n type=\"button\"\n (click)=\"close()\"\n class=\"snackbar__close\"\n aria-label=\"Dismiss\"\n [style.visibility]=\"showClose ? 'visible' : 'hidden'\"\n >\n \u00D7\n </button>\n @if (showProgressBar) {\n <div class=\"snackbar__progress\">\n <div class=\"snackbar__progress-fill\"></div>\n </div>\n }\n </section>\n}\n", styles: [":root{--border-radius-md: 4px;--colors-border-primary: #d0d0d0;--colors-surface-primary: white;--colors-border-information: #457fa6;--colors-surface-information: #ecf2f6;--colors-border-success: #3ba35a;--colors-surface-success: #ebf6ee;--colors-border-warning: #d1c844;--colors-surface-warning: #faf9ec;--colors-border-error: #c73e3d;--colors-surface-error: #f9ecec}.snackbar{--snackbar-border-color: var(--colors-border-primary);--snackbar-bg: var(--colors-surface-primary);--snackbar-text: #141414;--snackbar-title-color: var(--snackbar-text);--snackbar-icon-bg: #e5edf8;--snackbar-accent: #0047ba;--snackbar-input-border: var(--snackbar-border-color);--snackbar-progress: var(--snackbar-accent);display:flex;width:26.25rem;padding:1.25rem 1rem;justify-content:space-between;align-items:flex-start;gap:.25rem;position:relative;border-radius:var(--border-radius-md, 4px);border:1px solid var(--snackbar-border-color);background:var(--snackbar-bg);box-shadow:1px 1px 4px #00000040;color:var(--snackbar-text)}.snackbar__left{display:grid;gap:.5rem;flex:1 1 auto}.snackbar__left>.snackbar__title-row~*{margin-left:var(--snackbar-right-gutter, 1.85rem)}.snackbar__title-row{display:flex;align-items:center;gap:.25rem}.snackbar__title{margin:0;font-weight:400;font-size:1.2rem;line-height:1.2;color:var(--snackbar-title-color)}.snackbar__title-icon{display:inline-flex;align-items:center;justify-content:center;width:1.5em;height:1.5em}.snackbar__title-icon.has-icon{border-radius:50%;color:var(--snackbar-accent)}.snackbar__icon{font-weight:700;font-size:1rem;line-height:1}.snackbar__desc{margin:0;opacity:.9}.snackbar__input{width:100%;max-width:100%;box-sizing:border-box;padding:.5em;border-radius:4px;border:1px solid var(--snackbar-input-border);background:#fff;color:inherit}.snackbar__close{margin-left:.5em;border:0;background:transparent;color:inherit;opacity:.7;font-size:1.5rem;padding:.25rem;line-height:1;cursor:pointer}.snackbar__progress{position:absolute;left:0;right:0;bottom:-1px;height:4px;overflow:hidden;border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.snackbar__progress-fill{height:100%;width:0%;background:var(--snackbar-progress);animation:fill var(--snackbar-progress-duration, 4s) linear forwards}@keyframes fill{to{width:100%}}.snackbar.is-default{--snackbar-border-color: var(--colors-border-primary, #d0d0d0);--snackbar-bg: var(--colors-surface-primary, white);--snackbar-text: #141414;--snackbar-title-color: #003995;--snackbar-icon-bg: #e5edf8;--snackbar-accent: #003995;--snackbar-input-border: #d0d0d0;--snackbar-progress: #003995}.snackbar.is-info{--snackbar-border-color: var(--colors-border-information, #457fa6);--snackbar-bg: var(--colors-surface-information, #ecf2f6);--snackbar-text: #24495c;--snackbar-title-color: #24495c;--snackbar-icon-bg: #a2bfd3;--snackbar-accent: #306a88;--snackbar-input-border: #a2bfd3;--snackbar-progress: #306a88}.snackbar.is-success{--snackbar-border-color: var(--colors-border-success, #3ba35a);--snackbar-bg: var(--colors-surface-success, #ebf6ee);--snackbar-text: #20522e;--snackbar-title-color: #20522e;--snackbar-icon-bg: #9dd1ac;--snackbar-accent: #2e7d3f;--snackbar-input-border: #9dd1ac;--snackbar-progress: #2e7d3f}.snackbar.is-warning{--snackbar-border-color: var(--colors-border-warning, #d1c844);--snackbar-bg: var(--colors-surface-warning, #faf9ec);--snackbar-text: #514a18;--snackbar-title-color: #514a18;--snackbar-icon-bg: #e8e3a1;--snackbar-accent: #7a6f13;--snackbar-input-border: #d1c844;--snackbar-progress: #7a6f13}.snackbar.is-error{--snackbar-border-color: var(--colors-border-error, #c73e3d);--snackbar-bg: var(--colors-surface-error, #f9ecec);--snackbar-text: #611a1a;--snackbar-title-color: #611a1a;--snackbar-icon-bg: #e39e9e;--snackbar-accent: #912525;--snackbar-input-border: #c73e3d;--snackbar-progress: #912525}.snackbar{position:fixed;top:24px;right:24px;z-index:1000;transform:translate(120%);transition:transform .6s ease}.snackbar.is-open{transform:translate(0)}lib-status-button{--status-button-padding: .25em .5em .25em 0}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: StatusButtonComponent, selector: "lib-status-button", inputs: ["label", "status", "leftIcon", "rightIcon", "hideLeftIcon", "hideRightIcon", "clearVariant"] }] }); }
|
|
2440
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: SnackbarComponent, isStandalone: true, selector: "lib-snackbar", inputs: { purpose: "purpose", title: "title", description: "description", fieldPlaceholder: "fieldPlaceholder", titleIcon: "titleIcon", showTitle: ["showTitle", "showTitle", booleanAttribute], showTitleIcon: ["showTitleIcon", "showTitleIcon", booleanAttribute], titleIconFilled: ["titleIconFilled", "titleIconFilled", booleanAttribute], showClose: ["showClose", "showClose", booleanAttribute], showProgressBar: ["showProgressBar", "showProgressBar", booleanAttribute], showField: ["showField", "showField", booleanAttribute], showButton: ["showButton", "showButton", booleanAttribute], autoDismiss: ["autoDismiss", "autoDismiss", booleanAttribute], visible: ["visible", "visible", booleanAttribute], progressMs: "progressMs", y: "y", statusButtonInputs: "statusButtonInputs" }, outputs: { closed: "closed", visibleChange: "visibleChange", buttonClicked: "buttonClicked" }, usesOnChanges: true, ngImport: i0, template: "@if (visible) {\n <section\n class=\"snackbar\"\n [ngClass]=\"{\n 'is-default': purpose === 'default',\n 'is-info': purpose === 'info',\n 'is-success': purpose === 'success',\n 'is-error': purpose === 'error',\n 'is-warning': purpose === 'warning',\n 'is-open': isOpen,\n }\"\n role=\"status\"\n aria-live=\"polite\"\n [style.--snackbar-progress-duration]=\"progressMs + 'ms'\"\n [style.top.px]=\"y\"\n >\n <div class=\"snackbar__left\">\n @if(showTitle) {\n <div class=\"snackbar__title-row\">\n <span \n [ngClass]=\"{\n 'snackbar__title-icon': true,\n 'has-icon': showTitle && showTitleIcon\n }\"\n aria-hidden=\"true\"\n >\n @if (showTitle && showTitleIcon) {\n <i [ngClass]=\"titleIconFilled ? 'material-icons' : 'material-icons-outlined'\"\n id=\"snackbar-title-icon\"\n aria-hidden=\"true\"\n >\n {{ titleIcon }}\n </i>\n }\n </span>\n @if (showTitle) {\n <h3 class=\"snackbar__title\">{{ title }}</h3>\n }\n </div>\n }\n <p class=\"snackbar__desc\">\n {{ description }}\n </p>\n @if (showField) {\n <input class=\"snackbar__input\" type=\"text\" [placeholder]=\"fieldPlaceholder\" />\n }\n @if (showButton) {\n <lib-status-button\n label={{statusButtonInputs?.label}}\n [status]=\"status\"\n leftIcon={{statusButtonInputs?.leftIcon}}\n rightIcon={{statusButtonInputs?.rightIcon}}\n [hideLeftIcon]=\"statusButtonInputs?.hideLeftIcon\"\n [hideRightIcon]=\"statusButtonInputs?.hideRightIcon\"\n [clearVariant]=\"statusButtonInputs?.clearVariant\"\n (click)=\"close(true)\"\n ></lib-status-button>\n\n }\n </div>\n <button \n type=\"button\"\n (click)=\"close()\"\n class=\"snackbar__close\"\n aria-label=\"Dismiss\"\n [style.visibility]=\"showClose ? 'visible' : 'hidden'\"\n >\n \u00D7\n </button>\n @if (showProgressBar) {\n <div class=\"snackbar__progress\">\n <div class=\"snackbar__progress-fill\"></div>\n </div>\n }\n </section>\n}\n", styles: [":root{--border-radius-md: 4px;--colors-border-primary: #d0d0d0;--colors-surface-primary: white;--colors-border-information: #457fa6;--colors-surface-information: #ecf2f6;--colors-border-success: #3ba35a;--colors-surface-success: #ebf6ee;--colors-border-warning: #d1c844;--colors-surface-warning: #faf9ec;--colors-border-error: #c73e3d;--colors-surface-error: #f9ecec}.snackbar{--snackbar-border-color: var(--colors-border-primary);--snackbar-bg: var(--colors-surface-primary);--snackbar-text: #141414;--snackbar-title-color: var(--snackbar-text);--snackbar-icon-bg: #e5edf8;--snackbar-accent: #0047ba;--snackbar-input-border: var(--snackbar-border-color);--snackbar-progress: var(--snackbar-accent);display:flex;max-width:26.25rem;width:calc(100% - 5rem);padding:1.25rem 1rem;justify-content:space-between;align-items:flex-start;gap:.25rem;position:relative;border-radius:var(--border-radius-md, 4px);border:1px solid var(--snackbar-border-color);background:var(--snackbar-bg);box-shadow:1px 1px 4px #00000040;color:var(--snackbar-text)}.snackbar__left{display:grid;gap:.5rem;flex:1 1 auto}.snackbar__left>.snackbar__title-row~*{margin-left:var(--snackbar-right-gutter, 1.85rem)}.snackbar__title-row{display:flex;align-items:center;gap:.25rem}.snackbar__title{margin:0;font-weight:400;font-size:1.2rem;line-height:1.2;color:var(--snackbar-title-color)}.snackbar__title-icon{display:inline-flex;align-items:center;justify-content:center;width:1.5em;height:1.5em}.snackbar__title-icon.has-icon{border-radius:50%;color:var(--snackbar-accent)}.snackbar__icon{font-weight:700;font-size:1rem;line-height:1}.snackbar__desc{margin:0;opacity:.9}.snackbar__input{width:100%;max-width:100%;box-sizing:border-box;padding:.5em;border-radius:4px;border:1px solid var(--snackbar-input-border);background:#fff;color:inherit}.snackbar__close{margin-left:.5em;border:0;background:transparent;color:inherit;opacity:.7;font-size:1.5rem;padding:.25rem;line-height:1;cursor:pointer}.snackbar__progress{position:absolute;left:0;right:0;bottom:-1px;height:4px;overflow:hidden;border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.snackbar__progress-fill{height:100%;width:0%;background:var(--snackbar-progress);animation:fill var(--snackbar-progress-duration, 4s) linear forwards}@keyframes fill{to{width:100%}}.snackbar.is-default{--snackbar-border-color: var(--colors-border-primary, #d0d0d0);--snackbar-bg: var(--colors-surface-primary, white);--snackbar-text: #141414;--snackbar-title-color: #003995;--snackbar-icon-bg: #e5edf8;--snackbar-accent: #003995;--snackbar-input-border: #d0d0d0;--snackbar-progress: #003995}.snackbar.is-info{--snackbar-border-color: var(--colors-border-information, #457fa6);--snackbar-bg: var(--colors-surface-information, #ecf2f6);--snackbar-text: #24495c;--snackbar-title-color: #24495c;--snackbar-icon-bg: #a2bfd3;--snackbar-accent: #306a88;--snackbar-input-border: #a2bfd3;--snackbar-progress: #306a88}.snackbar.is-success{--snackbar-border-color: var(--colors-border-success, #3ba35a);--snackbar-bg: var(--colors-surface-success, #ebf6ee);--snackbar-text: #20522e;--snackbar-title-color: #20522e;--snackbar-icon-bg: #9dd1ac;--snackbar-accent: #2e7d3f;--snackbar-input-border: #9dd1ac;--snackbar-progress: #2e7d3f}.snackbar.is-warning{--snackbar-border-color: var(--colors-border-warning, #d1c844);--snackbar-bg: var(--colors-surface-warning, #faf9ec);--snackbar-text: #514a18;--snackbar-title-color: #514a18;--snackbar-icon-bg: #e8e3a1;--snackbar-accent: #7a6f13;--snackbar-input-border: #d1c844;--snackbar-progress: #7a6f13}.snackbar.is-error{--snackbar-border-color: var(--colors-border-error, #c73e3d);--snackbar-bg: var(--colors-surface-error, #f9ecec);--snackbar-text: #611a1a;--snackbar-title-color: #611a1a;--snackbar-icon-bg: #e39e9e;--snackbar-accent: #912525;--snackbar-input-border: #c73e3d;--snackbar-progress: #912525}.snackbar{position:fixed;top:24px;right:24px;z-index:1000;transform:translate(120%);transition:transform .6s ease}.snackbar.is-open{transform:translate(0)}lib-status-button{--status-button-padding: .25em .5em .25em 0}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: StatusButtonComponent, selector: "lib-status-button", inputs: ["label", "status", "leftIcon", "rightIcon", "hideLeftIcon", "hideRightIcon", "clearVariant"] }] }); }
|
|
2354
2441
|
}
|
|
2355
2442
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SnackbarComponent, decorators: [{
|
|
2356
2443
|
type: Component,
|
|
2357
|
-
args: [{ selector: 'lib-snackbar', imports: [NgClass, StatusButtonComponent], template: "@if (visible) {\n <section\n class=\"snackbar\"\n [ngClass]=\"{\n 'is-default': purpose === 'default',\n 'is-info': purpose === 'info',\n 'is-success': purpose === 'success',\n 'is-error': purpose === 'error',\n 'is-warning': purpose === 'warning',\n 'is-open': isOpen,\n }\"\n role=\"status\"\n aria-live=\"polite\"\n [style.--snackbar-progress-duration]=\"progressMs + 'ms'\"\n [style.top.px]=\"y\"\n >\n <div class=\"snackbar__left\">\n @if(showTitle) {\n <div class=\"snackbar__title-row\">\n <span \n [ngClass]=\"{\n 'snackbar__title-icon': true,\n 'has-icon': showTitle && showTitleIcon\n }\"\n aria-hidden=\"true\"\n >\n @if (showTitle && showTitleIcon) {\n <i [ngClass]=\"titleIconFilled ? 'material-icons' : 'material-icons-outlined'\"\n id=\"snackbar-title-icon\"\n aria-hidden=\"true\"\n >\n {{ titleIcon }}\n </i>\n }\n </span>\n @if (showTitle) {\n <h3 class=\"snackbar__title\">{{ title }}</h3>\n }\n </div>\n }\n <p class=\"snackbar__desc\">\n {{ description }}\n </p>\n @if (showField) {\n <input class=\"snackbar__input\" type=\"text\" [placeholder]=\"fieldPlaceholder\" />\n }\n @if (showButton) {\n <lib-status-button\n label={{statusButtonInputs?.label}}\n [status]=\"status\"\n leftIcon={{statusButtonInputs?.leftIcon}}\n rightIcon={{statusButtonInputs?.rightIcon}}\n [hideLeftIcon]=\"statusButtonInputs?.hideLeftIcon\"\n [hideRightIcon]=\"statusButtonInputs?.hideRightIcon\"\n [clearVariant]=\"statusButtonInputs?.clearVariant\"\n (click)=\"close(true)\"\n ></lib-status-button>\n\n }\n </div>\n <button \n type=\"button\"\n (click)=\"close()\"\n class=\"snackbar__close\"\n aria-label=\"Dismiss\"\n [style.visibility]=\"showClose ? 'visible' : 'hidden'\"\n >\n \u00D7\n </button>\n @if (showProgressBar) {\n <div class=\"snackbar__progress\">\n <div class=\"snackbar__progress-fill\"></div>\n </div>\n }\n </section>\n}\n", styles: [":root{--border-radius-md: 4px;--colors-border-primary: #d0d0d0;--colors-surface-primary: white;--colors-border-information: #457fa6;--colors-surface-information: #ecf2f6;--colors-border-success: #3ba35a;--colors-surface-success: #ebf6ee;--colors-border-warning: #d1c844;--colors-surface-warning: #faf9ec;--colors-border-error: #c73e3d;--colors-surface-error: #f9ecec}.snackbar{--snackbar-border-color: var(--colors-border-primary);--snackbar-bg: var(--colors-surface-primary);--snackbar-text: #141414;--snackbar-title-color: var(--snackbar-text);--snackbar-icon-bg: #e5edf8;--snackbar-accent: #0047ba;--snackbar-input-border: var(--snackbar-border-color);--snackbar-progress: var(--snackbar-accent);display:flex;width:26.25rem;padding:1.25rem 1rem;justify-content:space-between;align-items:flex-start;gap:.25rem;position:relative;border-radius:var(--border-radius-md, 4px);border:1px solid var(--snackbar-border-color);background:var(--snackbar-bg);box-shadow:1px 1px 4px #00000040;color:var(--snackbar-text)}.snackbar__left{display:grid;gap:.5rem;flex:1 1 auto}.snackbar__left>.snackbar__title-row~*{margin-left:var(--snackbar-right-gutter, 1.85rem)}.snackbar__title-row{display:flex;align-items:center;gap:.25rem}.snackbar__title{margin:0;font-weight:400;font-size:1.2rem;line-height:1.2;color:var(--snackbar-title-color)}.snackbar__title-icon{display:inline-flex;align-items:center;justify-content:center;width:1.5em;height:1.5em}.snackbar__title-icon.has-icon{border-radius:50%;color:var(--snackbar-accent)}.snackbar__icon{font-weight:700;font-size:1rem;line-height:1}.snackbar__desc{margin:0;opacity:.9}.snackbar__input{width:100%;max-width:100%;box-sizing:border-box;padding:.5em;border-radius:4px;border:1px solid var(--snackbar-input-border);background:#fff;color:inherit}.snackbar__close{margin-left:.5em;border:0;background:transparent;color:inherit;opacity:.7;font-size:1.5rem;padding:.25rem;line-height:1;cursor:pointer}.snackbar__progress{position:absolute;left:0;right:0;bottom:-1px;height:4px;overflow:hidden;border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.snackbar__progress-fill{height:100%;width:0%;background:var(--snackbar-progress);animation:fill var(--snackbar-progress-duration, 4s) linear forwards}@keyframes fill{to{width:100%}}.snackbar.is-default{--snackbar-border-color: var(--colors-border-primary, #d0d0d0);--snackbar-bg: var(--colors-surface-primary, white);--snackbar-text: #141414;--snackbar-title-color: #003995;--snackbar-icon-bg: #e5edf8;--snackbar-accent: #003995;--snackbar-input-border: #d0d0d0;--snackbar-progress: #003995}.snackbar.is-info{--snackbar-border-color: var(--colors-border-information, #457fa6);--snackbar-bg: var(--colors-surface-information, #ecf2f6);--snackbar-text: #24495c;--snackbar-title-color: #24495c;--snackbar-icon-bg: #a2bfd3;--snackbar-accent: #306a88;--snackbar-input-border: #a2bfd3;--snackbar-progress: #306a88}.snackbar.is-success{--snackbar-border-color: var(--colors-border-success, #3ba35a);--snackbar-bg: var(--colors-surface-success, #ebf6ee);--snackbar-text: #20522e;--snackbar-title-color: #20522e;--snackbar-icon-bg: #9dd1ac;--snackbar-accent: #2e7d3f;--snackbar-input-border: #9dd1ac;--snackbar-progress: #2e7d3f}.snackbar.is-warning{--snackbar-border-color: var(--colors-border-warning, #d1c844);--snackbar-bg: var(--colors-surface-warning, #faf9ec);--snackbar-text: #514a18;--snackbar-title-color: #514a18;--snackbar-icon-bg: #e8e3a1;--snackbar-accent: #7a6f13;--snackbar-input-border: #d1c844;--snackbar-progress: #7a6f13}.snackbar.is-error{--snackbar-border-color: var(--colors-border-error, #c73e3d);--snackbar-bg: var(--colors-surface-error, #f9ecec);--snackbar-text: #611a1a;--snackbar-title-color: #611a1a;--snackbar-icon-bg: #e39e9e;--snackbar-accent: #912525;--snackbar-input-border: #c73e3d;--snackbar-progress: #912525}.snackbar{position:fixed;top:24px;right:24px;z-index:1000;transform:translate(120%);transition:transform .6s ease}.snackbar.is-open{transform:translate(0)}lib-status-button{--status-button-padding: .25em .5em .25em 0}\n"] }]
|
|
2444
|
+
args: [{ selector: 'lib-snackbar', imports: [NgClass, StatusButtonComponent], template: "@if (visible) {\n <section\n class=\"snackbar\"\n [ngClass]=\"{\n 'is-default': purpose === 'default',\n 'is-info': purpose === 'info',\n 'is-success': purpose === 'success',\n 'is-error': purpose === 'error',\n 'is-warning': purpose === 'warning',\n 'is-open': isOpen,\n }\"\n role=\"status\"\n aria-live=\"polite\"\n [style.--snackbar-progress-duration]=\"progressMs + 'ms'\"\n [style.top.px]=\"y\"\n >\n <div class=\"snackbar__left\">\n @if(showTitle) {\n <div class=\"snackbar__title-row\">\n <span \n [ngClass]=\"{\n 'snackbar__title-icon': true,\n 'has-icon': showTitle && showTitleIcon\n }\"\n aria-hidden=\"true\"\n >\n @if (showTitle && showTitleIcon) {\n <i [ngClass]=\"titleIconFilled ? 'material-icons' : 'material-icons-outlined'\"\n id=\"snackbar-title-icon\"\n aria-hidden=\"true\"\n >\n {{ titleIcon }}\n </i>\n }\n </span>\n @if (showTitle) {\n <h3 class=\"snackbar__title\">{{ title }}</h3>\n }\n </div>\n }\n <p class=\"snackbar__desc\">\n {{ description }}\n </p>\n @if (showField) {\n <input class=\"snackbar__input\" type=\"text\" [placeholder]=\"fieldPlaceholder\" />\n }\n @if (showButton) {\n <lib-status-button\n label={{statusButtonInputs?.label}}\n [status]=\"status\"\n leftIcon={{statusButtonInputs?.leftIcon}}\n rightIcon={{statusButtonInputs?.rightIcon}}\n [hideLeftIcon]=\"statusButtonInputs?.hideLeftIcon\"\n [hideRightIcon]=\"statusButtonInputs?.hideRightIcon\"\n [clearVariant]=\"statusButtonInputs?.clearVariant\"\n (click)=\"close(true)\"\n ></lib-status-button>\n\n }\n </div>\n <button \n type=\"button\"\n (click)=\"close()\"\n class=\"snackbar__close\"\n aria-label=\"Dismiss\"\n [style.visibility]=\"showClose ? 'visible' : 'hidden'\"\n >\n \u00D7\n </button>\n @if (showProgressBar) {\n <div class=\"snackbar__progress\">\n <div class=\"snackbar__progress-fill\"></div>\n </div>\n }\n </section>\n}\n", styles: [":root{--border-radius-md: 4px;--colors-border-primary: #d0d0d0;--colors-surface-primary: white;--colors-border-information: #457fa6;--colors-surface-information: #ecf2f6;--colors-border-success: #3ba35a;--colors-surface-success: #ebf6ee;--colors-border-warning: #d1c844;--colors-surface-warning: #faf9ec;--colors-border-error: #c73e3d;--colors-surface-error: #f9ecec}.snackbar{--snackbar-border-color: var(--colors-border-primary);--snackbar-bg: var(--colors-surface-primary);--snackbar-text: #141414;--snackbar-title-color: var(--snackbar-text);--snackbar-icon-bg: #e5edf8;--snackbar-accent: #0047ba;--snackbar-input-border: var(--snackbar-border-color);--snackbar-progress: var(--snackbar-accent);display:flex;max-width:26.25rem;width:calc(100% - 5rem);padding:1.25rem 1rem;justify-content:space-between;align-items:flex-start;gap:.25rem;position:relative;border-radius:var(--border-radius-md, 4px);border:1px solid var(--snackbar-border-color);background:var(--snackbar-bg);box-shadow:1px 1px 4px #00000040;color:var(--snackbar-text)}.snackbar__left{display:grid;gap:.5rem;flex:1 1 auto}.snackbar__left>.snackbar__title-row~*{margin-left:var(--snackbar-right-gutter, 1.85rem)}.snackbar__title-row{display:flex;align-items:center;gap:.25rem}.snackbar__title{margin:0;font-weight:400;font-size:1.2rem;line-height:1.2;color:var(--snackbar-title-color)}.snackbar__title-icon{display:inline-flex;align-items:center;justify-content:center;width:1.5em;height:1.5em}.snackbar__title-icon.has-icon{border-radius:50%;color:var(--snackbar-accent)}.snackbar__icon{font-weight:700;font-size:1rem;line-height:1}.snackbar__desc{margin:0;opacity:.9}.snackbar__input{width:100%;max-width:100%;box-sizing:border-box;padding:.5em;border-radius:4px;border:1px solid var(--snackbar-input-border);background:#fff;color:inherit}.snackbar__close{margin-left:.5em;border:0;background:transparent;color:inherit;opacity:.7;font-size:1.5rem;padding:.25rem;line-height:1;cursor:pointer}.snackbar__progress{position:absolute;left:0;right:0;bottom:-1px;height:4px;overflow:hidden;border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.snackbar__progress-fill{height:100%;width:0%;background:var(--snackbar-progress);animation:fill var(--snackbar-progress-duration, 4s) linear forwards}@keyframes fill{to{width:100%}}.snackbar.is-default{--snackbar-border-color: var(--colors-border-primary, #d0d0d0);--snackbar-bg: var(--colors-surface-primary, white);--snackbar-text: #141414;--snackbar-title-color: #003995;--snackbar-icon-bg: #e5edf8;--snackbar-accent: #003995;--snackbar-input-border: #d0d0d0;--snackbar-progress: #003995}.snackbar.is-info{--snackbar-border-color: var(--colors-border-information, #457fa6);--snackbar-bg: var(--colors-surface-information, #ecf2f6);--snackbar-text: #24495c;--snackbar-title-color: #24495c;--snackbar-icon-bg: #a2bfd3;--snackbar-accent: #306a88;--snackbar-input-border: #a2bfd3;--snackbar-progress: #306a88}.snackbar.is-success{--snackbar-border-color: var(--colors-border-success, #3ba35a);--snackbar-bg: var(--colors-surface-success, #ebf6ee);--snackbar-text: #20522e;--snackbar-title-color: #20522e;--snackbar-icon-bg: #9dd1ac;--snackbar-accent: #2e7d3f;--snackbar-input-border: #9dd1ac;--snackbar-progress: #2e7d3f}.snackbar.is-warning{--snackbar-border-color: var(--colors-border-warning, #d1c844);--snackbar-bg: var(--colors-surface-warning, #faf9ec);--snackbar-text: #514a18;--snackbar-title-color: #514a18;--snackbar-icon-bg: #e8e3a1;--snackbar-accent: #7a6f13;--snackbar-input-border: #d1c844;--snackbar-progress: #7a6f13}.snackbar.is-error{--snackbar-border-color: var(--colors-border-error, #c73e3d);--snackbar-bg: var(--colors-surface-error, #f9ecec);--snackbar-text: #611a1a;--snackbar-title-color: #611a1a;--snackbar-icon-bg: #e39e9e;--snackbar-accent: #912525;--snackbar-input-border: #c73e3d;--snackbar-progress: #912525}.snackbar{position:fixed;top:24px;right:24px;z-index:1000;transform:translate(120%);transition:transform .6s ease}.snackbar.is-open{transform:translate(0)}lib-status-button{--status-button-padding: .25em .5em .25em 0}\n"] }]
|
|
2358
2445
|
}], propDecorators: { purpose: [{
|
|
2359
2446
|
type: Input
|
|
2360
2447
|
}], title: [{
|
|
@@ -2493,6 +2580,271 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
2493
2580
|
args: [{ providedIn: 'root' }]
|
|
2494
2581
|
}], ctorParameters: () => [{ type: i0.ApplicationRef }, { type: i0.EnvironmentInjector }] });
|
|
2495
2582
|
|
|
2583
|
+
/**
|
|
2584
|
+
* A flexible, reusable button component that supports multiple button types
|
|
2585
|
+
* and various content combinations (icon before, title, icon after).
|
|
2586
|
+
*
|
|
2587
|
+
* @example
|
|
2588
|
+
* ```html
|
|
2589
|
+
* <!-- Primary button with icon and title -->
|
|
2590
|
+
* <lib-button
|
|
2591
|
+
* buttonType="primary"
|
|
2592
|
+
* title="Copy Citation"
|
|
2593
|
+
* iconBefore="content_copy"
|
|
2594
|
+
* (buttonClick)="copyCitation()">
|
|
2595
|
+
* </lib-button>
|
|
2596
|
+
*
|
|
2597
|
+
* <!-- Secondary button with title only -->
|
|
2598
|
+
* <lib-button
|
|
2599
|
+
* buttonType="secondary"
|
|
2600
|
+
* title="Cancel"
|
|
2601
|
+
* (buttonClick)="cancelAction()">
|
|
2602
|
+
* </lib-button>
|
|
2603
|
+
*
|
|
2604
|
+
* <!-- Transparent button with icon after title -->
|
|
2605
|
+
* <lib-button
|
|
2606
|
+
* buttonType="transparent"
|
|
2607
|
+
* title="Download"
|
|
2608
|
+
* iconAfter="download"
|
|
2609
|
+
* (buttonClick)="downloadFile()">
|
|
2610
|
+
* </lib-button>
|
|
2611
|
+
*
|
|
2612
|
+
* <!-- Thin button -->
|
|
2613
|
+
* <lib-button
|
|
2614
|
+
* buttonType="primary"
|
|
2615
|
+
* title="Submit"
|
|
2616
|
+
* [isThin]="true"
|
|
2617
|
+
* (buttonClick)="submitForm()">
|
|
2618
|
+
* </lib-button>
|
|
2619
|
+
* ```
|
|
2620
|
+
*/
|
|
2621
|
+
class ButtonComponent {
|
|
2622
|
+
constructor() {
|
|
2623
|
+
this.buttonType = 'primary';
|
|
2624
|
+
this.disabled = false;
|
|
2625
|
+
this.isThin = false;
|
|
2626
|
+
this.buttonClick = new EventEmitter();
|
|
2627
|
+
}
|
|
2628
|
+
/**
|
|
2629
|
+
* Handles button click events and emits the buttonClick event if the button is not disabled.
|
|
2630
|
+
*/
|
|
2631
|
+
onButtonClick() {
|
|
2632
|
+
if (!this.disabled) {
|
|
2633
|
+
this.buttonClick.emit();
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2637
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: ButtonComponent, isStandalone: true, selector: "lib-button", inputs: { buttonType: "buttonType", title: "title", iconBefore: "iconBefore", iconAfter: "iconAfter", disabled: "disabled", isThin: "isThin", ariaLabel: "ariaLabel" }, outputs: { buttonClick: "buttonClick" }, ngImport: i0, template: "<button\n type=\"button\"\n [class]=\"'btn btn-' + buttonType + (isThin ? ' btn-thin' : '') + (title ? '' : ' btn-icon-only')\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"ariaLabel ? ariaLabel : null\"\n (click)=\"onButtonClick()\"\n [attr.tabindex]=\"disabled ? -1 : 0\"\n>\n @if (iconBefore) {\n <span class=\"icon material-symbols-outlined\" [ngClass]=\"{ 'icon-before': title }\">{{ iconBefore }}</span>\n }\n\n @if (title) {\n <span class=\"button-title\">{{ title }}</span>\n }\n\n @if (iconAfter) {\n <span class=\"icon icon-after material-symbols-outlined\">{{ iconAfter }}</span>\n }\n</button>\n", styles: [".btn{padding:.75rem 1.5rem;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;line-height:1.5rem}.btn.btn-thin{padding:.25rem 2.25rem;border-radius:.5rem;font-weight:400}.btn.btn-thin .icon img{height:1.25rem}.btn:disabled{cursor:not-allowed;color:#767676}.btn:disabled:not(.btn-transparent){background-color:#e7e7e7;border:.0625rem solid #767676}.btn:focus-visible{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-icon-only{padding:.75rem}.btn-primary{background-color:#0047ba;color:#fff;border:.0625rem solid #0047ba}.btn-primary:hover:not(:disabled){background-color:#003995}.btn-secondary{background-color:#fff;color:#00245d;border:.0625rem 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}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
|
|
2638
|
+
}
|
|
2639
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ButtonComponent, decorators: [{
|
|
2640
|
+
type: Component,
|
|
2641
|
+
args: [{ selector: 'lib-button', standalone: true, imports: [CommonModule], template: "<button\n type=\"button\"\n [class]=\"'btn btn-' + buttonType + (isThin ? ' btn-thin' : '') + (title ? '' : ' btn-icon-only')\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"ariaLabel ? ariaLabel : null\"\n (click)=\"onButtonClick()\"\n [attr.tabindex]=\"disabled ? -1 : 0\"\n>\n @if (iconBefore) {\n <span class=\"icon material-symbols-outlined\" [ngClass]=\"{ 'icon-before': title }\">{{ iconBefore }}</span>\n }\n\n @if (title) {\n <span class=\"button-title\">{{ title }}</span>\n }\n\n @if (iconAfter) {\n <span class=\"icon icon-after material-symbols-outlined\">{{ iconAfter }}</span>\n }\n</button>\n", styles: [".btn{padding:.75rem 1.5rem;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;line-height:1.5rem}.btn.btn-thin{padding:.25rem 2.25rem;border-radius:.5rem;font-weight:400}.btn.btn-thin .icon img{height:1.25rem}.btn:disabled{cursor:not-allowed;color:#767676}.btn:disabled:not(.btn-transparent){background-color:#e7e7e7;border:.0625rem solid #767676}.btn:focus-visible{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-icon-only{padding:.75rem}.btn-primary{background-color:#0047ba;color:#fff;border:.0625rem solid #0047ba}.btn-primary:hover:not(:disabled){background-color:#003995}.btn-secondary{background-color:#fff;color:#00245d;border:.0625rem 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}\n"] }]
|
|
2642
|
+
}], propDecorators: { buttonType: [{
|
|
2643
|
+
type: Input
|
|
2644
|
+
}], title: [{
|
|
2645
|
+
type: Input
|
|
2646
|
+
}], iconBefore: [{
|
|
2647
|
+
type: Input
|
|
2648
|
+
}], iconAfter: [{
|
|
2649
|
+
type: Input
|
|
2650
|
+
}], disabled: [{
|
|
2651
|
+
type: Input
|
|
2652
|
+
}], isThin: [{
|
|
2653
|
+
type: Input
|
|
2654
|
+
}], ariaLabel: [{
|
|
2655
|
+
type: Input
|
|
2656
|
+
}], buttonClick: [{
|
|
2657
|
+
type: Output
|
|
2658
|
+
}] } });
|
|
2659
|
+
|
|
2660
|
+
class ButtonGroupItemComponent {
|
|
2661
|
+
constructor() {
|
|
2662
|
+
this.disabled = false;
|
|
2663
|
+
this.buttonClick = new EventEmitter();
|
|
2664
|
+
// Set by parent button group after content projection
|
|
2665
|
+
this.isActive = false;
|
|
2666
|
+
}
|
|
2667
|
+
/**
|
|
2668
|
+
* Handles the button click event and emits the button's ID to the parent component.
|
|
2669
|
+
*/
|
|
2670
|
+
onClick() {
|
|
2671
|
+
this.buttonClick.emit(this.id);
|
|
2672
|
+
}
|
|
2673
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ButtonGroupItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2674
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: ButtonGroupItemComponent, isStandalone: true, selector: "lib-button-group-item", inputs: { id: "id", title: "title", icon: "icon", ariaLabel: "ariaLabel", disabled: "disabled", position: "position" }, outputs: { buttonClick: "buttonClick" }, ngImport: i0, template: "<button\n type=\"button\"\n class=\"tab-btn\"\n [class.first]=\"position === 'first'\"\n [class.last]=\"position === 'last'\"\n [class.active]=\"isActive\"\n [disabled]=\"disabled\"\n (click)=\"onClick()\"\n role=\"tab\"\n [attr.aria-pressed]=\"isActive\"\n [attr.aria-label]=\"ariaLabel ? ariaLabel : null\"\n [attr.tabindex]=\"disabled ? -1 : 0\"\n>\n @if (icon) {\n <span class=\"icon material-symbols-outlined\">{{ icon }}</span>\n }\n\n @if (title) {\n <span class=\"button-title\">{{ title }}</span>\n }\n</button>\n", styles: [".tab-btn{padding:.5rem 1rem;font-size:1rem;font-weight:400;line-height:1.5rem;color:#00245d;background-color:#fff;cursor:pointer;position:relative;border:.0625rem solid #d0d0d0;border-left:none;border-radius:0;transition:all .2s ease;display:inline-flex;align-items:center;justify-content:center;height:2.5rem}.tab-btn:not(:disabled):hover{background-color:#e5edf8}.tab-btn.active{background-color:#0047ba;color:#fff;z-index:2}.tab-btn.active:not(:disabled):hover{background-color:#003995}.tab-btn:focus-visible{outline:.125rem solid #b967c7;outline-offset:.125rem;z-index:3}.tab-btn:disabled{cursor:not-allowed;color:#767676;background-color:#e7e7e7;border-color:#767676}.tab-btn .icon{display:flex;align-items:center;justify-content:center}.tab-btn .icon img{height:1.5rem;width:auto}.tab-btn .button-title{flex-shrink:0}.tab-btn.first{border-radius:.25rem 0 0 .25rem;border-left:.0625rem solid #d0d0d0}.tab-btn.first:disabled{border-left:.0625rem solid #767676}.tab-btn.last{border-radius:0 .25rem .25rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] }); }
|
|
2675
|
+
}
|
|
2676
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ButtonGroupItemComponent, decorators: [{
|
|
2677
|
+
type: Component,
|
|
2678
|
+
args: [{ selector: 'lib-button-group-item', standalone: true, imports: [CommonModule], template: "<button\n type=\"button\"\n class=\"tab-btn\"\n [class.first]=\"position === 'first'\"\n [class.last]=\"position === 'last'\"\n [class.active]=\"isActive\"\n [disabled]=\"disabled\"\n (click)=\"onClick()\"\n role=\"tab\"\n [attr.aria-pressed]=\"isActive\"\n [attr.aria-label]=\"ariaLabel ? ariaLabel : null\"\n [attr.tabindex]=\"disabled ? -1 : 0\"\n>\n @if (icon) {\n <span class=\"icon material-symbols-outlined\">{{ icon }}</span>\n }\n\n @if (title) {\n <span class=\"button-title\">{{ title }}</span>\n }\n</button>\n", styles: [".tab-btn{padding:.5rem 1rem;font-size:1rem;font-weight:400;line-height:1.5rem;color:#00245d;background-color:#fff;cursor:pointer;position:relative;border:.0625rem solid #d0d0d0;border-left:none;border-radius:0;transition:all .2s ease;display:inline-flex;align-items:center;justify-content:center;height:2.5rem}.tab-btn:not(:disabled):hover{background-color:#e5edf8}.tab-btn.active{background-color:#0047ba;color:#fff;z-index:2}.tab-btn.active:not(:disabled):hover{background-color:#003995}.tab-btn:focus-visible{outline:.125rem solid #b967c7;outline-offset:.125rem;z-index:3}.tab-btn:disabled{cursor:not-allowed;color:#767676;background-color:#e7e7e7;border-color:#767676}.tab-btn .icon{display:flex;align-items:center;justify-content:center}.tab-btn .icon img{height:1.5rem;width:auto}.tab-btn .button-title{flex-shrink:0}.tab-btn.first{border-radius:.25rem 0 0 .25rem;border-left:.0625rem solid #d0d0d0}.tab-btn.first:disabled{border-left:.0625rem solid #767676}.tab-btn.last{border-radius:0 .25rem .25rem 0}\n"] }]
|
|
2679
|
+
}], propDecorators: { id: [{
|
|
2680
|
+
type: Input,
|
|
2681
|
+
args: [{ required: true }]
|
|
2682
|
+
}], title: [{
|
|
2683
|
+
type: Input
|
|
2684
|
+
}], icon: [{
|
|
2685
|
+
type: Input
|
|
2686
|
+
}], ariaLabel: [{
|
|
2687
|
+
type: Input
|
|
2688
|
+
}], disabled: [{
|
|
2689
|
+
type: Input
|
|
2690
|
+
}], position: [{
|
|
2691
|
+
type: Input
|
|
2692
|
+
}], buttonClick: [{
|
|
2693
|
+
type: Output
|
|
2694
|
+
}] } });
|
|
2695
|
+
|
|
2696
|
+
class ButtonGroupComponent {
|
|
2697
|
+
constructor() {
|
|
2698
|
+
this.activeButtonChange = new EventEmitter();
|
|
2699
|
+
this.elementRef = inject(ElementRef);
|
|
2700
|
+
this.activeButtonId = signal('');
|
|
2701
|
+
this.subscriptions = [];
|
|
2702
|
+
}
|
|
2703
|
+
/**
|
|
2704
|
+
* Angular lifecycle hook called after content projection is initialized.
|
|
2705
|
+
* Sets up the initial active button and subscribes to button click events from all button items.
|
|
2706
|
+
*/
|
|
2707
|
+
ngAfterContentInit() {
|
|
2708
|
+
// Handle both Angular components (via ContentChildren) and custom elements (via DOM)
|
|
2709
|
+
if (this.buttonItems.length > 0) {
|
|
2710
|
+
// Angular component usage
|
|
2711
|
+
this.initializeActiveButton();
|
|
2712
|
+
this.buttonItems.forEach((item) => {
|
|
2713
|
+
const sub = item.buttonClick.subscribe((id) => this.onButtonClick(id));
|
|
2714
|
+
this.subscriptions.push(sub);
|
|
2715
|
+
});
|
|
2716
|
+
}
|
|
2717
|
+
else {
|
|
2718
|
+
// Custom element usage - delay slightly to ensure custom elements are fully initialized
|
|
2719
|
+
setTimeout(() => {
|
|
2720
|
+
this.initializeCustomElements();
|
|
2721
|
+
}, 0);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
/**
|
|
2725
|
+
* Initializes custom element children when used as web components.
|
|
2726
|
+
*/
|
|
2727
|
+
initializeCustomElements() {
|
|
2728
|
+
const hostElement = this.elementRef.nativeElement;
|
|
2729
|
+
const customElementChildren = Array.from(hostElement.querySelectorAll('hbll-button-group-item, lib-button-group-item'));
|
|
2730
|
+
if (customElementChildren.length > 0) {
|
|
2731
|
+
// Set position attributes - first and last explicitly, rest are middle
|
|
2732
|
+
customElementChildren[0].setAttribute('position', 'first');
|
|
2733
|
+
if (customElementChildren.length > 1) {
|
|
2734
|
+
customElementChildren[customElementChildren.length - 1].setAttribute('position', 'last');
|
|
2735
|
+
}
|
|
2736
|
+
for (let i = 1; i < customElementChildren.length - 1; i++) {
|
|
2737
|
+
customElementChildren[i].setAttribute('position', 'middle');
|
|
2738
|
+
}
|
|
2739
|
+
const initialId = this.initialActiveId;
|
|
2740
|
+
if (initialId) {
|
|
2741
|
+
this.activeButtonId.set(initialId);
|
|
2742
|
+
customElementChildren.forEach((item) => {
|
|
2743
|
+
const itemId = item.getAttribute('id');
|
|
2744
|
+
if (itemId === initialId) {
|
|
2745
|
+
this.setActiveState(item, true);
|
|
2746
|
+
}
|
|
2747
|
+
else {
|
|
2748
|
+
this.setActiveState(item, false);
|
|
2749
|
+
}
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2752
|
+
// Listen to clicks on custom elements
|
|
2753
|
+
customElementChildren.forEach((item) => {
|
|
2754
|
+
item.addEventListener('click', () => {
|
|
2755
|
+
const itemId = item.getAttribute('id');
|
|
2756
|
+
if (itemId && !item.hasAttribute('disabled')) {
|
|
2757
|
+
this.handleCustomElementClick(itemId, customElementChildren);
|
|
2758
|
+
}
|
|
2759
|
+
});
|
|
2760
|
+
});
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
/**
|
|
2764
|
+
* Sets the active state on a custom element by toggling the active class on its button.
|
|
2765
|
+
*/
|
|
2766
|
+
setActiveState(element, isActive) {
|
|
2767
|
+
const button = element.querySelector('.tab-btn');
|
|
2768
|
+
if (button) {
|
|
2769
|
+
if (isActive) {
|
|
2770
|
+
button.classList.add('active');
|
|
2771
|
+
}
|
|
2772
|
+
else {
|
|
2773
|
+
button.classList.remove('active');
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
/**
|
|
2778
|
+
* Handles click events for custom element children.
|
|
2779
|
+
*/
|
|
2780
|
+
handleCustomElementClick(buttonId, children) {
|
|
2781
|
+
this.activeButtonId.set(buttonId);
|
|
2782
|
+
this.activeButtonChange.emit(buttonId);
|
|
2783
|
+
children.forEach((item) => {
|
|
2784
|
+
const itemId = item.getAttribute('id');
|
|
2785
|
+
const isActive = itemId === buttonId;
|
|
2786
|
+
this.setActiveState(item, isActive);
|
|
2787
|
+
});
|
|
2788
|
+
}
|
|
2789
|
+
/**
|
|
2790
|
+
* Angular lifecycle hook called when the component is destroyed.
|
|
2791
|
+
* Cleans up all subscriptions to prevent memory leaks.
|
|
2792
|
+
*/
|
|
2793
|
+
ngOnDestroy() {
|
|
2794
|
+
this.subscriptions.forEach((sub) => sub.unsubscribe());
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Initializes the active button state based on the initialActiveId input.
|
|
2798
|
+
* Sets the active state on the matching button item if an initial ID is provided.
|
|
2799
|
+
*/
|
|
2800
|
+
initializeActiveButton() {
|
|
2801
|
+
const items = this.buttonItems.toArray();
|
|
2802
|
+
const initialId = this.initialActiveId;
|
|
2803
|
+
// Set position - first and last explicitly, rest are middle
|
|
2804
|
+
if (items.length > 0) {
|
|
2805
|
+
items[0].position = 'first';
|
|
2806
|
+
if (items.length > 1) {
|
|
2807
|
+
items[items.length - 1].position = 'last';
|
|
2808
|
+
}
|
|
2809
|
+
for (let i = 1; i < items.length - 1; i++) {
|
|
2810
|
+
items[i].position = 'middle';
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
// Set active state
|
|
2814
|
+
if (initialId) {
|
|
2815
|
+
items.forEach((item) => {
|
|
2816
|
+
item.isActive = item.id === initialId;
|
|
2817
|
+
});
|
|
2818
|
+
this.activeButtonId.set(initialId);
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
/**
|
|
2822
|
+
* Handles button click events from child button items.
|
|
2823
|
+
* Updates the active button state and emits the activeButtonChange event.
|
|
2824
|
+
* @param buttonId - The ID of the clicked button
|
|
2825
|
+
*/
|
|
2826
|
+
onButtonClick(buttonId) {
|
|
2827
|
+
this.activeButtonId.set(buttonId);
|
|
2828
|
+
this.activeButtonChange.emit(buttonId);
|
|
2829
|
+
this.buttonItems.forEach((item) => {
|
|
2830
|
+
item.isActive = item.id === buttonId;
|
|
2831
|
+
});
|
|
2832
|
+
}
|
|
2833
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ButtonGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2834
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: ButtonGroupComponent, isStandalone: true, selector: "lib-button-group", inputs: { initialActiveId: "initialActiveId" }, outputs: { activeButtonChange: "activeButtonChange" }, queries: [{ propertyName: "buttonItems", predicate: ButtonGroupItemComponent }], ngImport: i0, template: "<div class=\"button-group\" role=\"tablist\">\n <ng-content></ng-content>\n</div>\n", styles: [".button-group{display:flex;overflow-x:auto;overflow-y:visible;-webkit-overflow-scrolling:touch;padding:.25rem}\n"] }); }
|
|
2835
|
+
}
|
|
2836
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ButtonGroupComponent, decorators: [{
|
|
2837
|
+
type: Component,
|
|
2838
|
+
args: [{ selector: 'lib-button-group', standalone: true, imports: [ButtonGroupItemComponent], template: "<div class=\"button-group\" role=\"tablist\">\n <ng-content></ng-content>\n</div>\n", styles: [".button-group{display:flex;overflow-x:auto;overflow-y:visible;-webkit-overflow-scrolling:touch;padding:.25rem}\n"] }]
|
|
2839
|
+
}], propDecorators: { initialActiveId: [{
|
|
2840
|
+
type: Input
|
|
2841
|
+
}], activeButtonChange: [{
|
|
2842
|
+
type: Output
|
|
2843
|
+
}], buttonItems: [{
|
|
2844
|
+
type: ContentChildren,
|
|
2845
|
+
args: [ButtonGroupItemComponent]
|
|
2846
|
+
}] } });
|
|
2847
|
+
|
|
2496
2848
|
/*
|
|
2497
2849
|
* Public API Surface of components
|
|
2498
2850
|
*/
|
|
@@ -2501,5 +2853,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
2501
2853
|
* Generated bundle index. Do not edit.
|
|
2502
2854
|
*/
|
|
2503
2855
|
|
|
2504
|
-
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 };
|
|
2856
|
+
export { ADVANCED_SEARCH_FIELD_MAP, ADVANCED_SEARCH_OPTIONS, ADVANCED_SEARCH_QUALIFIER_MAP, ButtonComponent, ButtonGroupComponent, ButtonGroupItemComponent, HbllFooterComponent, HbllHeaderComponent, HbllItemTypeIconPipe, HeaderWithImpersonationComponent, ImpersonateModalComponent, ImpersonateUserPipe, ImpersonationBannerComponent, LIBRARY_HOURS_API_URL, SnackbarComponent, SnackbarService, SsSearchBarComponent, StatusButtonComponent, defaultOidcBaseUri, defaultOidcDefaultIdp, getUserStatusFromRoles, isAdvancedSearchExternalFieldOption, isAdvancedSearchFieldOption, isAdvancedSearchLocalFieldOption, isSearchScope };
|
|
2505
2857
|
//# sourceMappingURL=byuhbll-components.mjs.map
|